diff --git a/CMakeLists.txt b/CMakeLists.txt index e36d5dd..41badd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,12 +13,13 @@ option(GREEN_VALGRIND "Run tests with memory leak checker." OFF) if (GREEN_GCOV) message(STATUS "Code coverage enabled.") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 --coverage") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 --coverage") set(CMAKE_STATIC_LINKER_FLAGS="--coverage") set(CMAKE_MODULE_LINKER_FLAGS="--coverage") set(CMAKE_EXE_LINKER_FLAGS="--coverage") endif() +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") + # Configure stuff. try_run(HAVE_UCONTEXT_RESULT HAVE_UCONTEXT "${CMAKE_CURRENT_BINARY_DIR}/have-ucontext" @@ -59,6 +60,9 @@ add_library(green "src/green.c" ) +# libm is required for functions from . +target_link_libraries(green m) + # This enables `ctest -T memcheck`. if (GREEN_VALGRIND) find_program(MEMORYCHECK_COMMAND "valgrind") @@ -80,4 +84,6 @@ if(GREEN_TESTS) green_add_test(test-init-term "tests/test-init-term.c") green_add_test(test-loop "tests/test-loop.c") green_add_test(test-coroutine "tests/test-coroutine.c") + green_add_test(test-poller "tests/test-poller.c") + green_add_test(test-future "tests/test-future.c") endif() diff --git a/docs/index.rst b/docs/index.rst index c58c4f1..31b22f1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -198,26 +198,269 @@ Coroutine .. note:: This function is implemented as a macro. -.. c:function:: green_future_t green_coroutine_join(green_coroutine_t coro) +.. c:function:: int green_coroutine_acquire(green_coroutine_t coro) - :arg coro: Coroutine whose completion you are interested in. - :return: A future that will be completed when ``coro`` returns. + Increase the reference count. - .. note:: Cancelling this future does **not** cancel the coroutine. + :return: Zero if the function succeeds. -.. c:function:: int green_coroutine_acquire(green_coroutine_t coro) +.. c:function:: int green_coroutine_release(green_coroutine_t coro) + + Decrease the reference count and destroy the object if necessary. + + :return: Zero if the function succeeds. + + +.. _future: + +Future +~~~~~~ + +The future is the foundation of ``libgreen``. It represents the promise of +completion of an asynchronous operation. Combined with the poller_ and +:c:func:`green_select`, it can be used to multiplex multiple asynchronous +operations in the same coroutine_. + +.. c:type:: green_future_t + + This is an opaque pointer type to a reference-counted object. + +.. c:function:: green_future_t green_future_init(green_hub_t hub) + + Create a custom future. When your asynchronous operation completes, call + :c:func:`green_future_set_result` to mark it as complete and unblock a + coroutine if one is waiting on this future. + + :arg hub: Hub to which the future will be attached. The coroutine that + completes this future **MUST** be running from this hub. + :return: A future that you can complete whenever you wish. + +.. c:function:: 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 + :c:func:`green_select` with a poller in which this future is registered, + then that coroutine will be unblocked and resumed soon. + + :arg future: Future to complete. + :arg p: Pointer result. Will be returned by :c:func:`green_future_result`. + :arg i: Integer result. Will be returned by :c:func:`green_future_result`. + :return: Zero if the function succeeds. + +.. c:function:: int green_future_done(green_future_t future) + + Check if the future is completd or canceled. + + :arg future: Future to check for completion. + :return: Non-zero if the future is completed or cancelled. Zero if the + future is still pending. + +.. c:function:: int green_future_canceled(green_future_t future) + + Check if the future is canceled. + + :arg future: Future to check for cancellation. + :return: Non-zero if the future is cancelled. Zero if the future is still + pending or completed. + +.. c:function:: int green_future_result(green_future_t future, void ** p, int * i) + + Return the result that was stored when the future was completed. + + :arg future: Future to check for result after completion. + :arg p: Pointer into which the value passed to + :c:func:`green_future_set_result` will be stored. + :arg p: Integer into which the value passed to + :c:func:`green_future_set_result` will be stored. + :return: Zero if the function succeeds, :c:macro:`GREEN_EBUSY` if the future + is pending, :c:macro:`GREEN_EBADFD` if the future is canceled. + +.. c:function:: 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 :c:func:`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 :c:func:`green_select`, but the future + may not be deleted until it is actually completed. + + :arg future: The future to cancel. + :return: Zero on success. + +.. c:function:: int green_future_acquire(green_future_t future) Increase the reference count. :return: Zero if the function succeeds. -.. c:function:: int green_coroutine_release(green_coroutine_t coro) +.. c:function:: int green_future_release(green_future_t future) Decrease the reference count and destroy the object if necessary. :return: Zero if the function succeeds. +.. _poller: + +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 :c:func:`green_poller_add` +to add the future_ to the poller. When your coroutine is ready, call +:c:func:`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 + :c:func:`green_select` may result in undefined behavior. + +.. c:type:: green_poller_t + + This is an opaque pointer type to a reference-counted object. + + Use :c:func:`green_poller_release` when you are done with such a pointer. + If you need to grab extra references, call :c:func:`green_poller_acquire`. + Make sure you call :c:func:`green_poller_release` once for each call to + :c:func:`green_poller_acquire`. + +.. c:function:: green_poller_t green_poller_init(green_hub_t hub, size_t size) + + Create a new poller for use with :c:func:`green_select`. + + :arg hub: Hub to which the current coroutine is attached. + :arg size: Maximum number of futures you intend on adding to this poller. + :return: A new poller that can be passed to :c:func:`green_select`. Call + :c:func:`green_poller_release` when you are done with this poller. + +.. c:function:: size_t green_poller_size(green_poller_t poller) + + Get the total number of slots in the future. + + :arg poller: Poller to check for maximum size. + :return: The maximum number of futures that can be stored in the poller. + +.. c:function:: size_t green_poller_used(green_poller_t poller) + + Get the number of slots currently used by futures (pending and completed). + + :arg poller: Poller to check for current size. + :return: The current number of futures stored in the poller. + +.. c:function:: 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 :c:func:`green_poller_pop` before + it returns ``NULL``. + + :arg poller: Poller to check for completed futures. + :return: The number of completed futures currently stored in the poller. + +.. c:function:: 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 :c:func:`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. + + :arg poller: Poller to which the future should be added. + :arg future: Future that should be added to the poller. + :return: Zero if the function succeeds. + +.. c:function:: 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. + + :arg poller: Poller from which the future should be removed. + :arg future: Future that should be removed from the poller. + :return: Zero if the function succeeds. + +.. c:function:: green_future_t green_poller_pop(green_poller_t poller) + + Grab the next completed future from the poller. You can call + :c:func:`green_poller_done` to determine how many times you can call this + function before it returns ``NULL``. + + :arg poller: Poller from which a completed future should be removed. + :return: A completed future. If ``poller`` contains no completed futures, + ``NULL`` is returned. + +.. c:function:: int green_poller_acquire(green_poller_t poller) + + Increase the reference count. + + :return: Zero if the function succeeds. + +.. c:function:: int green_poller_release(green_poller_t poller) + + Decrease the reference count and destroy the object if necessary. + + :return: Zero if the function succeeds. + +.. c:function:: 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. + + :arg poller: The poller in which all futures that can unblock the current + coroutine are registered. + :return: A completed future if the function succeeds, else ``NULL``. + + .. note:: This function is implemented as a macro. + +Error codes +~~~~~~~~~~~ + +.. c:macro:: GREEN_SUCCESS + + The call completed successfully. This value is guaranteed to be zero. + +.. c:macro:: GREEN_EBUSY + + The future is still pending. + +.. c:macro:: GREEN_ENOMEM + + Could not allocate enough memory. + +.. c:macro:: GREEN_EBADFD + + The future is in an invalid state. + +.. c:macro:: GREEN_ECANCELED + + Cannot complete the future because it is already canceled. + +.. c:macro:: GREEN_EALREADY + + Cannot add the future to the poller because the future is alredy in a + poller. + +.. c:macro:: GREEN_ENOENT + + Cannot remove the future from the poller because it was not found inside the + poller. + +.. c:macro:: GREEN_ENFILE + + Cannot add the future to the poller because the poller is already full. + Indices and tables ================== diff --git a/include/green.h b/include/green.h index bd629a7..6d0e499 100644 --- a/include/green.h +++ b/include/green.h @@ -28,6 +28,17 @@ GREEN_STRING(GREEN_MINOR) "." \ GREEN_STRING(GREEN_PATCH) +// Error codes. +#define GREEN_SUCCESS 0 +#define GREEN_EINVAL 1 +#define GREEN_ENOMEM 2 +#define GREEN_EBUSY 3 +#define GREEN_ECANCELED 4 +#define GREEN_EALREADY 5 +#define GREEN_ENOENT 6 +#define GREEN_ENFILE 7 +#define GREEN_EBADFD 8 + // Lib version. int green_version(); const char * green_version_string(); @@ -64,4 +75,33 @@ int green_coroutine_result(green_coroutine_t coro); int green_coroutine_acquire(green_coroutine_t coro); int green_coroutine_release(green_coroutine_t coro); +// Future. +typedef struct green_future * green_future_t; +green_future_t green_future_init(green_loop_t loop); +int green_future_done(green_future_t future); +int green_future_canceled(green_future_t future); +int green_future_set_result(green_future_t future, void * p, int i); +int green_future_result(green_future_t future, void ** p, int * i); +int green_future_cancel(green_future_t future); + +int green_future_acquire(green_future_t future); +int green_future_release(green_future_t future); + +// Poller. +typedef struct green_poller * green_poller_t; +green_poller_t green_poller_init(green_loop_t loop, size_t size); +size_t green_poller_size(green_poller_t poller); +size_t green_poller_used(green_poller_t poller); +size_t green_poller_done(green_poller_t poller); +int green_poller_add(green_poller_t poller, green_future_t future); +int green_poller_rem(green_poller_t poller, green_future_t future); +green_future_t green_poller_pop(green_poller_t poller); + +int green_poller_acquire(green_poller_t poller); +int green_poller_release(green_poller_t poller); + +green_future_t _green_select(green_poller_t poller, const char * source); +#define green_select(poller) \ + green_select_ex(poller, timeout, __FILE__ ":" GREEN_STRING(__LINE__)) + #endif // _GREEN_H__ diff --git a/src/green.c b/src/green.c index 97dab0d..62bd342 100644 --- a/src/green.c +++ b/src/green.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "configure.h" #if GREEN_USE_UCONTEXT @@ -57,6 +58,44 @@ struct green_coroutine { const char * source; }; + +typedef enum green_future_state { + + green_future_pending, + green_future_aborted, + green_future_complete, + +} green_future_state_t; + +struct green_future { + + green_loop_t loop; + int refs; + + struct { + void * p; + int i; + } result; + + green_future_state_t state; + + // Intrusive set. + green_poller_t poller; + int slot; +}; + +struct green_poller { + + green_loop_t loop; + int refs; + + // Intrusive set. + green_future_t * futures; + size_t used; + size_t size; + size_t busy; +}; + #define green_panic() \ do { \ fflush(stderr); \ @@ -100,17 +139,17 @@ const char * green_version_string() int _green_init(int major, int minor) { if (major != GREEN_MAJOR) { - return 1; + return GREEN_EINVAL; } if (minor > GREEN_MINOR) { - return 1; + return GREEN_EINVAL; } - return 0; + return GREEN_SUCCESS; } int green_term() { - return 0; + return GREEN_SUCCESS; } green_loop_t green_loop_init() @@ -128,7 +167,7 @@ int green_loop_acquire(green_loop_t loop) { green_assert(loop != NULL); ++loop->refs; - return 0; + return GREEN_SUCCESS; } int green_loop_release(green_loop_t loop) @@ -137,7 +176,7 @@ int green_loop_release(green_loop_t loop) green_assert(loop->refs > 0); if (--(loop->refs) > 0) { - return 0; + return GREEN_SUCCESS; } green_assert(loop != NULL); @@ -145,7 +184,7 @@ int green_loop_release(green_loop_t loop) green_assert(loop->coroutines == 0); green_free(loop); - return 0; + return GREEN_SUCCESS; } static void _coroutine(green_coroutine_t coro) @@ -252,7 +291,7 @@ int _green_yield(green_loop_t loop, green_coroutine_t coro, const char * source) loop->currentcoro->state = running; } - return 0; + return GREEN_SUCCESS; } int green_coroutine_result(green_coroutine_t coro) @@ -266,7 +305,7 @@ int green_coroutine_acquire(green_coroutine_t coro) { green_assert(coro != NULL); ++coro->refs; - return 0; + return GREEN_SUCCESS; } int green_coroutine_release(green_coroutine_t coro) @@ -282,5 +321,352 @@ int green_coroutine_release(green_coroutine_t coro) --coro->loop->coroutines; green_free(coro); } - return 0; + return GREEN_SUCCESS; +} + +static void green_poller_dump(green_poller_t poller) +{ + static const char* states[] = { + "pending", + "aborted", + "complete", + }; + fprintf(stderr, "poller @ 0x%p, size=%zu, used=%zu, busy=%zu:\n", + poller, poller->size, poller->used, poller->busy); + int w = (int)ceil(log10((double)poller->size)); + for (size_t i = 0; (i < poller->size); ++i) { + green_future_t f = poller->futures[i]; + if (i == 0) { + fprintf(stderr, " --busy--\n"); + } + if (i == poller->busy) { + fprintf(stderr, " --done--\n"); + } + if (i == poller->used) { + fprintf(stderr, " --free--\n"); + } + if (f) { + fprintf(stderr, " %*zu => (%*d) %p (%s)\n", + w, i, w, f->slot, f, states[f->state]); + } + else { + fprintf(stderr, " %*zu => null\n", w, i); + } + } + fprintf(stderr, " --------\n"); +} + +static void green_poller_swap(green_poller_t poller, int lhs, int rhs) +{ + fprintf(stderr, "swapping %d and %d.\n", lhs, rhs); + if (lhs == rhs) { + return; + } + green_poller_dump(poller); + green_future_t f1 = poller->futures[lhs]; + green_future_t f2 = poller->futures[rhs]; + green_assert(f1->slot == lhs); + green_assert(f2->slot == rhs); + poller->futures[lhs] = f2, f2->slot = lhs; + poller->futures[rhs] = f1, f1->slot = rhs; + green_poller_dump(poller); +} + +green_poller_t green_poller_init(green_loop_t loop, size_t size) +{ + if ((loop == NULL) || (size == 0)) { + return NULL; + } + green_assert(loop->refs > 0); + + green_poller_t poller = green_malloc(sizeof(struct green_poller)); + poller->loop = loop; + poller->refs = 1; + poller->futures = green_malloc(size * sizeof(green_future_t)); + poller->size = size; + + return poller; +} + +size_t green_poller_size(green_poller_t poller) +{ + if (poller == NULL) { + return 0; + } + green_assert(poller->loop != NULL); + green_assert(poller->size >= poller->used); + green_assert(poller->used >= poller->busy); + return poller->size; +} + +size_t green_poller_used(green_poller_t poller) +{ + if (poller == NULL) { + return 0; + } + green_assert(poller->loop != NULL); + green_assert(poller->size >= poller->used); + green_assert(poller->used >= poller->busy); + return poller->used; +} + +size_t green_poller_done(green_poller_t poller) +{ + if (poller == NULL) { + return 0; + } + green_assert(poller->loop != NULL); + green_assert(poller->size >= poller->used); + green_assert(poller->used >= poller->busy); + return poller->used - poller->busy; +} + +int green_poller_acquire(green_poller_t poller) +{ + if (poller == NULL) { + return GREEN_EINVAL; + } + green_assert(poller->refs > 0); + ++poller->refs; + return GREEN_SUCCESS; +} + +int green_poller_release(green_poller_t poller) +{ + fprintf(stderr, "green_poller_release()\n"); + + if (poller == NULL) { + return GREEN_EINVAL; + } + green_assert(poller->refs > 0); + if (--poller->refs == 0) { + for (int i = 0; i < poller->used; ++i) { + poller->futures[i]->poller = NULL; + poller->futures[i]->slot = -1; + green_future_release(poller->futures[i]); + poller->futures[i] = NULL; + } + green_free(poller->futures); + poller->futures = NULL; + poller->loop = NULL; + green_free(poller); + } + return GREEN_SUCCESS; +} + +int green_poller_add(green_poller_t poller, green_future_t future) +{ + fprintf(stderr, "green_poller_add()\n"); + + // Check for required arguments. + if ((poller == NULL) || (future == NULL)) { + return GREEN_EINVAL; + } + + // Event loop must match. + if (future->loop != poller->loop) { + return GREEN_EINVAL; + } + + // Future cannot be registered twice. + if (future->poller != NULL) { + return GREEN_EALREADY; + } + + // Cannot exceed maximum poller size. + if (poller->used == poller->size) { + return GREEN_ENFILE; + } + + green_assert(poller->refs > 0); + green_assert(future->refs > 0); + green_assert(future->slot < 0); + green_assert(poller->used >= 0); + green_assert(poller->used <= poller->size); + + green_poller_dump(poller); + + green_future_acquire(future); + poller->futures[poller->used] = future; + future->slot = poller->used++; + future->poller = poller; + if (future->state == green_future_pending) { + green_poller_swap(poller, future->slot, poller->busy++); + } + + return GREEN_SUCCESS; +} + +int green_poller_rem(green_poller_t poller, green_future_t future) +{ + fprintf(stderr, "green_poller_rem()\n"); + if ((poller == NULL) || (future == NULL)) { + return GREEN_EINVAL; + } + if (future->poller != poller) { + return GREEN_ENOENT; + } + + green_assert(poller->refs > 0); + green_assert(future->refs > 0); + green_assert(poller->loop == future->loop); + green_assert(future->poller == poller); + green_assert(future->slot >= 0); + + if (future->slot >= poller->busy) { + green_assert(future->state != green_future_pending); + green_poller_swap(poller, future->slot, poller->used-1); + } + else { + green_assert(future->state == green_future_pending); + green_poller_swap(poller, future->slot, --poller->busy); + green_poller_swap(poller, poller->busy, poller->used-1); + } + future->slot = -1; + future->poller = NULL; + poller->futures[--poller->used] = NULL; + green_future_release(future); + return GREEN_SUCCESS; +} + +green_future_t green_poller_pop(green_poller_t poller) +{ + fprintf(stderr, "green_poller_pop()\n"); + + if (poller == NULL) { + return NULL; + } + green_poller_dump(poller); + if ((poller->used == 0) || (poller->busy == poller->used)) { + return NULL; + } + green_future_t f = poller->futures[poller->busy]; + green_assert(green_poller_rem(poller, f) == 0); + return f; +} + +green_future_t green_future_init(green_loop_t loop) +{ + if (loop == NULL) { + return NULL; + } + green_future_t future = green_malloc(sizeof(struct green_future)); + future->loop = loop; + future->state = green_future_pending; + future->refs = 1; + future->slot = -1; + return future; +} + +int green_future_acquire(green_future_t future) +{ + if (future == NULL) { + return GREEN_EINVAL; + } + green_assert(future != NULL); + green_assert(future->refs > 0); + ++future->refs; + return GREEN_SUCCESS; +} + +int green_future_release(green_future_t future) +{ + if (future == NULL) { + return GREEN_EINVAL; + } + fprintf(stderr, "green_future_release(%p, %d)\n", future, future->refs); + // NOTE: async operations hold an implicit ref count, so there is no risk + // of async operations dereferencing a dangling pointer when + // attempting to resolve the future. + green_assert(future != NULL); + green_assert(future->refs > 0); + if (--future->refs == 0) { + green_assert(future->poller == NULL); + green_free(future); + } + return GREEN_SUCCESS; +} + +int green_future_done(green_future_t future) +{ + if (future == NULL) { + return GREEN_EINVAL; + } + green_assert(future->refs > 0); + return (future->state == green_future_complete) || + (future->state == green_future_aborted); +} + +int green_future_set_result(green_future_t future, void * p, int i) +{ + if (future == NULL) { + return GREEN_EINVAL; + } + green_assert(future->refs > 0); + + if (future->state == green_future_aborted) { + return GREEN_ECANCELED; + } + if (future->state == green_future_complete) { + return GREEN_EBADFD; + } + + // Store result. + future->result.i = i; + future->result.p = p; + + // Mark as complete. + future->state = green_future_complete; + + // Restore poller invariant. + if (future->poller) { + green_poller_swap(future->poller, + future->slot, --future->poller->busy); + + // TODO: resume coroutine blocked on poller, if any. + } + + return GREEN_SUCCESS; +} + +int green_future_result(green_future_t future, void ** p, int * i) +{ + if (future == NULL) { + return GREEN_EINVAL; + } + green_assert(future->refs > 0); + + if (future->state == green_future_pending) { + return GREEN_EBUSY; + } + if (future->state == green_future_aborted) { + return GREEN_EBADFD; + } + if (p) { + *p = future->result.p; + } + if (i) { + *i = future->result.i; + } + return GREEN_SUCCESS; +} + +int green_future_cancel(green_future_t future) +{ + // NOTE: when the async operation completes, the attempt to resolve the + // future will may omit to post a completion notification if it is + // possible to do so. However, it's also possible that the + // completion notification is already in flight. In that case, the + // coroutine that wakes up should also avoid to return control to + // application code as a result of the completion notification for a + // canceled future. + if (future == NULL) { + return GREEN_EINVAL; + } + green_assert(future->refs > 0); + if (future->state != green_future_pending) { + return GREEN_EBADFD; + } + future->state = green_future_aborted; + return GREEN_SUCCESS; } diff --git a/tests/loop-fixture.c b/tests/loop-fixture.c index 2ce6c8f..daf0eba 100644 --- a/tests/loop-fixture.c +++ b/tests/loop-fixture.c @@ -10,30 +10,12 @@ int main(int argc, char ** argv) fprintf(stderr, "green library version: \"%s\".\n", green_version_string()); - if (green_init()) { - fprintf(stderr, "green_init()\n"); - return EXIT_FAILURE; - } + check_eq(green_init(), 0); green_loop_t loop = green_loop_init(); - if (loop == NULL) { - fprintf(stderr, "green_loop_init()\n"); - return EXIT_FAILURE; - } - - if (test(loop) != 0) { - fprintf(stderr, "green_test()\n"); - return EXIT_FAILURE; - } - - if (green_loop_release(loop)) { - fprintf(stderr, "green_loop_release()\n"); - return EXIT_FAILURE; - } - loop = NULL; - if (green_term()) { - fprintf(stderr, "green_term()\n"); - return EXIT_FAILURE; - } + check_ne(loop, NULL); + check_eq(test(loop), 0); + check_eq(green_loop_release(loop), 0); loop = NULL; + check_eq(green_term(), 0); return EXIT_SUCCESS; } diff --git a/tests/test-coroutine.c b/tests/test-coroutine.c index a68129d..834f99d 100644 --- a/tests/test-coroutine.c +++ b/tests/test-coroutine.c @@ -7,16 +7,9 @@ int mycoroutine(green_loop_t loop, void * object) { fprintf(stderr, "in coroutine (%d).\n", __COUNTER__); - if (loop == NULL) { - return 1; - } - if (object != NULL) { - return 1; - } - - if (green_yield(loop, NULL)) { - return 2; - } + check_ne(loop, NULL); + check_eq(object, NULL); + check_eq(green_yield(loop, NULL), 0); fprintf(stderr, "in coroutine (%d).\n", __COUNTER__); return 777; diff --git a/tests/test-future.c b/tests/test-future.c new file mode 100644 index 0000000..b13c640 --- /dev/null +++ b/tests/test-future.c @@ -0,0 +1,101 @@ +//////////////////////////////////////////////////////////////////////// +// Copyright(c) libgreen contributors. See LICENSE file for details. // +//////////////////////////////////////////////////////////////////////// + +#include "loop-fixture.h" + +int test(green_loop_t loop) +{ + green_future_t f = NULL; + + // Poller is required. + f = green_future_init(NULL); + check_eq(f, NULL); + + // Common case. + f = green_future_init(loop); + check_ne(f, NULL); + + void * p = NULL; + int i = 0; + + // Future is required. + check_eq(green_future_result(NULL, &p, &i), GREEN_EINVAL); + check_eq(p, NULL); + check_eq(i, 0); + + // Future is required. + check_eq(green_future_set_result(NULL, p, i), GREEN_EINVAL); + + // Future is required. + check_eq(green_future_acquire(NULL), GREEN_EINVAL); + check_eq(green_future_release(NULL), GREEN_EINVAL); + + // Check result before completion is not supported. + check_eq(green_future_result(f, &p, &i), GREEN_EBUSY); + + // Function is NULL-proof. + check_ne(green_future_done(NULL), 0); + + // Future is not done until set_result is called. + check_eq(green_future_done(f), 0); + + // Once complete, we should be able to get the result. + check_eq(green_future_set_result(f, &i, 7), 0); + check_eq(green_future_result(f, &p, &i), 0); + check_eq(p, &i); + check_eq(i, 7); + + // After set_result, future is done. + check_ne(green_future_done(f), 0); + + // We can get a sub-set of the result. + p = NULL; + i = 0; + check_eq(green_future_result(f, &p, NULL), 0); + check_eq(p, &i); + check_eq(i, 0); + + // We can get a sub-set of the result. + p = NULL; + i = 0; + check_eq(green_future_result(f, NULL, &i), 0); + check_eq(p, NULL); + check_eq(i, 7); + + // Setting the result again is prohibited. + check_eq(green_future_set_result(f, &i, 7), GREEN_EBADFD); + + // Let's get us another future. + check_eq(green_future_release(f), 0); f = NULL; + f = green_future_init(loop); + check_ne(f, NULL); + + // Future is required. + check_eq(green_future_cancel(NULL), GREEN_EINVAL); + + // Once canceled, the future should be done. + p = NULL; + i = 0; + check_eq(green_future_cancel(f), 0); + check_eq(green_future_result(f, &p, &i), GREEN_EBADFD); + check_ne(green_future_done(f), 0); + check_eq(p, NULL); + check_eq(i, 0); + + // Cannot be canceled twice. + check_eq(green_future_cancel(f), GREEN_EBADFD); + + // Cannot complete the future after it has been canceled. + check_eq(green_future_set_result(f, NULL, 0), GREEN_ECANCELED); + + // After set_result, future is done. + check_ne(green_future_done(f), 0); + + // Done. + check_eq(green_future_release(f), 0); f = NULL; + + return EXIT_SUCCESS; +} + +#include "loop-fixture.c" diff --git a/tests/test-poller.c b/tests/test-poller.c new file mode 100644 index 0000000..7c074ff --- /dev/null +++ b/tests/test-poller.c @@ -0,0 +1,141 @@ +//////////////////////////////////////////////////////////////////////// +// Copyright(c) libgreen contributors. See LICENSE file for details. // +//////////////////////////////////////////////////////////////////////// + +#include "loop-fixture.h" + +int test(green_loop_t loop) +{ + green_poller_t poller = NULL; + + // Can't create poller without an event loop. + poller = green_poller_init(NULL, 2); + check_eq(poller, NULL); + + // Can't create poller that can't store any futures. + poller = green_poller_init(loop, 0); + check_eq(poller, NULL); + + // OK, good to go. + poller = green_poller_init(loop, 2); + check_ne(poller, NULL); + check_eq(green_poller_size(poller), 2); + check_eq(green_poller_used(poller), 0); + check_eq(green_poller_done(poller), 0); + + // Poller is reference counted. + check_eq(green_poller_acquire(poller), 0); + check_eq(green_poller_release(poller), 0); + + // Methods are NULL-safe. + check_eq(green_poller_acquire(NULL), GREEN_EINVAL); + check_eq(green_poller_release(NULL), GREEN_EINVAL); + + // NULL poller has invalid size. + check_eq(green_poller_size(NULL), 0); + check_eq(green_poller_used(NULL), 0); + check_eq(green_poller_done(NULL), 0); + + green_future_t f1 = green_future_init(loop); + check_ne(f1, NULL); + green_future_t f2 = green_future_init(loop); + check_ne(f2, NULL); + green_future_t f3 = green_future_init(loop); + check_ne(f3, NULL); + + // Always pop NULL from NULL poller. + check_eq(green_poller_pop(NULL), NULL); + + // Always pop NULL from empty poller. + check_eq(green_poller_pop(poller), NULL); + + // Arguments are required. + check_eq(green_poller_add(NULL, f1), GREEN_EINVAL); + check_eq(green_poller_add(poller, NULL), GREEN_EINVAL); + check_eq(green_poller_rem(NULL, f1), GREEN_EINVAL); + check_eq(green_poller_rem(poller, NULL), GREEN_EINVAL); + + // Can't remove a future that's not in the poller. + check_eq(green_poller_rem(poller, f3), GREEN_ENOENT); + + // Poller has a maximum size. + check_eq(green_poller_add(poller, f1), 0); + check_eq(green_poller_used(poller), 1); + check_eq(green_poller_done(poller), 0); + check_eq(green_poller_add(poller, f2), 0); + check_eq(green_poller_used(poller), 2); + check_eq(green_poller_done(poller), 0); + check_eq(green_poller_add(poller, f3), GREEN_ENFILE); + check_eq(green_poller_used(poller), 2); + check_eq(green_poller_done(poller), 0); + + // Double-registration is prohibited. + check_eq(green_poller_add(poller, f1), GREEN_EALREADY); + check_eq(green_poller_add(poller, f2), GREEN_EALREADY); + check_eq(green_poller_used(poller), 2); + check_eq(green_poller_done(poller), 0); + + // Always pop NULL from poller unless something is ready. + check_eq(green_poller_pop(poller), NULL); + check_eq(green_poller_used(poller), 2); + check_eq(green_poller_done(poller), 0); + + // When a future is completed, we can pop it. + check_eq(green_future_set_result(f1, NULL, 0), 0); + check_eq(green_future_result(f1, NULL, NULL), 0); + check_eq(green_poller_used(poller), 2); + check_eq(green_poller_done(poller), 1); + check_eq(green_poller_pop(poller), f1); + check_eq(green_poller_used(poller), 1); + check_eq(green_poller_done(poller), 0); + + // We can add a completed future. + check_eq(green_poller_add(poller, f1), 0); + check_eq(green_poller_used(poller), 2); + check_eq(green_poller_done(poller), 1); + check_eq(green_poller_pop(poller), f1); + + // Always pop NULL from poller unless something is ready. + check_eq(green_poller_pop(poller), NULL); + + // We can remove a pending future. + check_eq(green_poller_rem(poller, f2), 0); + check_eq(green_poller_add(poller, f2), 0); + + // When a future is completed, we can pop it. + check_eq(green_future_set_result(f2, NULL, 0), 0); + check_eq(green_future_result(f2, NULL, NULL), 0); + check_eq(green_poller_used(poller), 1); + check_eq(green_poller_done(poller), 1); + + // When the last future is completed, we can pop it. + check_eq(green_poller_pop(poller), f2); + check_eq(green_poller_used(poller), 0); + check_eq(green_poller_done(poller), 0); + + // Always pop NULL from empty poller. + check_eq(green_poller_pop(poller), NULL); + + // Can't add a future to a poller attached to a different event loop. + green_loop_t loop2 = green_loop_init(); + green_future_t f4 = green_future_init(loop2); + check_eq(green_poller_add(poller, f4), GREEN_EINVAL); + check_eq(green_future_release(f4), 0); f4 = NULL; + check_eq(green_loop_release(loop2), 0); loop2 = NULL; + + // Future will be released even if the poller holds the last reference. + // + // NOTE: the real assert that proves the future refernce is released + // correctly will be done by the memory checker. + check_eq(green_poller_add(poller, f3), 0); + + // Done. + check_eq(green_future_release(f3), 0); f3 = NULL; + check_eq(green_future_release(f2), 0); f2 = NULL; + check_eq(green_future_release(f1), 0); f1 = NULL; + check_eq(green_poller_release(poller), 0); poller = NULL; + + return EXIT_SUCCESS; +} + +#include "loop-fixture.c"