184 lines7.1 KB
1#ifndef COROUTINE_H
2#define COROUTINE_H
3
4#include <stdbool.h>
5#include <stddef.h>
6#include <stdint.h>
7
8///////////////////////////////////////////////////////////////////////////////
9// Coroutine
10//
11// Coroutines for C, based on setjmp/longjmp.
12// Thread safe - each thread has its own coroutine system
13// Coroutines are cooperatively scheduled
14// Coroutines have their own stack (currently 16K each)
15// A coroutine can be continued, queried, or deleted on a different thread.
16//
17// Usage:
18// Coroutine_StartSystem(); // call once per thread before using coroutines
19// Coroutine *co = Coroutine_New(start_function);
20// void *result;
21// if (Coroutine_Run(co, initial_value, &result)) {
22// // Handle the failure
23// }
24// Coroutine_Delete(co);
25// Coroutine_StopSystem(); // call once per thread when done with coroutines
26//
27// Inside the coroutine function:
28// void *value = Coroutine_Yield(yield_value, on_yield, this);
29// ...
30// return return_value;
31//
32// To create a coroutine:
33// Coroutine *co = Coroutine_New(start_function);
34// To start or continue a coroutine:
35// void *result = Coroutine_Continue(co, value, early);
36// // early=true puts the coroutine at the head of the run queue
37// // early=false puts the coroutine at the tail of the run queue
38// To yield from inside a coroutine:
39// void *value = Coroutine_Yield(yield_value, on_yield, this);
40// // on_yield is called before the next coroutine is run
41// // 'this' is passed to on_yield as its parameter
42// // value is the value passed to Coroutine_Continue
43// To delete a coroutine:
44// Coroutine_Delete(co);
45// To get the value yielded from, or returned by a corotuine:
46// void *value = Coroutine_GetValue(co);
47// To get the currently running coroutine (NULL if none):
48// Coroutine *co = Coroutine_GetActive();
49// To check if a coroutine is currently running:
50// bool running = Coroutine_IsRunning(co);
51//
52// Notes:
53// Coroutine is not expected to be used directly, but as a foundation for
54// higher level constructs such as Generators, Async, etc.
55//
56///////////////////////////////////////////////////////////////////////////////
57
58
59// The stack is used as follows:
60// +------------------+ <- stack top
61// | coroutine header | <- more claimed as needed in Coroutine_New
62// +------------------+ <-
63// | coroutine stack | <-
64// +------------------+ <-
65// | coroutine header |
66// +------------------+
67// | coroutine stack |
68// +------------------+
69// | coroutine header |
70// +------------------+
71// | coroutine stack |
72// +------------------+
73// | coroutine header |
74// +------------------+
75// | coroutine stack |
76// +------------------+
77// | coroutine header |
78// +------------------+
79// | startup space | <- set aside by Coroutine_StartSystem
80// +------------------+
81// | caller | <- This calls Coroutine_StartSystem etc
82// +------------------+
83// | used stack |
84// +------------------+ <- stack bottom
85
86// Each coroutine has this much stack:
87// For Python, we set it to 17 * (enough for a PyEval_EvalDefault), so we get at least 7
88// calls deep before we need a new chunk, ie maximum multi-chunk wastage is under 6% address space.
89//
90// There's a trade-off between smaller chunk sizes, which allow more async tasks to co-exist
91// on a thread, and larger chunk sizes which waste less memory in part-used chunks.
92//
93// ... which means 10000 async tasks need a 2.6 GB stack, which fits comfortably in the address map.
94//
95// Note, when developing the use of Coroutine in Python, the author found the following used
96// excessive amounts of stack space:
97// Tk_Init: on an Intel 64 bit Mac it used 72k.
98// _decimal multplies of big decimal numbers: 256k+640 (2 x 128k buffers in squaretrans_pow2() + workings)
99//
100// On 64 bit macos, PYOS_STACK_MARGIN_BYTES is 2k * sizeof(void *), ie 16k, or 17 of those, 272k, should give enough slack to operate well.
101
102// This allows you to rename all Coroutine things with your own namespace.
103#define Coroutine_NS(N) Coroutine_##N
104
105// No coroutine will ask for less stack than this
106#ifndef COROUTINE_MINIMUM_STACK_SIZE
107 #define COROUTINE_MINIMUM_STACK_SIZE (4096 * sizeof(void *))
108#endif
109
110// When Coroutine is started, an amount of stack is set aside to give
111// the caller of Coroutine_StartSystem a bit of room to work before calling
112// Coroutine_Run(), that is this amount:
113#ifndef COROUTINE_STARTUP_STACK_SIZE
114 #define COROUTINE_STARTUP_STACK_SIZE (128 * sizeof(void *))
115#endif
116
117// This is *expensive* to turn on, especially if you have lots of stack pieces (eg when there's lots of Tasks)
118#ifndef COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK
119 #define COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK 0
120#endif
121
122#ifndef COROUTINE_RECORD_LOWEST_HEADROOM
123 #define COROUTINE_RECORD_LOWEST_HEADROOM 1
124#endif
125
126// Returned by Coroutine_StopSystem(), this summarises the coroutine session
127typedef struct Coroutine_Report {
128 unsigned coroutines_created;
129 unsigned coroutines_pool_size;
130 size_t lowest_headroom;
131 size_t largest_stack;
132} Coroutine_Report;
133
134typedef enum Coroutine_Err {
135 Coroutine_OK = 0,
136 Coroutine_Err_SystemNotRunning,
137 Coroutine_Err_SystemRunning,
138 Coroutine_Err_NoStack,
139 Coroutine_Err_CoroutineFromWrongThread,
140 Coroutine_Err_ACoroutineIsAlreadyRunning,
141 Coroutine_Err_ExitWithRunningCoroutines,
142 Coroutine_Err_StackOverrun,
143 Coroutine_Err_InternalInsistency,
144 Coroutine_Err_CouldNotInitialiseSystem,
145 Coroutine_Err_WrongState,
146 Coroutine_Err_Canceled
147} Coroutine_Err;
148
149typedef struct Coroutine Coroutine;
150
151typedef void (*Coroutine_NS(YieldCallback))(void *me);
152typedef Coroutine_Err (*Coroutine_SystemStart)(void *);
153typedef void *(*Coroutine_Start)(void *);
154
155extern void Coroutine_NS(SetStackLimit)(void *);
156extern Coroutine_Report Coroutine_NS(GetReport)(void);
157#ifndef NDEBUG
158 extern Coroutine_Err Coroutine_NS(CheckIntegrity)(void);
159#else
160 static inline Coroutine_Err Coroutine_NS(CheckIntegrity)(void){return Coroutine_OK;}
161#endif
162extern Coroutine *Coroutine_NS(New)(size_t size, Coroutine_Start start);
163extern Coroutine_Err Coroutine_NS(Run_Coroutine)(Coroutine *cor, void *value);
164extern Coroutine_Err Coroutine_NS(RunSystem)(Coroutine_SystemStart start, void *value);
165extern Coroutine_Err Coroutine_NS(Run)(size_t size, Coroutine_Start start, void *value, void **result);
166extern void Coroutine_NS(Delete)(Coroutine *cor);
167extern Coroutine_Err Coroutine_NS(Continue)(Coroutine *cor, void *value, bool early);
168extern void *Coroutine_NS(Yield)(void *value, Coroutine_NS(YieldCallback) on_yield, void *me);
169extern void *Coroutine_NS(GetValue)(Coroutine *cor);
170extern Coroutine *Coroutine_NS(GetActive)(void);
171extern intptr_t Coroutine_NS(GetStackHeadroom)(void);
172extern void *Coroutine_NS(GetStackHWM)(void);
173extern void Coroutine_NS(ClearStackForHWM)(void);
174extern bool Coroutine_NS(CanStartCoroutine)(size_t size);
175extern void *Coroutine_NS(GetCStackTop)(void);
176extern Coroutine_Err Coroutine_NS(Chain)(size_t size, Coroutine_Start start, void *value, void **result);
177extern bool Coroutine_NS(IsStarted)(void);
178extern bool Coroutine_NS(IsRunning)(Coroutine *cor);
179extern bool Coroutine_NS(IsComplete)(Coroutine *cor);
180
181extern void Coroutine_NS(Dump_)(void);
182
183#endif
184