2019-05-03 22:03:38 -05:00
// 3 may 2019
2019-05-04 11:11:31 -05:00
// TODO pin down minimum POSIX versions (depends on what macOS 10.8 conforms to and what GLib/GTK+ require)
2020-01-02 21:30:31 -06:00
// TODO also pin down which of these I should be defining, because apparently FreeBSD only checks for _XOPEN_SOURCE (and 2004 POSIX says that defining this as 600 *implies* _POSIX_C_SOURCE being 200112L so only _XOPEN_SOURCE is needed...)
2019-05-04 11:11:31 -05:00
# define _POSIX_C_SOURCE 200112L
2020-01-02 21:30:31 -06:00
# define _XOPEN_SOURCE 600
2019-05-03 22:03:38 -05:00
# include <errno.h>
# include <pthread.h>
2019-05-04 11:11:31 -05:00
# include <setjmp.h>
# include <signal.h>
2019-05-03 22:03:38 -05:00
# include <stdio.h>
# include <stdlib.h>
2019-05-04 11:11:31 -05:00
# include <string.h>
# include <sys/time.h>
2019-05-03 22:03:38 -05:00
# include <time.h>
2019-05-04 11:11:31 -05:00
# ifdef __APPLE__
# include <mach/mach.h>
# include <mach/mach_time.h>
2019-05-03 22:03:38 -05:00
# endif
# include "timer.h"
2019-05-05 02:17:11 -05:00
# include "timerpriv.h"
static void mustpthread_once ( pthread_once_t * once , void ( * init ) ( void ) )
{
int err ;
err = pthread_once ( once , init ) ;
if ( err ! = 0 ) {
fprintf ( stderr , " *** internal error in timerMonotonicNow(): pthread_once() failed: %s (%d) \n " , strerror ( err ) , err ) ;
abort ( ) ;
}
}
2019-05-03 22:03:38 -05:00
# ifdef __APPLE__
2019-05-05 02:17:11 -05:00
static uint64_t base ;
static mach_timebase_info_data_t mt ;
static pthread_once_t baseOnce = PTHREAD_ONCE_INIT ;
static void baseInit ( void )
2019-05-03 22:03:38 -05:00
{
2019-05-05 02:17:11 -05:00
kern_return_t err ;
base = mach_absolute_time ( ) ;
err = mach_timebase_info ( & mt ) ;
if ( err ! = KERN_SUCCESS ) {
fprintf ( stderr , " *** internal error in timerMonotonicNow(): mach_timebase_info() failed: kern_return_t %d \n " , err ) ;
abort ( ) ;
}
2019-05-03 22:03:38 -05:00
}
2019-05-05 02:17:11 -05:00
timerTime timerMonotonicNow ( void )
2019-05-03 22:03:38 -05:00
{
2019-05-05 02:17:11 -05:00
uint64_t t ;
timerprivInt128 quot ;
mustpthread_once ( & baseOnce , baseInit ) ;
t = mach_absolute_time ( ) - base ;
timerprivMulDivUint64 ( t , mt . numer , mt . denom , & quot ) ;
// on overflow, return the maximum possible timerTime; this is inspired by what Go does
2019-05-05 10:16:17 -05:00
// the limit check will ensure we can safely cast the return value to a timerTime
return ( timerTime ) timerprivInt128ToUint64 ( & quot ,
( uint64_t ) timerTimeMax , ( uint64_t ) timerTimeMax ) ;
2019-05-03 22:03:38 -05:00
}
# else
static void mustclock_gettime ( clockid_t id , struct timespec * ts )
{
int err ;
errno = 0 ;
if ( clock_gettime ( id , ts ) ! = 0 ) {
err = errno ;
fprintf ( stderr , " *** internal error in timerMonotonicNow(): clock_gettime() failed: %s (%d) \n " , strerror ( err ) , err ) ;
abort ( ) ;
}
}
static struct timespec base ;
static pthread_once_t baseOnce = PTHREAD_ONCE_INIT ;
static void baseInit ( void )
{
mustclock_gettime ( CLOCK_MONOTONIC , & base ) ;
}
timerTime timerMonotonicNow ( void )
{
struct timespec ts ;
2019-05-04 13:44:29 -05:00
timerTime ret ;
2019-05-03 22:03:38 -05:00
2019-05-05 02:17:11 -05:00
mustpthread_once ( & baseOnce , baseInit ) ;
2019-05-03 22:03:38 -05:00
mustclock_gettime ( CLOCK_MONOTONIC , & ts ) ;
ts . tv_sec - = base . tv_sec ;
ts . tv_nsec - = base . tv_nsec ;
if ( ts . tv_nsec < 0 ) { // this is safe because POSIX requires this to be of type long
ts . tv_sec - - ;
2019-05-04 13:24:22 -05:00
ts . tv_nsec + = timerSecond ;
2019-05-03 22:03:38 -05:00
}
ret = ( ( timerTime ) ( ts . tv_sec ) ) * timerSecond ;
ret + = ( timerTime ) ( ts . tv_nsec ) ;
return ret ;
}
2019-05-05 02:17:11 -05:00
# endif
2019-05-03 22:03:38 -05:00
timerDuration timerTimeSub ( timerTime end , timerTime start )
{
return end - start ;
}
2019-05-04 11:11:31 -05:00
struct timeoutParams {
jmp_buf retpos ;
2019-05-04 13:24:22 -05:00
struct itimerval prevDuration ;
struct sigaction prevSig ;
2019-05-04 11:11:31 -05:00
} ;
static struct timeoutParams p ;
static void onTimeout ( int sig , siginfo_t * info , void * ctx )
{
2019-05-04 13:31:33 -05:00
longjmp ( p . retpos , 1 ) ;
2019-05-04 11:11:31 -05:00
}
// POSIX doesn't have atomic operations :|
// We could have special OS X-specific code that uses OSAtomic.h, but they have signedness type mismatches :| :|
// There's also the GCC intrinsic atomic operations, but I cannot find a way to portably detect their presence :| :| :|
// And pthread_mutex_lock() and pthread_mutex_unlock() CAN fail :| :| :| :|
static pthread_mutex_t nonReentranceMutex = PTHREAD_MUTEX_INITIALIZER ;
static volatile uint32_t nonReentranceInCall = 0 ;
static void mustpthread_mutex_lock ( pthread_mutex_t * mu )
{
int err ;
err = pthread_mutex_lock ( mu ) ;
if ( err ! = 0 ) {
fprintf ( stderr , " *** internal error in timerRunWithTimeout(): pthread_mutex_lock() failed: %s (%d) \n " , strerror ( err ) , err ) ;
abort ( ) ;
}
}
static void mustpthread_mutex_unlock ( pthread_mutex_t * mu )
{
int err ;
err = pthread_mutex_unlock ( mu ) ;
if ( err ! = 0 ) {
fprintf ( stderr , " *** internal error in timerRunWithTimeout(): pthread_mutex_unlock() failed: %s (%d) \n " , strerror ( err ) , err ) ;
abort ( ) ;
}
}
static int setupNonReentrance ( void )
{
int err ;
mustpthread_mutex_lock ( & nonReentranceMutex ) ;
err = 0 ;
if ( nonReentranceInCall )
err = EALREADY ;
else
nonReentranceInCall = 1 ;
mustpthread_mutex_unlock ( & nonReentranceMutex ) ;
return err ;
}
static void teardownNonReentrance ( void )
{
mustpthread_mutex_lock ( & nonReentranceMutex ) ;
nonReentranceInCall = 0 ;
mustpthread_mutex_unlock ( & nonReentranceMutex ) ;
}
2019-05-30 21:09:45 -05:00
timerSysError timerRunWithTimeout ( timerDuration d , void ( * f ) ( void * data ) , void * data , bool * timedOut )
2019-05-04 11:11:31 -05:00
{
sigset_t sigalrm , allsigs ;
sigset_t prevMask ;
2019-05-30 21:09:45 -05:00
volatile bool restorePrevMask = false ;
2019-05-04 11:11:31 -05:00
struct sigaction sig ;
2019-05-30 21:09:45 -05:00
volatile bool restoreSignal = false ;
2019-05-04 13:24:22 -05:00
struct itimerval duration ;
2019-05-30 21:09:45 -05:00
volatile bool destroyTimer = false ;
2019-05-04 11:11:31 -05:00
int err = 0 ;
2019-05-30 21:09:45 -05:00
* timedOut = false ;
2019-05-04 11:11:31 -05:00
err = setupNonReentrance ( ) ;
if ( err ! = 0 )
return ( timerSysError ) err ;
memset ( & p , 0 , sizeof ( struct timeoutParams ) ) ;
errno = 0 ;
if ( sigemptyset ( & sigalrm ) ! = 0 )
return ( timerSysError ) errno ;
errno = 0 ;
if ( sigaddset ( & sigalrm , SIGALRM ) ! = 0 )
return ( timerSysError ) errno ;
errno = 0 ;
if ( sigfillset ( & allsigs ) ! = 0 )
return ( timerSysError ) errno ;
err = pthread_sigmask ( SIG_BLOCK , & sigalrm , & prevMask ) ;
if ( err ! = 0 )
return ( timerSysError ) err ;
2019-05-30 21:09:45 -05:00
restorePrevMask = true ;
2019-05-04 11:11:31 -05:00
if ( setjmp ( p . retpos ) = = 0 ) {
2019-05-04 13:24:22 -05:00
sig . sa_mask = allsigs ;
2019-05-04 11:11:31 -05:00
sig . sa_flags = SA_SIGINFO ;
sig . sa_sigaction = onTimeout ;
errno = 0 ;
2019-05-04 13:24:22 -05:00
if ( sigaction ( SIGALRM , & sig , & ( p . prevSig ) ) ! = 0 ) {
2019-05-04 11:11:31 -05:00
err = errno ;
goto out ;
}
2019-05-30 21:09:45 -05:00
restoreSignal = true ;
2019-05-04 11:11:31 -05:00
duration . it_interval . tv_sec = 0 ;
2019-05-04 13:24:22 -05:00
duration . it_interval . tv_usec = 0 ;
duration . it_value . tv_sec = d / timerSecond ;
duration . it_value . tv_usec = ( d % timerSecond ) / timerMicrosecond ;
2019-05-04 11:11:31 -05:00
errno = 0 ;
2019-05-04 13:24:22 -05:00
if ( setitimer ( ITIMER_REAL , & duration , & ( p . prevDuration ) ) ! = 0 ) {
2019-05-04 11:11:31 -05:00
err = errno ;
goto out ;
}
2019-05-30 21:09:45 -05:00
destroyTimer = true ;
2019-05-04 13:24:22 -05:00
2019-05-04 11:11:31 -05:00
// and fire away
err = pthread_sigmask ( SIG_UNBLOCK , & sigalrm , NULL ) ;
if ( err ! = 0 )
goto out ;
( * f ) ( data ) ;
} else
2019-05-30 21:09:45 -05:00
* timedOut = true ;
2019-05-04 11:11:31 -05:00
err = 0 ;
out :
if ( destroyTimer )
2019-05-04 13:24:22 -05:00
setitimer ( ITIMER_REAL , & ( p . prevDuration ) , NULL ) ;
if ( restoreSignal )
sigaction ( SIGALRM , & ( p . prevSig ) , NULL ) ;
2019-05-04 11:11:31 -05:00
if ( restorePrevMask )
pthread_sigmask ( SIG_SETMASK , & prevMask , NULL ) ;
teardownNonReentrance ( ) ;
return ( timerSysError ) err ;
}
timerSysError timerSleep ( timerDuration d )
{
struct timespec duration , remaining ;
int err ;
2019-05-04 13:24:22 -05:00
duration . tv_sec = d / timerSecond ;
duration . tv_nsec = d % timerSecond ;
2019-05-04 11:11:31 -05:00
for ( ; ; ) {
errno = 0 ;
if ( nanosleep ( & duration , & remaining ) = = 0 )
return 0 ;
err = errno ;
if ( err ! = EINTR )
return ( timerSysError ) err ;
duration = remaining ;
}
}