I want a "killer" thread to wait for a duration after which it thinks it'll have something to do while other "worker" threads modify how long that will be. I'm worried about the race condition where the killer oversleeps cos some task gets brought forward between when the killer calculates its sleep time and actually starts sleeping. pthread_cond_timedwait is precisely tailored to this situation but doesn't work for process cpu time. So I came up with the following, and I really don't know why it isn't working...
void nsToTs(Nanosecs ns, struct timespec * pTs) {
memset(pTs, 0, sizeof(*pTs));
lldiv_t qr = lldiv(ns, 1000000000);
pTs->tv_sec = qr.quot;
pTs->tv_nsec= qr.rem;
}
Nanosecs age(clockid_t what) {
struct timespec ts;
clock_gettime(what, &ts);
return ts.tv_sec*1000000000 + ts.tv_nsec;
}
Nanosecs ageOfProcess() { return age(CLOCK_PROCESS_CPUTIME_ID); }
void * sweat(void * p) {
uint64_t z=1;
for (int b=0;b<10;b++) {
for (int a=0;a<99999999;a++) {
sched_yield();
z*=a;
}
}
return (void*)z;
}
void * sweat_forever(void * p) {
while(true) sweat(0);
}
pthread_t background(void * (*f)(void *)) {
pthread_t pid;
pthread_create(&pid, 0, f, 0);
return pid;
}
/////////////////////////////////////////////
////// lib/timer.c
#include <pthread.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MobBulk_pile/1.h"
#include "misc/h.h"
#include "h.h"
#define TIMER_SIG (SIGRTMIN+4)
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static timer_t timer;
static sigset_t set;
static struct sigevent sev;
static pid_t gettid_linux(void) { return (pid_t)syscall(SYS_gettid); }
void lockMobTimer(bool lock) {
if (lock) pthread_mutex_lock (&mutex);
else pthread_mutex_unlock(&mutex);
}
static void lock(bool lock) { lockMobTimer(lock); }
static void arm(Nanosecs nsRel) {
struct itimerspec its;
nsToTs(nsRel, &its.it_value);
printf("arming to %'lds, %'ldns\n",
its.it_value.tv_sec,
its.it_value.tv_nsec);
timer_settime(timer, 0, &its, 0);
}
static void wait() {
siginfo_t info;
lock(false);
while(TIMER_SIG != sigwaitinfo(&set, &info)) ;
lock(true);
}
static void waitMax(Nanosecs max) {
struct timespec ts;
nsToTs(max, &ts);
siginfo_t info;
lock(false);
int res;
do {
printf("waitMax before sigtimedwait at %'ld\n", ageOfProcess());
res = sigtimedwait(&set, &info, &ts);
sigset_t pending;
sigpending(&pending);
if (sigismember(&pending, TIMER_SIG)) {
printf("Signal is pending but not caught\n"); //Doesn't happen
}
printf("waitMax after sigtimedwait at %'ld\n", ageOfProcess());
}
while (!( res == TIMER_SIG || (res==-1 && errno==EAGAIN)));
lock(true);
}
void blockMobTimerSignal() {
sigemptyset(&set);
sigaddset(&set, TIMER_SIG);
pthread_sigmask(SIG_BLOCK, &set, 0);
}
void unblockMobTimerSignal() {
sigemptyset(&set);
sigaddset(&set, TIMER_SIG);
pthread_sigmask(SIG_UNBLOCK, &set, 0);
}
void initMobTimer() {
blockMobTimerSignal();
memset(&sev, 0, sizeof(sev));
//sev.sigev_notify = SIGEV_THREAD_ID;
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = TIMER_SIG;
sev._sigev_un._tid = gettid_linux();
if (timer_create(CLOCK_PROCESS_CPUTIME_ID, &sev, &timer) != 0) {
perror("timer_create");
exit(1);
} else printf("Timer created\n");
}
void unitMobTimer() {
timer_delete(timer);
}
void loopOnMobTimer(Looper looper, Nanosecs max) {
Nanosecs nsRel;
printf("loopOnMobTmer before lock at %'ld\n", ageOfProcess());
lock(true);
printf("loopOnMobTmer after lock at %'ld\n", ageOfProcess());
while (1) {
int flags = looper(&nsRel);
if (flags & QUIT) { lock(false); return; }
if (flags & SET) arm(nsRel);
if (flags & WAIT) {
printf("loopOnMobTmer before wait at %'ld\n", ageOfProcess());
if (max) waitMax(max);
else wait();
printf("loopOnMobTmer after wait at %'ld\n", ageOfProcess());
}
}
}
/////////////////////////////////////////////
////// test/timer.c
#pragma GCC diagnostic ignored "-Wunused-function"
#include "test.h"
#include "misc/h.h"
#include "MobBulk_pile/1.h"
#include "Mob_timer/h.h"
Nanosecs ns;
pthread_t sweat_pid;
void * unblockAndSweat(void * p) {
(void)p;
unblockMobTimerSignal();
sweat_forever(0);
return 0;
}
static bool init() {
initMobTimer();
sweat_pid = background(unblockAndSweat); // Got to do work to advance CPU time ...
return true;
}
static void cleanup() {
unitMobTimer();
pthread_cancel(sweat_pid);
}
static bool beenhere;
static int looperQuitInABit(Nanosecs * pNsRel) {
printf("looperQuitInABit at %'ld\n", ageOfProcess());
if (beenhere) return QUIT;
*pNsRel = 2000000000;
beenhere = true;
printf("looperQuitInABit returning %'ld\n", *pNsRel);
return SET|WAIT;
}
static bool quitsInABit() {
beenhere=false;
TIME_VOID_PROC(loopOnMobTimer(looperQuitInABit, 10000000000));
assertLongCond(ns, >1900000000ull)
assertLongCond(ns, <2100000000ull)
return true;
}
static bool test() {
return
quitsInABit() &&
true;
}
bool timer() { return bkt("timer", init, test, cleanup); }
//////////////////////////////////
////// Results:
Timer created
loopOnMobTmer before lock at 479,209
loopOnMobTmer after lock at 484,339
looperQuitInABit at 486,954
looperQuitInABit returning 2,000,000,000
arming to 2s, 0ns
loopOnMobTmer before wait at 522,150
waitMax before sigtimedwait at 529,494
waitMax after sigtimedwait at 9,958,748,197
loopOnMobTmer after wait at 9,958,776,881
looperQuitInABit at 9,958,783,964
bin/test/timer.c:45: Expected: <2100000000ull; Got: 9,958,317,428
The code is in adrianmay's github repo called digilife if you want to try stuff. TIA!
mainroutine and compiles without any changes or additions from what you post.sweat()is bizarre, returning auint64_tcast to a pointer, which will always be0, and ignores the funntion argument, and the caller ignores the return value.main(), which focuses on "reproducible", would be to trim it back to something minimal. There is a whole lot of window dressing to wade through here. I anticipate that you could write a complete reproducer in something closer to about 30 lines of code.