From 4b558f50a42c97d461f1dede5aaaae490ea99e2e Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Tue, 4 Mar 2014 15:58:02 -0800 Subject: [PATCH] Rewrite the POSIX timer functions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a much simpler implementation that lets the kernel do as much as possible. Co-authored-by: Jörgen Strand Co-authored-by: Snild Dolkow Change-Id: Iad19f155de977667aea09410266d54e63e8a26bf --- libc/Android.mk | 2 +- libc/SYSCALLS.TXT | 10 +- libc/bionic/fork.cpp | 7 - libc/bionic/posix_timers.cpp | 221 ++++++++++++ libc/bionic/pthread_atfork.cpp | 13 +- libc/bionic/pthread_internal.h | 3 +- libc/bionic/timer.cpp | 636 --------------------------------- libc/include/sys/types.h | 2 +- tests/pthread_test.cpp | 10 +- tests/time_test.cpp | 212 ++++++++++- 10 files changed, 446 insertions(+), 670 deletions(-) create mode 100644 libc/bionic/posix_timers.cpp delete mode 100644 libc/bionic/timer.cpp diff --git a/libc/Android.mk b/libc/Android.mk index 2046beac3..f416b385d 100644 --- a/libc/Android.mk +++ b/libc/Android.mk @@ -165,6 +165,7 @@ libc_bionic_src_files := \ bionic/pipe.cpp \ bionic/poll.cpp \ bionic/posix_fallocate.cpp \ + bionic/posix_timers.cpp \ bionic/pthread_atfork.cpp \ bionic/pthread_attr.cpp \ bionic/pthread_cond.cpp \ @@ -224,7 +225,6 @@ libc_bionic_src_files := \ bionic/sys_signame.c \ bionic/tdestroy.cpp \ bionic/thread_atexit.cpp \ - bionic/timer.cpp \ bionic/tmpfile.cpp \ bionic/unlink.cpp \ bionic/utimes.cpp \ diff --git a/libc/SYSCALLS.TXT b/libc/SYSCALLS.TXT index 192816816..8d4b258f6 100644 --- a/libc/SYSCALLS.TXT +++ b/libc/SYSCALLS.TXT @@ -213,11 +213,11 @@ int clock_getres(clockid_t clk_id, struct timespec* res) all int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec* req, struct timespec* rem) all int getitimer(int, const struct itimerval*) all int setitimer(int, const struct itimerval*, struct itimerval*) all -int __timer_create:timer_create(clockid_t clockid, struct sigevent* evp, timer_t* timerid) all -int __timer_settime:timer_settime(timer_t, int, const struct itimerspec*, struct itimerspec*) all -int __timer_gettime:timer_gettime(timer_t, struct itimerspec*) all -int __timer_getoverrun:timer_getoverrun(timer_t) all -int __timer_delete:timer_delete(timer_t) all +int __timer_create:timer_create(clockid_t clockid, struct sigevent* evp, __kernel_timer_t* timerid) all +int __timer_settime:timer_settime(__kernel_timer_t, int, const struct itimerspec*, struct itimerspec*) all +int __timer_gettime:timer_gettime(__kernel_timer_t, struct itimerspec*) all +int __timer_getoverrun:timer_getoverrun(__kernel_timer_t) all +int __timer_delete:timer_delete(__kernel_timer_t) all int timerfd_create(clockid_t, int) all int timerfd_settime(int, int, const struct itimerspec*, struct itimerspec*) all int timerfd_gettime(int, struct itimerspec*) all diff --git a/libc/bionic/fork.cpp b/libc/bionic/fork.cpp index 9fa5fcf5f..a0f98e42f 100644 --- a/libc/bionic/fork.cpp +++ b/libc/bionic/fork.cpp @@ -29,15 +29,9 @@ #include #include -#include "private/libc_logging.h" #include "pthread_internal.h" int fork() { - // POSIX mandates that the timers of a fork child process be - // disarmed, but not destroyed. To avoid a race condition, we're - // going to stop all timers now, and only re-start them in case - // of error, or in the parent process - __timer_table_start_stop(1); __bionic_atfork_run_prepare(); pthread_internal_t* self = __get_thread(); @@ -50,7 +44,6 @@ int fork() { if (result == 0) { __bionic_atfork_run_child(); } else { - __timer_table_start_stop(0); __bionic_atfork_run_parent(); } return result; diff --git a/libc/bionic/posix_timers.cpp b/libc/bionic/posix_timers.cpp new file mode 100644 index 000000000..ffe213c33 --- /dev/null +++ b/libc/bionic/posix_timers.cpp @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "pthread_internal.h" +#include "private/bionic_futex.h" +#include "private/bionic_pthread.h" +#include "private/kernel_sigset_t.h" + +#include +#include +#include + +// System calls. +extern "C" int __rt_sigtimedwait(const sigset_t*, siginfo_t*, const struct timespec*, size_t); +extern "C" int __timer_create(clockid_t, sigevent*, __kernel_timer_t*); +extern "C" int __timer_delete(__kernel_timer_t); +extern "C" int __timer_getoverrun(__kernel_timer_t); +extern "C" int __timer_gettime(__kernel_timer_t, itimerspec*); +extern "C" int __timer_settime(__kernel_timer_t, int, const itimerspec*, itimerspec*); + +// Most POSIX timers are handled directly by the kernel. We translate SIGEV_THREAD timers +// into SIGEV_THREAD_ID timers so the kernel handles all the time-related stuff and we just +// need to worry about running user code on a thread. + +// We can't use SIGALRM because too many other C library functions throw that around, and since +// they don't send to a specific thread, all threads are eligible to handle the signal and we can +// end up with one of our POSIX timer threads handling it (meaning that the intended recipient +// doesn't). glibc uses SIGRTMIN for its POSIX timer implementation, so in the absence of any +// reason to use anything else, we use that too. +static const int TIMER_SIGNAL = SIGRTMIN; + +struct PosixTimer { + __kernel_timer_t kernel_timer_id; + + int sigev_notify; + + // These fields are only needed for a SIGEV_THREAD timer. + pthread_t callback_thread; + void (*callback)(sigval_t); + sigval_t callback_argument; + volatile int exiting; +}; + +static __kernel_timer_t to_kernel_timer_id(timer_t timer) { + return reinterpret_cast(timer)->kernel_timer_id; +} + +static void* __timer_thread_start(void* arg) { + PosixTimer* timer = reinterpret_cast(arg); + + kernel_sigset_t sigset; + sigaddset(sigset.get(), TIMER_SIGNAL); + + while (true) { + // Wait for a signal... + siginfo_t si; + memset(&si, 0, sizeof(si)); + int rc = __rt_sigtimedwait(sigset.get(), &si, NULL, sizeof(sigset)); + if (rc == -1) { + continue; + } + + if (si.si_code == SI_TIMER) { + // This signal was sent because a timer fired, so call the callback. + timer->callback(timer->callback_argument); + } else if (si.si_code == SI_TKILL) { + // This signal was sent because someone wants us to exit. + timer->exiting = 1; + __futex_wake(&timer->exiting, INT32_MAX); + return NULL; + } + } +} + +static void __timer_thread_stop(PosixTimer* timer) { + pthread_kill(timer->callback_thread, TIMER_SIGNAL); + + // We can't pthread_join because POSIX says "the threads created in response to a timer + // expiration are created detached, or in an unspecified way if the thread attribute's + // detachstate is PTHREAD_CREATE_JOINABLE". + while (timer->exiting == 0) { + __futex_wait(&timer->exiting, 0, NULL); + } +} + +// http://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_create.html +int timer_create(clockid_t clock_id, sigevent* evp, timer_t* timer_id) { + PosixTimer* new_timer = reinterpret_cast(malloc(sizeof(PosixTimer))); + if (new_timer == NULL) { + return -1; + } + + new_timer->sigev_notify = (evp == NULL) ? SIGEV_SIGNAL : evp->sigev_notify; + + // If not a SIGEV_THREAD timer, the kernel can handle it without our help. + if (new_timer->sigev_notify != SIGEV_THREAD) { + if (__timer_create(clock_id, evp, &new_timer->kernel_timer_id) == -1) { + free(new_timer); + return -1; + } + + *timer_id = new_timer; + return 0; + } + + // Otherwise, this must be SIGEV_THREAD timer... + new_timer->callback = evp->sigev_notify_function; + new_timer->callback_argument = evp->sigev_value; + new_timer->exiting = 0; + + // Check arguments that the kernel doesn't care about but we do. + if (new_timer->callback == NULL) { + free(new_timer); + errno = EINVAL; + return -1; + } + + // Create this timer's thread. + pthread_attr_t thread_attributes; + if (evp->sigev_notify_attributes == NULL) { + pthread_attr_init(&thread_attributes); + } else { + thread_attributes = *reinterpret_cast(evp->sigev_notify_attributes); + } + pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_DETACHED); + + // We start the thread with TIMER_SIGNAL blocked by blocking the signal here and letting it + // inherit. If it tried to block the signal itself, there would be a race. + kernel_sigset_t sigset; + sigaddset(sigset.get(), TIMER_SIGNAL); + kernel_sigset_t old_sigset; + pthread_sigmask(SIG_BLOCK, sigset.get(), old_sigset.get()); + + int rc = pthread_create(&new_timer->callback_thread, &thread_attributes, __timer_thread_start, new_timer); + + pthread_sigmask(SIG_SETMASK, old_sigset.get(), NULL); + + if (rc != 0) { + free(new_timer); + errno = rc; + return -1; + } + + sigevent se = *evp; + se.sigev_signo = TIMER_SIGNAL; + se.sigev_notify = SIGEV_THREAD_ID; + se.sigev_notify_thread_id = __pthread_gettid(new_timer->callback_thread); + if (__timer_create(clock_id, &se, &new_timer->kernel_timer_id) == -1) { + __timer_thread_stop(new_timer); + free(new_timer); + return -1; + } + + // Give the thread a meaningful name. + // It can't do this itself because the kernel timer isn't created until after it's running. + char name[32]; + snprintf(name, sizeof(name), "POSIX interval timer %d", to_kernel_timer_id(new_timer)); + pthread_setname_np(new_timer->callback_thread, name); + + *timer_id = new_timer; + return 0; +} + +// http://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_delete.html +int timer_delete(timer_t id) { + int rc = __timer_delete(to_kernel_timer_id(id)); + if (rc == -1) { + return -1; + } + + PosixTimer* timer = reinterpret_cast(id); + + // Make sure the timer's thread has exited before we free the timer data. + if (timer->sigev_notify == SIGEV_THREAD) { + __timer_thread_stop(timer); + } + + free(timer); + + return 0; +} + +// http://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_getoverrun.html +int timer_gettime(timer_t id, itimerspec* ts) { + return __timer_gettime(to_kernel_timer_id(id), ts); +} + +// http://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_getoverrun.html +int timer_settime(timer_t id, int flags, const itimerspec* ts, itimerspec* ots) { + return __timer_settime(to_kernel_timer_id(id), flags, ts, ots); +} + +// http://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_getoverrun.html +int timer_getoverrun(timer_t id) { + return __timer_getoverrun(to_kernel_timer_id(id)); +} diff --git a/libc/bionic/pthread_atfork.cpp b/libc/bionic/pthread_atfork.cpp index 5bf63fb41..c0664a946 100644 --- a/libc/bionic/pthread_atfork.cpp +++ b/libc/bionic/pthread_atfork.cpp @@ -48,14 +48,12 @@ struct atfork_list_t { static atfork_list_t gAtForkList = { NULL, NULL }; void __bionic_atfork_run_prepare() { - // We will lock this here, and unlock it in the parent and child functions. + // We lock the atfork list here, unlock it in the parent, and reset it in the child. // This ensures that nobody can modify the handler array between the calls // to the prepare and parent/child handlers. // - // TODO: If a handler mucks with the list, it could cause problems. Right - // now it's ok because all they can do is add new items to the end - // of the list, but if/when we implement cleanup in dlclose() things - // will get more interesting... + // TODO: If a handler tries to mutate the list, they'll block. We should probably copy + // the list before forking, and have prepare, parent, and child all work on the consistent copy. pthread_mutex_lock(&gAtForkListMutex); // Call pthread_atfork() prepare handlers. POSIX states that the prepare @@ -75,10 +73,7 @@ void __bionic_atfork_run_child() { } } - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(&gAtForkListMutex, &attr); + gAtForkListMutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER; } void __bionic_atfork_run_parent() { diff --git a/libc/bionic/pthread_internal.h b/libc/bionic/pthread_internal.h index 3825a4c5c..41f46368c 100644 --- a/libc/bionic/pthread_internal.h +++ b/libc/bionic/pthread_internal.h @@ -91,8 +91,7 @@ __LIBC_HIDDEN__ extern pthread_mutex_t gThreadListLock; __LIBC_HIDDEN__ int __timespec_from_absolute(timespec*, const timespec*, clockid_t); -/* needed by fork.c */ -__LIBC_HIDDEN__ extern void __timer_table_start_stop(int); +/* Needed by fork. */ __LIBC_HIDDEN__ extern void __bionic_atfork_run_prepare(); __LIBC_HIDDEN__ extern void __bionic_atfork_run_child(); __LIBC_HIDDEN__ extern void __bionic_atfork_run_parent(); diff --git a/libc/bionic/timer.cpp b/libc/bionic/timer.cpp deleted file mode 100644 index ed73821c9..000000000 --- a/libc/bionic/timer.cpp +++ /dev/null @@ -1,636 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include "pthread_internal.h" - -#include -#include -#include - -extern int __pthread_cond_timedwait(pthread_cond_t*, pthread_mutex_t*, const timespec*, clockid_t); -extern int __pthread_cond_timedwait_relative(pthread_cond_t*, pthread_mutex_t*, const timespec*); - -// Normal (i.e. non-SIGEV_THREAD) timers are created directly by the kernel -// and are passed as is to/from the caller. -// -// This file also implements the support required for SIGEV_THREAD ("POSIX interval") -// timers. See the following pages for additional details: -// -// www.opengroup.org/onlinepubs/000095399/functions/timer_create.html -// www.opengroup.org/onlinepubs/000095399/functions/timer_settime.html -// www.opengroup.org/onlinepubs/000095399/functions/xsh_chap02_04.html#tag_02_04_01 -// -// The Linux kernel doesn't support these, so we need to implement them in the -// C library. We use a very basic scheme where each timer is associated to a -// thread that will loop, waiting for timeouts or messages from the program -// corresponding to calls to timer_settime() and timer_delete(). -// -// Note also an important thing: Posix mandates that in the case of fork(), -// the timers of the child process should be disarmed, but not deleted. -// this is implemented by providing a fork() wrapper (see bionic/fork.c) which -// stops all timers before the fork, and only re-start them in case of error -// or in the parent process. -// -// This stop/start is implemented by the __timer_table_start_stop() function -// below. -// -// A SIGEV_THREAD timer ID will always have its TIMER_ID_WRAP_BIT -// set to 1. In this implementation, this is always bit 31, which is -// guaranteed to never be used by kernel-provided timer ids -// -// (See code in /lib/idr.c, used to manage IDs, to see why.) - -#define TIMER_ID_WRAP_BIT 0x80000000 -#define TIMER_ID_WRAP(id) ((timer_t)((id) | TIMER_ID_WRAP_BIT)) -#define TIMER_ID_UNWRAP(id) ((timer_t)((id) & ~TIMER_ID_WRAP_BIT)) -#define TIMER_ID_IS_WRAPPED(id) (((id) & TIMER_ID_WRAP_BIT) != 0) - -/* this value is used internally to indicate a 'free' or 'zombie' - * thr_timer structure. Here, 'zombie' means that timer_delete() - * has been called, but that the corresponding thread hasn't - * exited yet. - */ -#define TIMER_ID_NONE ((timer_t)0xffffffff) - -/* True iff a timer id is valid */ -#define TIMER_ID_IS_VALID(id) ((id) != TIMER_ID_NONE) - -/* the maximum value of overrun counters */ -#define DELAYTIMER_MAX 0x7fffffff - -typedef struct thr_timer thr_timer_t; -typedef struct thr_timer_table thr_timer_table_t; - -/* The Posix spec says the function receives an unsigned parameter, but - * it's really a 'union sigval' a.k.a. sigval_t */ -typedef void (*thr_timer_func_t)( sigval_t ); - -struct thr_timer { - thr_timer_t* next; /* next in free list */ - timer_t id; /* TIMER_ID_NONE iff free or dying */ - clockid_t clock; - pthread_t thread; - pthread_attr_t attributes; - thr_timer_func_t callback; - sigval_t value; - - /* the following are used to communicate between - * the timer thread and the timer_XXX() functions - */ - pthread_mutex_t mutex; /* lock */ - pthread_cond_t cond; /* signal a state change to thread */ - int volatile done; /* set by timer_delete */ - int volatile stopped; /* set by _start_stop() */ - timespec volatile expires; /* next expiration time, or 0 */ - timespec volatile period; /* reload value, or 0 */ - int volatile overruns; /* current number of overruns */ -}; - -#define MAX_THREAD_TIMERS 32 - -struct thr_timer_table { - pthread_mutex_t lock; - thr_timer_t* free_timer; - thr_timer_t timers[ MAX_THREAD_TIMERS ]; -}; - -/** GLOBAL TABLE OF THREAD TIMERS - **/ - -static void -thr_timer_table_init( thr_timer_table_t* t ) -{ - int nn; - - memset(t, 0, sizeof *t); - pthread_mutex_init( &t->lock, NULL ); - - for (nn = 0; nn < MAX_THREAD_TIMERS; nn++) - t->timers[nn].id = TIMER_ID_NONE; - - t->free_timer = &t->timers[0]; - for (nn = 1; nn < MAX_THREAD_TIMERS; nn++) - t->timers[nn-1].next = &t->timers[nn]; -} - - -static thr_timer_t* -thr_timer_table_alloc( thr_timer_table_t* t ) -{ - thr_timer_t* timer; - - if (t == NULL) - return NULL; - - pthread_mutex_lock(&t->lock); - timer = t->free_timer; - if (timer != NULL) { - t->free_timer = timer->next; - timer->next = NULL; - timer->id = TIMER_ID_WRAP((timer - t->timers)); - } - pthread_mutex_unlock(&t->lock); - return timer; -} - - -static void -thr_timer_table_free( thr_timer_table_t* t, thr_timer_t* timer ) -{ - pthread_mutex_lock( &t->lock ); - timer->id = TIMER_ID_NONE; - timer->thread = 0; - timer->next = t->free_timer; - t->free_timer = timer; - pthread_mutex_unlock( &t->lock ); -} - - -static void thr_timer_table_start_stop(thr_timer_table_t* t, int stop) { - if (t == NULL) { - return; - } - - pthread_mutex_lock(&t->lock); - for (int nn = 0; nn < MAX_THREAD_TIMERS; ++nn) { - thr_timer_t* timer = &t->timers[nn]; - if (TIMER_ID_IS_VALID(timer->id)) { - // Tell the thread to start/stop. - pthread_mutex_lock(&timer->mutex); - timer->stopped = stop; - pthread_cond_signal( &timer->cond ); - pthread_mutex_unlock(&timer->mutex); - } - } - pthread_mutex_unlock(&t->lock); -} - - -/* convert a timer_id into the corresponding thr_timer_t* pointer - * returns NULL if the id is not wrapped or is invalid/free - */ -static thr_timer_t* -thr_timer_table_from_id( thr_timer_table_t* t, - timer_t id, - int remove ) -{ - unsigned index; - thr_timer_t* timer; - - if (t == NULL || !TIMER_ID_IS_WRAPPED(id)) - return NULL; - - index = (unsigned) TIMER_ID_UNWRAP(id); - if (index >= MAX_THREAD_TIMERS) - return NULL; - - pthread_mutex_lock(&t->lock); - - timer = &t->timers[index]; - - if (!TIMER_ID_IS_VALID(timer->id)) { - timer = NULL; - } else { - /* if we're removing this timer, clear the id - * right now to prevent another thread to - * use the same id after the unlock */ - if (remove) - timer->id = TIMER_ID_NONE; - } - pthread_mutex_unlock(&t->lock); - - return timer; -} - -/* the static timer table - we only create it if the process - * really wants to use SIGEV_THREAD timers, which should be - * pretty infrequent - */ - -static pthread_once_t __timer_table_once = PTHREAD_ONCE_INIT; -static thr_timer_table_t* __timer_table; - -static void __timer_table_init(void) { - __timer_table = reinterpret_cast(calloc(1, sizeof(*__timer_table))); - if (__timer_table != NULL) { - thr_timer_table_init(__timer_table); - } -} - -static thr_timer_table_t* __timer_table_get(void) { - pthread_once(&__timer_table_once, __timer_table_init); - return __timer_table; -} - -/** POSIX THREAD TIMERS CLEANUP ON FORK - ** - ** this should be called from the 'fork()' wrapper to stop/start - ** all active thread timers. this is used to implement a Posix - ** requirements: the timers of fork child processes must be - ** disarmed but not deleted. - **/ -void __timer_table_start_stop(int stop) { - // We access __timer_table directly so we don't create it if it doesn't yet exist. - thr_timer_table_start_stop(__timer_table, stop); -} - -static thr_timer_t* -thr_timer_from_id( timer_t id ) -{ - thr_timer_table_t* table = __timer_table_get(); - thr_timer_t* timer = thr_timer_table_from_id( table, id, 0 ); - - return timer; -} - - -static __inline__ void -thr_timer_lock( thr_timer_t* t ) -{ - pthread_mutex_lock(&t->mutex); -} - -static __inline__ void -thr_timer_unlock( thr_timer_t* t ) -{ - pthread_mutex_unlock(&t->mutex); -} - - -static __inline__ void timespec_add(timespec* a, const timespec* b) { - a->tv_sec += b->tv_sec; - a->tv_nsec += b->tv_nsec; - if (a->tv_nsec >= 1000000000) { - a->tv_nsec -= 1000000000; - a->tv_sec += 1; - } -} - -static __inline__ void timespec_sub(timespec* a, const timespec* b) { - a->tv_sec -= b->tv_sec; - a->tv_nsec -= b->tv_nsec; - if (a->tv_nsec < 0) { - a->tv_nsec += 1000000000; - a->tv_sec -= 1; - } -} - -static __inline__ void timespec_zero(timespec* a) { - a->tv_sec = a->tv_nsec = 0; -} - -static __inline__ int timespec_is_zero(const timespec* a) { - return (a->tv_sec == 0 && a->tv_nsec == 0); -} - -static __inline__ int timespec_cmp(const timespec* a, const timespec* b) { - if (a->tv_sec < b->tv_sec) return -1; - if (a->tv_sec > b->tv_sec) return +1; - if (a->tv_nsec < b->tv_nsec) return -1; - if (a->tv_nsec > b->tv_nsec) return +1; - return 0; -} - -static __inline__ int timespec_cmp0(const timespec* a) { - if (a->tv_sec < 0) return -1; - if (a->tv_sec > 0) return +1; - if (a->tv_nsec < 0) return -1; - if (a->tv_nsec > 0) return +1; - return 0; -} - -/** POSIX TIMERS APIs */ - -extern "C" int __timer_create(clockid_t, sigevent*, timer_t*); -extern "C" int __timer_delete(timer_t); -extern "C" int __timer_gettime(timer_t, itimerspec*); -extern "C" int __timer_settime(timer_t, int, const itimerspec*, itimerspec*); -extern "C" int __timer_getoverrun(timer_t); - -static void* timer_thread_start(void*); - -int timer_create(clockid_t clock_id, sigevent* evp, timer_t* timer_id) { - // If not a SIGEV_THREAD timer, the kernel can handle it without our help. - if (__predict_true(evp == NULL || evp->sigev_notify != SIGEV_THREAD)) { - return __timer_create(clock_id, evp, timer_id); - } - - // Check arguments. - if (evp->sigev_notify_function == NULL) { - errno = EINVAL; - return -1; - } - - // Check that the clock id is supported by the kernel. - timespec dummy; - if (clock_gettime(clock_id, &dummy) < 0 && errno == EINVAL) { - return -1; - } - - // Create a new timer and its thread. - // TODO: use a single global thread for all timers. - thr_timer_table_t* table = __timer_table_get(); - thr_timer_t* timer = thr_timer_table_alloc(table); - if (timer == NULL) { - errno = ENOMEM; - return -1; - } - - // Copy the thread attributes. - if (evp->sigev_notify_attributes == NULL) { - pthread_attr_init(&timer->attributes); - } else { - timer->attributes = ((pthread_attr_t*) evp->sigev_notify_attributes)[0]; - } - - // Posix says that the default is PTHREAD_CREATE_DETACHED and - // that PTHREAD_CREATE_JOINABLE has undefined behavior. - // So simply always use DETACHED :-) - pthread_attr_setdetachstate(&timer->attributes, PTHREAD_CREATE_DETACHED); - - timer->callback = evp->sigev_notify_function; - timer->value = evp->sigev_value; - timer->clock = clock_id; - - pthread_mutex_init(&timer->mutex, NULL); - pthread_cond_init(&timer->cond, NULL); - - timer->done = 0; - timer->stopped = 0; - timer->expires.tv_sec = timer->expires.tv_nsec = 0; - timer->period.tv_sec = timer->period.tv_nsec = 0; - timer->overruns = 0; - - // Create the thread. - int rc = pthread_create(&timer->thread, &timer->attributes, timer_thread_start, timer); - if (rc != 0) { - thr_timer_table_free(table, timer); - errno = rc; - return -1; - } - - *timer_id = timer->id; - return 0; -} - - -int -timer_delete( timer_t id ) -{ - if ( __predict_true(!TIMER_ID_IS_WRAPPED(id)) ) - return __timer_delete( id ); - else - { - thr_timer_table_t* table = __timer_table_get(); - thr_timer_t* timer = thr_timer_table_from_id(table, id, 1); - - if (timer == NULL) { - errno = EINVAL; - return -1; - } - - /* tell the timer's thread to stop */ - thr_timer_lock(timer); - timer->done = 1; - pthread_cond_signal( &timer->cond ); - thr_timer_unlock(timer); - - /* NOTE: the thread will call __timer_table_free() to free the - * timer object. the '1' parameter to thr_timer_table_from_id - * above ensured that the object and its timer_id cannot be - * reused before that. - */ - return 0; - } -} - -/* return the relative time until the next expiration, or 0 if - * the timer is disarmed */ -static void timer_gettime_internal(thr_timer_t* timer, itimerspec* spec) { - timespec diff = const_cast(timer->expires); - if (!timespec_is_zero(&diff)) { - timespec now; - - clock_gettime(timer->clock, &now); - timespec_sub(&diff, &now); - - /* in case of overrun, return 0 */ - if (timespec_cmp0(&diff) < 0) { - timespec_zero(&diff); - } - } - - spec->it_value = diff; - spec->it_interval = const_cast(timer->period); -} - - -int timer_gettime(timer_t id, itimerspec* ospec) { - if (ospec == NULL) { - errno = EINVAL; - return -1; - } - - if ( __predict_true(!TIMER_ID_IS_WRAPPED(id)) ) { - return __timer_gettime( id, ospec ); - } else { - thr_timer_t* timer = thr_timer_from_id(id); - - if (timer == NULL) { - errno = EINVAL; - return -1; - } - thr_timer_lock(timer); - timer_gettime_internal( timer, ospec ); - thr_timer_unlock(timer); - } - return 0; -} - - -int -timer_settime(timer_t id, int flags, const itimerspec* spec, itimerspec* ospec) { - if (spec == NULL) { - errno = EINVAL; - return -1; - } - - if ( __predict_true(!TIMER_ID_IS_WRAPPED(id)) ) { - return __timer_settime( id, flags, spec, ospec ); - } else { - thr_timer_t* timer = thr_timer_from_id(id); - timespec expires, now; - - if (timer == NULL) { - errno = EINVAL; - return -1; - } - thr_timer_lock(timer); - - /* return current timer value if ospec isn't NULL */ - if (ospec != NULL) { - timer_gettime_internal(timer, ospec ); - } - - /* compute next expiration time. note that if the - * new it_interval is 0, we should disarm the timer - */ - expires = spec->it_value; - if (!timespec_is_zero(&expires)) { - clock_gettime( timer->clock, &now ); - if (!(flags & TIMER_ABSTIME)) { - timespec_add(&expires, &now); - } else { - if (timespec_cmp(&expires, &now) < 0) - expires = now; - } - } - const_cast(timer->expires) = expires; - const_cast(timer->period) = spec->it_interval; - thr_timer_unlock( timer ); - - /* signal the change to the thread */ - pthread_cond_signal( &timer->cond ); - } - return 0; -} - - -int -timer_getoverrun(timer_t id) -{ - if ( __predict_true(!TIMER_ID_IS_WRAPPED(id)) ) { - return __timer_getoverrun( id ); - } else { - thr_timer_t* timer = thr_timer_from_id(id); - int result; - - if (timer == NULL) { - errno = EINVAL; - return -1; - } - - thr_timer_lock(timer); - result = timer->overruns; - thr_timer_unlock(timer); - - return result; - } -} - - -static void* timer_thread_start(void* arg) { - thr_timer_t* timer = reinterpret_cast(arg); - - thr_timer_lock(timer); - - // Give this thread a meaningful name. - char name[32]; - snprintf(name, sizeof(name), "POSIX interval timer 0x%08x", timer->id); - pthread_setname_np(pthread_self(), name); - - // We loop until timer->done is set in timer_delete(). - while (!timer->done) { - timespec expires = const_cast(timer->expires); - timespec period = const_cast(timer->period); - - // If the timer is stopped or disarmed, wait indefinitely - // for a state change from timer_settime/_delete/_start_stop. - if (timer->stopped || timespec_is_zero(&expires)) { - pthread_cond_wait(&timer->cond, &timer->mutex); - continue; - } - - // Otherwise, we need to do a timed wait until either a - // state change of the timer expiration time. - timespec now; - clock_gettime(timer->clock, &now); - - if (timespec_cmp(&expires, &now) > 0) { - // Cool, there was no overrun, so compute the - // relative timeout as 'expires - now', then wait. - timespec diff = expires; - timespec_sub(&diff, &now); - - int ret = __pthread_cond_timedwait_relative(&timer->cond, &timer->mutex, &diff); - - // If we didn't time out, it means that a state change - // occurred, so loop to take care of it. - if (ret != ETIMEDOUT) { - continue; - } - } else { - // Overrun was detected before we could wait! - if (!timespec_is_zero(&period)) { - // For periodic timers, compute total overrun count. - do { - timespec_add(&expires, &period); - if (timer->overruns < DELAYTIMER_MAX) { - timer->overruns += 1; - } - } while (timespec_cmp(&expires, &now) < 0); - - // Backtrack the last one, because we're going to - // add the same value just a bit later. - timespec_sub(&expires, &period); - } else { - // For non-periodic timers, things are simple. - timer->overruns = 1; - } - } - - // If we get here, a timeout was detected. - // First reload/disarm the timer as needed. - if (!timespec_is_zero(&period)) { - timespec_add(&expires, &period); - } else { - timespec_zero(&expires); - } - const_cast(timer->expires) = expires; - - // Now call the timer callback function. Release the - // lock to allow the function to modify the timer setting - // or call timer_getoverrun(). - // NOTE: at this point we trust the callback not to be a - // total moron and pthread_kill() the timer thread - thr_timer_unlock(timer); - timer->callback(timer->value); - thr_timer_lock(timer); - - // Now clear the overruns counter. it only makes sense - // within the callback. - timer->overruns = 0; - } - - thr_timer_unlock(timer); - - // Free the timer object. - thr_timer_table_free(__timer_table_get(), timer); - - return NULL; -} diff --git a/libc/include/sys/types.h b/libc/include/sys/types.h index 534058538..f8261f47b 100644 --- a/libc/include/sys/types.h +++ b/libc/include/sys/types.h @@ -69,7 +69,7 @@ typedef __ino_t ino_t; typedef __uint32_t __nlink_t; typedef __nlink_t nlink_t; -typedef int __timer_t; +typedef void* __timer_t; typedef __timer_t timer_t; typedef __int32_t __suseconds_t; diff --git a/tests/pthread_test.cpp b/tests/pthread_test.cpp index 120bbc7bf..1e85a54a7 100644 --- a/tests/pthread_test.cpp +++ b/tests/pthread_test.cpp @@ -25,6 +25,8 @@ #include #include +#include "ScopedSignalHandler.h" + TEST(pthread, pthread_key_create) { pthread_key_t key; ASSERT_EQ(0, pthread_key_create(&key, NULL)); @@ -337,14 +339,8 @@ static void pthread_kill__in_signal_handler_helper(int signal_number) { } TEST(pthread, pthread_kill__in_signal_handler) { - struct sigaction action; - struct sigaction original_action; - sigemptyset(&action.sa_mask); - action.sa_flags = 0; - action.sa_handler = pthread_kill__in_signal_handler_helper; - ASSERT_EQ(0, sigaction(SIGALRM, &action, &original_action)); + ScopedSignalHandler ssh(SIGALRM, pthread_kill__in_signal_handler_helper); ASSERT_EQ(0, pthread_kill(pthread_self(), SIGALRM)); - ASSERT_EQ(0, sigaction(SIGALRM, &original_action, NULL)); } TEST(pthread, pthread_detach__no_such_thread) { diff --git a/tests/time_test.cpp b/tests/time_test.cpp index 869ee4baa..9563c78c0 100644 --- a/tests/time_test.cpp +++ b/tests/time_test.cpp @@ -14,11 +14,14 @@ * limitations under the License. */ -#include +#include + +#include #include #include +#include -#include +#include "ScopedSignalHandler.h" #if defined(__BIONIC__) // mktime_tz is a bionic extension. #include @@ -92,3 +95,208 @@ TEST(time, mktime_10310929) { #endif #endif } + +void SetTime(timer_t t, time_t value_s, time_t value_ns, time_t interval_s, time_t interval_ns) { + itimerspec ts; + ts.it_value.tv_sec = value_s; + ts.it_value.tv_nsec = value_ns; + ts.it_interval.tv_sec = interval_s; + ts.it_interval.tv_nsec = interval_ns; + ASSERT_EQ(0, timer_settime(t, TIMER_ABSTIME, &ts, NULL)); +} + +static void NoOpNotifyFunction(sigval_t) { +} + +TEST(time, timer_create) { + sigevent_t se; + memset(&se, 0, sizeof(se)); + se.sigev_notify = SIGEV_THREAD; + se.sigev_notify_function = NoOpNotifyFunction; + timer_t timer_id; + ASSERT_EQ(0, timer_create(CLOCK_MONOTONIC, &se, &timer_id)); + + int pid = fork(); + ASSERT_NE(-1, pid) << strerror(errno); + + if (pid == 0) { + // Timers are not inherited by the child. + ASSERT_EQ(-1, timer_delete(timer_id)); + ASSERT_EQ(EINVAL, errno); + _exit(0); + } + + int status; + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(0, WEXITSTATUS(status)); + + ASSERT_EQ(0, timer_delete(timer_id)); +} + +static int timer_create_SIGEV_SIGNAL_signal_handler_invocation_count = 0; +static void timer_create_SIGEV_SIGNAL_signal_handler(int signal_number) { + ++timer_create_SIGEV_SIGNAL_signal_handler_invocation_count; + ASSERT_EQ(SIGUSR1, signal_number); +} + +TEST(time, timer_create_SIGEV_SIGNAL) { + sigevent_t se; + memset(&se, 0, sizeof(se)); + se.sigev_notify = SIGEV_SIGNAL; + se.sigev_signo = SIGUSR1; + + timer_t timer_id; + ASSERT_EQ(0, timer_create(CLOCK_MONOTONIC, &se, &timer_id)); + + ScopedSignalHandler ssh(SIGUSR1, timer_create_SIGEV_SIGNAL_signal_handler); + + ASSERT_EQ(0, timer_create_SIGEV_SIGNAL_signal_handler_invocation_count); + + itimerspec ts; + ts.it_value.tv_sec = 0; + ts.it_value.tv_nsec = 1; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + ASSERT_EQ(0, timer_settime(timer_id, TIMER_ABSTIME, &ts, NULL)); + + usleep(500000); + ASSERT_EQ(1, timer_create_SIGEV_SIGNAL_signal_handler_invocation_count); +} + +struct Counter { + volatile int value; + timer_t timer_id; + sigevent_t se; + + Counter(void (*fn)(sigval_t)) : value(0) { + memset(&se, 0, sizeof(se)); + se.sigev_notify = SIGEV_THREAD; + se.sigev_notify_function = fn; + se.sigev_value.sival_ptr = this; + } + + void Create() { + ASSERT_EQ(0, timer_create(CLOCK_REALTIME, &se, &timer_id)); + } + + ~Counter() { + if (timer_delete(timer_id) != 0) { + abort(); + } + } + + static void CountNotifyFunction(sigval_t value) { + Counter* cd = reinterpret_cast(value.sival_ptr); + ++cd->value; + } + + static void CountAndDisarmNotifyFunction(sigval_t value) { + Counter* cd = reinterpret_cast(value.sival_ptr); + ++cd->value; + + // Setting the initial expiration time to 0 disarms the timer. + SetTime(cd->timer_id, 0, 0, 1, 0); + } +}; + +TEST(time, timer_settime_0) { + Counter counter(Counter::CountAndDisarmNotifyFunction); + counter.Create(); + + ASSERT_EQ(0, counter.value); + + SetTime(counter.timer_id, 0, 1, 1, 0); + usleep(500000); + + // The count should just be 1 because we disarmed the timer the first time it fired. + ASSERT_EQ(1, counter.value); +} + +TEST(time, timer_settime_repeats) { + Counter counter(Counter::CountNotifyFunction); + counter.Create(); + + ASSERT_EQ(0, counter.value); + + SetTime(counter.timer_id, 0, 1, 0, 10); + usleep(500000); + + // The count should just be > 1 because we let the timer repeat. + ASSERT_GT(counter.value, 1); +} + +static int timer_create_NULL_signal_handler_invocation_count = 0; +static void timer_create_NULL_signal_handler(int signal_number) { + ++timer_create_NULL_signal_handler_invocation_count; + ASSERT_EQ(SIGALRM, signal_number); +} + +TEST(time, timer_create_NULL) { + // A NULL sigevent* is equivalent to asking for SIGEV_SIGNAL for SIGALRM. + timer_t timer_id; + ASSERT_EQ(0, timer_create(CLOCK_MONOTONIC, NULL, &timer_id)); + + ScopedSignalHandler ssh(SIGALRM, timer_create_NULL_signal_handler); + + ASSERT_EQ(0, timer_create_NULL_signal_handler_invocation_count); + + SetTime(timer_id, 0, 1, 0, 0); + usleep(500000); + + ASSERT_EQ(1, timer_create_NULL_signal_handler_invocation_count); +} + +TEST(time, timer_create_EINVAL) { + clockid_t invalid_clock = 16; + + // A SIGEV_SIGNAL timer is easy; the kernel does all that. + timer_t timer_id; + ASSERT_EQ(-1, timer_create(invalid_clock, NULL, &timer_id)); + ASSERT_EQ(EINVAL, errno); + + // A SIGEV_THREAD timer is more interesting because we have stuff to clean up. + sigevent_t se; + memset(&se, 0, sizeof(se)); + se.sigev_notify = SIGEV_THREAD; + se.sigev_notify_function = NoOpNotifyFunction; + ASSERT_EQ(-1, timer_create(invalid_clock, &se, &timer_id)); + ASSERT_EQ(EINVAL, errno); +} + +TEST(time, timer_delete_multiple) { + timer_t timer_id; + ASSERT_EQ(0, timer_create(CLOCK_MONOTONIC, NULL, &timer_id)); + ASSERT_EQ(0, timer_delete(timer_id)); + ASSERT_EQ(-1, timer_delete(timer_id)); + ASSERT_EQ(EINVAL, errno); + + sigevent_t se; + memset(&se, 0, sizeof(se)); + se.sigev_notify = SIGEV_THREAD; + se.sigev_notify_function = NoOpNotifyFunction; + ASSERT_EQ(0, timer_create(CLOCK_MONOTONIC, &se, &timer_id)); + ASSERT_EQ(0, timer_delete(timer_id)); + ASSERT_EQ(-1, timer_delete(timer_id)); + ASSERT_EQ(EINVAL, errno); +} + +TEST(time, timer_create_multiple) { + Counter counter1(Counter::CountNotifyFunction); + counter1.Create(); + Counter counter2(Counter::CountNotifyFunction); + counter2.Create(); + Counter counter3(Counter::CountNotifyFunction); + counter3.Create(); + + ASSERT_EQ(0, counter1.value); + ASSERT_EQ(0, counter2.value); + ASSERT_EQ(0, counter3.value); + + SetTime(counter2.timer_id, 0, 1, 0, 0); + usleep(500000); + + EXPECT_EQ(0, counter1.value); + EXPECT_EQ(1, counter2.value); + EXPECT_EQ(0, counter3.value); +}