itimer: Don't free condvar until we know ticker is stopped
[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 return NULL;
151 }
152
153 void
154 initTicker (Time interval, TickProc handle_tick)
155 {
156 itimer_interval = interval;
157 stopped = false;
158 exited = false;
159
160 initCondition(&start_cond);
161 initMutex(&mutex);
162
163 /*
164 * We can't use the RTS's createOSThread here as we need to remain attached
165 * to the thread we create so we can later join to it if requested
166 */
167 if (! pthread_create(&thread, NULL, itimer_thread_func, (void*)handle_tick)) {
168 #if defined(HAVE_PTHREAD_SETNAME_NP)
169 pthread_setname_np(thread, "ghc_ticker");
170 #endif
171 } else {
172 barf("Itimer: Failed to spawn thread");
173 }
174 }
175
176 void
177 startTicker(void)
178 {
179 ACQUIRE_LOCK(&mutex);
180 stopped = 0;
181 signalCondition(&start_cond);
182 RELEASE_LOCK(&mutex);
183 }
184
185 /* There may be at most one additional tick fired after a call to this */
186 void
187 stopTicker(void)
188 {
189 ACQUIRE_LOCK(&mutex);
190 stopped = 1;
191 RELEASE_LOCK(&mutex);
192 }
193
194 /* There may be at most one additional tick fired after a call to this */
195 void
196 exitTicker (bool wait)
197 {
198 ASSERT(!exited);
199 exited = true;
200 // ensure that ticker wakes up if stopped
201 startTicker();
202
203 // wait for ticker to terminate if necessary
204 if (wait) {
205 if (pthread_join(thread, NULL)) {
206 sysErrorBelch("Itimer: Failed to join");
207 }
208 closeMutex(&mutex);
209 closeCondition(&start_cond);
210 } else {
211 pthread_detach(thread);
212 }
213 }
214
215 int
216 rtsTimerSignal(void)
217 {
218 return SIGALRM;
219 }