A large amount of refactoring is undergoing, and the code as it is currently will not function. I am commiting these changes as a form of backup. When the last commit that doesn't break the code is done, I will remove this warning.
Simple C library for asynchronous computation and event processing.
This library was written to abstract common inherently asynchronous operations such as waiting on non-blocking file descriptors and executing tasks asynchronously. Asyncio provides a simple API that hides the multithreading issues and allows the programmer not to worry about writing his or her own eventloops and threadpools.
The API currently allows the following functionalities:
- Dispatch a function to be executed asynchronously by a threadpool.
- Wait for events on a file descriptor, and call a user-provided callback when an event occurs.
- Execute a function asynchronously after a specified amount of time.
- Blocking until a previous asynchronous task has completed (joining).
- Cancelling an asynchronous task.
For more details about the API, see the API section.
The library was designed to comply with POSIX and standard C, so it should hopefully compile and run on any POSIX system with pthreads and a decent C compiler. So far, it has only been tested on Ubuntu Linux 12.04 (64-bit) and OS X 10.8.5 (64-bit), so let me know if you have tried it on other systems.
To build the library, simply type make
.
To also build tests, type make tests
.
To also build examples, type make examples
.
To clean everything produced during make, type make clean
.
The library uses POSIX pthreads and the poll() system call interface for file descriptor multiplexing.
asyncio_dispatch
int asyncio_dispatch(asyncio_dispatch_fn fn, void *arg, asyncio_flag_t flags, asyncio_handle_t *handle);
Dispatch a function to be executed asynchronously by a threadpool.
Parameter | Purpose |
---|---|
fn |
Pointer to function that will be executed. |
arg |
Argument passed to the function. Can be NULL . |
flags |
Flags controlling specific options for the threadpool (see FLAGS). |
handle |
Pointer to returned asyncio_handle . |
asyncio_fdevent
int asyncio_fdevent(int fd, asyncio_fdevent_t events, asyncio_fdevent_cb cb, void *arg, asyncio_flag_t flags, asyncio_handle_t *handle);
Wait for events on a file descriptor and call the user-provided callback when an event occurs.
The event is only caught once; to wait for following events, use asyncio_continue
or re-register with asyncio_fdevent
.
Parameter | Purpose |
---|---|
fd |
File descriptor to wait on. |
events |
Events to wait for. Possible values are ASYNCIO_FDEVENT_READ , ASYNCIO_FDEVENT_WRITE and ASYNCIO_FDEVENT_ERROR . |
cb |
Pointer to callback function called when event occurs. |
arg |
Argument passed to the function. Can be NULL . |
flags |
Flags controlling specific options for the threadpool (see FLAGS). |
handle |
Pointer to returned asyncio_handle . |
asyncio_timevent
int asyncio_timevent(asyncio_time_t timeout, asyncio_timevent_cb cb, void *arg, asyncio_flag_t flags, asyncio_handle_t *handle);
Execute a user-provided callback after a certain amount of time has elapsed.
Parameter | Purpose |
---|---|
timeout |
Time to wait before calling the callback (in milliseconds). |
cb |
Pointer to callback function called when timeout occurs. |
arg |
Argument passed to the function. Can be NULL . |
flags |
Flags controlling specific options for the threadpool (see FLAGS). |
handle |
Pointer to returned asyncio_handle . |
asyncio_join
int asyncio_join(asyncio_handle_t handle);
Block until the task referred to by the handle has completed.
This can be used on any handle which was returned from successful calls to asyncio_dispatch
, asyncio_fdevent
or asyncio_timevent
. Completion means that the function/callback has completed execution or that the task was cancelled by a call to asyncio_cancel
. For fdevents or timevents, if asyncio_continue
is called within the callback then the task does not count as completed.
Parameter | Purpose |
---|---|
handle |
Handle initialized by successful calls to asyncio_dispatch , asyncio_fdevent or asyncio_timevent . |
asyncio_cancel
int asyncio_cancel(asyncio_handle_t handle);
Cancel task referred to by the handle.
Task cancellability is controlled using the flag
parameter when creating the task. The ASYNCIO_FLAG_CANCELLABLE
flag allows a task to be cancellable (default is not cancellable). The ASYNCIO_FLAG_ASYNCCANCEL
flag allows the task to be asynchronously cancellable (default is deferred cancellation). A task without the ASYNCIO_FLAG_CANCELLABLE
flag set will not actually be cancelled after this function is called.
The difference between asynchronous and deferred cancellation is that asynchronously cancellable threads can be cancelled any time, while deferred cancellable threads will be cancelled when they reach a cancellation point. See the pthread documentation for more information (man pthread_setcanceltype
).
Care must be taken when allowing threads to be cancelled, because you might get memory leaks or deadlock if the thread allocated ressources or acquired a lock before getting cancelled, and did not have the time to release it.
Parameter | Purpose |
---|---|
handle |
Handle initialized by successful calls to asyncio_dispatch , asyncio_fdevent or asyncio_timevent . |
asyncio_acquire
int asyncio_acquire(asyncio_handle_t handle);
Acquire reference to handle before passing it to another thread.
This is not required when using a handle obtained from a successful call to asyncio_dispatch
, asyncio_fdevent
or asyncio_timevent
because they acquire a reference by default. It is mostly only needed if the thread that got this handle decides to pass it to another thread. This prevents race conditions where one thread uses the handle after its ressources have been freed by the other thread. The ressources will be freed only once all references to the handle have been released.
Parameter | Purpose |
---|---|
handle |
Handle initialized by successful calls to asyncio_dispatch , asyncio_fdevent or asyncio_timevent . |
asyncio_release
void asyncio_release(asyncio_handle_t handle);
Release reference to handle.
After this function is called, the calling thread must not use the handle anymore and not be passed to any other API functions. It must be considered as garbage. Other threads may still hold references to the handle and use it normally, so its ressources will be actually freed only once all references have been released. The API functions that return a handle always acquire a reference for the caller, so the caller must release the handle once it decides not to use it anymore in order to free its allocated ressources.
Parameter | Purpose |
---|---|
handle |
Handle initialized by successful calls to asyncio_dispatch , asyncio_fdevent or asyncio_timevent . |
asyncio_continue
void asyncio_continue(asyncio_continue_t continued);
Re-register the handle to continue waiting on event or timeout.
This is implemented as a macro, and is called within an asyncio_fdevent_cb
or asyncio_timevent_cb
to indicate that the task has not completed yet (the callback will be called again next time the event or timeout occurs). The task is considered completed only when the executed code path in the callback does not call asyncio_continue
anymore.
FLAGS
The following flags can be set independently in any of the asyncio_dispatch
, asyncio_fdevent
or asyncio_timevent
functions:
ASYNCIO_FLAG_CONTRACTOR
: Use a "contractor thread" instead of "worker thread" to run function/callback. The threadpool uses a fixed amount of worker threads executing tasks in a queue, but can also spawn additional threads called "contractors" if the user desires. This may be useful if the function will take a long time to complete, in order to avoid stalling the worker queue, but it does not lead to scalable code because eventually the operating system's thread limit will be reached.ASYNCIO_FLAG_CANCELLABLE
: Allow a task to be cancellable. Seeasyncio_cancel
and the pthread documentation for details.ASYNCIO_FLAG_ASYNCCANCEL
: Make task asynchronously cancellable. Seeasyncio_cancel
and the pthread documentation for details. Note that if this is set whileASYNCIO_FLAG_CANCELLABLE
is not set, this will have no effect.
Return values
All functions that return int
return 0
when successful, and -1
on failure.
Currently there isn't a very detailed error code mechanism, instead errors are logged through the ASYNCIO_SYSERROR
and ASYNCIO_ERROR
macros.
Certain #defines can be tweaked to get better performance on your system. They can usually be found at the top of the relevant source files.
Some examples that might be interesting:
MAX_WORKER_THREADS
(threadpool.c): Controls number of worker threads used for threadpool. Ideally should be close to number of cores. The default is set to 5.MAX_CONTRACTORS
(threadpool.c): Controls maximum number of contractor threads that can be spawned. Defaults to 1024.MAX_POLLFDS
(fdevents.c): Controls maximum number of file descriptors handled at once bypoll()
. The default is set to 10000.MAX_DEADLINES
(timevents.c): Controls maximum number of deadlines that can be handled by the timevents module. Defaults to 10000.MALLOC_IS_THREAD_SAFE
(safe_malloc.c): If defined, assumes the standard C library implementation formalloc()
is thread-safe. Defined by default in Makefile. Thesafe_malloc
macro will expand tomalloc
or tomalloc_locked
, which is a wrapper aroundmalloc
that uses a mutex before the call.DEBUG
(logging.c): If defined, detailed debugging information will be displayed with theASYNCIO_DEBUG_*
macros. These macros have not been used throughout the whole library due to the clutter they create in the code. They mostly show up in the threadpool code, used during initial debugging stages. Note that the debug macros will affect timing, and certain bugs may be triggered or hidden by using these macros. This is disabled by default.