d8f2497e3f21bc23038c4fa871b1739104825d12
[ghc.git] / rts / posix / itimer / Pthread.c
1 /* -----------------------------------------------------------------------------
2 *
3 * (c) The GHC Team, 1995-2007
4 *
5 * Interval timer for profiling and pre-emptive scheduling.
6 *
7 * ---------------------------------------------------------------------------*/
8
9 /*
10 * We use a realtime timer by default. I found this much more
11 * reliable than a CPU timer:
12 *
13 * Experiments with different frequencies: using
14 * CLOCK_REALTIME/CLOCK_MONOTONIC on Linux 2.6.32,
15 * 1000us has <1% impact on runtime
16 * 100us has ~2% impact on runtime
17 * 10us has ~40% impact on runtime
18 *
19 * using CLOCK_PROCESS_CPUTIME_ID on Linux 2.6.32,
20 * I cannot get it to tick faster than 10ms (10000us)
21 * which isn't great for profiling.
22 *
23 * In the threaded RTS, we can't tick in CPU time because the thread
24 * which has the virtual timer might be idle, so the tick would never
25 * fire. Therefore we used to tick in realtime in the threaded RTS and
26 * in CPU time otherwise, but now we always tick in realtime, for
27 * several reasons:
28 *
29 * - resolution (see above)
30 * - consistency (-threaded is the same as normal)
31 * - more consistency: Windows only has a realtime timer
32 *
33 * Note we want to use CLOCK_MONOTONIC rather than CLOCK_REALTIME,
34 * because the latter may jump around (NTP adjustments, leap seconds
35 * etc.).
36 */
37
38 #include "PosixSource.h"
39 #include "Rts.h"
40
41 #include "Ticker.h"
42 #include "Proftimer.h"
43 #include "Schedule.h"
44 #include "posix/Clock.h"
45
46 /* As recommended in the autoconf manual */
47 # if defined(TIME_WITH_SYS_TIME)
48 # include <sys/time.h>
49 # include <time.h>
50 # else
51 # if defined(HAVE_SYS_TIME_H)
52 # include <sys/time.h>
53 # else
54 # include <time.h>
55 # endif
56 # endif
57
58 #if defined(HAVE_SIGNAL_H)
59 # include <signal.h>
60 #endif
61
62 #include <string.h>
63
64 #include <pthread.h>
65 #include <unistd.h>
66 #include <fcntl.h>
67
68 #if defined(HAVE_SYS_TIMERFD_H)
69 #include <sys/timerfd.h>
70 #define USE_TIMERFD_FOR_ITIMER 1
71 #else
72 #define USE_TIMERFD_FOR_ITIMER 0
73 #endif
74
75 /*
76 * TFD_CLOEXEC has been added in Linux 2.6.26.
77 * If it is not available, we use fcntl(F_SETFD).
78 */
79 #if !defined(TFD_CLOEXEC)
80 #define TFD_CLOEXEC 0
81 #endif
82
83 static Time itimer_interval = DEFAULT_TICK_INTERVAL;
84
85 // Should we be firing ticks?
86 // Writers to this must hold the mutex below.
87 static volatile bool stopped = false;
88
89 // should the ticker thread exit?
90 // This can be set without holding the mutex.
91 static volatile bool exited = true;
92
93 // Signaled when we want to (re)start the timer
94 static Condition start_cond;
95 static Mutex mutex;
96 static OSThreadId thread;
97
98 static void *itimer_thread_func(void *_handle_tick)
99 {
100 TickProc handle_tick = _handle_tick;
101 uint64_t nticks;
102 int timerfd = -1;
103
104 #if defined(USE_TIMERFD_FOR_ITIMER) && USE_TIMERFD_FOR_ITIMER
105 struct itimerspec it;
106 it.it_value.tv_sec = TimeToSeconds(itimer_interval);
107 it.it_value.tv_nsec = TimeToNS(itimer_interval) % 1000000000;
108 it.it_interval = it.it_value;
109
110 timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
111 if (timerfd == -1) {
112 barf("timerfd_create");
113 }
114 if (!TFD_CLOEXEC) {
115 fcntl(timerfd, F_SETFD, FD_CLOEXEC);
116 }
117 if (timerfd_settime(timerfd, 0, &it, NULL)) {
118 barf("timerfd_settime");
119 }
120 #endif
121
122 while (!exited) {
123 if (USE_TIMERFD_FOR_ITIMER) {
124 if (read(timerfd, &nticks, sizeof(nticks)) != sizeof(nticks)) {
125 if (errno != EINTR) {
126 barf("Itimer: read(timerfd) failed");
127 }
128 }
129 } else {
130 if (usleep(TimeToUS(itimer_interval)) != 0 && errno != EINTR) {
131 sysErrorBelch("usleep(TimeToUS(itimer_interval) failed");
132 }
133 }
134
135 // first try a cheap test
136 if (stopped) {
137 ACQUIRE_LOCK(&mutex);
138 // should we really stop?
139 if (stopped) {
140 waitCondition(&start_cond, &mutex);
141 }
142 RELEASE_LOCK(&mutex);
143 } else {
144 handle_tick(0);
145 }
146 }
147
148 if (USE_TIMERFD_FOR_ITIMER)
149 close(timerfd);
150 closeMutex(&mutex);
151 closeCondition(&start_cond);
152 return NULL;
153 }
154
155 void
156 initTicker (Time interval, TickProc handle_tick)
157 {
158 itimer_interval = interval;
159 stopped = false;
160 exited = false;
161
162 initCondition(&start_cond);
163 initMutex(&mutex);
164
165 /*
166 * We can't use the RTS's createOSThread here as we need to remain attached
167 * to the thread we create so we can later join to it if requested
168 */
169 if (! pthread_create(&thread, NULL, itimer_thread_func, (void*)handle_tick)) {
170 #if defined(HAVE_PTHREAD_SETNAME_NP)
171 pthread_setname_np(thread, "ghc_ticker");
172 #endif
173 } else {
174 barf("Itimer: Failed to spawn thread");
175 }
176 }
177
178 void
179 startTicker(void)
180 {
181 ACQUIRE_LOCK(&mutex);
182 stopped = 0;
183 signalCondition(&start_cond);
184 RELEASE_LOCK(&mutex);
185 }
186
187 /* There may be at most one additional tick fired after a call to this */
188 void
189 stopTicker(void)
190 {
191 ACQUIRE_LOCK(&mutex);
192 stopped = 1;
193 RELEASE_LOCK(&mutex);
194 }
195
196 /* There may be at most one additional tick fired after a call to this */
197 void
198 exitTicker (bool wait)
199 {
200 ASSERT(!exited);
201 exited = true;
202 // ensure that ticker wakes up if stopped
203 startTicker();
204
205 // wait for ticker to terminate if necessary
206 if (wait) {
207 if (pthread_join(thread, NULL)) {
208 sysErrorBelch("Itimer: Failed to join");
209 }
210 } else {
211 pthread_detach(thread);
212 }
213 }
214
215 int
216 rtsTimerSignal(void)
217 {
218 return SIGALRM;
219 }