This guide documents breaking changes when upgrading from v1.x to v2.0 and introduces new Go-like runtime features.
v1.x:
void coco_timer(uint64_t delay_ms, void (*callback)(void*), void *arg);v2.0:
coco_timer_t *coco_timer(uint64_t delay_ms, void (*callback)(void*), void *arg);Migration: The function now returns a timer handle that can be used for O(1) cancellation. If you don't need cancellation, simply ignore the return value.
// v1.x
coco_timer(1000, my_callback, NULL);
// v2.0
coco_timer(1000, my_callback, NULL); // Still works, return value optionalv2.0 introduces O(1) timer cancellation:
coco_timer_t *timer = coco_timer(5000, my_callback, arg);
// ... later ...
coco_timer_cancel(timer); // O(1) operationv1.x: Global FD table shared across all schedulers.
v2.0: Each scheduler has its own FD table for isolation.
Migration: Code that created multiple schedulers and expected FD sharing needs to be restructured. This is rarely an issue for single-scheduler applications.
v1.x: Global signal handlers.
v2.0: Signal handling is per-scheduler.
Migration: If you relied on global signal state across schedulers, restructure to handle signals within each scheduler context.
v2.0 introduces priority-based scheduling:
// New APIs
void coco_set_priority(coco_coro_t *coro, coco_priority_t priority);
coco_priority_t coco_get_priority(coco_coro_t *coro);Priority levels:
COCO_PRIORITY_HIGH- Real-time tasks, critical pathsCOCO_PRIORITY_NORMAL- Default priorityCOCO_PRIORITY_LOW- Background tasksCOCO_PRIORITY_IDLE- Only runs when no other tasks
v2.0 introduces cooperative cancellation:
int coco_cancel(coco_coro_t *coro);
int coco_cancelled(void);Long-running coroutines should check coco_cancelled() periodically.
Coco now supports dynamic stack growth for coroutines that need more than the default 64KB stack.
To enable dynamic stack growth, create a coroutine with a small initial stack size:
// Create coroutine with 2KB initial stack (dynamic stack enabled)
coco_coro_t *coro = coco_create(sched, my_entry, arg, 2048);Key points:
- Default stack size remains 64KB (no change for existing code)
- Stack sizes < 64KB enable dynamic growth
- Maximum stack size: 8MB (
COCO_STACK_MAX_SIZE) - Requires stack map for pointer adjustment during growth
Dynamic stack growth requires a stack map file for pointer adjustment:
// Set stack map path before creating dynamic stack coroutines
setenv("COCO_STACKMAP_PATH", "/path/to/stack.map", 1);
// Or validate programmatically
if (coco_validate_stack_map(sched) != COCO_OK) {
fprintf(stderr, "Stack map not loaded, dynamic stack may fail\n");
}When a coroutine's stack approaches capacity:
- A new larger stack is allocated (2x current size)
- Stack contents are copied
- Frame pointers are adjusted
- Stack map pointers are updated
// Check if stack growth is needed
if (coro->stack_growable && /* stack nearly full */) {
// Runtime automatically grows the stack
}Time-slice based fair scheduling prevents CPU-bound coroutines from starving others.
coco_sched_t *sched = coco_sched_create();
// Enable fair scheduling with 10ms time slice
coco_sched_set_fairness(sched, true, 10);
// Or with custom time slice (5ms)
coco_sched_set_fairness(sched, true, 5);
// Disable fair scheduling
coco_sched_set_fairness(sched, false, 0);Default: Fair scheduling is disabled by default for backward compatibility.
When enabled:
- Each coroutine gets at most
time_slice_msmilliseconds per turn - CPU-bound coroutines are automatically preempted
- Other coroutines get fair scheduling opportunities
Signal-based preemption ensures long-running coroutines don't block the scheduler.
// Enable preemption for current coroutine
coco_preempt_enable();
// Disable preemption (for critical sections)
coco_preempt_disable();
// Check if preemption is pending
if (coco_preempt_is_pending()) {
// Preemption will occur soon
}
// Cooperative preemption checkpoint
coco_preempt_checkpoint(); // Yields if preemption pendingvoid cpu_intensive_coroutine(void *arg) {
coco_preempt_enable(); // Enable preemption
for (int i = 0; i < BIG_NUMBER; i++) {
do_work();
// Periodic checkpoint allows preemption
if (i % 100 == 0) {
coco_preempt_checkpoint();
}
}
}The stack pool reduces memory allocation overhead by reusing coroutine stacks:
- Stacks are returned to a pool when coroutines finish
- Configurable pool limits per stack size class
- Reduced memory fragmentation
Monitor stack usage for optimization:
size_t usage = coco_get_stack_usage(coro);
printf("Stack used: %zu bytes\n", usage);Launch coroutines across multiple threads for parallelism:
// Start global scheduler with auto-detected thread count
coco_global_sched_start(0); // 0 = auto-detect
// Or specify thread count
coco_global_sched_start(4);
// Launch coroutine (auto-distributed to workers)
coco_coro_t *coro = coco_go(my_entry, arg);
// Launch with options
coco_go_opts_t opts = {
.stack_size = 8192, // Custom stack size
.priority = COCO_PRIORITY_HIGH,
.p_id = -1 // Auto-select P
};
coco_go_with_opts(my_entry, arg, &opts);
// Wait for all coroutines to complete
coco_global_sched_wait();
// Stop scheduler
coco_global_sched_stop();| Metric | Target | Notes |
|---|---|---|
| Context switch | < 100ns | Baseline performance |
| Preemption latency p99 | <= 15ms | With fair scheduling enabled |
| Stack growth overhead | < 1μs | Stack pool allocation |
| Checkpoint overhead | < 10ns | Per checkpoint call |
| API | v1.x | v2.0 | Notes |
|---|---|---|---|
coco_sched_create |
Yes | Yes | Unchanged |
coco_create |
Yes | Yes | Now supports dynamic stack |
coco_timer |
void |
coco_timer_t* |
Returns handle |
coco_timer_cancel |
- | Yes | New API |
coco_set_priority |
- | Yes | New API |
coco_cancel |
- | Yes | New API |
coco_cancelled |
- | Yes | New API |
coco_get_stack_usage |
- | Yes | New API |
coco_sched_set_fairness |
- | Yes | New API |
coco_preempt_enable |
- | Yes | New API |
coco_preempt_disable |
- | Yes | New API |
coco_preempt_checkpoint |
- | Yes | New API |
coco_validate_stack_map |
- | Yes | New API |
coco_global_sched_start |
- | Yes | New API |
coco_go |
- | Yes | New API |
- Update timer calls to handle return value (optional for cancellation)
- Review multi-scheduler code for per-scheduler FD table changes
- Consider enabling fair scheduling for CPU-bound workloads
- Add
coco_cancelled()checks to long-running coroutines - Use
coco_preempt_checkpoint()in CPU-intensive loops - Consider dynamic stack for deep recursion use cases
- Test with new benchmark suite (
bench_preempt,bench_stack)
- Compile with
-DCOCO_DEPRECATED_WARNINGSto identify deprecated usage - Run the test suite:
ctest --output-on-failure - Run new benchmark tests:
./build/bench_preempt # Preemption latency ./build/bench_stack # Stack growth overhead
- Monitor for any timing-related issues (timer cancellation changes)
- Verify stack usage with
coco_get_stack_usage()to tune stack sizes
v2.0 Supported Platforms:
- Linux (x86-64, ARM64) - epoll for I/O multiplexing
- macOS (x86-64, ARM64) - kqueue for I/O multiplexing
Windows: Planned for future release.
No changes to build system. Continue using CMake:
mkdir build && cd build
cmake ..
makeFor benchmarks:
cmake -DCOCO_BUILD_TESTS=ON -DCOCO_BUILD_EXAMPLES=ON ..
makeSymptom: Coroutine crashes with stack overflow despite small initial stack.
Solution: Ensure stack map is loaded:
if (coco_validate_stack_map(sched) != COCO_OK) {
fprintf(stderr, "Set COCO_STACKMAP_PATH environment variable\n");
}Symptom: Long-running coroutines block the scheduler.
Solution:
- Enable fair scheduling:
coco_sched_set_fairness(sched, true, 10) - Add checkpoints:
coco_preempt_checkpoint() - Enable preemption:
coco_preempt_enable()
Symptom: Lower throughput with fair scheduling.
Solution: Fair scheduling adds overhead. Disable for I/O-bound workloads:
coco_sched_set_fairness(sched, false, 0);