-
Notifications
You must be signed in to change notification settings - Fork 0
simple C99 coroutines
License
sirhcm/libbraid
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
libbraid -- C99 library for coroutines.
WHAT
libbraid is heavily inspired by higan's libco and Russ Cox's libtask.
The goal is to provide the non-blocking IO features of libtask, while keeping
the simplicity of libco. Additionally, libbraid should be highly portable,
just by writing a bit of assembly for saving and restoring contexts (and some
glue in `ctxcreate`).
Non-blocking IO is supported using one of the following backends: kqueue,
io_uring (via liburing), and poll (requires timerfd).
INSTALLATION
First run `./configure`. Options are available using the `--help` flag.
Subsequently, `make` can be run, along with `make install`. After
installation, use pkg-config to determine dependencies.
TERMINOLOGY
* braid: a set of cooperatively scheduled cords.
* cord: a coroutine.
* context: a set of registers and stack that can be switched to.
USAGE
libbraid provides two APIs: the low-level context switching API and a
higher-level cooperative scheduling API. The low-level API can be accessed
through `braid/ctx.h`, which provides the following functions:
ctx_t ctxempty(void);
Creates a new, empty context.
ctx_t ctxcreate(void (*f)(usize), usize stacksize, usize arg);
Creates a new context with entry point `f` and stack size
`stacksize`. When switching to this context, `arg` will be passed.
void ctxswap(ctx_t *old, ctx_t new);
Swaps to the context `new`. The current context is saved in `old`.
Use `ctxempty` to create the first `old` context.
void ctxdel(ctx_t c);
Cleans up the memory associated with a context (including unmapping
the stack if it exists).
void *ctxstack(ctx_t c);
Returns a pointer to the context's stack. It is assumed that stacks
grow downward, so this will be a pointer to the guard page.
The higher-level API is available through `braid.h`, which provides the
following functions:
braid_t braidinit(void);
Creates a new braid.
cord_t braidadd(braid_t b, void (*f)(), usize stacksize, const char *name,
uchar flags, int nargs, ...);
cord_t braidvadd(braid_t b, void (*f)(), usize stacksize, const char *name,
uchar flags, int nargs, va_list args)
Adds a new cord to the braid with entry point `f` and stack size
`stacksize`. The cord will be scheduled cooperatively with other
cords in the braid. `nargs` variadic arguments will be passed to
`f`, unless `nargs` is 0, in which case just `b` will be passed.
The `name` argument is nullable. The `flags` arguement sets the
type of cord that is being created:
* CORD_NORMAL: a normal cord.
* CORD_SYSTEM: ignored when deciding the braid size.
Returns the newly created cord.
void braidstart(braid_t b);
Launches the braid scheduler, which continues executing until all
cords have exited.
void braidyield(braid_t b);
Called by a cord, yielding control back to the braid scheduler.
usize braidblock(braid_t b);
Called by a cord to block itself. The cord will not be scheduled
again until it is added back to the braid with `braidunblock`.
The return value is determined by the how the cord was added back
to the braid (see `braiunblock`).
int braidunblock(braid_t b, cord_t c, usize val);
Adds an existing cord `c` to the braid `b`. This function is mostly
used for plumbing blocking operations (see `braidblock`). If this
cord was stopped by `braidblock`, `val` is the value that will be
returned from the `braidblock` call. If the cord is not found in the
blocked list, returns -1, otherwise returns 0.
void braidstop(braid_t b);
Shuts down a braid, causing braidstart to return, and the braid to
be cleaned up.
void braidexit(braid_t b);
Called by a cord to exit the braid. The cord will be removed from the
braid and will not be scheduled again. If the braid size is now 0,
will cause braidlaunch to return.
cord_t braidcurr(braid_t b);
Returns the currently scheduled cord in the braid `b`.
uint braidcnt(braid_t b);
Returns the number of (non-system) scheduled cords in the braid `b`.
uint braidsys(braid_t b);
Returns the number of scheduled system cords in the braid `b`.
uint braidblk(const braid_t b)
Returns the number of blocked cords in the braid `b`
void **braiddata(braid_t b, uchar key);
Returns a pointer to braid-shared storage for the given key. If the
key is not already present, a new pointer will be allocated.
NB: the channel API stores data under the key 0xFC, the non-blocking
IO API stores data under the key 0xFD, and the clock API stores
data under the key 0xFE. As such, if you use any of these APIs, do
not use their keys for your own data.
void braidinfo(braid_t b);
Prints the state of all the cords in the braid `b` to stdout.
void cordhalt(braid_t b, cord_t c);
Halts the cord `c` in the braid `b`. The cord cannot be rescheduled.
void *cordmalloc(braid_t b, cord_t c, size_t sz);
void *cordzalloc(braid_t b, cord_t c, size_t sz);
void *cordrealloc(braid_t b, cord_t c, void *p, size_t sz);
void cordfree(braid_t b, cord_t c, void *p);
void *braidmalloc(braid_t b, size_t sz);
void *braidzalloc(braid_t b, size_t sz);
void *braidrealloc(braid_t b, void *p, size_t sz);
void braidfree(braid_t b, void *p);
Arena allocators, scoped to currently running cord or overall braid
lifetimes.
Non-blocking IO routines below all rely on the following system cord in
`braid/io.h`.
void iovisor(braid_t b);
This function must be started as a system cord in order for the
following non-blocking IO routines to function.
File descriptor manipulation is available through `braid/fd.h`.
void fdwait(braid_t b, int fd, char rw);
Yields the current cord until the file descriptor is ready for
reading or writing, as specified by `rw` ('r' for read, 'w' for
write).
int fdread(braid_t b, int fd, void *buf, size_t count);
Yields the current cord until `poll(2)` indicates that the file
descriptor `fd` is ready for reading, then performs a `read(2)`.
int fdwrite(braid_t b, int fd, const void *buf, size_t count);
Yields the current cord until `poll(2)` indicates that the file
descriptor `fd` is ready for writing, then performs a `write(2)`.
TCP/IP non-blocking IO is available through `braid/tcp.h`.
int tcpdial(braid_t b, int fd, const char *host, int port);
Connects to the specified host and port. The `host` argument should
be specified like "127.0.0.1" or "example.com". The `fd` argument can
be used to specify a preexisting socket, but if negative, `tcpdial`
will create a new socket. Returns the connected socket.
int tcplisten(const char *host, int port);
Helper function to create a TCP/IP listening socket. The `host`
argument can be NULL or "localhost" to listen on all interfaces,
or a specific IP address in the same format as `tcpdial`. Returns
the listening socket.
int tcpaccept(braid_t b, int fd);
Non-blocking waits for a connection on the listening socket `fd`.
Returns the accepted socket.
Go-style (unbuffered) channels are available through `braid/ch.h`.
ch_t chopen(braid_t b);
Creates a new channel.
void chclose(braid_t b, ch_t ch);
Closes the channel `ch`. No new sends will be accepted. Memory will
be freed as soon as the channel is drained.
int chsend(braid_t b, ch_t ch, usize data);
Sends `data` to the channel `ch` in the braid `b`, blocking until
the data is received. Returns 0 on success, or -1 if the channel was
closed.
usize chrecv(braid_t b, ch_t ch, char *ok);
Receives data from the channel `ch` in the braid `b`, blocking until
data is available. Returns the received data, or 0 if the channel
closed. `ok` is nullable, but if non-null will distinguish between
validly returning 0 or error.
Clock routines are available through `braid/ck.h`.
void cknsleep(braid_t b, ulong ns);
void ckusleep(braid_t b, ulong us);
void ckmsleep(braid_t b, ulong ms);
void cksleep(braid_t b, ulong s);
Sleeps for the specified amount of time, yielding the current cord
until the time has passed.
usize ckntimeout(braid_t b, usize (*f)(), usize sz, ulong ns, int nargs, ...);
usize ckutimeout(braid_t b, usize (*f)(), usize sz, ulong us, int nargs, ...);
usize ckmtimeout(braid_t b, usize (*f)(), usize sz, ulong ms, int nargs, ...);
usize cktimeout(braid_t b, usize (*f)(), usize sz, ulong s, int nargs, ...);
usize ckntimeoutv(braid_t b, usize (*f)(), usize sz, ulong ns, int nargs, va_list args);
usize ckutimeoutv(braid_t b, usize (*f)(), usize sz, ulong us, int nargs, va_list args);
usize ckmtimeoutv(braid_t b, usize (*f)(), usize sz, ulong ms, int nargs, va_list args);
usize cktimeoutv(braid_t b, usize (*f)(), usize sz, ulong s, int nargs, va_list args);
Runs the function f, and returns the value returned by f, or if the timer expires, 0.
About
simple C99 coroutines