Coder Social home page Coder Social logo

edubart / minicoro Goto Github PK

View Code? Open in Web Editor NEW
604.0 19.0 39.0 136 KB

Single header stackful cross-platform coroutine library in pure C.

License: Other

C 93.63% Makefile 6.37%
coroutine c coroutine-library coroutines lua ucontext yield asymmetric-coroutines nelua fibers

minicoro's People

Contributors

edubart avatar jserv avatar osor-io avatar randygaul avatar stephenrberg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

minicoro's Issues

stack overflow with VS2008 / VS2019

Hi,

first of all thank you for this library.
With the recent update (stack overflow check) I get a message about stack corruption.
I've tested it against simple.c, the output I get is:

coroutine 1
coroutine stack overflow, try increasing the stack size
coroutine 2
Assertion failed: mco_status(co) == MCO_SUSPENDED, file simple.c, line 27

I've compiled it with my ancient complier vc++ 2008 and with the last one vc++ 2019.
Same issue occurs.

Any help ?

Please note that vc2008 is a C89 compiler, are you interested in a PR that simply move vars definition at beginning of the function ?

Thank you,
Alessio

C++ Exceptions Revisited

Refactoring minicoro to be fully C++ compatible, especially regarding exceptions like mentioned in #2, I've had some success but am stuck at one point.

I managed to convert mco_coro into a type that cleans up after itself in the destructor.

Another thing that works is propagating exceptions from inside the coroutine to the outside.

(By wrapping the call to the user-supplied function in mco_main() in a try-catch block where the catch stores the result of std::current_exception() in a std::exception_ptr data member of mco_coro, and having mco_resume() use std::rethrow_exception() if it finds that pointer non-empty. This leaves the coroutine in state DEAD which is appropriate.)

Where I'm stuck is that it's not safe to destroy a C++ coroutine that hasn't finished it's main function yet, i.e. that is not DEAD.

(Because there might be C++ objects on the coroutine's stack that need their destructors to run to free resources.)

What I would need is a way to inject an exception into the coroutine so that it can "clean up" the stack and land in the aforementioned try-catch block in mco_main(), a kind of resume_and_throw().

Otherwise I'd have to make sure that all coroutines run to completion (or don't use non-trivial objects on the stack).

It should be possible to manipulate the program counter of the coroutine to point to code that throws the clean-up exception before calling resume(). But it's probably not quite that easy, and before I dive into ABI details I wanted to ask whether you have any thoughts on this subject.

Thanks!

PS: Is destroying a C coroutine in state SUSPENDED not potentially problematic for the same reason, having allocated resources that are only freed if it runs to completion?

Usage of "fiber_storage" in the Win32 implementation

Hello! ๐Ÿ˜„

I was trying to read up on good coroutine implementations to understand how they work. On the windows implementation I noticed that the fiber_storage member doesn't get initialized in _mco_makectx, so I believe it'll always be set to zero (cause of the memset in _mco_create_context).

void* fiber_storage;

This gets set in the TIB when switching to the coroutine context. Is this on purpose? Do you know of anywhere I can read up on this?

Anecdotally, on my machine, on the main thread, that value seems to be set to 7680. Changing it to 0 doesn't seem to affect anything, possibly because that thread is not a fiber but I'm not sure.

Cheers!

Ruben.

Release temporary variables on the stack


void coro_run(mco_coro* co)
{
	std::unique_ptr<int32_t, std::function<void(int32_t*)>> ptr(new int32_t(1000), [](int32_t* rawPtr) {
		printf("delete int: %d", *rawPtr);
	});

	for (auto i = 0; i < 10; ++i)
	{
		mco_yield(mco_running());
	}
}

int main(int argc, char** argv)
{
	printf("----------------------- begin\n");
	{
		mco_coro* ctx;
		mco_desc desc = mco_desc_init(coro_run, 128 * 1024);
		mco_result res = mco_create(&ctx, &desc);
		assert(res == MCO_SUCCESS);

		mco_resume(ctx);
		mco_resume(ctx);
		mco_resume(ctx);
		mco_resume(ctx);

		mco_destroy(ctx);
	}
	printf("----------------------- end\n");
}

How to output: "delete int: 1000"

Pages with no access to catch stack overflows?

Would it be feasible to use a memory page with PROT_NONE at both the top and bottom of a coroutine stack to catch overflows immediately instead of after the fact in mco_yield()? I realise this would crash the application, hard, but that might be desirable for some use cases...

Trying to get minicoro working with a C++ wrapper (and smart pointers)

Hi there,

Have been experimenting with minicoro for a few hours, with a mixture of successes and failures. (I suspect smart pointers are likely to have some issues.)

I built up a little C++ helper class and some macros to simplify some of the calls:

struct CoroutineManager
{
    mco_coro* m_pCo = nullptr;
    mco_desc desc;

    void Init(void (*func)(mco_coro*))
    {
        desc = mco_desc_init(func, 0);
        mco_result res = mco_create(&m_pCo, &desc);
        WASSERT(res == MCO_SUCCESS);
    }

    void PushParam(const void* src, size_t len)
    {
        mco_push(m_pCo, src, len);
    }

    ~CoroutineManager()
    {
        if (m_pCo != nullptr)
        {
            auto res = mco_destroy(m_pCo);
            WASSERT(res == MCO_SUCCESS);
        }
    }

    bool YieldNext(void* dest, size_t len)
    {
        if (mco_status(m_pCo) == MCO_SUSPENDED)
        {
            auto res = mco_resume(m_pCo);
            WASSERT(res == MCO_SUCCESS);

            res = mco_pop(m_pCo, dest, len);
            WASSERT(res == MCO_SUCCESS);
            return true;
        }
        else
        {
            return false;
        }
    }    
};

This seems to work (with some caveats), but if I move YieldNext(...) out of the header into the associated cpp file I created, it no longer functions properly. Are you able to tell me why this is?

Optimize per coroutine memory footprint with virtual memory mapping

Currently, minicoro uses a fixed stack size of roughly 56KB. It is possibly to use virtual memory mapping provided by the OS to allocate memory (such mmap() on POSIX system), yet do not trigger physical memory usage until the stack is really put to use, and even when used the OS will grow physical memory usage only when needed every 4KB block, effectively making it act as a "growable stack".

This will open the possibility of spawning thousands of coroutines with minimal memory footprint (when supported by the OS), it will also allow us to increase the default stack size to larger and safer size, such as 2MB per coroutine without really committing physical memory. For some uses cases the 56KB have proven to be too small.

To make this, there will be some API changes and probably a new config such as MCO_USE_VIRTUAL_MEMORY to enable an allocator with virtual memory mapping.

This feature needs to be backed by OS APIs, so it is not portable across all systems, therefore it would be off by default.

Documentation of saved state and compatible instruction extensions

There are two assembly implementations of the X86-64 context switching code, one for Windows that saves the necessary GPRs and the XMM registers, and one for other operating systems that only saves the GPRs.

Does this mean that on Linux the assembly switching code is only safe to use for pure integer code that doesn't use any FPU or SIMD instructions? What about on Windows when the 256bit sized YMM registers are used?

If I'm not chasing a red herring and there are restrictions on which instructions can be used in coroutines depending on the chosen context switching implementation then I recommend that these be documented.

C++ exceptions question

What's the reason for that C++ exceptions are not supported? What happens if an exception is thrown anyway? Would it be possible to add support for exceptions?

Makefile mt-example link fails due to -lpthread order wrong

Following patch resolves, this SO has a detailed post on the reason: https://stackoverflow.com/questions/11893996/why-does-the-order-of-l-option-in-gcc-matter

diff --git a/tests/Makefile b/tests/Makefile
index b7bc02e..3f8113f 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -36,7 +36,7 @@ example: example.c ../minicoro.h Makefile
 	$(CC) $(EXTRA_CFLAGS) $(CFLAGS) example.c -o example
 
 mt-example: mt-example.c ../minicoro.h Makefile
-	$(CC) $(EXTRA_CFLAGS) $(CFLAGS) -std=gnu11 -lpthread mt-example.c -o mt-example
+	$(CC) $(EXTRA_CFLAGS) $(CFLAGS) -std=gnu11 mt-example.c -lpthread -o mt-example
 
 simple: simple.c ../minicoro.h Makefile
 	$(CC) $(EXTRA_CFLAGS) $(CFLAGS) -std=c99 simple.c -o simple

Mild red zone bug.

Spotted a pretty mild bug related to red zones. The current code shouldn't cause any bugs. It just wastes a few bytes of stack space.
https://github.com/edubart/minicoro/blob/main/minicoro.h#L742

You subtract the 128 bytes from the size of the stack, and then use that size to find the high address of the stack. The red zone is the space with lower addresses than the current stack pointer, while your reserved space is above the start of the stack. Also, the red zone really only applies meaningfully to leaf functions so they can skip adjusting the stack pointer without getting stomped by interrupts. Any function that calls resume/yield is no longer a leaf function, so you don't have to worry about it in your implementation.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.