210 lines8.5 KB
1#ifndef COROUTINE_H
2#define COROUTINE_H
3
4#include <stdbool.h>
5#include <stddef.h>
6#include <stdint.h>
7#include "cor_platform_inc.h"
8#include "coroutine_names_def.h"
9
10///////////////////////////////////////////////////////////////////////////////
11// Coroutine
12//
13// Coroutines for C, based on setjmp/longjmp.
14// Thread safe - each thread has its own coroutine system
15// Coroutines are cooperatively scheduled
16// Coroutines have their own stack (currently 16K each)
17// A coroutine can be continued, queried, or deleted on a different thread.
18//
19// Usage:
20// Coroutine_StartSystem(); // call once per thread before using coroutines
21// Coroutine *co = Coroutine_New(start_function);
22// void *result;
23// if (Coroutine_Run(co, initial_value, &result)) {
24// // Handle the failure
25// }
26// Coroutine_Delete(co);
27// Coroutine_StopSystem(); // call once per thread when done with coroutines
28//
29// Inside the coroutine function:
30// void *value = Coroutine_Yield(yield_value, on_yield, this);
31// ...
32// return return_value;
33//
34// To create a coroutine:
35// Coroutine *co = Coroutine_New(start_function);
36// To start or continue a coroutine:
37// void *result = Coroutine_Continue(co, value, early);
38// // early=true puts the coroutine at the head of the run queue
39// // early=false puts the coroutine at the tail of the run queue
40// To yield from inside a coroutine:
41// void *value = Coroutine_Yield(yield_value, on_yield, this);
42// // on_yield is called before the next coroutine is run
43// // 'this' is passed to on_yield as its parameter
44// // value is the value passed to Coroutine_Continue
45// To delete a coroutine:
46// Coroutine_Delete(co);
47// To get the value yielded from, or returned by a corotuine:
48// void *value = Coroutine_GetValue(co);
49// To get the currently running coroutine (NULL if none):
50// Coroutine *co = Coroutine_GetActive();
51// To check if a coroutine is currently running:
52// bool running = Coroutine_IsRunning(co);
53//
54// Notes:
55// Coroutine is not expected to be used directly, but as a foundation for
56// higher level constructs such as Generators, Async, etc.
57//
58///////////////////////////////////////////////////////////////////////////////
59
60
61// The stack is used as follows:
62// +------------------+ <- stack top
63// | coroutine header | <- more claimed as needed in Coroutine_New
64// +------------------+ <-
65// | coroutine stack | <-
66// +------------------+ <-
67// | coroutine header |
68// +------------------+
69// | coroutine stack |
70// +------------------+
71// | coroutine header |
72// +------------------+
73// | coroutine stack |
74// +------------------+
75// | coroutine header |
76// +------------------+
77// | coroutine stack |
78// +------------------+
79// | coroutine header |
80// +------------------+
81// | startup space | <- set aside by Coroutine_StartSystem
82// +------------------+
83// | caller | <- This calls Coroutine_StartSystem etc
84// +------------------+
85// | used stack |
86// +------------------+ <- stack bottom
87
88// Each coroutine has this much stack:
89// For Python, we set it to 17 * (enough for a PyEval_EvalDefault), so we get at least 7
90// calls deep before we need a new chunk, ie maximum multi-chunk wastage is under 6% address space.
91//
92// There's a trade-off between smaller chunk sizes, which allow more async tasks to co-exist
93// on a thread, and larger chunk sizes which waste less memory in part-used chunks.
94//
95// ... which means 10000 async tasks need a 2.6 GB stack, which fits comfortably in the address map.
96//
97// Note, when developing the use of Coroutine in Python, the author found the following used
98// excessive amounts of stack space:
99// Tk_Init: on an Intel 64 bit Mac it used 72k.
100// _decimal multplies of big decimal numbers: 256k+640 (2 x 128k buffers in squaretrans_pow2() + workings)
101//
102// 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.
103
104
105#ifndef Coroutine_API_FUNC
106 #define Coroutine_API_FUNC(T) extern T
107#endif
108
109// No coroutine will ask for less stack than this
110#ifndef COROUTINE_MINIMUM_STACK_SIZE
111 #define COROUTINE_MINIMUM_STACK_SIZE (4096 * sizeof(void *))
112#endif
113
114// A guard pattern is placed at the end of every stack chunk to detect stack overruns.
115// The initial stack chunk ends at the C stack's limit, and is the only chunk not created
116// by calling a routine and using alloca().
117// On Windows, the OS assigns real memory pages for the used stack only (not unusual), but
118// restricts when new pages are assigned. My guess is that it assumes read/write of an
119// unassigned page which is also beyond the stack pointer is probably a bug. The consequence
120// is that you can't write a guard pattern at the C stack's limit, hence this configuration
121// control exists.
122#ifndef COROUTINE_GUARD_AT_C_STACK_LIMIT
123 #if defined(MS_WIN32)
124 #define COROUTINE_GUARD_AT_C_STACK_LIMIT 0
125 #else
126 #define COROUTINE_GUARD_AT_C_STACK_LIMIT 1
127 #endif
128#endif
129
130// When Coroutine is started, an amount of stack is set aside to give
131// the caller of Coroutine_StartSystem a bit of room to work before calling
132// Coroutine_Run(), that is this amount:
133#ifndef COROUTINE_STARTUP_STACK_SIZE
134 #ifndef _NDEBUG
135 #define COROUTINE_STARTUP_STACK_SIZE (1024 * sizeof(void *))
136 #else
137 #define COROUTINE_STARTUP_STACK_SIZE (128 * sizeof(void *))
138 #endif
139#endif
140
141// This is *expensive* to turn on, especially if you have lots of stack pieces (eg when there's lots of Tasks)
142#ifndef COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK
143 #define COROUTINE_CHECK_INTEGRITY_ON_STACK_CHECK 0
144#endif
145
146#ifndef COROUTINE_RECORD_LOWEST_HEADROOM
147 #define COROUTINE_RECORD_LOWEST_HEADROOM 1
148#endif
149
150// Returned by Coroutine_StopSystem(), this summarises the coroutine session
151typedef struct Coroutine_Report {
152 unsigned coroutines_created;
153 unsigned coroutines_pool_size;
154 size_t lowest_headroom;
155 size_t largest_stack;
156} Coroutine_Report;
157
158typedef enum Coroutine_Err {
159 Coroutine_OK = 0,
160 Coroutine_Err_SystemNotRunning,
161 Coroutine_Err_SystemRunning,
162 Coroutine_Err_NoStack,
163 Coroutine_Err_CoroutineFromWrongThread,
164 Coroutine_Err_ACoroutineIsAlreadyRunning,
165 Coroutine_Err_ExitWithRunningCoroutines,
166 Coroutine_Err_StackOverrun,
167 Coroutine_Err_InternalInsistency,
168 Coroutine_Err_CouldNotInitialiseSystem,
169 Coroutine_Err_WrongState,
170 Coroutine_Err_Canceled
171} Coroutine_Err;
172
173typedef struct Coroutine Coroutine;
174
175typedef void (*Coroutine_YieldCallback)(void *me);
176typedef Coroutine_Err (*Coroutine_SystemStart)(void *, Coroutine *);
177typedef void *(*Coroutine_Start)(void *);
178
179Coroutine_API_FUNC(void) Coroutine_SetStackLimit(void *);
180Coroutine_API_FUNC(Coroutine_Report) Coroutine_GetReport(void);
181#ifndef NDEBUG
182 Coroutine_API_FUNC(Coroutine_Err) Coroutine_CheckIntegrity(void);
183#else
184 static inline Coroutine_Err Coroutine_CheckIntegrity(void){return Coroutine_OK;}
185#endif
186Coroutine_API_FUNC(Coroutine *) Coroutine_New(size_t min_size, size_t min_headroom, Coroutine_Start start);
187Coroutine_API_FUNC(Coroutine_Err) Coroutine_RunSystem(size_t min_size, size_t min_headroom, Coroutine_SystemStart start, void *value);
188Coroutine_API_FUNC(Coroutine_Err) Coroutine_Run(size_t min_size, size_t min_headroom, Coroutine_Start start, void *value, void **result);
189Coroutine_API_FUNC(void) Coroutine_Delete(Coroutine *cor);
190Coroutine_API_FUNC(Coroutine_Err) Coroutine_Continue(Coroutine *cor, void *value, bool early);
191Coroutine_API_FUNC(void *) Coroutine_Yield(void *value, Coroutine_YieldCallback on_yield, void *me);
192Coroutine_API_FUNC(void *) Coroutine_GetValue(Coroutine *cor);
193Coroutine_API_FUNC(Coroutine *) Coroutine_GetActive(void);
194Coroutine_API_FUNC(ptrdiff_t) Coroutine_GetStackHeadroom(void);
195Coroutine_API_FUNC(void *) Coroutine_GetStackHWM(void);
196Coroutine_API_FUNC(void) Coroutine_ClearStackForHWM(void);
197Coroutine_API_FUNC(bool) Coroutine_CanStartCoroutine(size_t size);
198Coroutine_API_FUNC(size_t) Coroutine_GetUsefulFreeSpace(size_t min_size, size_t overhead);
199Coroutine_API_FUNC(void *) Coroutine_GetCStackTop(void);
200Coroutine_API_FUNC(Coroutine_Err) Coroutine_Chain(size_t min_size, size_t min_headroom, Coroutine_Start start, void *value, void **result);
201Coroutine_API_FUNC(Coroutine_Err) Coroutine_CallWithMaxStack(Coroutine_Start start, void *value, void **result);
202Coroutine_API_FUNC(bool) Coroutine_IsStarted(void);
203Coroutine_API_FUNC(bool) Coroutine_IsRunning(Coroutine *cor);
204Coroutine_API_FUNC(bool) Coroutine_IsComplete(Coroutine *cor);
205
206Coroutine_API_FUNC(void) Coroutine_Dump_(void);
207
208#include "coroutine_names_undef.h"
209#endif
210