454 lines
17 KiB
C
454 lines
17 KiB
C
// https://github.com/tidwall/neco
|
|
//
|
|
// Copyright 2024 Joshua J Baker. All rights reserved.
|
|
// Use of this source code is governed by an MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
//
|
|
// Neco -- Coroutine library for C
|
|
|
|
#ifndef NECO_H
|
|
#define NECO_H
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <sys/types.h>
|
|
#ifdef _WIN32
|
|
#include <ws2tcpip.h>
|
|
#else
|
|
#include <netdb.h>
|
|
#include <sys/socket.h>
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// basic operations
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup BasicOperations Basic operations
|
|
/// Neco provides standard operations for starting a coroutine, sleeping,
|
|
/// suspending, resuming, yielding to another coroutine, joining/waiting for
|
|
/// child coroutines, and exiting a running coroutine.
|
|
/// @{
|
|
int neco_start(void(*coroutine)(int argc, void *argv[]), int argc, ...);
|
|
int neco_startv(void(*coroutine)(int argc, void *argv[]), int argc, void *argv[]);
|
|
int neco_yield(void);
|
|
int neco_sleep(int64_t nanosecs);
|
|
int neco_sleep_dl(int64_t deadline);
|
|
int neco_join(int64_t id);
|
|
int neco_join_dl(int64_t id, int64_t deadline);
|
|
int neco_suspend(void);
|
|
int neco_suspend_dl(int64_t deadline);
|
|
int neco_resume(int64_t id);
|
|
void neco_exit(void);
|
|
int64_t neco_getid(void);
|
|
int64_t neco_lastid(void);
|
|
int64_t neco_starterid(void);
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// channels
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Channels Channels
|
|
/// Channels allow for sending and receiving values between coroutines.
|
|
/// By default, sends and receives will block until the other side is ready.
|
|
/// This allows the coroutines to synchronize without using locks or condition
|
|
/// variables.
|
|
/// @{
|
|
typedef struct neco_chan neco_chan;
|
|
|
|
int neco_chan_make(neco_chan **chan, size_t data_size, size_t capacity);
|
|
int neco_chan_retain(neco_chan *chan);
|
|
int neco_chan_release(neco_chan *chan);
|
|
int neco_chan_send(neco_chan *chan, void *data);
|
|
int neco_chan_send_dl(neco_chan *chan, void *data, int64_t deadline);
|
|
int neco_chan_broadcast(neco_chan *chan, void *data);
|
|
int neco_chan_recv(neco_chan *chan, void *data);
|
|
int neco_chan_recv_dl(neco_chan *chan, void *data, int64_t deadline);
|
|
int neco_chan_tryrecv(neco_chan *chan, void *data);
|
|
int neco_chan_close(neco_chan *chan);
|
|
int neco_chan_select(int nchans, ...);
|
|
int neco_chan_select_dl(int64_t deadline, int nchans, ...);
|
|
int neco_chan_selectv(int nchans, neco_chan *chans[]);
|
|
int neco_chan_selectv_dl(int nchans, neco_chan *chans[], int64_t deadline);
|
|
int neco_chan_tryselect(int nchans, ...);
|
|
int neco_chan_tryselectv(int nchans, neco_chan *chans[]);
|
|
int neco_chan_case(neco_chan *chan, void *data);
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// generators
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Generators Generators
|
|
/// A generator is a specialized iterator-bound coroutine that can produce a
|
|
/// sequence of values to be iterated over.
|
|
/// @{
|
|
typedef struct neco_gen neco_gen;
|
|
|
|
int neco_gen_start(neco_gen **gen, size_t data_size, void(*coroutine)(int argc, void *argv[]), int argc, ...);
|
|
int neco_gen_startv(neco_gen **gen, size_t data_size, void(*coroutine)(int argc, void *argv[]), int argc, void *argv[]);
|
|
int neco_gen_retain(neco_gen *gen);
|
|
int neco_gen_release(neco_gen *gen);
|
|
int neco_gen_yield(void *data);
|
|
int neco_gen_yield_dl(void *data, int64_t deadline);
|
|
int neco_gen_next(neco_gen *gen, void *data);
|
|
int neco_gen_next_dl(neco_gen *gen, void *data, int64_t deadline);
|
|
int neco_gen_close(neco_gen *gen);
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// synchronization mechanisms
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Mutexes Mutexes
|
|
/// A mutex is synchronization mechanism that blocks access to variables by
|
|
/// multiple coroutines at once. This enforces exclusive access by a coroutine
|
|
/// to a variable or set of variables and helps to avoid data inconsistencies
|
|
/// due to race conditions.
|
|
/// @{
|
|
typedef struct { _Alignas(16) char _[48]; } neco_mutex;
|
|
|
|
#define NECO_MUTEX_INITIALIZER { 0 }
|
|
|
|
int neco_mutex_init(neco_mutex *mutex);
|
|
int neco_mutex_lock(neco_mutex *mutex);
|
|
int neco_mutex_lock_dl(neco_mutex *mutex, int64_t deadline);
|
|
int neco_mutex_trylock(neco_mutex *mutex);
|
|
int neco_mutex_unlock(neco_mutex *mutex);
|
|
int neco_mutex_rdlock(neco_mutex *mutex);
|
|
int neco_mutex_rdlock_dl(neco_mutex *mutex, int64_t deadline);
|
|
int neco_mutex_tryrdlock(neco_mutex *mutex);
|
|
/// @}
|
|
|
|
/// @defgroup WaitGroups WaitGroups
|
|
/// A WaitGroup waits for a multiple coroutines to finish.
|
|
/// The main coroutine calls neco_waitgroup_add() to set the number of
|
|
/// coroutines to wait for. Then each of the coroutines runs and calls
|
|
/// neco_waitgroup_done() when complete.
|
|
/// At the same time, neco_waitgroup_wait() can be used to block until all
|
|
/// coroutines are completed.
|
|
/// @{
|
|
typedef struct { _Alignas(16) char _[48]; } neco_waitgroup;
|
|
|
|
#define NECO_WAITGROUP_INITIALIZER { 0 }
|
|
|
|
int neco_waitgroup_init(neco_waitgroup *waitgroup);
|
|
int neco_waitgroup_add(neco_waitgroup *waitgroup, int delta);
|
|
int neco_waitgroup_done(neco_waitgroup *waitgroup);
|
|
int neco_waitgroup_wait(neco_waitgroup *waitgroup);
|
|
int neco_waitgroup_wait_dl(neco_waitgroup *waitgroup, int64_t deadline);
|
|
/// @}
|
|
|
|
/// @defgroup CondVar Condition variables
|
|
/// A condition variable is a synchronization mechanism that allows coroutines
|
|
/// to suspend execution until some condition is true.
|
|
/// @{
|
|
typedef struct { _Alignas(16) char _[48]; } neco_cond;
|
|
#define NECO_COND_INITIALIZER { 0 }
|
|
|
|
int neco_cond_init(neco_cond *cond);
|
|
int neco_cond_signal(neco_cond *cond);
|
|
int neco_cond_broadcast(neco_cond *cond);
|
|
int neco_cond_wait(neco_cond *cond, neco_mutex *mutex);
|
|
int neco_cond_wait_dl(neco_cond *cond, neco_mutex *mutex, int64_t deadline);
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// file descriptors
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Posix Posix wrappers
|
|
/// Functions that work like their Posix counterpart but do not block, allowing
|
|
/// for usage in a Neco coroutine.
|
|
/// @{
|
|
|
|
// wrappers for various posix operations.
|
|
ssize_t neco_read(int fd, void *data, size_t nbytes);
|
|
ssize_t neco_read_dl(int fd, void *data, size_t nbytes, int64_t deadline);
|
|
ssize_t neco_write(int fd, const void *data, size_t nbytes);
|
|
ssize_t neco_write_dl(int fd, const void *data, size_t nbytes, int64_t deadline);
|
|
int neco_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
|
|
int neco_accept_dl(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int64_t deadline);
|
|
int neco_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
|
int neco_connect_dl(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int64_t deadline);
|
|
int neco_getaddrinfo(const char *node, const char *service,
|
|
const struct addrinfo *hints, struct addrinfo **res);
|
|
int neco_getaddrinfo_dl(const char *node, const char *service,
|
|
const struct addrinfo *hints, struct addrinfo **res, int64_t deadline);
|
|
/// @}
|
|
|
|
/// @defgroup Posix2 File descriptor helpers
|
|
/// Functions for working with file descriptors.
|
|
/// @{
|
|
|
|
// utility for enabling non-blocking on existing file descriptors
|
|
int neco_setnonblock(int fd, bool nonblock, bool *oldnonblock);
|
|
|
|
// wait for a file descriptor to be readable or writeable.
|
|
#define NECO_WAIT_READ 1
|
|
#define NECO_WAIT_WRITE 2
|
|
|
|
int neco_wait(int fd, int mode);
|
|
int neco_wait_dl(int fd, int mode, int64_t deadline);
|
|
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// networking
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Networking Networking utilities
|
|
/// @{
|
|
|
|
int neco_serve(const char *network, const char *address);
|
|
int neco_serve_dl(const char *network, const char *address, int64_t deadline);
|
|
int neco_dial(const char *network, const char *address);
|
|
int neco_dial_dl(const char *network, const char *address, int64_t deadline);
|
|
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// cancelation
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Cancelation Cancelation
|
|
/// @{
|
|
|
|
int neco_cancel(int64_t id);
|
|
int neco_cancel_dl(int64_t id, int64_t deadline);
|
|
|
|
#define NECO_CANCEL_ASYNC 1
|
|
#define NECO_CANCEL_INLINE 2
|
|
#define NECO_CANCEL_ENABLE 3
|
|
#define NECO_CANCEL_DISABLE 4
|
|
|
|
int neco_setcanceltype(int type, int *oldtype);
|
|
int neco_setcancelstate(int state, int *oldstate);
|
|
|
|
#define neco_cleanup_push(routine, arg) {__neco_c0(&(char[32]){0},routine,arg);
|
|
#define neco_cleanup_pop(execute) __neco_c1(execute);}
|
|
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// random number generator
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Random Random number generator
|
|
/// @{
|
|
|
|
#define NECO_CSPRNG 0 // Cryptographically secure pseudorandom number generator
|
|
#define NECO_PRNG 1 // Pseudorandom number generator (non-crypto, faster)
|
|
|
|
int neco_rand_setseed(int64_t seed, int64_t *oldseed);
|
|
int neco_rand(void *data, size_t nbytes, int attr);
|
|
int neco_rand_dl(void *data, size_t nbytes, int attr, int64_t deadline);
|
|
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// signal handling
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Signals Signals
|
|
/// Allows for signals, such as SIGINT (Ctrl-C), to be intercepted or ignored.
|
|
/// @{
|
|
|
|
int neco_signal_watch(int signo);
|
|
int neco_signal_wait(void);
|
|
int neco_signal_wait_dl(int64_t deadline);
|
|
int neco_signal_unwatch(int signo);
|
|
|
|
/// @}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// background worker
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Worker Background worker
|
|
/// Run arbitrary code in a background worker thread
|
|
/// @{
|
|
|
|
int neco_work(int64_t pin, void(*work)(void *udata), void *udata);
|
|
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Stats and information
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Stats Stats and information
|
|
/// @{
|
|
|
|
typedef struct neco_stats {
|
|
size_t coroutines; ///< Number of active coroutines
|
|
size_t sleepers; ///< Number of sleeping coroutines
|
|
size_t evwaiters; ///< Number of coroutines waiting on I/O events
|
|
size_t sigwaiters; ///<
|
|
size_t senders; ///<
|
|
size_t receivers; ///<
|
|
size_t locked; ///<
|
|
size_t waitgroupers; ///<
|
|
size_t condwaiters; ///<
|
|
size_t suspended; ///<
|
|
size_t workers; ///< Number of background worker threads
|
|
} neco_stats;
|
|
|
|
int neco_getstats(neco_stats *stats);
|
|
int neco_is_main_thread(void);
|
|
const char *neco_switch_method(void);
|
|
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// global behaviors
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup GlobalFuncs Global environment
|
|
/// @{
|
|
|
|
void neco_env_setallocator(void *(*malloc)(size_t), void *(*realloc)(void*, size_t), void (*free)(void*));
|
|
void neco_env_setpaniconerror(bool paniconerror);
|
|
void neco_env_setcanceltype(int type);
|
|
void neco_env_setcancelstate(int state);
|
|
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// time and duration
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Time Time
|
|
/// Functions for working with time.
|
|
///
|
|
/// The following defines are available for convenience.
|
|
///
|
|
/// ```c
|
|
/// #define NECO_NANOSECOND INT64_C(1)
|
|
/// #define NECO_MICROSECOND INT64_C(1000)
|
|
/// #define NECO_MILLISECOND INT64_C(1000000)
|
|
/// #define NECO_SECOND INT64_C(1000000000)
|
|
/// #define NECO_MINUTE INT64_C(60000000000)
|
|
/// #define NECO_HOUR INT64_C(3600000000000)
|
|
/// ```
|
|
///
|
|
/// @{
|
|
|
|
#define NECO_NANOSECOND INT64_C(1)
|
|
#define NECO_MICROSECOND INT64_C(1000)
|
|
#define NECO_MILLISECOND INT64_C(1000000)
|
|
#define NECO_SECOND INT64_C(1000000000)
|
|
#define NECO_MINUTE INT64_C(60000000000)
|
|
#define NECO_HOUR INT64_C(3600000000000)
|
|
|
|
int64_t neco_now(void);
|
|
|
|
///@}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// errors
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup ErrorFuncs Error handling
|
|
/// Functions for working with [Neco errors](./API.md#neco-errors).
|
|
/// @{
|
|
|
|
#define NECO_OK 0 ///< Successful result (no error)
|
|
#define NECO_ERROR -1 ///< System error (check errno)
|
|
#define NECO_INVAL -2 ///< Invalid argument
|
|
#define NECO_PERM -3 ///< Operation not permitted
|
|
#define NECO_NOMEM -4 ///< Cannot allocate memory
|
|
#define NECO_EOF -5 ///< End of file or stream (neco_stream_*)
|
|
#define NECO_NOTFOUND -6 ///< No such coroutine (neco_cancel)
|
|
#define NECO_NOSIGWATCH -7 ///< Not watching on a signal
|
|
#define NECO_CLOSED -8 ///< Channel is closed
|
|
#define NECO_EMPTY -9 ///< Channel is empty (neco_chan_tryrecv)
|
|
#define NECO_TIMEDOUT -10 ///< Deadline has elapsed (neco_*_dl)
|
|
#define NECO_CANCELED -11 ///< Operation canceled (by neco_cancel)
|
|
#define NECO_BUSY -12 ///< Resource busy (mutex_trylock)
|
|
#define NECO_NEGWAITGRP -13 ///< Negative waitgroup counter
|
|
#define NECO_GAIERROR -14 ///< Error with getaddrinfo (check neco_gai_error)
|
|
#define NECO_UNREADFAIL -15 ///< Failed to unread byte (neco_stream_unread_byte)
|
|
#define NECO_PARTIALWRITE -16 ///< Failed to write all data (neco_stream_flush)
|
|
#define NECO_NOTGENERATOR -17 ///< Coroutine is not a generator (neco_gen_yield)
|
|
#define NECO_NOTSUSPENDED -18 ///< Coroutine is not suspended (neco_resume)
|
|
|
|
const char *neco_strerror(ssize_t errcode);
|
|
int neco_lasterr(void);
|
|
int neco_gai_lasterr(void);
|
|
int neco_panic(const char *fmt, ...);
|
|
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// streama
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @defgroup Streams Streams and Buffered I/O
|
|
/// Create a Neco stream from a file descriptor using neco_stream_make() or
|
|
/// a buffered stream using neco_stream_make_buffered().
|
|
/// @{
|
|
|
|
typedef struct neco_stream neco_stream;
|
|
|
|
int neco_stream_make(neco_stream **stream, int fd);
|
|
int neco_stream_make_buffered(neco_stream **stream, int fd);
|
|
int neco_stream_close(neco_stream *stream);
|
|
int neco_stream_close_dl(neco_stream *stream, int64_t deadline);
|
|
ssize_t neco_stream_read(neco_stream *stream, void *data, size_t nbytes);
|
|
ssize_t neco_stream_read_dl(neco_stream *stream, void *data, size_t nbytes, int64_t deadline);
|
|
ssize_t neco_stream_write(neco_stream *stream, const void *data, size_t nbytes);
|
|
ssize_t neco_stream_write_dl(neco_stream *stream, const void *data, size_t nbytes, int64_t deadline);
|
|
ssize_t neco_stream_readfull(neco_stream *stream, void *data, size_t nbytes);
|
|
ssize_t neco_stream_readfull_dl(neco_stream *stream, void *data, size_t nbytes, int64_t deadline);
|
|
int neco_stream_read_byte(neco_stream *stream);
|
|
int neco_stream_read_byte_dl(neco_stream *stream, int64_t deadline);
|
|
int neco_stream_unread_byte(neco_stream *stream);
|
|
int neco_stream_flush(neco_stream *stream);
|
|
int neco_stream_flush_dl(neco_stream *stream, int64_t deadline);
|
|
ssize_t neco_stream_buffered_read_size(neco_stream *stream);
|
|
ssize_t neco_stream_buffered_write_size(neco_stream *stream);
|
|
|
|
/// @}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// happy convenience macro
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// int neco_main(int argc, char *argv[]);
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#define neco_main \
|
|
__neco_main(int argc, char *argv[]); \
|
|
static void _neco_main(int argc, void *argv[]) { \
|
|
(void)argc; \
|
|
__neco_exit_prog(__neco_main(*(int*)argv[0], *(char***)argv[1])); \
|
|
} \
|
|
int main(int argc, char *argv[]) { \
|
|
neco_env_setpaniconerror(true); \
|
|
neco_env_setcanceltype(NECO_CANCEL_ASYNC); \
|
|
int ret = neco_start(_neco_main, 2, &argc, &argv); \
|
|
fprintf(stderr, "neco_start: %s (code %d)\n", neco_strerror(ret), ret); \
|
|
return -1; \
|
|
}; \
|
|
int __neco_main
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// private functions, not to be call directly
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void __neco_c0(void*,void(*)(void*),void*);
|
|
void __neco_c1(int);
|
|
void __neco_exit_prog(int);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef EAI_SYSTEM
|
|
#define EAI_SYSTEM 11
|
|
#endif
|
|
|
|
#endif // NECO_H
|