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
|
||||
|
||||
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 {
|
||||
pthread_t thread;
|
||||
void (*f)(void *data);
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
// 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__
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
#else
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#endif
|
||||
#include "timer.h"
|
||||
|
||||
|
@ -81,3 +86,187 @@ timerDuration timerTimeSub(timerTime end, timerTime start)
|
|||
}
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
static DWORD timeoutParamsSlot;
|
||||
static HRESULT timeoutParamsHRESULT = S_OK;
|
||||
static INIT_ONCE timeoutParamsOnce = INIT_ONCE_STATIC_INIT;
|
||||
|
||||
static void onTimeout(void)
|
||||
{
|
||||
struct timeoutParams *p;
|
||||
|
@ -218,6 +214,10 @@ static void onTimeout(void)
|
|||
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)
|
||||
{
|
||||
SetLastError(0);
|
||||
|
@ -317,6 +317,7 @@ static unsigned __stdcall timerThreadProc(void *data)
|
|||
timerSysError timerRunWithTimeout(timerDuration d, void (*f)(void *data), void *data, int *timedOut)
|
||||
{
|
||||
struct timeoutParams *p;
|
||||
int doTeardownNonReentrance = 0;
|
||||
MSG msg;
|
||||
volatile HANDLE timerThread = NULL;
|
||||
LARGE_INTEGER duration;
|
||||
|
@ -332,6 +333,7 @@ timerSysError timerRunWithTimeout(timerDuration d, void (*f)(void *data), void *
|
|||
hr = setupNonReentrance(p);
|
||||
if (hr != S_OK)
|
||||
goto out;
|
||||
doTeardownNonReentrance = 1;
|
||||
|
||||
// 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);
|
||||
|
@ -401,6 +403,7 @@ out:
|
|||
CloseHandle(p->timer);
|
||||
if (p->targetThread != NULL)
|
||||
CloseHandle(p->targetThread);
|
||||
if (doTeardownNonReentrance)
|
||||
teardownNonReentrance();
|
||||
free(p);
|
||||
return (timerSysError) hr;
|
||||
|
|
Loading…
Reference in New Issue