|
libtealet 0.4.2
|
Version 0.4.3
LibTealet is a lightweight co-routine library for C. It is based on the technique of stack-slicing, where the execution stack is saved and restored in order to maintain separate execution context. It uses the Stackman library library for low level stack operations, providing implementation for common modern desktop platforms. For ease of integration, this repository bundles Stackman v1.2.0 in stackman/ including pre-built platform libraries used by the default build. The official Stackman repository is: https://github.com/stackless-dev/stackman There are no other run-time dependencies, ecxcept for memcpy(), and the default use of malloc() can be replaced with a custom memory allocator, and assert() which is used by debug builds.
In today's common parlance, using coroutines refers to co-operative multitasking within a single operating system thread, where execution in a function stack is halted in order to be resumed later. This is commonly achieved using the coroutine programming construct, as available in Python 3.5 and later, and C++20] although these are technically asymmetric coroutines since they can only be suspended and resumed by their immediate caller. To be able to suspend a stack of functions, all of the functions involved must therefore be coroutines, often decorated with a special keyword such as async.
Libtealet does not require special functions. Its approach is more akin to threading in that a whole execution stack is suspended, and control passed to another stack. Perhaps it is prudent to talk of co-stacks in this regard. No special compiler or language support is required.
The approach used here employs stack-slicing, a term coined by Christian Tismer to desctibe the technique employed by Stackless-Python.
Instead of each coroutine (or co-stack) having its own stack in virtual memory like an operating system thread does, parts of the C stack that belong to different execution contexts are stored on the heap and restored to the system stack as required.
For each platform, a small piece of assembly code is required. This code stores cpu registers on the stack, then calls functions to save/restore the stack to or from the heap, and adjusts the stack pointer as required. Then the cpu state is restored from the restored stack and a new co-routine is running. This support is provided by the Stackman library.
Stackless Python and Gevent use a similar mechanism, and this code is based on that work.
For C, there also exist some other approaches. For an overview, see Coroutines for C
Unlike traditional coroutine implementations that save entire stacks, libtealet uses an incremental stack-saving technique with chunk chaining. When switching contexts, it saves only the portion needed for the current target stack boundary (that is, the overlapping stack region required for that switch). Stack chunks are linked via a g_prev chain, enabling:
This makes libtealet suitable for applications with thousands of concurrent coroutines.
Compile: gcc -o example example.c -Isrc -Lbin -ltealet
For contributor workflows, clang-format is required.
make check-format in .github/workflows/build-test.yml.make check-format (verify formatting)make format (apply formatting)Install clang-format:
sudo apt install clang-formatbrew install clang-formatwinget install LLVM.LLVMchoco install llvmAfter installing on Windows, ensure clang-format.exe is on your PATH.
The library provides the basic mechanism to create coroutine and to switch between them. It takes care of allocating and saving the stack for dormant coroutines. A way to pass simple values between coroutines is provided but the user must be careful to pass any more complicated data on the heap, and not via stack-local variables.
No form of scheduler is implemented.
libtealet includes optional runtime stack-integrity checks that combine page protection and snapshot verification. These checks help verify the promise that each tealet does not access stack areas outside its allowed bounds. They are especially useful during application development and integration to catch boundary-violation bugs early. They come with runtime overhead and are generally intended for development/testing builds, not production deployments.
TEALET_CONFIGF_STACK_GUARD) protects full pages (typically via mprotect() where available).TEALET_CONFIGF_STACK_SNAPSHOT) verifies byte-level regions, including sub-page areas guard mode cannot represent exactly.tealet_configure_check_stack() to enable a sensible default check profile (ideally from a program top-level function, since it derives stack_guard_limit from its own stack frame).tealet_configure_get() / tealet_configure_set() for explicit control.By default, the project build enables both backends:
TEALET_WITH_STACK_GUARD=1TEALET_WITH_STACK_SNAPSHOT=1You can compile them out when needed, for example:
When compiled out, configuration calls are canonicalized to the supported subset on the current build/platform.
In addition to the traditional approach where each tealet exists within the execution scope of a function (created via tealet_new() or tealet_create()), libtealet now supports Unix-like fork semantics through tealet_fork().
This functionality was available in Stackless Python but has historically been omitted from this library to maintain the clean discipline of function-scoped coroutines. tealet_fork() breaks out of this restriction, enabling more dynamic coroutine creation patterns.
Important responsibilities when using fork:
tealet_set_far() first to bound the stack; otherwise tealet_fork() can fail with TEALET_ERR_UNFORKABLE.tealet_exit() (without TEALET_EXIT_DEFER).far boundaryThis feature enables advanced use cases like coroutine cloning and continuation capture, but requires careful management of stack boundaries and explicit lifetime control.
If your code needs to branch behavior at runtime, use tealet_get_origin() (or TEALET_IS_MAIN_LINEAGE() / TEALET_IS_FORK()) to detect whether a tealet is main-lineage and/or fork-originated.
| Metric | libtealet | OS Threads |
|---|---|---|
| Context switch | ~100-500 cycles | ~1000-10000 cycles |
| Memory per coroutine | ~2-16 KB (incremental) | 1-8 MB (fixed) |
| Creation overhead | ~1-2 µs | ~50-200 µs |
| Parallelism | No (single thread) | Yes (multi-core) |
| Scheduling | Manual | OS kernel |
| System calls | None (pure user-space) | Required |
Trade-offs:
For an example on how to implement longjmp() like functionality, as with the setcontext() library see the file tests/setcontext.c
The tealet code was originally extracted from the Python Greenlet project by Armin Rigo and the original version was written by him. Armin had previously created the Greenlet project by extracting the stack slicing code from from Stackless Python.
See CHANGELOG.md for release history and version information.