1 /*++
2 
3 Copyright (c) Microsoft Corporation
4 
5 ModuleName:
6 
7     MxWorkItemUm.h
8 
9 Abstract:
10 
11     User mode implementation of work item
12     class defined in MxWorkItem.h
13 
14     ***********PLEASE NOTE*****************************
15     A significant difference from kernel mode implementation of work item
16     is that user mode version of MxWorkItem::_Free synchronously waits for
17     callback to return.
18 
19     This implies that _Free cannot be invoked from within the callback otherwise
20     it would lead to a deadlock.
21 
22     PLEASE NOTE that _Free cannot be made to return without waiting without
23     significant changes.
24 
25     If Free is not made to wait synchronously there is a potential for binary
26     unload while workitem is running - even with waiting for an event/reference
27     count etc., the tail instructions may be running.
28     The only way to resolve that is to move work-item code out of framework
29     binary and into the host so that host can take a reference on framework
30     binary around the work item callback invocation (similar to the way I/O
31     manager keeps a reference on the device object around the invocation of
32 
33     workitem callback).
34     ****************************************************
35 
36 Author:
37 
38 
39 
40 Revision History:
41 
42 
43 
44 
45 
46 
47 --*/
48 
49 #pragma once
50 
51 typedef
52 VOID
53 MX_WORKITEM_ROUTINE (
54     __in MdDeviceObject DeviceObject,
55     __in_opt PVOID Context
56     );
57 
58 typedef MX_WORKITEM_ROUTINE *PMX_WORKITEM_ROUTINE;
59 
60 typedef struct {
61     MdDeviceObject DeviceObject;
62 
63     //
64     // threadpool wait block
65     //
66     PTP_WAIT WaitBlock;
67 
68     HANDLE WorkItemEvent;
69 
70     PMX_WORKITEM_ROUTINE Callback;
71 
72     PVOID Context;
73 
74     //
75     // True if callbacks run in the default thread pool environment,
76     // rather than in an environment explicitly owned by the driver.
77     // This has implications in MxWorkItem::_Free.
78     //
79     BOOLEAN DefaultThreadpoolEnv;
80 } UmWorkItem;
81 
82 typedef UmWorkItem*     MdWorkItem;
83 
84 #include "MxWorkItem.h"
85 
86 __inline
87 MxWorkItem::MxWorkItem(
88     )
89 {
90     m_WorkItem = NULL;
91 }
92 
93 _Must_inspect_result_
94 __inline
95 NTSTATUS
96 MxWorkItem::Allocate(
97     __in MdDeviceObject DeviceObject,
98     __in_opt PVOID ThreadPoolEnv
99     )
100 {
101     DWORD err = 0;
102 
103     m_WorkItem = (MdWorkItem)::HeapAlloc(
104         GetProcessHeap(),
105         0,
106         sizeof(UmWorkItem)
107         );
108 
109     if (NULL == m_WorkItem) {
110         return STATUS_INSUFFICIENT_RESOURCES;
111     }
112 
113     ZeroMemory(m_WorkItem, sizeof(UmWorkItem));
114 
115     m_WorkItem->WorkItemEvent = CreateEvent(
116         NULL,
117         FALSE,
118         FALSE,
119         NULL);
120 
121     if (NULL == m_WorkItem->WorkItemEvent) {
122         err = GetLastError();
123         goto exit;
124     }
125 
126     m_WorkItem->WaitBlock = CreateThreadpoolWait(
127                         _WorkerThunk,
128                         this->GetWorkItem(), // Context to callback function
129                         (PTP_CALLBACK_ENVIRON)ThreadPoolEnv
130                         );
131 
132     if (m_WorkItem->WaitBlock == NULL) {
133         err = GetLastError();
134         goto exit;
135     }
136 
137     m_WorkItem->DefaultThreadpoolEnv = (NULL == ThreadPoolEnv);
138 
139     m_WorkItem->DeviceObject = DeviceObject;
140 
141 exit:
142     //
143     // Cleanup in case of failure
144     //
145     if (0 != err) {
146         if (NULL != m_WorkItem->WorkItemEvent) {
147             CloseHandle(m_WorkItem->WorkItemEvent);
148             m_WorkItem->WorkItemEvent = NULL;
149         }
150 
151         ::HeapFree(GetProcessHeap(), 0, m_WorkItem);
152         m_WorkItem = NULL;
153     }
154 
155     return NTSTATUS_FROM_WIN32(err);
156 }
157 
158 __inline
159 VOID
160 MxWorkItem::Enqueue(
161     __in PMX_WORKITEM_ROUTINE Callback,
162     __in PVOID Context
163     )
164 {
165     //
166     // ASSUMPTION: This function assumes that another call to Enqueue
167     // is made only after the callback has been invoked, altough it is OK
168     // to make another call from within the callback.
169     //
170     // It is up to a higher layer/caller to ensure this.
171     // For example: FxSystemWorkItem layered on top of MxWorkItem ensures this.
172     //
173 
174     //
175     // Since multiple calls to Enqueue cannot be made at the same time
176     // as explained above, it is OK to store callback and context in
177     // the workitem itself.
178     //
179     // This behavior is similar to that of IoQueueWorkItem which accepts
180     // a callback and a context which are stored within the work-item.
181     //
182 
183     m_WorkItem->Callback = Callback;
184     m_WorkItem->Context = Context;
185 
186     //
187     // We must register the event with the wait object before signaling it
188     // to trigger the wait callback.
189     //
190     SetThreadpoolWait(m_WorkItem->WaitBlock,
191                       m_WorkItem->WorkItemEvent,
192                       NULL  // timeout
193                       );
194 
195     SetEvent(m_WorkItem->WorkItemEvent);
196 }
197 
198 __inline
199 MdWorkItem
200 MxWorkItem::GetWorkItem(
201     )
202 {
203     return m_WorkItem;
204 }
205 
206 __inline
207 VOID
208 MxWorkItem::_Free(
209     __in MdWorkItem Item
210     )
211 {
212     //
213     // PLEASE NOTE that _Free waits for callback to return synchronously.
214     //
215     // DO NOT call _Free from work item callback otherwise it would cause a
216     // deadlock.
217     //
218     // Please see comments on the top of the file.
219     //
220 
221     if (NULL != Item) {
222         //
223         // Wait indefinitely for work item to complete
224         //
225         if (NULL != Item->WaitBlock) {
226             //
227             // this will prevent any new waits to be queued but callbacks
228             // already queued will still occur.
229             //
230             SetThreadpoolWait(Item->WaitBlock, NULL, NULL);
231 
232             //
233             // If the callbacks ran in the default thread pool environment,
234             // wait for callbacks to finish.
235             // If they ran in an environment explicitly owned by the driver,
236             // then this wait will happen before the driver DLL is unloaded,
237             // the host takes care of this.
238             //
239             if (Item->DefaultThreadpoolEnv) {
240                 WaitForThreadpoolWaitCallbacks(Item->WaitBlock,
241                                                FALSE   // donot cancel pending waits
242                                                );
243             }
244 
245             //
246             // Release the wait object.
247             //
248             CloseThreadpoolWait(Item->WaitBlock);
249         }
250 
251         if (NULL != Item->WorkItemEvent) {
252             CloseHandle(Item->WorkItemEvent);
253             Item->WorkItemEvent = NULL;
254         }
255 
256         ::HeapFree(
257             GetProcessHeap(),
258             0,
259             Item
260             );
261     }
262 }
263 
264 __inline
265 VOID
266 MxWorkItem::Free(
267     )
268 {
269     //
270     // PLEASE NOTE that _Free waits for callback to return synchronously.
271     //
272     // DO NOT call Free from work item callback otherwise it would cause a
273     // deadlock.
274     //
275     // Please see comments on the top of the file.
276     //
277 
278     if (NULL != m_WorkItem) {
279         MxWorkItem::_Free(m_WorkItem);
280         m_WorkItem = NULL;
281     }
282 }
283 
284 //
285 // FxAutoWorkitem
286 //
287 __inline
288 MxAutoWorkItem::~MxAutoWorkItem(
289     )
290 {
291     this->Free();
292 }
293 
294 
295