Finished porting over the timer functions from testing_darwinunix.c to timer_darwinunix.c. This also includes changing timerRunWIthTimeout() from using setitimer() to using timer_create() and friends, and removing the fallbacks from timerSleep().
Also fixed a possible incorrect use of TlsSetValue() in the Windows code that I spotted while writing an intermediate version of the Unix code.
This commit is contained in:
parent
6a25efce63
commit
8655bbf19c
|
@ -18,103 +18,6 @@
|
||||||
|
|
||||||
// TODO don't start the timer on any platform until after we call setjmp(); also decide whether to start the timer before or after resuming the thread on Windows
|
// TODO don't start the timer on any platform until after we call setjmp(); also decide whether to start the timer before or after resuming the thread on Windows
|
||||||
|
|
||||||
static jmp_buf timeout_ret;
|
|
||||||
|
|
||||||
static void onTimeout(int sig)
|
|
||||||
{
|
|
||||||
longjmp(timeout_ret, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testingprivRunWithTimeout(testingT *t, const char *file, long line, int64_t timeout, void (*f)(testingT *t, void *data), void *data, const char *comment, int failNowOnError)
|
|
||||||
{
|
|
||||||
char *timeoutstr;
|
|
||||||
void (*prevsig)(int);
|
|
||||||
struct itimerval duration, prevDuration;
|
|
||||||
int setitimerError = 0;
|
|
||||||
|
|
||||||
timeoutstr = testingNsecString(timeout);
|
|
||||||
prevsig = signal(SIGALRM, onTimeout);
|
|
||||||
|
|
||||||
duration.it_interval.tv_sec = 0;
|
|
||||||
duration.it_interval.tv_usec = 0;
|
|
||||||
duration.it_value.tv_sec = timeout / testingNsecPerSec;
|
|
||||||
duration.it_value.tv_usec = (timeout % testingNsecPerSec) / testingNsecPerUsec;
|
|
||||||
if (setitimer(ITIMER_REAL, &duration, &prevDuration) != 0) {
|
|
||||||
setitimerError = errno;
|
|
||||||
testingprivTLogfFull(t, file, line, "error applying %s timeout: %s", comment, strerror(setitimerError));
|
|
||||||
testingTFail(t);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setjmp(timeout_ret) == 0) {
|
|
||||||
(*f)(t, data);
|
|
||||||
failNowOnError = 0; // we succeeded
|
|
||||||
} else {
|
|
||||||
testingprivTLogfFull(t, file, line, "%s timeout passed (%s)", comment, timeoutstr);
|
|
||||||
testingTFail(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
if (setitimerError == 0)
|
|
||||||
setitimer(ITIMER_REAL, &prevDuration, NULL);
|
|
||||||
signal(SIGALRM, prevsig);
|
|
||||||
testingFreeNsecString(timeoutstr);
|
|
||||||
if (failNowOnError)
|
|
||||||
testingTFailNow(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testingSleep(int64_t nsec)
|
|
||||||
{
|
|
||||||
struct timespec duration, remaining;
|
|
||||||
void (*prevsig)(int);
|
|
||||||
sigset_t set, prevSet;
|
|
||||||
struct itimerval setiDuration, prevSetiDuration;
|
|
||||||
unsigned sec;
|
|
||||||
|
|
||||||
duration.tv_sec = nsec / testingNsecPerSec;
|
|
||||||
duration.tv_nsec = nsec % testingNsecPerSec;
|
|
||||||
for (;;) {
|
|
||||||
errno = 0;
|
|
||||||
if (nanosleep(&duration, &remaining) == 0)
|
|
||||||
return;
|
|
||||||
if (errno != EINTR)
|
|
||||||
break;
|
|
||||||
duration = remaining;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we got here, nanosleep() failed outright
|
|
||||||
if (sigemptyset(&set) != 0)
|
|
||||||
goto fallback;
|
|
||||||
if (sigaddset(&set, SIGABRT) != 0)
|
|
||||||
goto fallback;
|
|
||||||
if (pthread_sigmask(SIG_BLOCK, &set, &prevSet) != 0)
|
|
||||||
goto fallback;
|
|
||||||
prevsig = signal(SIGALRM, SIG_IGN);
|
|
||||||
setiDuration.it_interval.tv_sec = 0;
|
|
||||||
setiDuration.it_interval.tv_usec = 0;
|
|
||||||
// keep using duration for this in case nanosleep() was interrupted before it failed
|
|
||||||
setiDuration.it_value.tv_sec = duration.tv_sec;
|
|
||||||
setiDuration.it_value.tv_usec = duration.tv_nsec / testingNsecPerUsec;
|
|
||||||
if (setitimer(ITIMER_REAL, &setiDuration, &prevSetiDuration) != 0) {
|
|
||||||
pthread_sigmask(SIG_SETMASK, &prevSet, NULL);
|
|
||||||
signal(SIGALRM, prevsig);
|
|
||||||
goto fallback;
|
|
||||||
}
|
|
||||||
// TODO can this return an errno other than EINTR?
|
|
||||||
sigsuspend(&prevSet);
|
|
||||||
setitimer(ITIMER_REAL, &prevSetiDuration, NULL);
|
|
||||||
signal(SIGALRM, prevsig);
|
|
||||||
return;
|
|
||||||
|
|
||||||
fallback:
|
|
||||||
// hopefully we never reach this point, because it has the least granularity of all, but there are no errors, so...
|
|
||||||
sec = duration.tv_sec;
|
|
||||||
if (duration.tv_nsec > 0)
|
|
||||||
sec++;
|
|
||||||
while (sec > 0)
|
|
||||||
sec = sleep(sec);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct testingThread {
|
struct testingThread {
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
void (*f)(void *data);
|
void (*f)(void *data);
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
// 3 may 2019
|
// 3 may 2019
|
||||||
|
// TODO pin down minimum POSIX versions (depends on what macOS 10.8 conforms to and what GLib/GTK+ require)
|
||||||
|
#define _POSIX_C_SOURCE 200112L
|
||||||
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <time.h>
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include <mach/mach.h>
|
#include <mach/mach.h>
|
||||||
#include <mach/mach_time.h>
|
#include <mach/mach_time.h>
|
||||||
#else
|
|
||||||
#include <errno.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <time.h>
|
|
||||||
#endif
|
#endif
|
||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
|
|
||||||
|
@ -81,3 +86,187 @@ timerDuration timerTimeSub(timerTime end, timerTime start)
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct timeoutParams {
|
||||||
|
jmp_buf retpos;
|
||||||
|
timer_t timer;
|
||||||
|
struct sigaction oldsig;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct timeoutParams p;
|
||||||
|
|
||||||
|
static void onTimeout(int sig, siginfo_t *info, void *ctx)
|
||||||
|
{
|
||||||
|
if (info->si_value.sival_ptr == &p)
|
||||||
|
longjmp(p.retpos, 1);
|
||||||
|
// otherwise, call the overloaded SIGALRM handler
|
||||||
|
if ((p.oldsig.sa_flags & SA_SIGINFO) != 0) {
|
||||||
|
(*(p.oldsig.sa_sigaction))(sig, info, ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (p.oldsig.sa_handler == SIG_IGN)
|
||||||
|
return;
|
||||||
|
if (p.oldsig.sa_handler == SIG_DFL) {
|
||||||
|
// SIG_DFL for SIGALRM is to terminate the program
|
||||||
|
// because POSIX doesn't specify how to convert from signal number to exit code, we will have to do this instead
|
||||||
|
// (POSIX does say these should be safe to call unless the signal was explicitly raised, which we aren't doing, and timer_create() isn't documented as doing that either...)
|
||||||
|
signal(sig, SIG_DFL);
|
||||||
|
raise(sig);
|
||||||
|
}
|
||||||
|
(*(p.oldsig.sa_handler))(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
timerSysError timerRunWithTimeout(timerDuration d, void (*f)(void *data), void *data, int *timedOut)
|
||||||
|
{
|
||||||
|
sigset_t sigalrm, allsigs;
|
||||||
|
sigset_t prevMask;
|
||||||
|
volatile int restorePrevMask = 0;
|
||||||
|
struct sigevent event;
|
||||||
|
volatile int destroyTimer = 0;
|
||||||
|
struct sigaction sig;
|
||||||
|
volatile int restoreSignal = 0;
|
||||||
|
struct itimerspec duration;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
*timedOut = 0;
|
||||||
|
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;
|
||||||
|
restorePrevMask = 1;
|
||||||
|
|
||||||
|
memset(&event, 0, sizeof (struct sigevent));
|
||||||
|
event.sigev_notify = SIGEV_SIGNAL;
|
||||||
|
event.sigev_signo = SIGALRM;
|
||||||
|
event.sigev_value.sival_ptr = &p;
|
||||||
|
errno = 0;
|
||||||
|
if (timer_create(CLOCK_REALTIME, &event, &(p.timer)) != 0) {
|
||||||
|
err = errno;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
destroyTimer = 1;
|
||||||
|
|
||||||
|
if (setjmp(p.retpos) == 0) {
|
||||||
|
sig.sa_mask = &allsigs;
|
||||||
|
sig.sa_flags = SA_SIGINFO;
|
||||||
|
sig.sa_sigaction = onTimeout;
|
||||||
|
errno = 0;
|
||||||
|
if (sigaction(SIGALRM, &sig, &(p.oldsig)) != 0) {
|
||||||
|
err = errno;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
restoreSignal = 1;
|
||||||
|
|
||||||
|
duration.it_interval.tv_sec = 0;
|
||||||
|
duration.it_interval.tv_nsec = 0;
|
||||||
|
duration.it_value.tv_sec = d / testingNsecPerSec;
|
||||||
|
duration.it_value.tv_nsec = d % testingNsecPerSec;
|
||||||
|
errno = 0;
|
||||||
|
if (timer_settimer(p.timer, 0, &duration, NULL) != 0) {
|
||||||
|
err = errno;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
// and fire away
|
||||||
|
err = pthread_sigmask(SIG_UNBLOCK, &sigalrm, NULL);
|
||||||
|
if (err != 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
(*f)(data);
|
||||||
|
} else
|
||||||
|
*timedOut = 1;
|
||||||
|
err = 0;
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (restoreSignal)
|
||||||
|
sigaction(SIGALRM, &(p.oldsig), NULL);
|
||||||
|
if (destroyTimer)
|
||||||
|
timer_delete(p.timer);
|
||||||
|
if (restorePrevMask)
|
||||||
|
pthread_sigmask(SIG_SETMASK, &prevMask, NULL);
|
||||||
|
teardownNonReentrance();
|
||||||
|
return (timerSysError) err;
|
||||||
|
}
|
||||||
|
|
||||||
|
timerSysError timerSleep(timerDuration d)
|
||||||
|
{
|
||||||
|
struct timespec duration, remaining;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
duration.tv_sec = d / testingNsecPerSec;
|
||||||
|
duration.tv_nsec = d % testingNsecPerSec;
|
||||||
|
for (;;) {
|
||||||
|
errno = 0;
|
||||||
|
if (nanosleep(&duration, &remaining) == 0)
|
||||||
|
return 0;
|
||||||
|
err = errno;
|
||||||
|
if (err != EINTR)
|
||||||
|
return (timerSysError) err;
|
||||||
|
duration = remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -206,10 +206,6 @@ struct timeoutParams {
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
};
|
};
|
||||||
|
|
||||||
static DWORD timeoutParamsSlot;
|
|
||||||
static HRESULT timeoutParamsHRESULT = S_OK;
|
|
||||||
static INIT_ONCE timeoutParamsOnce = INIT_ONCE_STATIC_INIT;
|
|
||||||
|
|
||||||
static void onTimeout(void)
|
static void onTimeout(void)
|
||||||
{
|
{
|
||||||
struct timeoutParams *p;
|
struct timeoutParams *p;
|
||||||
|
@ -218,6 +214,10 @@ static void onTimeout(void)
|
||||||
longjmp(p->retpos, 1);
|
longjmp(p->retpos, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static DWORD timeoutParamsSlot;
|
||||||
|
static HRESULT timeoutParamsHRESULT = S_OK;
|
||||||
|
static INIT_ONCE timeoutParamsOnce = INIT_ONCE_STATIC_INIT;
|
||||||
|
|
||||||
static BOOL CALLBACK timeoutParamsSlotInit(PINIT_ONCE once, PVOID param, PVOID *ctx)
|
static BOOL CALLBACK timeoutParamsSlotInit(PINIT_ONCE once, PVOID param, PVOID *ctx)
|
||||||
{
|
{
|
||||||
SetLastError(0);
|
SetLastError(0);
|
||||||
|
@ -317,6 +317,7 @@ static unsigned __stdcall timerThreadProc(void *data)
|
||||||
timerSysError timerRunWithTimeout(timerDuration d, void (*f)(void *data), void *data, int *timedOut)
|
timerSysError timerRunWithTimeout(timerDuration d, void (*f)(void *data), void *data, int *timedOut)
|
||||||
{
|
{
|
||||||
struct timeoutParams *p;
|
struct timeoutParams *p;
|
||||||
|
int doTeardownNonReentrance = 0;
|
||||||
MSG msg;
|
MSG msg;
|
||||||
volatile HANDLE timerThread = NULL;
|
volatile HANDLE timerThread = NULL;
|
||||||
LARGE_INTEGER duration;
|
LARGE_INTEGER duration;
|
||||||
|
@ -332,6 +333,7 @@ timerSysError timerRunWithTimeout(timerDuration d, void (*f)(void *data), void *
|
||||||
hr = setupNonReentrance(p);
|
hr = setupNonReentrance(p);
|
||||||
if (hr != S_OK)
|
if (hr != S_OK)
|
||||||
goto out;
|
goto out;
|
||||||
|
doTeardownNonReentrance = 1;
|
||||||
|
|
||||||
// to ensure that the PostThreadMessage() above will not fail because the thread doesn't have a message queue
|
// to ensure that the PostThreadMessage() above will not fail because the thread doesn't have a message queue
|
||||||
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
|
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
|
||||||
|
@ -401,6 +403,7 @@ out:
|
||||||
CloseHandle(p->timer);
|
CloseHandle(p->timer);
|
||||||
if (p->targetThread != NULL)
|
if (p->targetThread != NULL)
|
||||||
CloseHandle(p->targetThread);
|
CloseHandle(p->targetThread);
|
||||||
|
if (doTeardownNonReentrance)
|
||||||
teardownNonReentrance();
|
teardownNonReentrance();
|
||||||
free(p);
|
free(p);
|
||||||
return (timerSysError) hr;
|
return (timerSysError) hr;
|
||||||
|
|
Loading…
Reference in New Issue