libgreen – Python-style asyncio in C

Introduction

libgreen is a coroutine-based library for friendly asynchronous I/O in C.

API

Library version

The library follows semantic versioning. The (GREEN_MINOR, GREEN_MINOR) pair identifes the API contract version while GREEN_PATCH is incremented for internal changes that fix bugs.

This library’s API offers several facilities for using the library version at compile-time (for conditional compilation) and at run-time (usally for diagnostics).

GREEN_MAJOR

The library’s API version. This value is increased with each breaking API change.

See GREEN_VERSION and GREEN_MAKE_VERSION() for version testing.

GREEN_MINOR

The library’s feature version. This value is increased when adding new features that do not break existing APIs.

See GREEN_VERSION and GREEN_MAKE_VERSION() for version testing.

GREEN_PATCH

The library’s patch version. This value is increased for releases that contain only internal changes, typically to fix bugs without changing the API.

Attention

This library treats a discrepancy between the documentation and the behavior as a bug in the code. If your application relies on a bug in the library, it may be surprised by an apparent change in behavior.

GREEN_VERSION

A single number that encodes GREEN_MAJOR, GREEN_MINOR and GREEN_PATCH for compile-time version testing.

This is typically used to compare against a call to GREEN_MAKE_VERSION.

Here is how to use this macro to handle multiple contract versions in the same library:

Compile-time workaround for API break
#if GREEN_VERSION < GREEN_MAKE_VERSION(1, 0, 0)
    // Workaround for pre-1.0 versions.
#endif

Here is how to use this macro to handle presence and absence of a specific feature:

Compile-time feature testing
#define HAVE_GREEN_XYZ \
    (GREEN_VERSION >= GREEN_MAKE_VERSION(1, 1, 0))

// ...

#if HAVE_GREEN_XYZ
    // Use XYZ.
#endif

See green_version() to see which version your application is currently running against.

GREEN_MAKE_VERSION(major, minor, patch)

Build a value which can be used to compare against GREEN_VERSION or the result of green_version().

See GREEN_VERSION for examples on how to use this macro.

int green_version()

See GREEN_VERSION to see which version your application was compiled against.

GREEN_VERSION_STRING

Build a dotted version string that can be used for display.

See green_version_string() to see which version your application is currently running against.

const char * green_version_string()

Build a dotted version string that can be used for display.

See GREEN_VERSION_STRING() to see which version your application was compiled against.

Library setup

int green_init()
Returns:Zero if the function succeeds.

Note

This function is implemented as a macro.

int green_term()
Returns:Zero if the function succeeds.

Loop

The event loop is the central hub for coordination between a set of coroutine instances running in the same OS thread.

green_loop_t

This is an opaque pointer type to a reference-counted object.

green_loop_t green_loop_init()

Create a new event loop.

Your application can create as many event loops as it wishes, but it make no sense to have more than one per thread at any given time since coroutines in different loops cannot resume each other.

Use green_coroutine_init() to launch new coroutines in the new event loop.

Returns:A new event loop.
int green_loop_acquire(green_loop_t loop)

Increase the reference count.

Returns:Zero if the function succeeds.
int green_loop_release(green_loop_t loop)

Decrease the reference count and destroy the object if necessary.

Returns:Zero if the function succeeds.

Coroutine

green_coroutine_t

This is an opaque pointer type to a reference-counted object.

green_coroutine_t green_coroutine_init(green_loop_t loop, int(*method)(void*,void*), void * object, size_t stack_size)

Call method(loop, object) in a new coroutine.

Parameters:
  • loop – Loop that owns the current coroutine.
  • method – Pointer to application callback that will be called inside a new coroutine.
  • object – Pointer to application data that will be passed uninterpreted to method.
  • stack_size – Size of the stack in bytes. When zero, a default and possibly system-specific stack size is selected.
Returns:

A new coroutine.

Note

This function is implemented as a macro.

int green_yield(green_loop_t loop, green_coroutine_t coro)

Block until any other coroutine yields back.

Parameters:
  • loop – Loop that owns the current coroutine (and coro).
  • coro – Coroutine to which control should be yielded. When NULL, control is returned to the loop.
Returns:

Zero if the function succeeds.

Note

This function is implemented as a macro.

int green_coroutine_acquire(green_coroutine_t coro)

Increase the reference count.

Returns:Zero if the function succeeds.
int green_coroutine_release(green_coroutine_t coro)

Decrease the reference count and destroy the object if necessary.

Returns:Zero if the function succeeds.

Future

The future is the foundation of libgreen. It represents the promise of completion of an asynchronous operation. Combined with the poller and green_select(), it can be used to multiplex multiple asynchronous operations in the same coroutine.

green_future_t

This is an opaque pointer type to a reference-counted object.

green_future_t green_future_init(green_hub_t hub)

Create a custom future. When your asynchronous operation completes, call green_future_set_result() to mark it as complete and unblock a coroutine if one is waiting on this future.

Parameters:
  • hub – Hub to which the future will be attached. The coroutine that completes this future MUST be running from this hub.
Returns:

A future that you can complete whenever you wish.

int green_future_set_result(green_future_t future, void * p, int i)

Mark the future as completed. If any coroutine is currently blocking on green_select() with a poller in which this future is registered, then that coroutine will be unblocked and resumed soon.

Parameters:
Returns:

Zero if the function succeeds.

int green_future_done(green_future_t future)

Check if the future is completd or canceled.

Parameters:
  • future – Future to check for completion.
Returns:

Non-zero if the future is completed or cancelled. Zero if the future is still pending.

int green_future_canceled(green_future_t future)

Check if the future is canceled.

Parameters:
  • future – Future to check for cancellation.
Returns:

Non-zero if the future is cancelled. Zero if the future is still pending or completed.

int green_future_result(green_future_t future, void ** p, int * i)

Return the result that was stored when the future was completed.

Parameters:
Returns:

Zero if the function succeeds, GREEN_EBUSY if the future is pending, GREEN_EBADFD if the future is canceled.

int green_future_cancel(green_future_t future)

Since there is no reason to keep around a cancelled future, canceling a future automatically decrements the reference count and there is no need to call green_future_release() after canceling the future.

Attention

Cancellation is usually asynchronous and may not be natively supported by all asynchronous operations or by all platforms for a given asynchronous operation. The basic cancellation guarantee is that the future will never be returned by green_select(), but the future may not be deleted until it is actually completed.

Parameters:
  • future – The future to cancel.
Returns:

Zero on success.

int green_future_acquire(green_future_t future)

Increase the reference count.

Returns:Zero if the function succeeds.
int green_future_release(green_future_t future)

Decrease the reference count and destroy the object if necessary.

Returns:Zero if the function succeeds.

Poller

The poller is a specialized container that stores a set of future refrences in way that allows very efficient dispatch of future completion and implicit unblocking of a coroutine currently waiting on a completed future.

When you initiate a new asynchronous operation, call green_poller_add() to add the future to the poller. When your coroutine is ready, call green_select() to block until one or more such futures complete.

The poller is similar to the fd_set that is used with select(), but implemented in a way that allows O(1) dispatch.

Attention

Pollers MUST NOT be shared between coroutines. Any modification of a poller on which another coroutine is blocking via green_select() may result in undefined behavior.

green_poller_t

This is an opaque pointer type to a reference-counted object.

Use green_poller_release() when you are done with such a pointer. If you need to grab extra references, call green_poller_acquire(). Make sure you call green_poller_release() once for each call to green_poller_acquire().

green_poller_t green_poller_init(green_hub_t hub, size_t size)

Create a new poller for use with green_select().

Parameters:
  • hub – Hub to which the current coroutine is attached.
  • size – Maximum number of futures you intend on adding to this poller.
Returns:

A new poller that can be passed to green_select(). Call green_poller_release() when you are done with this poller.

size_t green_poller_size(green_poller_t poller)

Get the total number of slots in the future.

Parameters:
  • poller – Poller to check for maximum size.
Returns:

The maximum number of futures that can be stored in the poller.

size_t green_poller_used(green_poller_t poller)

Get the number of slots currently used by futures (pending and completed).

Parameters:
  • poller – Poller to check for current size.
Returns:

The current number of futures stored in the poller.

size_t green_poller_done(green_poller_t poller)

Get the number of slots currently used by completed futures. This number indicates the number of times you can call green_poller_pop() before it returns NULL.

Parameters:
  • poller – Poller to check for completed futures.
Returns:

The number of completed futures currently stored in the poller.

int green_poller_add(green_poller_t poller, green_future_t future)

Add a future to the set.

It is legal to add a completed future to a poller. In that case, the next call to green_select() with that poller will not block. This is especially convenient to handle synchronous completion of some asynchronous operations as it removes the need for an alternate code path to handle synchronous completion – especially for operations that may be synchronous only on some platforms.

Parameters:
  • poller – Poller to which the future should be added.
  • future – Future that should be added to the poller.
Returns:

Zero if the function succeeds.

int green_poller_rem(green_poller_t poller, green_future_t future)

Remove a future from the poller without fulfilling or cancelling it.

Futures are automatically removed from the poller when fulfilled or cancelled. However, the implicit removal of a cancelled future may be asynchronous. If you cancel a future and then need the slot immediately to add a new future, you can call this to free the slot immediately.

Parameters:
  • poller – Poller from which the future should be removed.
  • future – Future that should be removed from the poller.
Returns:

Zero if the function succeeds.

green_future_t green_poller_pop(green_poller_t poller)

Grab the next completed future from the poller. You can call green_poller_done() to determine how many times you can call this function before it returns NULL.

Parameters:
  • poller – Poller from which a completed future should be removed.
Returns:

A completed future. If poller contains no completed futures, NULL is returned.

int green_poller_acquire(green_poller_t poller)

Increase the reference count.

Returns:Zero if the function succeeds.
int green_poller_release(green_poller_t poller)

Decrease the reference count and destroy the object if necessary.

Returns:Zero if the function succeeds.
green_future_t green_select(green_poller_t poller)

Block until any of the futures registered in poller are completed. If the poller contains any completed futures, the function returns immediately.

Parameters:
  • poller – The poller in which all futures that can unblock the current coroutine are registered.
Returns:

A completed future if the function succeeds, else NULL.

Note

This function is implemented as a macro.

Error codes

GREEN_SUCCESS

The call completed successfully. This value is guaranteed to be zero.

GREEN_EBUSY

The future is still pending.

GREEN_ENOMEM

Could not allocate enough memory.

GREEN_EBADFD

The future is in an invalid state.

GREEN_ECANCELED

Cannot complete the future because it is already canceled.

GREEN_EALREADY

Cannot add the future to the poller because the future is alredy in a poller.

GREEN_ENOENT

Cannot remove the future from the poller because it was not found inside the poller.

GREEN_ENFILE

Cannot add the future to the poller because the poller is already full.

Indices and tables