summaryrefslogtreecommitdiff
path: root/libcr/coroutine.h
blob: 4d83181ae5423e2817e51662bd13fb18ea080e17 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* coroutine.h - Simple embeddable coroutine implementation
 *
 * Copyright (C) 2024  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-Licence-Identifier: AGPL-3.0-or-later
 */

/**
 * A coroutine is a cooperatively-multitasked form of threading.
 *
 * This coroutine.{h,c} form a lightweight coroutine implementation
 * for non-multithreaded environments (only one coroutine may be
 * running at a time (so no m:n scheduling), coroutine_{add,main} may
 * not be called concurrently with eachother).
 *
 * Unlike many other lightweight or embeddable coroutine
 * implementations, coroutine.{h,c} allow coroutines to use the stack
 * as normal C functions and do not forbid switch() blocks in
 * coroutines.
 *
 * See also: coroutine_rpc.h is a request/response system built on top
 * of coroutine.{h,c}.
 *
 * See also: coroutine_chan.h is a 1-way channel system built on top
 * of coroutine.{h,c}.
 */
#ifndef _COROUTINE_H_
#define _COROUTINE_H_

#include <stddef.h>  /* for size_t */
#include <stdbool.h> /* for bool */

/* typedefs *******************************************************************/

/**
 * A cid_t is a "coroutine ID", used to refer to a coroutine.
 *
 * May be reused if a coroutine exits.
 *
 * Valid IDs are `1 <= cid <= COROUTINE_NUM`; `0` is used for
 * "none/invalid".
 */
typedef size_t cid_t;

/**
 * The root function for a coroutine; a coroutine function should be
 * declared as
 *
 *    COROUTINE myfunc(void *_args) {
 *        myargtype *args = args;
 *        cr_begin();
 *
 *        ...
 *
 *        cr_end();
 *    }
 *
 * The function MUST NOT ever return (GCC enforces this); call
 * cr_exit() or cr_end() instead of returning.
 *
 * When creating a coroutine with coroutine_add(), the bit before
 * cr_begin() runs before coroutine_add() returns; if `_args` points
 * to a place on another coroutine's stack that may go away, then this
 * is an opportunity to copy it to this coroutine's stack.  Otherwise
 * you shouldn't do anything else before calling cr_begin().
 * Specifically, coroutine_add() and
 * cr_{yield,pause_and_yield,exit,end}() are explicitly forbidden to
 * call from within a coroutine before cr_begin() (note that the
 * cr_rpc_*() and cr_chan_*() macros call these functions).
 */
typedef void (*cr_fn_t)(void *args);
#define COROUTINE __attribute__ ((noreturn)) void

/* managing coroutines ********************************************************/

/**
 * Call `fn(args)` in a new coroutine with stack size `stack_size`.
 *
 * See the doc comment on c_fn_t for the requirements imposed on fn.
 *
 * May be called from outside any coroutine (before calling
 * coroutine_main()) or from inside of a coroutine (after it cas
 * called cr_begin()).
 *
 * Returns the cid of the newly-created coroutine.  May return 0 if
 * there are already COROUTINE_NUM active coroutines.
 */
cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args);

/**
 * Like coroutine_add_with_stack_size(), but uses a default stack size so
 * you don't need to think about it.
 */
#define coroutine_add(fn, args) coroutine_add_with_stack_size(0, fn, args)

/**
 * The main scheduler loop.
 *
 * "Should" never return, but will print a message to stderr and
 * return if the program has deadlocked and there are no runnable
 * coroutines.  So be sure to call coroutine_add() at least once
 * before calling this.
 */
void coroutine_main(void);

/* inside of coroutines *******************************************************/

/** cr_begin() goes at the beginning of a coroutine, after it has initialized its stack.  */
void cr_begin( void);
/** cr_exit() terminates the currently-running coroutine.  */
__attribute__ ((noreturn)) void cr_exit(void);
/** cr_yield() switches to another coroutine (if there is another runnable coroutine to switch to).  */
void cr_yield(void);
/** cr_pause_and_yield() marks the current coroutine as not-runnable and switches to another coroutine.  */
void cr_pause_and_yield(void);
/** cr_unpause() marks a coroutine as runnable that has previously marked itself as non-runnable with cr_pause_and_yield().  */
void cr_unpause(cid_t);
/** cr_unpause_from_sighandler() is like cr_unpause(), but safe to call from a signal handler that might race with the coroutine actually pausing itself.  */
void cr_unpause_from_sighandler(cid_t);
/** cr_end() is a counterpart to cr_begin(), but is really just cr_exit().  */
#define cr_end cr_exit

/** cr_getcid() returns the cid of the currently-running coroutine.  */
cid_t cr_getcid(void);

/**
 * It is not possible for one coroutine to kill another or to mark
 * another as paused; a coroutine may only do those things to itself.
 */

#endif /* _COROUTINE_H_ */