Commit 65e13f66 authored by Ben Gamari's avatar Ben Gamari Committed by Ben Gamari

rts: Split up Itimer.c

This shouldn't have any functional changes. It merely splits up what are
essentially three distinct codepaths which are melding together with
CPP.

At the moment I merely #include the implementation to use with CPP
although this really feels very yucky.

Reviewers: erikd, austin, simonmar

Reviewed By: simonmar

Subscribers: thomie

Differential Revision: https://phabricator.haskell.org/D2130
parent 16a51a6c
......@@ -13,37 +13,13 @@
* Solaris 2.3 only seems to provide support for @CLOCK_REAL@, whereas we're
* keen on getting access to @CLOCK_VIRTUAL@.
*
* Hence, we use the old-fashioned @setitimer@ that just about everyone seems
* to support. So much for standards.
* Hence, we often use the old-fashioned @setitimer@ that just about everyone
* seems to support. So much for standards.
*/
#include "PosixSource.h"
#include "Rts.h"
#include "Ticker.h"
#include "Itimer.h"
#include "Proftimer.h"
#include "Schedule.h"
#include "Clock.h"
/* As recommended in the autoconf manual */
# ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
# else
# ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
# endif
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <string.h>
/*
* timer_create doesn't exist and setitimer doesn't fire on iOS, so we're using
* a pthreads-based implementation. It may be to do with interference with the
......@@ -60,58 +36,10 @@
* modified in user code using signals.
*/
#if defined(linux_HOST_OS) && defined(THREADED_RTS) && HAVE_SYS_TIMERFD_H
#include <sys/timerfd.h>
#include <fcntl.h>
#define USE_PTHREAD_FOR_ITIMER
#define USE_TIMERFD_FOR_ITIMER 1
#undef USE_TIMER_CREATE
#else
#define USE_TIMERFD_FOR_ITIMER 0
#endif
/*
* TFD_CLOEXEC has been added in Linux 2.6.26.
* If it is not available, we use fcntl(F_SETFD).
*/
#ifndef TFD_CLOEXEC
#define TFD_CLOEXEC 0
#endif
#if defined(USE_PTHREAD_FOR_ITIMER)
#include <pthread.h>
#include <unistd.h>
#endif
/*
* We use a realtime timer by default. I found this much more
* reliable than a CPU timer:
*
* Experiments with different frequences: using
* CLOCK_REALTIME/CLOCK_MONOTONIC on Linux 2.6.32,
* 1000us has <1% impact on runtime
* 100us has ~2% impact on runtime
* 10us has ~40% impact on runtime
*
* using CLOCK_PROCESS_CPUTIME_ID on Linux 2.6.32,
* I cannot get it to tick faster than 10ms (10000us)
* which isn't great for profiling.
*
* In the threaded RTS, we can't tick in CPU time because the thread
* which has the virtual timer might be idle, so the tick would never
* fire. Therfore we used to tick in realtime in the threaded RTS and
* in CPU time otherwise, but now we always tick in realtime, for
* several reasons:
*
* - resolution (see above)
* - consistency (-threaded is the same as normal)
* - more consistency: Windows only has a realtime timer
*
* Note we want to use CLOCK_MONOTONIC rather than CLOCK_REALTIME,
* because the latter may jump around (NTP adjustments, leap seconds
* etc.).
*/
#if defined(solaris2_HOST_OS)
/* USE_TIMER_CREATE is usually disabled for Solaris. In fact it is
supported well on this OS, but requires additional privilege. When
......@@ -130,232 +58,11 @@ ghc-stage2: timer_create: Not owner
#undef USE_TIMER_CREATE
#endif /* solaris2_HOST_OS */
#if defined(USE_TIMER_CREATE)
# define ITIMER_SIGNAL SIGVTALRM
#elif defined(HAVE_SETITIMER)
# define ITIMER_SIGNAL SIGALRM
// Using SIGALRM can leads to problems, see #850. But we have no
// option if timer_create() is not available.
#else
# error No way to set an interval timer.
#endif
#if defined(USE_TIMER_CREATE)
static timer_t timer;
#endif
static Time itimer_interval = DEFAULT_TICK_INTERVAL;
#if !defined(USE_PTHREAD_FOR_ITIMER)
static void install_vtalrm_handler(TickProc handle_tick)
{
struct sigaction action;
action.sa_handler = handle_tick;
sigemptyset(&action.sa_mask);
#ifdef SA_RESTART
// specify SA_RESTART. One consequence if we don't do this is
// that readline gets confused by the -threaded RTS. It seems
// that if a SIGALRM handler is installed without SA_RESTART,
// readline installs its own SIGALRM signal handler (see
// readline's signals.c), and this somehow causes readline to go
// wrong when the input exceeds a single line (try it).
action.sa_flags = SA_RESTART;
#else
action.sa_flags = 0;
#endif
if (sigaction(ITIMER_SIGNAL, &action, NULL) == -1) {
sysErrorBelch("sigaction");
stg_exit(EXIT_FAILURE);
}
}
#endif
// Select the variant to use
#if defined(USE_PTHREAD_FOR_ITIMER)
enum ItimerState {STOPPED, RUNNING, STOPPING, EXITED};
static volatile enum ItimerState itimer_state = STOPPED;
static void *itimer_thread_func(void *_handle_tick)
{
TickProc handle_tick = _handle_tick;
uint64_t nticks;
int timerfd = -1;
#if USE_TIMERFD_FOR_ITIMER
struct itimerspec it;
it.it_value.tv_sec = TimeToSeconds(itimer_interval);
it.it_value.tv_nsec = TimeToNS(itimer_interval) % 1000000000;
it.it_interval = it.it_value;
timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (timerfd == -1) {
sysErrorBelch("timerfd_create");
stg_exit(EXIT_FAILURE);
}
if (!TFD_CLOEXEC) {
fcntl(timerfd, F_SETFD, FD_CLOEXEC);
}
int ret = timerfd_settime(timerfd, 0, &it, NULL);
#endif
while (1) {
if (USE_TIMERFD_FOR_ITIMER) {
if (read(timerfd, &nticks, sizeof(nticks)) != sizeof(nticks)) {
if (errno != EINTR) {
sysErrorBelch("Itimer: read(timerfd) failed");
}
}
} else {
if (usleep(TimeToUS(itimer_interval)) != 0 && errno != EINTR) {
sysErrorBelch("usleep(TimeToUS(itimer_interval) failed");
}
}
switch (itimer_state) {
case RUNNING:
handle_tick(0);
break;
case STOPPED:
break;
case STOPPING:
itimer_state = STOPPED;
break;
case EXITED:
if (USE_TIMERFD_FOR_ITIMER)
close(timerfd);
return NULL;
}
}
return NULL; // Never reached.
}
#endif
void
initTicker (Time interval, TickProc handle_tick)
{
itimer_interval = interval;
#if defined(USE_PTHREAD_FOR_ITIMER)
pthread_t tid;
int r = pthread_create(&tid, NULL, itimer_thread_func, (void*)handle_tick);
if (!r) {
pthread_detach(tid);
#if HAVE_PTHREAD_SETNAME_NP
pthread_setname_np(tid, "ghc_ticker");
#endif
}
#elif defined(USE_TIMER_CREATE)
{
struct sigevent ev;
// Keep programs like valgrind happy
memset(&ev, 0, sizeof(ev));
ev.sigev_notify = SIGEV_SIGNAL;
ev.sigev_signo = ITIMER_SIGNAL;
if (timer_create(CLOCK_ID, &ev, &timer) != 0) {
sysErrorBelch("timer_create");
stg_exit(EXIT_FAILURE);
}
}
install_vtalrm_handler(handle_tick);
#else
install_vtalrm_handler(handle_tick);
#endif
}
void
startTicker(void)
{
#if defined(USE_PTHREAD_FOR_ITIMER)
// sanity check
if (itimer_state == EXITED) {
sysErrorBelch("ITimer: Tried to start a dead timer!\n");
stg_exit(EXIT_FAILURE);
}
itimer_state = RUNNING;
#include "itimer/Pthread.c"
#elif defined(USE_TIMER_CREATE)
{
struct itimerspec it;
it.it_value.tv_sec = TimeToSeconds(itimer_interval);
it.it_value.tv_nsec = TimeToNS(itimer_interval) % 1000000000;
it.it_interval = it.it_value;
if (timer_settime(timer, 0, &it, NULL) != 0) {
sysErrorBelch("timer_settime");
stg_exit(EXIT_FAILURE);
}
}
#else
{
struct itimerval it;
it.it_value.tv_sec = TimeToSeconds(itimer_interval);
it.it_value.tv_usec = TimeToUS(itimer_interval) % 1000000;
it.it_interval = it.it_value;
if (setitimer(ITIMER_REAL, &it, NULL) != 0) {
sysErrorBelch("setitimer");
stg_exit(EXIT_FAILURE);
}
}
#endif
}
void
stopTicker(void)
{
#if defined(USE_PTHREAD_FOR_ITIMER)
if (itimer_state == RUNNING) {
itimer_state = STOPPING;
// Note that the timer may fire once more, but that's okay;
// handle_tick is only called when itimer_state == RUNNING
}
#elif defined(USE_TIMER_CREATE)
struct itimerspec it;
it.it_value.tv_sec = 0;
it.it_value.tv_nsec = 0;
it.it_interval = it.it_value;
if (timer_settime(timer, 0, &it, NULL) != 0) {
sysErrorBelch("timer_settime");
stg_exit(EXIT_FAILURE);
}
#include "itimer/TimerCreate.c"
#else
struct itimerval it;
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;
it.it_interval = it.it_value;
if (setitimer(ITIMER_REAL, &it, NULL) != 0) {
sysErrorBelch("setitimer");
stg_exit(EXIT_FAILURE);
}
#include "itimer/Setitimer.c"
#endif
}
void
exitTicker (rtsBool wait STG_UNUSED)
{
#if defined(USE_PTHREAD_FOR_ITIMER)
itimer_state = EXITED;
#elif defined(USE_TIMER_CREATE)
// Before deleting the timer set the signal to ignore to avoid the
// possibility of the signal being delivered after the timer is deleted.
signal(ITIMER_SIGNAL, SIG_IGN);
timer_delete(timer);
// ignore errors - we don't really care if it fails.
#endif
}
int
rtsTimerSignal(void)
{
return ITIMER_SIGNAL;
}
......@@ -14,6 +14,7 @@
#include "Signals.h"
#include "RtsUtils.h"
#include "Prelude.h"
#include "Ticker.h"
#include "Stable.h"
#include "Libdw.h"
......@@ -626,6 +627,34 @@ set_sigtstp_action (rtsBool handle)
}
}
/* Used by ItimerTimerCreate and ItimerSetitimer implementations */
void
install_vtalrm_handler(int sig, TickProc handle_tick)
{
struct sigaction action;
action.sa_handler = handle_tick;
sigemptyset(&action.sa_mask);
#ifdef SA_RESTART
// specify SA_RESTART. One consequence if we don't do this is
// that readline gets confused by the -threaded RTS. It seems
// that if a SIGALRM handler is installed without SA_RESTART,
// readline installs its own SIGALRM signal handler (see
// readline's signals.c), and this somehow causes readline to go
// wrong when the input exceeds a single line (try it).
action.sa_flags = SA_RESTART;
#else
action.sa_flags = 0;
#endif
if (sigaction(sig, &action, NULL) == -1) {
sysErrorBelch("sigaction");
stg_exit(EXIT_FAILURE);
}
}
/* -----------------------------------------------------------------------------
* Install default signal handlers.
*
......
......@@ -13,6 +13,8 @@
# include <signal.h>
#endif
#include "Ticker.h"
#include "BeginPrivate.h"
rtsBool anyUserHandlers(void);
......@@ -24,6 +26,8 @@ extern siginfo_t *next_pending_handler;
void startSignalHandlers(Capability *cap);
#endif
void install_vtalrm_handler(int sig, TickProc handle_tick);
void ioManagerStartCap (/* inout */ Capability **cap);
extern StgInt *signal_handlers;
......
/* -----------------------------------------------------------------------------
*
* (c) The GHC Team, 1995-2007
*
* Interval timer for profiling and pre-emptive scheduling.
*
* ---------------------------------------------------------------------------*/
/*
* We use a realtime timer by default. I found this much more
* reliable than a CPU timer:
*
* Experiments with different frequences: using
* CLOCK_REALTIME/CLOCK_MONOTONIC on Linux 2.6.32,
* 1000us has <1% impact on runtime
* 100us has ~2% impact on runtime
* 10us has ~40% impact on runtime
*
* using CLOCK_PROCESS_CPUTIME_ID on Linux 2.6.32,
* I cannot get it to tick faster than 10ms (10000us)
* which isn't great for profiling.
*
* In the threaded RTS, we can't tick in CPU time because the thread
* which has the virtual timer might be idle, so the tick would never
* fire. Therfore we used to tick in realtime in the threaded RTS and
* in CPU time otherwise, but now we always tick in realtime, for
* several reasons:
*
* - resolution (see above)
* - consistency (-threaded is the same as normal)
* - more consistency: Windows only has a realtime timer
*
* Note we want to use CLOCK_MONOTONIC rather than CLOCK_REALTIME,
* because the latter may jump around (NTP adjustments, leap seconds
* etc.).
*/
#include "PosixSource.h"
#include "Rts.h"
#include "Ticker.h"
#include "posix/Itimer.h"
#include "Proftimer.h"
#include "Schedule.h"
#include "posix/Clock.h"
/* As recommended in the autoconf manual */
# ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
# else
# ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
# endif
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#if HAVE_SYS_TIMERFD_H
#include <sys/timerfd.h>
#define USE_TIMERFD_FOR_ITIMER 1
#else
#define USE_TIMERFD_FOR_ITIMER 0
#endif
/*
* TFD_CLOEXEC has been added in Linux 2.6.26.
* If it is not available, we use fcntl(F_SETFD).
*/
#ifndef TFD_CLOEXEC
#define TFD_CLOEXEC 0
#endif
static Time itimer_interval = DEFAULT_TICK_INTERVAL;
enum ItimerState {STOPPED, RUNNING, STOPPING, EXITED};
static volatile enum ItimerState itimer_state = STOPPED;
static void *itimer_thread_func(void *_handle_tick)
{
TickProc handle_tick = _handle_tick;
uint64_t nticks;
int timerfd = -1;
#if USE_TIMERFD_FOR_ITIMER
struct itimerspec it;
it.it_value.tv_sec = TimeToSeconds(itimer_interval);
it.it_value.tv_nsec = TimeToNS(itimer_interval) % 1000000000;
it.it_interval = it.it_value;
timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (timerfd == -1) {
sysErrorBelch("timerfd_create");
stg_exit(EXIT_FAILURE);
}
if (!TFD_CLOEXEC) {
fcntl(timerfd, F_SETFD, FD_CLOEXEC);
}
int ret = timerfd_settime(timerfd, 0, &it, NULL);
#endif
while (1) {
if (USE_TIMERFD_FOR_ITIMER) {
if (read(timerfd, &nticks, sizeof(nticks)) != sizeof(nticks)) {
if (errno != EINTR) {
sysErrorBelch("Itimer: read(timerfd) failed");
}
}
} else {
if (usleep(TimeToUS(itimer_interval)) != 0 && errno != EINTR) {
sysErrorBelch("usleep(TimeToUS(itimer_interval) failed");
}
}
switch (itimer_state) {
case RUNNING:
handle_tick(0);
break;
case STOPPED:
break;
case STOPPING:
itimer_state = STOPPED;
break;
case EXITED:
if (USE_TIMERFD_FOR_ITIMER)
close(timerfd);
return NULL;
}
}
return NULL; // Never reached.
}
void
initTicker (Time interval, TickProc handle_tick)
{
itimer_interval = interval;
pthread_t tid;
int r = pthread_create(&tid, NULL, itimer_thread_func, (void*)handle_tick);
if (!r) {
pthread_detach(tid);
#if HAVE_PTHREAD_SETNAME_NP
pthread_setname_np(tid, "ghc_ticker");
#endif
}
}
void
startTicker(void)
{
// sanity check
if (itimer_state == EXITED) {
sysErrorBelch("ITimer: Tried to start a dead timer!\n");
stg_exit(EXIT_FAILURE);
}
itimer_state = RUNNING;
}
void
stopTicker(void)
{
if (itimer_state == RUNNING) {
itimer_state = STOPPING;
// Note that the timer may fire once more, but that's okay;
// handle_tick is only called when itimer_state == RUNNING
}
}
void
exitTicker (rtsBool wait STG_UNUSED)
{
itimer_state = EXITED;
}
int
rtsTimerSignal(void)
{
return SIGALRM;
}
/* -----------------------------------------------------------------------------
*
* (c) The GHC Team, 1995-2007
*
* Interval timer for profiling and pre-emptive scheduling.
*
* ---------------------------------------------------------------------------*/
#include "PosixSource.h"
#include "Rts.h"
#include "Ticker.h"
#include "posix/Itimer.h"
#include "Proftimer.h"
#include "Schedule.h"
#include "posix/Clock.h"
#include "posix/Signals.h"
/* As recommended in the autoconf manual */
# ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
# else
# ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
# endif
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <string.h>
static Time itimer_interval = DEFAULT_TICK_INTERVAL;
void
initTicker (Time interval, TickProc handle_tick)
{
itimer_interval = interval;
install_vtalrm_handler(SIGALRM, handle_tick);
}
void
startTicker(void)
{
struct itimerval it;
it.it_value.tv_sec = TimeToSeconds(itimer_interval);
it.it_value.tv_usec = TimeToUS(itimer_interval) % 1000000;
it.it_interval = it.it_value;
if (setitimer(ITIMER_REAL, &it, NULL) != 0) {
sysErrorBelch("setitimer");
stg_exit(EXIT_FAILURE);
}
}
void
stopTicker(void)
{
struct itimerval it;
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;
it.it_interval = it.it_value;
if (setitimer(ITIMER_REAL, &it, NULL) != 0) {
sysErrorBelch("setitimer");
stg_exit(EXIT_FAILURE);
}
}
void
exitTicker (rtsBool wait STG_UNUSED)
{
return;
}
int
rtsTimerSignal(void)
{
return SIGALRM;
// Using SIGALRM can leads to problems, see #850. But we have no
// option if timer_create() is not available.
}
/* -----------------------------------------------------------------------------
*
* (c) The GHC Team, 1995-2007
*
* Interval timer for profiling and pre-emptive scheduling.
*
* ---------------------------------------------------------------------------*/
#include "PosixSource.h"
#include "Rts.h"
#include "Ticker.h"
#include "posix/Itimer.h"
#include "Proftimer.h"
#include "Schedule.h"
#include "posix/Clock.h"
#include "posix/Signals.h"
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <string.h>
static Time itimer_interval = DEFAULT_TICK_INTERVAL;
static timer_t timer;
void
initTicker (Time interval, TickProc handle_tick)
{
itimer_interval = interval;
struct sigevent ev;
// Keep programs like valgrind happy
memset(&ev, 0, sizeof(ev));
ev.sigev_notify = SIGEV_SIGNAL;
ev.sigev_signo = SIGVTALRM;
if (timer_create(CLOCK_ID, &ev, &timer) != 0) {
sysErrorBelch("timer_create");
stg_exit(EXIT_FAILURE);
}
install_vtalrm_handler(SIGVTALRM, handle_tick);
}
void
startTicker(void)
{
struct itimerspec it;
it.it_value.tv_sec = TimeToSeconds(itimer_interval);
it.it_value.tv_nsec = TimeToNS(itimer_interval) % 1000000000;
it.it_interval = it.it_value;
if (timer_settime(timer, 0, &it, NULL) != 0) {
sysErrorBelch("timer_settime");
stg_exit(EXIT_FAILURE);
}
}
void
stopTicker(void)
{
struct itimerspec it;
it.it_value.tv_sec = 0;
it.it_value.tv_nsec = 0;
it.it_interval = it.it_value;