From 7d0453f6a1d8355a643743b5db09d95ec42aaf8f Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Sat, 25 Feb 2012 12:14:34 -0800 Subject: [PATCH] MYSQL-15: Implement server-side statement timeout support Introduce an implementation of per-thread timers. The API can be used to create one-shot timers that use a monotonically increasing clock as the timing base. A timer expires in a given amount of time (in milliseconds) from when the timer is armed. Upon timer expiration, a given callback function is invoked in a separate thread. Timers can also be cancelled (disarmed) and provide an indication of the state (signaled or non-signaled) of the timer at the time of cancellation. The timer API is currently only implemented on Linux and Mac OS X. --- config.h.cmake | 4 + configure.cmake | 17 +++ include/my_global.h | 17 ++- include/my_timer.h | 69 +++++++++ mysys/CMakeLists.txt | 8 + mysys/kqueue_timers.c | 219 +++++++++++++++++++++++++++ mysys/posix_timers.c | 256 ++++++++++++++++++++++++++++++++ unittest/mysys/CMakeLists.txt | 4 + unittest/mysys/my_timer-t.c | 269 ++++++++++++++++++++++++++++++++++ 9 files changed, 860 insertions(+), 3 deletions(-) create mode 100644 include/my_timer.h create mode 100644 mysys/kqueue_timers.c create mode 100644 mysys/posix_timers.c create mode 100644 unittest/mysys/my_timer-t.c diff --git a/config.h.cmake b/config.h.cmake index 67ed72c5899..289bbaed445 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -308,6 +308,10 @@ #cmakedefine STRUCT_DIRENT_HAS_D_NAMLEN 1 #cmakedefine SPRINTF_RETURNS_INT 1 +#cmakedefine HAVE_POSIX_TIMERS 1 +#cmakedefine HAVE_KQUEUE_TIMERS 1 +#cmakedefine HAVE_MY_TIMER 1 + #define USE_MB 1 #define USE_MB_IDENT 1 diff --git a/configure.cmake b/configure.cmake index e55b4058a54..2735f46c477 100644 --- a/configure.cmake +++ b/configure.cmake @@ -147,6 +147,7 @@ IF(UNIX) IF(NOT LIBRT) MY_SEARCH_LIBS(clock_gettime rt LIBRT) ENDIF() + MY_SEARCH_LIBS(timer_create rt LIBRT) FIND_PACKAGE(Threads) SET(CMAKE_REQUIRED_LIBRARIES @@ -454,6 +455,9 @@ CHECK_FUNCTION_EXISTS (valloc HAVE_VALLOC) CHECK_FUNCTION_EXISTS (memalign HAVE_MEMALIGN) CHECK_FUNCTION_EXISTS (chown HAVE_CHOWN) CHECK_FUNCTION_EXISTS (nl_langinfo HAVE_NL_LANGINFO) +CHECK_FUNCTION_EXISTS (timer_create HAVE_TIMER_CREATE) +CHECK_FUNCTION_EXISTS (timer_settime HAVE_TIMER_SETTIME) +CHECK_FUNCTION_EXISTS (kqueue HAVE_KQUEUE) #-------------------------------------------------------------------- # Support for WL#2373 (Use cycle counter for timing) @@ -495,6 +499,8 @@ CHECK_SYMBOL_EXISTS(FIONREAD "sys/ioctl.h" FIONREAD_IN_SYS_IOCTL) CHECK_SYMBOL_EXISTS(TIOCSTAT "sys/ioctl.h" TIOCSTAT_IN_SYS_IOCTL) CHECK_SYMBOL_EXISTS(FIONREAD "sys/filio.h" FIONREAD_IN_SYS_FILIO) CHECK_SYMBOL_EXISTS(gettimeofday "sys/time.h" HAVE_GETTIMEOFDAY) +CHECK_SYMBOL_EXISTS(SIGEV_THREAD_ID "signal.h;time.h" HAVE_SIGEV_THREAD_ID) +CHECK_SYMBOL_EXISTS(EVFILT_TIMER "sys/types.h;sys/event.h;sys/time.h" HAVE_EVFILT_TIMER) CHECK_SYMBOL_EXISTS(finite "math.h" HAVE_FINITE_IN_MATH_H) IF(HAVE_FINITE_IN_MATH_H) @@ -514,7 +520,18 @@ int main() { return 0; }" HAVE_ISINF) +# Check for the Linux-specific POSIX timers API. +IF(HAVE_TIMER_CREATE AND HAVE_TIMER_SETTIME AND HAVE_SIGEV_THREAD_ID) + SET(HAVE_POSIX_TIMERS 1 CACHE INTERNAL "Have POSIX timer-related functions") +ENDIF() + +IF(HAVE_KQUEUE AND HAVE_EVFILT_TIMER) + SET(HAVE_KQUEUE_TIMERS 1 CACHE INTERNAL "Have kqueue timer-related filter") +ENDIF() +IF(HAVE_POSIX_TIMERS OR HAVE_KQUEUE_TIMERS) + SET(HAVE_MY_TIMER 1 CACHE INTERNAL "Have mysys timer-related functions") +ENDIF() # # Test for endianess diff --git a/include/my_global.h b/include/my_global.h index 11ff377c706..aa1b38e8007 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -377,6 +377,19 @@ C_MODE_END #include #endif +/** + Cast a member of a structure to the structure that contains it. + + @param ptr Pointer to the member. + @param type Type of the structure that contains the member. + @param member Name of the member within the structure. +*/ +#define my_container_of(ptr, type, member) \ + ({ \ + const typeof(((type *)0)->member) *__mptr= (ptr); \ + (type *)((char *)__mptr - offsetof(type, member)); \ + }) + /* A lot of our programs uses asserts, so better to always include it This also fixes a problem when people uses DBUG_ASSERT without including @@ -1418,9 +1431,7 @@ do { doubleget_union _tmp; \ #define NEED_EXPLICIT_SYNC_DIR 1 #endif -#if !defined(__cplusplus) && !defined(bool) -#define bool In_C_you_should_use_my_bool_instead() -#endif +#include /* Provide __func__ macro definition for platforms that miss it. */ #if __STDC_VERSION__ < 199901L diff --git a/include/my_timer.h b/include/my_timer.h new file mode 100644 index 00000000000..3df734bce43 --- /dev/null +++ b/include/my_timer.h @@ -0,0 +1,69 @@ +/* Copyright (c) 2012, Twitter, Inc. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +#ifndef MY_TIMER_H +#define MY_TIMER_H + +#include "my_global.h" /* C_MODE_START, C_MODE_END */ +#include "my_config.h" /* HAVE_*_TIMERS */ + +/* POSIX timers API. */ +#ifdef HAVE_POSIX_TIMERS +# include /* timer_t */ + typedef timer_t os_timer_t; +#elif HAVE_KQUEUE_TIMERS +# include /* uintptr_t */ + typedef uintptr_t os_timer_t; +#endif + +/* Whether timer API is implemented. */ +#ifdef HAVE_MY_TIMER + +C_MODE_START + +typedef struct st_my_timer my_timer_t; + +/** Non-copyable timer object. */ +struct st_my_timer +{ + /** Timer ID used to identify the timer in timer requests. */ + os_timer_t id; + + /** Timer expiration notification function. */ + void (*notify_function)(my_timer_t *); +}; + +/* Initialize internal components. */ +int my_timer_init_ext(void); + +/* Release any resources acquired. */ +void my_timer_deinit(void); + +/* Create a timer object. */ +int my_timer_create(my_timer_t *timer); + +/* Set the time (in milliseconds) until the next expiration of the timer. */ +int my_timer_set(my_timer_t *timer, unsigned long time); + +/* Reset the time until the next expiration of the timer. */ +int my_timer_reset(my_timer_t *timer, bool *state); + +/* Delete a timer object. */ +void my_timer_delete(my_timer_t *timer); + +C_MODE_END + +#endif /* HAVE_MY_TIMER */ +#endif /* MY_TIMER_H */ diff --git a/mysys/CMakeLists.txt b/mysys/CMakeLists.txt index 814f093c2d6..4e8b7575a3c 100644 --- a/mysys/CMakeLists.txt +++ b/mysys/CMakeLists.txt @@ -44,6 +44,14 @@ IF(HAVE_ALARM) SET(MYSYS_SOURCES ${MYSYS_SOURCES} my_alarm.c) ENDIF() +IF(HAVE_POSIX_TIMERS) + SET(MYSYS_SOURCES ${MYSYS_SOURCES} posix_timers.c) +ENDIF() + +IF(HAVE_KQUEUE_TIMERS) + SET(MYSYS_SOURCES ${MYSYS_SOURCES} kqueue_timers.c) +ENDIF() + IF(NOT HAVE_CXX_NEW) # gcc as C++ compiler does not have new/delete SET(MYSYS_SOURCES ${MYSYS_SOURCES} my_new.cc) diff --git a/mysys/kqueue_timers.c b/mysys/kqueue_timers.c new file mode 100644 index 00000000000..5e8c2e68d14 --- /dev/null +++ b/mysys/kqueue_timers.c @@ -0,0 +1,219 @@ +/* Copyright (c) 2012, Twitter, Inc. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +#include "my_timer.h" /* my_timer_t */ +#include "my_pthread.h" /* my_thread_init, my_thread_end */ + +#include +#include +#include +#include +#include + +static int kq_fd= -1; +static pthread_t thread; + +/** + Timer expiration notification thread. + + @param arg Unused. +*/ + +static void * +timer_notify_thread(void *arg __attribute__((unused))) +{ + my_timer_t *timer; + struct kevent kev; + + my_thread_init(); + + while (1) + { + if (kevent(kq_fd, NULL, 0, &kev, 1, NULL) < 0) + continue; + + if (kev.filter == EVFILT_TIMER) + { + timer= kev.udata; + assert(timer->id == kev.ident); + timer->notify_function(timer); + } + else if (kev.filter == EVFILT_USER) + break; + } + + my_thread_end(); + + return NULL; +} + + +/** + Create a helper thread to dispatch timer expiration notifications. + + @return On success, 0. On error, -1 is returned. +*/ + +static int +start_helper_thread(void) +{ + struct kevent kev; + + EV_SET(&kev, 0, EVFILT_USER, EV_ADD, 0, 0, 0); + + if (kevent(kq_fd, &kev, 1, NULL, 0, NULL) < 0) + return -1; + + return pthread_create(&thread, NULL, timer_notify_thread, NULL); +} + + +/** + Initialize internal components. + + @return On success, 0. + On error, -1 is returned, and errno is set to indicate the error. +*/ + +int +my_timer_init_ext(void) +{ + int rc; + + /* Create a file descriptor for event notification. */ + if ((kq_fd= kqueue()) < 0) + return -1; + + /* Create a helper thread. */ + if ((rc= start_helper_thread())) + close(kq_fd); + + return rc; +} + + +/** + Release any resources that were allocated as part of initialization. +*/ + +void +my_timer_deinit(void) +{ + struct kevent kev; + + EV_SET(&kev, 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0); + + /* There's not much to do if triggering the event fails. */ + if (kevent(kq_fd, &kev, 1, NULL, 0, NULL) > -1) + pthread_join(thread, NULL); + + close(kq_fd); +} + + +/** + Create a timer object. + + @param timer Timer object. + + @return On success, 0. + On error, -1 is returned, and errno is set to indicate the error. +*/ + +int +my_timer_create(my_timer_t *timer) +{ + assert(kq_fd >= 0); + + timer->id= (uintptr_t) timer; + + return 0; +} + + +/** + Set the time until the next expiration of the timer. + + @param timer Timer object. + @param time Amount of time (in milliseconds) before the timer expires. + + @return On success, 0. + On error, -1 is returned, and errno is set to indicate the error. +*/ + +int +my_timer_set(my_timer_t *timer, unsigned long time) +{ + struct kevent kev; + + EV_SET(&kev, timer->id, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, time, timer); + + return kevent(kq_fd, &kev, 1, NULL, 0, NULL); +} + + +/** + Reset the time until the next expiration of the timer. + + @param timer Timer object. + @param state The state of the timer at the time of cancellation, either + signaled (false) or nonsignaled (true). + + @return On success, 0. + On error, -1 is returned, and errno is set to indicate the error. +*/ + +int +my_timer_reset(my_timer_t *timer, bool *state) +{ + int status; + struct kevent kev; + + EV_SET(&kev, timer->id, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); + + status= kevent(kq_fd, &kev, 1, NULL, 0, NULL); + + /* + If the event was retrieved from the kqueue (at which point we + consider it to be signaled), the timer was automatically deleted. + */ + if (!status) + *state= 1; + else if (errno == ENOENT) + { + *state= 0; + status= 0; + } + + return status; +} + + +/** + Delete a timer object. + + @param timer Timer object. +*/ + +void +my_timer_delete(my_timer_t *timer) +{ + struct kevent kev; + + EV_SET(&kev, timer->id, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); + + kevent(kq_fd, &kev, 1, NULL, 0, NULL); +} + diff --git a/mysys/posix_timers.c b/mysys/posix_timers.c new file mode 100644 index 00000000000..7d886e58819 --- /dev/null +++ b/mysys/posix_timers.c @@ -0,0 +1,256 @@ +/* Copyright (c) 2012, Twitter, Inc. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +#include "my_timer.h" /* my_timer_t */ +#include "my_pthread.h" /* my_thread_init, my_thread_end */ +#include /* SYS_gettid */ +#include /* memset */ + +#ifndef sigev_notify_thread_id +#define sigev_notify_thread_id _sigev_un._tid +#endif + +#define MY_TIMER_EVENT_SIGNO (SIGRTMIN) +#define MY_TIMER_KILL_SIGNO (SIGRTMIN+1) + +/* Timer thread object. */ +static pthread_t thread; + +/* Timer thread ID (TID). */ +static pid_t thread_id; + +/** + Timer expiration notification function. + + @param sigev_value Signal (notification) value. + + @remark The notification function is usually run in a helper thread + and is called each time the timer expires. +*/ + +static void +timer_notify_function(sigval_t sigev_value) +{ + my_timer_t *timer= sigev_value.sival_ptr; + timer->notify_function(timer); +} + + +/** + Timer expiration notification thread. + + @param arg Barrier object. +*/ + +static void * +timer_notify_thread(void *arg) +{ + sigset_t set; + siginfo_t info; + pthread_barrier_t *barrier= arg; + + my_thread_init(); + + sigemptyset(&set); + sigaddset(&set, MY_TIMER_EVENT_SIGNO); + sigaddset(&set, MY_TIMER_KILL_SIGNO); + + /* Get the thread ID of the current thread. */ + thread_id= (pid_t) syscall(SYS_gettid); + + /* Wake up parent thread, thread_id is available. */ + pthread_barrier_wait(barrier); + + while (1) + { + if (sigwaitinfo(&set, &info) < 0) + continue; + + if (info.si_signo == MY_TIMER_EVENT_SIGNO) + timer_notify_function(info.si_value); + else if (info.si_signo == MY_TIMER_KILL_SIGNO) + break; + } + + my_thread_end(); + + return NULL; +} + + +/** + Create a helper thread to dispatch timer expiration notifications. + + @return On success, 0. On error, -1 is returned. +*/ + +static int +start_helper_thread(void) +{ + pthread_barrier_t barrier; + + if (pthread_barrier_init(&barrier, NULL, 2)) + return -1; + + if (pthread_create(&thread, NULL, timer_notify_thread, &barrier)) + return -1; + + pthread_barrier_wait(&barrier); + pthread_barrier_destroy(&barrier); + + return 0; +} + + +/** + Initialize internal components. + + @return On success, 0. + On error, -1 is returned, and errno is set to indicate the error. +*/ + +int +my_timer_init_ext(void) +{ + int rc; + sigset_t set, old_set; + + if (sigfillset(&set)) + return -1; + + /* + Temporarily block all signals. New thread will inherit signal + mask of the current thread. + */ + if (pthread_sigmask(SIG_BLOCK, &set, &old_set)) + return -1; + + /* Create a helper thread. */ + rc= start_helper_thread(); + + /* Restore the signal mask. */ + pthread_sigmask(SIG_SETMASK, &old_set, NULL); + + return rc; +} + + +/** + Release any resources that were allocated as part of initialization. +*/ + +void +my_timer_deinit(void) +{ + /* Kill helper thread. */ + pthread_kill(thread, MY_TIMER_KILL_SIGNO); + + /* Wait for helper thread termination. */ + pthread_join(thread, NULL); +} + + +/** + Create a timer object. + + @param timer Location where the timer ID is returned. + + @return On success, 0. + On error, -1 is returned, and errno is set to indicate the error. +*/ + +int +my_timer_create(my_timer_t *timer) +{ + struct sigevent sigev; + + memset(&sigev, 0, sizeof(sigev)); + + sigev.sigev_value.sival_ptr= timer; + sigev.sigev_signo= MY_TIMER_EVENT_SIGNO; + sigev.sigev_notify= SIGEV_SIGNAL | SIGEV_THREAD_ID; + sigev.sigev_notify_thread_id= thread_id; + + return timer_create(CLOCK_MONOTONIC, &sigev, &timer->id); +} + + +/** + Set the time until the next expiration of the timer. + + @param timer Timer object. + @param time Amount of time (in milliseconds) before the timer expires. + + @return On success, 0. + On error, -1 is returned, and errno is set to indicate the error. +*/ + +int +my_timer_set(my_timer_t *timer, unsigned long time) +{ + const struct itimerspec spec= { + .it_interval= {.tv_sec= 0, .tv_nsec= 0}, + .it_value= {.tv_sec= time / 1000, + .tv_nsec= (time % 1000) * 1000000} + }; + + return timer_settime(timer->id, 0, &spec, NULL); +} + + +/** + Reset the time until the next expiration of the timer. + + @param timer Timer object. + @param state The state of the timer at the time of cancellation, either + signaled (false) or nonsignaled (true). + + @return On success, 0. + On error, -1 is returned, and errno is set to indicate the error. +*/ + +int +my_timer_reset(my_timer_t *timer, bool *state) +{ + int status; + struct itimerspec old_spec; + + /* A zeroed initial expiration value disarms the timer. */ + const struct timespec zero_time= { .tv_sec= 0, .tv_nsec= 0 }; + const struct itimerspec zero_spec= { .it_value= zero_time }; + + /* + timer_settime returns the amount of time before the timer + would have expired or zero if the timer was disarmed. + */ + if (! (status= timer_settime(timer->id, 0, &zero_spec, &old_spec))) + *state= old_spec.it_value.tv_sec || old_spec.it_value.tv_nsec; + + return status; +} + + +/** + Delete a timer object. + + @param timer Timer object. +*/ + +void +my_timer_delete(my_timer_t *timer) +{ + timer_delete(timer->id); +} + diff --git a/unittest/mysys/CMakeLists.txt b/unittest/mysys/CMakeLists.txt index 7bf046162c5..f301846b9a6 100644 --- a/unittest/mysys/CMakeLists.txt +++ b/unittest/mysys/CMakeLists.txt @@ -30,3 +30,7 @@ ENDMACRO() FOREACH(testname bitmap base64 my_vsnprintf my_atomic my_rdtsc lf my_malloc) MY_ADD_TEST(${testname}) ENDFOREACH() + +IF(HAVE_MY_TIMER) + MY_ADD_TEST(my_timer) +ENDIF() diff --git a/unittest/mysys/my_timer-t.c b/unittest/mysys/my_timer-t.c new file mode 100644 index 00000000000..6d43065fc88 --- /dev/null +++ b/unittest/mysys/my_timer-t.c @@ -0,0 +1,269 @@ +/* Copyright (c) 2012, Twitter, Inc. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +#include + +#include "my_timer.h" +#include "thr_template.c" + +typedef struct +{ + my_timer_t timer; + unsigned int fired; + pthread_mutex_t mutex; + pthread_cond_t cond; +} test_timer_t; + +static void timer_notify_function(my_timer_t *timer) +{ + test_timer_t *test= my_container_of(timer, test_timer_t, timer); + + pthread_mutex_lock(&test->mutex); + test->fired++; + pthread_cond_signal(&test->cond); + pthread_mutex_unlock(&test->mutex); +} + +static void test_timer_create(test_timer_t *test) +{ + memset(test, 0, sizeof(test_timer_t)); + pthread_mutex_init(&test->mutex, NULL); + pthread_cond_init(&test->cond, NULL); + ok(my_timer_create(&test->timer) == 0, "my_timer_create"); + test->timer.notify_function= timer_notify_function; +} + +static void test_timer_destroy(test_timer_t *test) +{ + pthread_mutex_destroy(&test->mutex); + pthread_cond_destroy(&test->cond); + my_timer_delete(&test->timer); +} + +static void test_create_and_delete(void) +{ + int rc; + my_timer_t timer; + + diag("test_create_and_delete"); + + memset(&timer, 0, sizeof(timer)); + + rc= my_timer_create(&timer); + ok(rc == 0, "my_timer_create"); + + my_timer_delete(&timer); +} + +static void test_reset(void) +{ + int rc; + bool state; + test_timer_t test; + + diag("test_reset"); + + test_timer_create(&test); + + rc= my_timer_set(&test.timer, 3600000U); + ok(rc == 0, "my_timer_set"); + + rc= my_timer_reset(&test.timer, &state); + ok(rc == 0, "my_timer_reset"); + + ok(state == 1, "timer state is nonsignaled"); + ok(test.fired == 0, "timer has not fired"); + + test_timer_destroy(&test); +} + +static void test_timer(void) +{ + int rc; + bool state; + test_timer_t test; + + diag("test_timer"); + + test_timer_create(&test); + + pthread_mutex_lock(&test.mutex); + + rc= my_timer_set(&test.timer, 5); + ok(rc == 0, "my_timer_set"); + + ok(test.fired == 0, "not fired yet"); + + while (!test.fired) + pthread_cond_wait(&test.cond, &test.mutex); + + ok(test.fired == 1, "timer fired once"); + + rc= my_timer_reset(&test.timer, &state); + ok(rc == 0, "my_timer_reset"); + + ok(state == 0, "timer state was signaled"); + + pthread_mutex_unlock(&test.mutex); + + test_timer_destroy(&test); +} + +static void timer_set_and_wait(test_timer_t *test, unsigned int fired_count) +{ + int rc; + bool state; + + rc= my_timer_set(&test->timer, 5); + ok(rc == 0, "my_timer_set"); + + ok(test->fired != fired_count, "not fired yet"); + + while (test->fired != fired_count) + pthread_cond_wait(&test->cond, &test->mutex); + + ok(test->fired == fired_count, "timer fired"); + + rc= my_timer_reset(&test->timer, &state); + ok(rc == 0, "my_timer_reset"); + + ok(state == 0, "timer state was signaled"); +} + +static void test_timer_reuse(void) +{ + test_timer_t test; + + diag("test_timer_reuse"); + + test_timer_create(&test); + + pthread_mutex_lock(&test.mutex); + + timer_set_and_wait(&test, 1); + timer_set_and_wait(&test, 2); + timer_set_and_wait(&test, 3); + + pthread_mutex_unlock(&test.mutex); + + test_timer_destroy(&test); +} + +static void test_independent_timers(void) +{ + int rc; + bool state; + test_timer_t test; + + diag("test_independent_timers"); + + test_timer_create(&test); + + rc= my_timer_set(&test.timer, 3600000U); + ok(rc == 0, "my_timer_set"); + + test_timer(); + + rc= my_timer_reset(&test.timer, &state); + ok(rc == 0, "my_timer_reset"); + + ok(state == 1, "timer state is nonsignaled"); + ok(test.fired == 0, "timer has not fired"); + + test_timer_destroy(&test); +} + +static void test_timer_no_tap(void) +{ + int rc; + bool state; + test_timer_t test; + + memset(&test, 0, sizeof(test_timer_t)); + + pthread_mutex_init(&test.mutex, NULL); + pthread_cond_init(&test.cond, NULL); + + test.timer.notify_function= timer_notify_function; + + rc= my_timer_create(&test.timer); + assert(rc == 0); + + pthread_mutex_lock(&test.mutex); + + rc= my_timer_set(&test.timer, 5); + assert(rc == 0); + + assert(test.fired == 0); /* not fired yet */ + + while (!test.fired) + pthread_cond_wait(&test.cond, &test.mutex); + + assert(test.fired == 1); /* timer fired once */ + + rc= my_timer_reset(&test.timer, &state); + assert(rc == 0); + + assert(state == 0); /* timer state was signaled */ + + pthread_mutex_unlock(&test.mutex); + + pthread_mutex_destroy(&test.mutex); + pthread_cond_destroy(&test.cond); + my_timer_delete(&test.timer); +} + +static pthread_handler_t test_timer_per_thread(void *arg) +{ + int iter= *(int *) arg; + + while (iter--) + test_timer_no_tap(); + + pthread_mutex_lock(&mutex); + if (!--running_threads) + pthread_cond_signal(&cond); + pthread_mutex_unlock(&mutex); + + return NULL; +} + +static void test_reinitialization(void) +{ + diag("test_reinitialization"); + + my_timer_deinit(); + ok(my_timer_init_ext() == 0, "my_timer_init_ext"); + test_timer(); + my_timer_deinit(); + ok(my_timer_init_ext() == 0, "my_timer_init_ext"); +} + +void do_tests() +{ + plan(49); + + ok(my_timer_init_ext() == 0, "my_timer_init_ext"); + + test_create_and_delete(); + test_reset(); + test_timer(); + test_timer_reuse(); + test_independent_timers(); + test_concurrently("per-thread", test_timer_per_thread, THREADS, 5); + test_reinitialization(); + + my_timer_deinit(); +}