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:
Pietro Gagliardi 2019-05-04 12:11:31 -04:00
parent 6a25efce63
commit 8655bbf19c
3 changed files with 203 additions and 108 deletions

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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;