From 64934db83ed06de7701ccdde26f1cc09e74f5823 Mon Sep 17 00:00:00 2001 From: Roman Tsisyk <roman@tarantool.org> Date: Fri, 14 Jul 2017 12:38:36 +0300 Subject: [PATCH] Export fiber_cond to public C API A part of #1451 --- extra/exports | 6 +++ src/CMakeLists.txt | 1 + src/fiber_cond.c | 31 +++++++++++++ src/fiber_cond.h | 74 ++++++++++++++++++++++++++---- test/unit/CMakeLists.txt | 3 ++ test/unit/fiber_cond.c | 89 +++++++++++++++++++++++++++++++++++++ test/unit/fiber_cond.result | 8 ++++ 7 files changed, 203 insertions(+), 9 deletions(-) create mode 100644 test/unit/fiber_cond.c create mode 100644 test/unit/fiber_cond.result diff --git a/extra/exports b/extra/exports index 22ba8e5341..2e31b4afc6 100644 --- a/extra/exports +++ b/extra/exports @@ -99,6 +99,12 @@ fiber_is_cancelled fiber_time fiber_time64 fiber_reschedule +fiber_cond_new +fiber_cond_delete +fiber_cond_signal +fiber_cond_broadcast +fiber_cond_wait_timeout +fiber_cond_wait cord_slab_cache coio_wait coio_close diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e56025c288..7e02de070d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -160,6 +160,7 @@ set(api_headers ${CMAKE_SOURCE_DIR}/src/trivia/util.h ${CMAKE_SOURCE_DIR}/src/say.h ${CMAKE_SOURCE_DIR}/src/fiber.h + ${CMAKE_SOURCE_DIR}/src/fiber_cond.h ${CMAKE_SOURCE_DIR}/src/coio.h ${CMAKE_SOURCE_DIR}/src/coio_task.h ${CMAKE_SOURCE_DIR}/src/lua/utils.h diff --git a/src/fiber_cond.c b/src/fiber_cond.c index 783f2654b4..d9e74434bd 100644 --- a/src/fiber_cond.c +++ b/src/fiber_cond.c @@ -31,6 +31,7 @@ #include "fiber_cond.h" +#include <small/mempool.h> #include <tarantool_ev.h> #include "fiber.h" @@ -48,6 +49,36 @@ fiber_cond_destroy(struct fiber_cond *c) assert(rlist_empty(&c->waiters)); } +static __thread struct mempool cond_pool; + +struct fiber_cond * +fiber_cond_new() +{ + struct fiber_cond *cond; + if (! mempool_is_initialized(&cond_pool)) { + /* + * We don't need to bother with + * destruction since the entire slab cache + * is freed when the thread ends. + */ + mempool_create(&cond_pool, &cord()->slabc, sizeof(*cond)); + } + cond = mempool_alloc(&cond_pool); + if (cond == NULL) { + diag_set(OutOfMemory, sizeof(*cond), "fiber_cond_pool", + "struct fiber_cond"); + return NULL; + } + fiber_cond_create(cond); + return cond; +} + +void +fiber_cond_delete(struct fiber_cond *cond) +{ + mempool_free(&cond_pool, cond); +} + void fiber_cond_signal(struct fiber_cond *e) { diff --git a/src/fiber_cond.h b/src/fiber_cond.h index ed0c1f7280..ee51b2a167 100644 --- a/src/fiber_cond.h +++ b/src/fiber_cond.h @@ -37,40 +37,96 @@ extern "C" { #endif /* defined(__cplusplus) */ +/** \cond public */ + +/** + * Conditional variable for cooperative multitasking (fibers). + * + * A cond (short for "condition variable") is a synchronization primitive + * that allow fibers to yield until some predicate is satisfied. Fiber + * conditions have two basic operations - wait() and signal(). wait() + * suspends execution of fiber (i.e. yields) until signal() is called. + * Unlike pthread_cond, fiber_cond doesn't require mutex/latch wrapping. + * + */ +struct fiber_cond; + +/** \endcond public */ + struct fiber_cond { + /** Waiting fibers */ struct rlist waiters; }; /** - * Initialize a cond - semantics as in POSIX condition variable. + * Initialize the fiber condition variable. + * + * @param cond condition */ void -fiber_cond_create(struct fiber_cond *c); +fiber_cond_create(struct fiber_cond *cond); /** - * Finalize a cond. UB if there are fibers waiting for a cond. + * Finalize the cond. + * Behaviour is undefined if there are fiber waiting for the cond. + * @param cond condition */ void -fiber_cond_destroy(struct fiber_cond *c); +fiber_cond_destroy(struct fiber_cond *cond); + +/** \cond public */ + +/** + * Instantiate a new fiber cond object. + */ +struct fiber_cond * +fiber_cond_new(void); + +/** + * Delete the fiber cond object. + * Behaviour is undefined if there are fiber waiting for the cond. + */ +void +fiber_cond_delete(struct fiber_cond *cond); /** * Wake one fiber waiting for the cond. * Does nothing if no one is waiting. + * @param cond condition */ void -fiber_cond_signal(struct fiber_cond *c); +fiber_cond_signal(struct fiber_cond *cond); /** - * Wake all fibers waiting for the cond. + * Wake up all fibers waiting for the cond. + * @param cond condition */ void -fiber_cond_broadcast(struct fiber_cond *c); +fiber_cond_broadcast(struct fiber_cond *cond); +/** + * Suspend the execution of the current fiber (i.e. yield) until + * fiber_cond_signal() is called. Like pthread_cond, fiber_cond can issue + * spurious wake ups caused by explicit fiber_wakeup() or fiber_cancel() + * calls. It is highly recommended to wrap calls to this function into a loop + * and check an actual predicate and fiber_testcancel() on every iteration. + * + * @param cond condition + * @param timeout timeout in seconds + * @retval 0 on fiber_cond_signal() call or a spurious wake up + * @retval -1 on timeout, diag is set to TimedOut + */ int -fiber_cond_wait_timeout(struct fiber_cond *c, double timeout); +fiber_cond_wait_timeout(struct fiber_cond *cond, double timeout); +/** + * Shortcut for fiber_cond_wait_timeout(). + * @see fiber_cond_wait_timeout() + */ int -fiber_cond_wait(struct fiber_cond *c); +fiber_cond_wait(struct fiber_cond *cond); + +/** \endcond public */ #if defined(__cplusplus) } /* extern "C" */ diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 30718fe5c1..59400dadbb 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -80,6 +80,9 @@ endif () add_executable(fiber_stress.test fiber_stress.cc) target_link_libraries(fiber_stress.test core) +add_executable(fiber_cond.test fiber_cond.c unit.c) +target_link_libraries(fiber_cond.test core) + add_executable(fiber_channel.test fiber_channel.cc unit.c) target_link_libraries(fiber_channel.test core) diff --git a/test/unit/fiber_cond.c b/test/unit/fiber_cond.c new file mode 100644 index 0000000000..bfc4b46dcc --- /dev/null +++ b/test/unit/fiber_cond.c @@ -0,0 +1,89 @@ +#include "memory.h" +#include "fiber.h" +#include "fiber_cond.h" +#include "unit.h" + +static int +fiber_cond_basic_f(va_list ap) +{ + struct fiber_cond *cond = va_arg(ap, struct fiber_cond *); + int *check = va_arg(ap, int *); + + int rc; + + rc = fiber_cond_wait_timeout(cond, 0.0); + ok(rc != 0, "timeout"); + + rc = fiber_cond_wait(cond); + is(rc, 0, "signal"); + + (*check)++; + + rc = fiber_cond_wait(cond); + is(rc, 0, "broadcast"); + + return 0; +} + +static void +fiber_cond_basic() +{ + struct fiber_cond *cond = fiber_cond_new(); + int check = 0; + + struct fiber *f1 = fiber_new("f1", fiber_cond_basic_f); + assert(f1 != NULL); + fiber_start(f1, cond, &check); + fiber_set_joinable(f1, true); + + struct fiber *f2 = fiber_new("f2", fiber_cond_basic_f); + assert(f2 != NULL); + fiber_start(f2, cond, &check); + fiber_set_joinable(f2, true); + + /* check timeout */ + fiber_sleep(0.0); + fiber_sleep(0.0); + + /* Wake up the first fiber */ + fiber_cond_signal(cond); + fiber_sleep(0.0); + + /* Wake ip the second fiber */ + fiber_cond_signal(cond); + fiber_sleep(0.0); + + /* Check that fiber scheduling is fair */ + is(check, 2, "order"); + + fiber_cond_broadcast(cond); + fiber_sleep(0.0); + + fiber_join(f1); + fiber_join(f2); + + fiber_cond_delete(cond); +} + +static int +main_f(va_list ap) +{ + (void) ap; + fiber_cond_basic(); + ev_break(loop(), EVBREAK_ALL); + return 0; +} + +int +main() +{ + plan(7); + memory_init(); + fiber_init(fiber_c_invoke); + struct fiber *f = fiber_new("main", main_f); + fiber_wakeup(f); + ev_run(loop(), 0); + fiber_free(); + memory_free(); + return check_plan(); +} diff --git a/test/unit/fiber_cond.result b/test/unit/fiber_cond.result new file mode 100644 index 0000000000..20539e2be0 --- /dev/null +++ b/test/unit/fiber_cond.result @@ -0,0 +1,8 @@ +1..7 +ok 1 - timeout +ok 2 - timeout +ok 3 - signal +ok 4 - signal +ok 5 - order +ok 6 - broadcast +ok 7 - broadcast -- GitLab