1 /*++
2 
3 Copyright (c) Microsoft Corporation
4 
5 ModuleName:
6 
7     MxTimerUm.h
8 
9 Abstract:
10 
11     User mode implementation of timer defined in
12     MxTimer.h
13 
14 Author:
15 
16 
17 Revision History:
18 
19 
20 
21 --*/
22 
23 #pragma once
24 
25 typedef struct _MdTimer {
26     //
27     // Callback function to be invoked upon timer expiration and the context to
28     // be passed in to the callback function
29     //
30     MdDeferredRoutine m_TimerCallback;
31     PVOID m_TimerContext;
32 
33     //
34     // The timer period
35     //
36     LONG m_Period;
37 
38     //
39     // Handle to the timer object
40     //
41     PTP_TIMER m_TimerHandle;
42 
43     //
44     // Work object to be executed by threadpool upon timer expiration
45     //
46     PTP_WORK m_WorkObject;
47 
48     //
49     // Flag to indicate that the timer callback has started running
50     //
51     BOOL m_CallbackStartedRunning;
52 
53     //
54     // Flag to indicate that the timer was started
55     // since it was created or since it was last stopped.
56     //
57     BOOL m_TimerWasStarted;
58 
59     _MdTimer(
60         VOID
61         )
62     {
63         m_TimerHandle = NULL;
64         m_WorkObject = NULL;
65         m_TimerCallback = NULL;
66         m_TimerContext = NULL;
67         m_Period = 0;
68         m_CallbackStartedRunning = FALSE;
69         m_TimerWasStarted = FALSE;
70     }
71 
72     ~_MdTimer(
73         VOID
74         )
75     {
76         //
77         // Release the timer object
78         //
79         if (m_TimerHandle)
80         {
81             CloseThreadpoolTimer(m_TimerHandle);
82             m_TimerHandle = NULL;
83         }
84 
85         //
86         // Release the work object
87         //
88         if (m_WorkObject)
89         {
90             CloseThreadpoolWork(m_WorkObject);
91             m_WorkObject = NULL;
92         }
93     }
94 
95     BOOLEAN
96     IsInSystemQueue(
97         VOID
98         )
99     {
100         //
101         // Timer was not started since it was created or since
102         // it was last stopped, so it can't be in the system timer queue.
103         //
104         if (!m_TimerWasStarted) {
105             return FALSE;
106         }
107 
108         //
109         // Periodic timers are always in the system timer queue.
110         //
111         if (m_Period != 0) {
112             return TRUE;
113         }
114 
115         //
116         // Non-periodic timer:
117         //
118         // At this point, the timer callback function has either been canceled or
119         // has finished running. Examine the m_CallbackStartedRunning value to see
120         // which one of these happened.
121         //
122         if (m_CallbackStartedRunning)
123         {
124             //
125             // Timer cancellation was too late. Timer callback already started
126             // running and the timer was removed from the system timer queue.
127             //
128             return FALSE;
129         }
130         else
131         {
132             //
133             // Timer cancellation happened on time and prevented the timer callback
134             // from running.
135             //
136             return TRUE;
137         }
138     }
139 
140     _Must_inspect_result_
141     NTSTATUS
142     Initialize(
143         __in_opt PVOID TimerContext,
144         __in MdDeferredRoutine TimerCallback,
145         __in LONG Period
146         )
147     {
148         NTSTATUS ntStatus = STATUS_SUCCESS;
149 
150         m_TimerCallback = TimerCallback;
151         m_TimerContext = TimerContext;
152         m_Period = Period;
153 
154         //
155         // Create the timer object
156         //
157         m_TimerHandle = CreateThreadpoolTimer(_MdTimer::s_MdTimerCallback,
158                                               this,
159                                               NULL);
160         if (NULL == m_TimerHandle)
161         {
162             ntStatus = WinErrorToNtStatus(GetLastError());
163         }
164 
165         //
166         // Create the work object
167         //
168         if (NT_SUCCESS(ntStatus))
169         {
170             m_WorkObject = CreateThreadpoolWork(_MdTimer::s_MdWorkCallback,
171                                                 this,
172                                                 NULL);
173             if (NULL == m_WorkObject)
174             {
175                 ntStatus = WinErrorToNtStatus(GetLastError());
176             }
177         }
178 
179         return ntStatus;
180     }
181 
182     BOOLEAN
183     Start(
184         __in LARGE_INTEGER DueTime,
185         __in ULONG TolerableDelay
186         )
187     {
188         BOOLEAN bRetVal;
189         FILETIME dueFileTime;
190 
191         if (m_TimerWasStarted) {
192             //
193             // Cancel the previously pended timer callback,
194             // we want it to execute after a full period elapsed.
195             //
196             WaitForThreadpoolTimerCallbacks(m_TimerHandle, TRUE);
197         }
198 
199         //
200         // Return TRUE if the timer is in the system timer queue.
201         //
202         bRetVal = IsInSystemQueue();
203 
204         //
205         // This is a fresh start for the timer, so clear the flag that the
206         // timer callback function may have previously set.
207         //
208         m_CallbackStartedRunning = FALSE;
209 
210         //
211         // Set the timer started flag.
212         //
213         m_TimerWasStarted = TRUE;
214 
215         //
216         // Copy the due time into a FILETIME structure
217         //
218         dueFileTime.dwLowDateTime = DueTime.LowPart;
219         dueFileTime.dwHighDateTime = (DWORD) DueTime.HighPart;
220 
221         //
222         // Start the timer
223         //
224         SetThreadpoolTimer(m_TimerHandle,
225                            &dueFileTime,
226                            (DWORD) m_Period,
227                            TolerableDelay);
228 
229         return bRetVal;
230     }
231 
232     _Must_inspect_result_
233     BOOLEAN
234     Stop(
235         VOID
236         )
237     {
238         BOOLEAN bRetVal;
239 
240         bRetVal = IsInSystemQueue();
241 
242         //
243         // Stop the timer
244         //
245         SetThreadpoolTimer(m_TimerHandle,
246                            NULL, // pftDueTime
247                            0, // msPeriod
248                            0 // msWindowLength
249                            );
250 
251         //
252         // Cancel pending callbacks that have not yet started to execute and wait
253         // for outstanding callbacks to complete.
254         //
255         WaitForThreadpoolTimerCallbacks(m_TimerHandle,
256                                         TRUE // cancel pending callbacks
257                                         );
258 
259         //
260         // Reset the timer started flag.
261         //
262         m_TimerWasStarted = FALSE;
263 
264         return bRetVal;
265     }
266 
267     VOID
268     TimerCallback(
269         VOID
270         )
271     {
272         //
273         // Invoke the user's callback function
274         //
275         m_TimerCallback(NULL, /* Reserved1 */
276                         m_TimerContext,
277                         NULL, /* Reserved2 */
278                         NULL /* Reserved3 */
279                         );
280 
281         return;
282     }
283 
284     static
285     VOID CALLBACK
286     s_MdWorkCallback(
287         __inout PTP_CALLBACK_INSTANCE Instance,
288         __inout_opt PVOID Context,
289         __inout PTP_WORK Work
290         )
291     {
292         struct _MdTimer *pThis = NULL;
293 
294         UNREFERENCED_PARAMETER(Instance);
295         UNREFERENCED_PARAMETER(Work);
296 
297         pThis = (struct _MdTimer*) Context;
298         pThis->TimerCallback();
299 
300         return;
301     }
302 
303     static
304     VOID CALLBACK
305     s_MdTimerCallback(
306         __inout PTP_CALLBACK_INSTANCE Instance,
307         __inout_opt PVOID Context,
308         __inout PTP_TIMER Timer
309         )
310     {
311         struct _MdTimer *pThis = NULL;
312 
313         UNREFERENCED_PARAMETER(Instance);
314         UNREFERENCED_PARAMETER(Timer);
315 
316         pThis = (struct _MdTimer*) Context;
317 
318         //
319         // First, indicate that the callback has started running
320         //
321         pThis->m_CallbackStartedRunning = TRUE;
322 
323         //
324         // Post a work object to execute the callback function supplied by the
325         // user of MxTimer.
326         //
327         // We do not execute the user-supplied callback here because we could
328         // run into a deadlock if the user is trying to cancel the timer by
329         // calling MxTimer::Stop. MxTimer::Stop actually blocks waiting for
330         // MdTimer::s_MdTimerCallback to finish executing, so that it can know
331         // where its attempt to cancel the timer was successful. If we were to
332         // execute the user's callback in MdTimer::s_MdTimerCallback, the user
333         // would have to be careful not to call MxTimer::Stop while holding a
334         // lock that the user's callback tries to acquire. In order to avoid
335         // imposing such a restriction on the user, we allow
336         // MdTimer::s_MdTimerCallback to return immediately after posting a
337         // work object to run the user's callback.
338         //
339         SubmitThreadpoolWork(pThis->m_WorkObject);
340 
341         return;
342     }
343 
344     BOOLEAN
345     StartWithReturn(
346         __in LARGE_INTEGER DueTime,
347         __in ULONG TolerableDelay
348         )
349     {
350         return Start(DueTime, TolerableDelay);
351     }
352 } MdTimer;
353 
354 #include "MxTimer.h"
355 
356 //
357 // Implementation of MxTimer functions
358 //
359 MxTimer::MxTimer(
360     VOID
361     )
362 {
363 }
364 
365 MxTimer::~MxTimer(
366     VOID
367     )
368 {
369 }
370 
371 _Must_inspect_result_
372 NTSTATUS
373 MxTimer::Initialize(
374     __in_opt PVOID TimerContext,
375     __in MdDeferredRoutine TimerCallback,
376     __in LONG Period
377     )
378 /*++
379 Routine description:
380     Initializes the MxTimer object.
381 
382 Arguments:
383     TimerContext - Context information that will be passed in to the timer
384         callback function.
385 
386     TimerCallback - The timer callback function.
387 
388         *** IMPORTANT NOTE ***
389         MxTimer object must not be freed inside the timer callback function
390         because in the pre-Vista, user mode implementation of MxTimer, the
391         destructor blocks waiting for all callback functions to finish
392         executing. Hence freeing the MxTimer object inside the callback
393         function will result in a deadlock.
394 
395     Period - The period of the timer in milliseconds.
396 
397 Return value:
398     An NTSTATUS value that indicates whether or not we succeeded in
399     initializing the MxTimer
400 --*/
401 {
402     NTSTATUS ntStatus;
403 
404     ntStatus = m_Timer.Initialize(TimerContext,
405                                   TimerCallback,
406                                   Period);
407 
408     return ntStatus;
409 }
410 
411 _Must_inspect_result_
412 NTSTATUS
413 MxTimer::InitializeEx(
414     __in_opt PVOID TimerContext,
415     __in MdExtCallback TimerCallback,
416     __in LONG Period,
417     __in ULONG TolerableDelay,
418     __in BOOLEAN UseHighResolutionTimer
419     )
420 {
421 
422     UNREFERENCED_PARAMETER(TolerableDelay);
423     UNREFERENCED_PARAMETER(UseHighResolutionTimer);
424     UNREFERENCED_PARAMETER(TimerCallback);
425     UNREFERENCED_PARAMETER(TimerContext);
426     UNREFERENCED_PARAMETER(Period);
427     ASSERTMSG("Not implemented for UMDF\n", FALSE);
428     return STATUS_NOT_IMPLEMENTED;
429 
430 }
431 
432 VOID
433 MxTimer::Start(
434     __in LARGE_INTEGER DueTime,
435     __in ULONG TolerableDelay
436     )
437 {
438     m_Timer.Start(DueTime, TolerableDelay);
439 
440     return;
441 }
442 
443 _Must_inspect_result_
444 BOOLEAN
445 MxTimer::Stop(
446     VOID
447     )
448 {
449     BOOLEAN bRetVal;
450 
451     bRetVal = m_Timer.Stop();
452 
453     return bRetVal;
454 }
455 
456 _Must_inspect_result_
457 BOOLEAN
458 MxTimer::StartWithReturn(
459     __in LARGE_INTEGER DueTime,
460     __in ULONG TolerableDelay
461     )
462 {
463     BOOLEAN bRetVal = TRUE;
464 
465     bRetVal = m_Timer.StartWithReturn(DueTime, TolerableDelay);
466 
467     return bRetVal;
468 }
469 
470 VOID
471 MxTimer::FlushQueuedDpcs(
472     VOID
473     )
474 {
475     WaitForThreadpoolWorkCallbacks(m_Timer.m_WorkObject,
476                                    TRUE // cancel pending callbacks
477                                    );
478 }
479 
480