/*
* This software is copyright (c) 2010 by Leon Timmermans <leont@cpan.org>.
*
* This is free software; you can redistribute it and/or modify it under
* the same terms as perl itself.
*
*/
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#include <signal.h>
#include <time.h>
#ifndef DEFAULT_SIGNO
# define DEFAULT_SIGNO (SIGRTMIN + 3)
#endif
#if _XOPEN_SOURCE >= 600
#define HAVE_CLOCK_NANOSLEEP
#endif
static void get_sys_error(char* buffer, size_t buffer_size) {
#ifdef _GNU_SOURCE
const char* message = strerror_r(errno, buffer, buffer_size);
if (message != buffer) {
memcpy(buffer, message, buffer_size -1);
buffer[buffer_size] = '\0';
}
#else
strerror_r(errno, buffer, buffer_size);
#endif
}
static void S_die_sys(pTHX_ const char* format) {
char buffer[128];
get_sys_error(buffer, sizeof buffer);
Perl_croak(aTHX_ format, buffer);
}
#define die_sys(format) S_die_sys(aTHX_ format)
typedef struct { const char* key; clockid_t value; } map[];
static map clocks = {
{ "realtime" , CLOCK_REALTIME }
#ifdef CLOCK_MONOTONIC
, { "monotonic", CLOCK_MONOTONIC }
#elif defined CLOCK_HIGHRES
, { "monotonic", CLOCK_HIGHRES }
#endif
#ifdef CLOCK_PROCESS_CPUTIME_ID
, { "process", CLOCK_PROCESS_CPUTIME_ID }
#elif defined CLOCK_PROF
, { "process", CLOCK_PROF }
#endif
#ifdef CLOCK_THREAD_CPUTIME_ID
, { "thread", CLOCK_THREAD_CPUTIME_ID }
#endif
#ifdef CLOCK_UPTIME
, { "uptime", CLOCK_UPTIME }
#endif
#ifdef CLOCK_VIRTUAL
, { "virtual", CLOCK_VIRTUAL }
#endif
};
static clockid_t S_get_clockid(pTHX_ const char* clock_name) {
int i;
for (i = 0; i < sizeof clocks / sizeof *clocks; ++i) {
if (strEQ(clock_name, clocks[i].key))
return clocks[i].value;
}
Perl_croak(aTHX_ "No such timer '%s' known", clock_name);
}
#define get_clockid(name) S_get_clockid(aTHX_ name)
int S_get_signo(pTHX) {
SV** tmp = hv_fetch(PL_modglobal, "POSIX::RT::Timer::SIGNO", 23, FALSE);
return SvIV(*tmp);
}
#define get_signo() S_get_signo(aTHX)
static void init_event(struct sigevent* event, int signo, void* ptr) {
event->sigev_notify = SIGEV_SIGNAL;
event->sigev_signo = signo;
event->sigev_value.sival_ptr = ptr;
}
CV* create_callback(pTHX_ SV* arg) {
HV* stash;
GV* gv;
CV* ret = sv_2cv(arg, &stash, &gv, 0);
if (!ret)
Perl_croak(aTHX_ "Can't make a codeval out of %s", SvPV_nolen(arg));
return ret;
}
static MAGIC* S_get_magic(pTHX_ SV* ref, const char* funcname) {
SV* value;
MAGIC* magic;
if (!SvROK(ref) || !(value = SvRV(ref)) || !SvMAGICAL(value) || (magic = mg_find(value, PERL_MAGIC_ext)) == NULL)
Perl_croak(aTHX_ "Could not %s: this variable is not a timer", funcname);
return magic;
}
#define get_magic(ref, funcname) S_get_magic(aTHX_ ref, funcname)
#define get_timer(ref, funcname) (*(timer_t*)get_magic(ref, funcname)->mg_ptr)
static clockid_t S_get_clock(pTHX_ SV* ref, const char* funcname) {
SV* value;
if (!SvROK(ref) || !(value = SvRV(ref)))
Perl_croak(aTHX_ "Could not %s: this variable is not a clock", funcname);
return SvIV(value);
}
#define get_clock(ref, func) S_get_clock(aTHX_ ref, func)
#define NANO_SECONDS 1000000000
static NV timespec_to_nv(struct timespec* time) {
return time->tv_sec + time->tv_nsec / (double)NANO_SECONDS;
}
static void nv_to_timespec(NV input, struct timespec* output) {
output->tv_sec = (time_t) floor(input);
output->tv_nsec = (long) ((input - output->tv_sec) * NANO_SECONDS);
}
XS(callback) {
#ifdef dVAR
dVAR; dXSARGS;
#else
dXSARGS;
#endif
PERL_UNUSED_VAR(cv); /* -W */
PERL_UNUSED_VAR(ax); /* -Wall */
SV* signal = ST(0);
SV* action = ST(2);
SP -= items;
siginfo_t* info = (siginfo_t*) SvPV_nolen(action);
SV* timer = (SV*)info->si_value.sival_ptr;
if (timer != 0) {
MAGIC* magic = mg_find(timer, PERL_MAGIC_ext);
SV* callback = (SV*) magic->mg_obj;
PUSHMARK(SP);
mXPUSHs(newRV_inc(timer));
PUTBACK;
call_sv(callback, GIMME_V);
SPAGAIN;
}
else
Perl_warn(aTHX_ "Got a signal without a value on slot %d\n", info->si_signo);
PUTBACK;
}
void register_callback(pTHX) {
dSP;
CV* callback_cv = newXS("", callback, __FILE__);
ENTER;
SAVETMPS;
PUSHMARK(SP);
mXPUSHp("POSIX::SigSet", 13);
PUTBACK;
call_method("new", G_SCALAR);
SPAGAIN;
SV* sigset = POPs;
#if PERL_VERSION < 10
SvREFCNT_inc((SV*)callback_cv);
#endif
PUSHMARK(SP);
mXPUSHp("POSIX::SigAction", 16);
mXPUSHs(newRV_noinc((SV*)callback_cv));
XPUSHs(sigset);
mXPUSHi(SA_SIGINFO);
PUTBACK;
call_method("new", G_SCALAR);
SPAGAIN;
SV* sigaction = POPs;
PUSHMARK(SP);
mXPUSHi(get_signo());
XPUSHs(sigaction);
PUTBACK;
call_pv("POSIX::sigaction", G_VOID | G_DISCARD);
SPAGAIN;
FREETMPS;
LEAVE;
}
int timer_destroy(pTHX_ SV* var, MAGIC* magic) {
if (timer_delete(*(timer_t*)magic->mg_ptr))
die_sys("Can't delete timer: %s");
}
MGVTBL timer_magic = { NULL, NULL, NULL, NULL, timer_destroy };
SV* S_create_timer(pTHX_ const char* class, clockid_t clockid, const char* type, SV* arg) {
struct sigevent event;
timer_t timer;
SV *tmp;
SV* retval;
CV* callback;
tmp = newSV(0);
retval = sv_2mortal(sv_bless(newRV_noinc(tmp), gv_stashpv(class, 0)));
SvREADONLY_on(tmp);
if (strEQ(type, "signal")) {
init_event(&event, SvIV(arg), NULL);
callback = NULL;
}
else if (strEQ(type, "callback")) {
init_event(&event, get_signo(), tmp);
callback = create_callback(aTHX_ arg);
}
else
Perl_croak(aTHX_ "Unknown type '%s'", type);
if (timer_create(clockid, &event, &timer) == -1)
die_sys("Couldn't create timer: %s");
MAGIC* magic = sv_magicext(tmp, (SV*)callback, PERL_MAGIC_ext, &timer_magic, (const char*)&timer, sizeof timer);
return retval;
}
#define create_timer(class, clockid, type, arg) S_create_timer(aTHX_ class, clockid, type, arg)
SV* S_create_clock(pTHX_ clockid_t clockid, const char* class) {
SV *tmp, *retval;
tmp = newSViv(clockid);
retval = newRV_noinc(tmp);
sv_bless(retval, gv_stashpv(class, 0));
SvREADONLY_on(tmp);
return retval;
}
#define create_clock(clockid, class) S_create_clock(aTHX_ clockid, class)
#ifdef HAVE_CLOCK_NANOSLEEP
int my_clock_nanosleep(pTHX_ clockid_t clockid, int flags, const struct timespec* request, struct timespec* remain) {
U32 saved = PL_signals;
int ret;
PL_signals |= PERL_SIGNALS_UNSAFE_FLAG;
ret = clock_nanosleep(clockid, flags, request, remain);
PL_signals = saved;
if (ret != 0 && ret != EINTR) {
errno = ret;
die_sys("Could not sleep: %s");
}
return ret;
}
#endif
#define clock_nanosleep(clockid, flags, request, remain) my_clock_nanosleep(aTHX_ clockid, flags, request, remain)
MODULE = POSIX::RT::Timer PACKAGE = POSIX::RT::Timer
PROTOTYPES: DISABLED
BOOT:
SV* signo = get_sv("POSIX::RT::Timer::SIGNO", GV_ADD | GV_ADDMULTI);
if (!SvOK(signo))
sv_setiv(signo, DEFAULT_SIGNO);
SvREADONLY_on(signo);
hv_store(PL_modglobal, "POSIX::RT::Timer::SIGNO", 23, newSVsv(signo), 0);
register_callback(aTHX);
void
get_timeout(self)
SV* self;
PREINIT:
timer_t timer;
struct itimerspec value;
PPCODE:
timer = get_timer(self, "get_timeout");
if (timer_gettime(timer, &value) == -1)
die_sys("Couldn't get_time: %s");
mXPUSHn(timespec_to_nv(&value.it_value));
if (GIMME_V == G_ARRAY)
mXPUSHn(timespec_to_nv(&value.it_interval));
void
set_timeout(self, new_value, new_interval = 0, abstime = 0)
SV* self;
NV new_value;
NV new_interval;
IV abstime;
PREINIT:
timer_t timer;
struct itimerspec new_itimer, old_itimer;
PPCODE:
timer = get_timer(self, "set_timeout");
nv_to_timespec(new_value, &new_itimer.it_value);
nv_to_timespec(new_interval, &new_itimer.it_interval);
if (timer_settime(timer, (abstime ? TIMER_ABSTIME : 0), &new_itimer, &old_itimer) == -1)
die_sys("Couldn't set_time: %s");
mXPUSHn(timespec_to_nv(&old_itimer.it_value));
if (GIMME_V == G_ARRAY)
mXPUSHn(timespec_to_nv(&old_itimer.it_interval));
SV*
get_callback(self)
SV* self;
PREINIT:
MAGIC* magic;
CODE:
magic = get_magic(self, "get_callback");
if (magic->mg_obj)
RETVAL = SvREFCNT_inc(magic->mg_obj);
else
RETVAL = &PL_sv_undef;
OUTPUT:
RETVAL
SV*
set_callback(self, callback)
SV* self;
SV* callback;
PREINIT:
MAGIC* magic;
CODE:
magic = get_magic(self, "set_callback");
if (!magic->mg_obj)
Perl_croak(aTHX_ "Can't set callback for this timer object");
RETVAL = magic->mg_obj;
magic->mg_obj = SvREFCNT_inc((SV*)create_callback(aTHX_ callback));
OUTPUT:
RETVAL
IV
get_overrun(self)
SV* self;
PREINIT:
timer_t timer;
CODE:
timer = get_timer(self, "get_overrun");
RETVAL = timer_getoverrun(timer);
if (RETVAL == -1)
die_sys("Couldn't get_overrun: %s");
OUTPUT:
RETVAL
MODULE = POSIX::RT::Timer PACKAGE = POSIX::RT::Clock
PROTOTYPES: DISABLED
SV*
new(class, clock_type)
const char* class;
const char* clock_type;
CODE:
RETVAL = create_clock(get_clockid(clock_type), class);
OUTPUT:
RETVAL
#ifdef linux
SV*
get_cpuclock(class, pid = 0)
const char* class;
IV pid;
PREINIT:
clockid_t clockid;
CODE:
if (clock_getcpuclockid(pid, &clockid) != 0)
die_sys("Could not get cpuclock");
RETVAL = create_clock(clockid, class);
OUTPUT:
RETVAL
#endif
void
get_clocks(class)
SV* class;
PREINIT:
size_t i;
const size_t max = sizeof clocks / sizeof *clocks;
PPCODE:
for (i = 0; i < max; ++i)
mXPUSHp(clocks[i].key, strlen(clocks[i].key));
XSRETURN(max);
void
_timer(self, class, type, arg)
SV* self;
const char* class;
const char* type;
SV* arg;
PPCODE:
XPUSHs(create_timer(class, get_clock(self, "timer"), type, arg));
NV
get_time(self)
SV* self;
PREINIT:
clockid_t clockid;
struct timespec time;
CODE:
clockid = get_clock(self, "get_time");
if (clock_gettime(clockid, &time) == -1)
die_sys("Couldn't get time: %s");
RETVAL = timespec_to_nv(&time);
OUTPUT:
RETVAL
void
set_time(self, frac_time)
SV* self;
NV frac_time;
PREINIT:
clockid_t clockid;
struct timespec time;
CODE:
clockid = get_clock(self, "set_time");
nv_to_timespec(frac_time, &time);
if (clock_settime(clockid, &time) == -1)
die_sys("Couldn't set time: %s");
NV
get_resolution(self)
SV* self;
PREINIT:
clockid_t clockid;
struct timespec time;
CODE:
clockid = get_clock(self, "get_resolution");
if (clock_getres(clockid, &time) == -1)
die_sys("Couldn't get resolution: %s");
RETVAL = timespec_to_nv(&time);
OUTPUT:
RETVAL
#ifdef HAVE_CLOCK_NANOSLEEP
NV
sleep(self, frac_time, abstime = 0)
SV* self;
NV frac_time;
int abstime;
PREINIT:
clockid_t clockid;
struct timespec sleep_time, remain_time;
int flags;
CODE:
clockid = get_clock(self, "sleep");
flags = abstime ? TIMER_ABSTIME : 0;
nv_to_timespec(frac_time, &sleep_time);
if (clock_nanosleep(clockid, flags, &sleep_time, &remain_time) == EINTR)
RETVAL = abstime ? frac_time : timespec_to_nv(&remain_time);
else
RETVAL = 0;
OUTPUT:
RETVAL
NV
sleep_deeply(self, frac_time, abstime = 0)
SV* self;
NV frac_time;
int abstime;
PREINIT:
clockid_t clockid;
struct timespec sleep_time;
NV real_time;
CODE:
clockid = get_clock(self, "sleep_deeply");
if (abstime)
nv_to_timespec(frac_time, &sleep_time);
else {
if (clock_gettime(clockid, &sleep_time) == -1)
die_sys("Couldn't get time: %s");
nv_to_timespec(timespec_to_nv(&sleep_time) + frac_time, &sleep_time);
}
while (clock_nanosleep(clockid, TIMER_ABSTIME, &sleep_time, NULL) == EINTR);
RETVAL = 0;
OUTPUT:
RETVAL
#endif