edubart / minicoro Goto Github PK
View Code? Open in Web Editor NEWSingle header stackful cross-platform coroutine library in pure C.
License: Other
Single header stackful cross-platform coroutine library in pure C.
License: Other
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
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?
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...
Probably a super easy question to answer, but I'm sort of a noob at this. Wikipedia says (https://en.wikipedia.org/wiki/ARM_Cortex-A57) the CPU is ARMv8-A 64-bit instruction set
, does that mean it would fall under the ARM64 assembly implementation?
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?
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?
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.
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
Hi, I'm new to minicoro and try to understand its capabilities. The readme states:
The
mco_coro
object is not thread safe, you should lock each coroutine into a thread.
Does that mean that it's impossible to interrupt a long-running coroutine and move it to a different os-level thread?
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.
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"
Any plans to support symmetric coroutines?
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.
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
).
Line 579 in 4237c63
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.