1 krisbash 1.1 #include <pal/palcommon.h>
2 #include <pal/atomic.h>
3 #include <pal/lock.h>
4 #include <pal/sleep.h>
5 #include <base/Strand.h>
6 #include <base/log.h>
7 /*
8 *==============================================================================
9 *
10 *
11 * Windows Implementation
12 *
13 *
14 *==============================================================================
15 */
16 #if defined(CONFIG_OS_WINDOWS)
17
18 void Timer_SetSelector(
19 _In_ Selector* selector )
20 {
21 /* Selector is not needed for Windows, so this is a NO-OP */
22 krisbash 1.1 PAL_UNUSED(selector);
23 }
24
25 VOID CALLBACK _WinTimerCallback(
26 _Inout_ PTP_CALLBACK_INSTANCE Instance,
27 _Inout_opt_ PVOID Context,
28 _Inout_ PTP_TIMER pTimer )
29 {
30 HMODULE newHModule;
31 PAL_UNUSED(pTimer);
32
33 /* CallbackMayRunLong provides a hint to thread pool that this handler
34 * may take a long time to execute. It allows the thread pool to optimize
35 * as needed. */
36 if (CallbackMayRunLong( Instance ))
37 {
38 trace_Timer_CallbackMayRunLong_True();
39 }
40 else
41 {
42 trace_Timer_CallbackMayRunLong_False();
43 krisbash 1.1 }
44
45 GetModuleHandleExW(
46 GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
47 (LPCWSTR) _WinTimerCallback,
48 &newHModule);
49
50 FreeLibraryWhenCallbackReturns(Instance, newHModule);
51
52 _Strand_ScheduleTimer( (Strand*)Context );
53 }
54
55 /* SHIM to fault inject CreateThreadpoolTimer */
56 PTP_TIMER WINAPI CreateThreadpoolTimer_Injected(_In_ PTP_TIMER_CALLBACK pfnti,
57 _Inout_opt_ PVOID pv,
58 _In_opt_ PTP_CALLBACK_ENVIRON pcbe,
59 NitsCallSite cs)
60 {
61 return CreateThreadpoolTimer(pfnti, pv, pcbe);
62 }
63
64 krisbash 1.1 /* Starts a Timer using the specified values. */
65 _Use_decl_annotations_
66 TimerResult Timer_Start(
67 Timer* timer,
68 Strand* strand )
69 {
70 DEBUG_ASSERT( timer );
71 DEBUG_ASSERT( strand );
72
73 if (NULL != timer->pTimer)
74 {
75 trace_Timer_CannotStartTimer_AlreadyRunning( timer );
76 return TimerResult_InvalidArgument;
77 }
78
79 timer->reason = TimerReason_Expired;
80
81 timer->pTimer = CreateThreadpoolTimer_Injected( _WinTimerCallback, strand, NULL, NitsHere() );
82
83 if (NULL == timer->pTimer)
84 {
85 krisbash 1.1 trace_Timer_Initialization_Failed(GetLastError());
86 return TimerResult_Failed;
87 }
88 /* Convert specified interval and start the timer */
89 {
90 FILETIME ft;
91 ULARGE_INTEGER tmp;
92
93 tmp.QuadPart = 0;
94 tmp.QuadPart -= timer->timeoutInUsec * 10; /* convert to 100 nanosecond units and set time */
95 ft.dwLowDateTime = tmp.u.LowPart;
96 ft.dwHighDateTime = tmp.u.HighPart;
97
98 SetThreadpoolTimer( timer->pTimer, &ft, 0, 0);
99 }
100
101 trace_Timer_Started_MSCVER(timer->timeoutInUsec * 10);
102 return TimerResult_Success;
103 }
104
105 _Use_decl_annotations_
106 krisbash 1.1 void Timer_Fire(
107 Timer* timer,
108 Strand* strand,
109 TimerReason reason )
110 {
111 DEBUG_ASSERT( timer );
112 DEBUG_ASSERT( strand );
113
114 /* Timer_Close NULLs pTimer, so a NULL value means that the Timer has been
115 * closed, but not restarted. */
116 if (NULL != timer->pTimer)
117 {
118 SetThreadpoolTimer( timer->pTimer, NULL, 0, 0 ); /* Triggers immediate cancel and clears pending callbacks */
119 WaitForThreadpoolTimerCallbacks( timer->pTimer, MI_TRUE ); /* Blocks until all pending callbacks have completed */
120
121 timer->reason = reason;
122
123 trace_Timer_ManualTrigger( timer, strand );
124 _Strand_ScheduleTimer( strand );
125 }
126 }
127 krisbash 1.1
128 _Use_decl_annotations_
129 void Timer_Close(
130 Timer* timer )
131 {
132 DEBUG_ASSERT( timer );
133
134 /*
135 * Note: It is not necessary to call SetThreadpoolTimer( , NULL, , ) and
136 * WaitForThreadpoolTimerCallbacks( , MI_TRUE ) here because the timer will
137 * have already been cancelled or triggered in order for execution to reach
138 * this point.
139 *
140 * Per MSDN: pTimer is freed immediately if ther are no callbacks pending.
141 * Otherwise it is freed after the last callback completes.
142 */
143 if (NULL == timer ||
144 NULL == timer->pTimer)
145 {
146 trace_Timer_Double_Close( timer );
147 }
148 krisbash 1.1 else
149 {
150 CloseThreadpoolTimer( timer->pTimer );
151 timer->pTimer = NULL;
152 timer->reason = TimerReason_Expired;
153 trace_Timer_Close( timer );
154 }
155 }
156
157 #endif /* defined(CONFIG_OS_WINDOWS) */
158
159 /*
160 *==============================================================================
161 *
162 *
163 * POSIX Implementation
164 *
165 *
166 *==============================================================================
167 */
168
169 krisbash 1.1 #if defined(CONFIG_POSIX)
170
171 static Selector* timerSelector = NULL;
172
173 void Timer_SetSelector(
174 _In_ Selector* selector )
175 {
176 timerSelector = selector;
177 }
178
179 MI_Boolean _HandlerTimerCallback(
180 Selector* sel,
181 Handler* handler,
182 MI_Uint32 mask,
183 MI_Uint64 currentTimeUsec)
184 {
185 if (mask & SELECTOR_TIMEOUT)
186 {
187 /* Returns MI_FALSE to signal it is OK to remove the Handler.
188 * actual handling will occur once the Selector calls this func
189 * again with SELECTOR_REMOVE. This is expected to happen
190 krisbash 1.1 * immediately. */
191 return MI_FALSE;
192 }
193 else if (mask & SELECTOR_REMOVE || mask & SELECTOR_DESTROY)
194 {
195 _Strand_ScheduleTimer( (Strand*)handler->data );
196 }
197 else if (mask & SELECTOR_ADD)
198 {
199 trace_Timer_Selector_Added();
200 }
201 else
202 {
203 /* No other mask values should be passed. If so, this will catch them. */
204 trace_Timer_Unexpected_Selector_Mask( mask );
205 DEBUG_ASSERT(MI_FALSE);
206 }
207
208 return MI_TRUE;
209 }
210
211 krisbash 1.1 /* State checks have already been performed. This function just needs to
212 * perform the requested action according to its OS type. */
213 _Use_decl_annotations_
214 TimerResult Timer_Start(
215 Timer* timer,
216 Strand* strand )
217 {
218 PAL_Uint64 currentTimeUsec = 0;
219
220 DEBUG_ASSERT( timer );
221 DEBUG_ASSERT( strand );
222
223 timer->selector = timerSelector; /* Keeping it as a member allows it to be checked once */
224
225 if (NULL == timer->selector ||
226 NULL == timer->selector->rep)
227 {
228 trace_Timer_Selector_Missing( timer->selector );
229 return TimerResult_InvalidArgument;
230 }
231
232 krisbash 1.1 if (MI_RESULT_OK == Selector_ContainsHandler(timer->selector, &timer->handler))
233 {
234 trace_Timer_CannotStartTimer_AlreadyRunning( timer );
235 return TimerResult_InvalidArgument;
236 }
237
238 if (PAL_TRUE != PAL_Time(¤tTimeUsec))
239 {
240 trace_Timer_Cannot_AccessCurrentTime();
241 return TimerResult_Failed;
242 }
243
244 timer->reason = TimerReason_Expired;
245
246 timer->handler.fireTimeoutAt = currentTimeUsec + timer->timeoutInUsec;
247 timer->handler.sock = INVALID_SOCK;
248 timer->handler.data = strand;
249 timer->handler.callback = _HandlerTimerCallback;
250
251 if (MI_RESULT_OK != Selector_AddHandler( timer->selector, &timer->handler ))
252 {
253 krisbash 1.1 trace_Timer_Cannot_AddHandlerToSelector( timer->selector );
254 return TimerResult_Failed;
255 }
256
257 trace_Timer_Started_POSIX( timer->timeoutInUsec );
258 return TimerResult_Success;
259 }
260
261 _Use_decl_annotations_
262 void Timer_Fire(
263 Timer* timer,
264 Strand* strand,
265 TimerReason reason )
266 {
267 DEBUG_ASSERT( timer );
268 DEBUG_ASSERT( strand );
269
270 /* Handler is zero'd during Timer_Start and Timer_Close. A NULL callback
271 * means that this timer is not active. */
272 if (NULL != timer->handler.callback) // TODO: Selector_ContainsHandler is a more thorough check, but less efficient
273 {
274 krisbash 1.1 PAL_Uint64 currentTimeUsec = 0;
275
276 if (PAL_TRUE != PAL_Time(¤tTimeUsec))
277 {
278 /* Guarantee it will be a time in the past if current time is not accessible. */
279 currentTimeUsec = TIME_NEVER + 1;
280 trace_Timer_Cannot_AccessCurrentTime();
281 }
282
283 if ( TimerReason_Canceled == reason || timer->handler.fireTimeoutAt > currentTimeUsec )
284 {
285 /* Due to how Selector works, Selector will not check for timeouts
286 * during long running operations. In those instances, the time
287 * of fireTimeoutAt will have already occurred, but the Selector
288 * will have not gotten a chance to detect it yet. That is treated
289 * as the default scenario. This signals that the timeout is treated
290 * as a manuallly triggered timeout. */
291 timer->reason = reason;
292 }
293 timer->handler.fireTimeoutAt = currentTimeUsec;
294
295 krisbash 1.1 trace_Timer_ManualTrigger( timer, strand );
296
297 Selector_Wakeup( timer->selector, MI_TRUE );
298 }
299 }
300
301 _Use_decl_annotations_
302 void Timer_Close(
303 Timer* timer )
304 {
305 DEBUG_ASSERT( timer );
306
307 if (NULL == timer->handler.callback)
308 {
309 trace_Timer_Double_Close( timer );
310 }
311 else
312 {
313 /* Handler is not present in the Selector's list since this will only
314 * trigger, if SELECTOR_REMOVE has gone through _HandlerTimerCallback,
315 * so it is OK to zero out.
316 krisbash 1.1 */
317 memset( &timer->handler, 0, sizeof(Handler) );
318 timer->reason = TimerReason_Expired;
319
320 trace_Timer_Close( timer );
321 }
322 }
323
324 #endif /* defined(CONFIG_POSIX) */
325
|