2 * ReactOS Cancel-Safe Queue library
3 * Copyright (c) 2004, Vizzini (vizzini@plasmic.com)
4 * Licensed under the GNU GPL for the ReactOS project
6 * This file implements the ReactOS CSQ library. For background and overview
7 * information on these routines, read csq.h. For the authoritative reference
8 * to using these routines, see the current DDK (IoCsqXXX and CsqXxx callbacks).
10 * There are a couple of subtle races that this library is designed to avoid.
11 * Please read the code (particularly IoCsqInsertIrpEx and IoCsqRemoveIrp) for
14 * In general, we try here to avoid the race between these queue/dequeue
15 * interfaces and our own cancel routine. This library supplies a cancel
16 * routine that is used in all IRPs that are queued to it. The major race
17 * conditions surround the proper handling of in-between cases, such as in-progress
18 * queue and de-queue operations.
20 * When you're thinking about these operations, keep in mind that three or four
21 * processors can have queue and dequeue operations in progress simultaneously,
22 * and a user thread may cancel any IRP at any time. Also, these operations don't
23 * all happen at DISPATCH_LEVEL all of the time, so thread switching on a single
24 * processor can create races too.
29 #undef DECLSPEC_IMPORT
30 #define DECLSPEC_IMPORT
34 static VOID NTAPI
IopCsqCancelRoutine(PDEVICE_OBJECT DeviceObject
,
37 * FUNCTION: Cancel routine that is installed on any IRP that this library manages
39 * [Called back by the system]
41 * - We assume that Irp->Tail.Overlay.DriverContext[3] has either a IO_CSQ
42 * or an IO_CSQ_IRP_CONTEXT in it, but we have to figure out which it is
43 * - By the time this routine executes, the I/O Manager has already cleared
44 * the cancel routine pointer in the IRP, so it will only be canceled once
45 * - Because of this, we're guaranteed that Irp is valid the whole time
46 * - Don't forget to release the cancel spinlock ASAP --> #1 hot lock in the
48 * - May be called at high IRQL
54 /* First things first: */
55 IoReleaseCancelSpinLock(Irp
->CancelIrql
);
57 /* We could either get a context or just a csq */
58 Csq
= (PIO_CSQ
)Irp
->Tail
.Overlay
.DriverContext
[3];
60 if(Csq
->Type
== IO_TYPE_CSQ_IRP_CONTEXT
)
62 PIO_CSQ_IRP_CONTEXT Context
= (PIO_CSQ_IRP_CONTEXT
)Csq
;
65 /* clean up context while we're here */
69 /* Now that we have our CSQ, complete the IRP */
70 Csq
->CsqAcquireLock(Csq
, &Irql
);
72 Csq
->CsqRemoveIrp(Csq
, Irp
);
73 Csq
->CsqCompleteCanceledIrp(Csq
, Irp
);
75 Csq
->CsqReleaseLock(Csq
, Irql
);
79 NTSTATUS NTAPI
IoCsqInitialize(PIO_CSQ Csq
,
80 PIO_CSQ_INSERT_IRP CsqInsertIrp
,
81 PIO_CSQ_REMOVE_IRP CsqRemoveIrp
,
82 PIO_CSQ_PEEK_NEXT_IRP CsqPeekNextIrp
,
83 PIO_CSQ_ACQUIRE_LOCK CsqAcquireLock
,
84 PIO_CSQ_RELEASE_LOCK CsqReleaseLock
,
85 PIO_CSQ_COMPLETE_CANCELED_IRP CsqCompleteCanceledIrp
)
87 * FUNCTION: Set up a CSQ struct to initialize the queue
89 * Csq: Caller-allocated non-paged space for our IO_CSQ to be initialized
90 * CsqInsertIrp: Insert routine
91 * CsqRemoveIrp: Remove routine
92 * CsqPeekNextIrp: Routine to paeek at the next IRP in queue
93 * CsqAcquireLock: Acquire the queue's lock
94 * CsqReleaseLock: Release the queue's lock
95 * CsqCompleteCanceledIrp: Routine to complete IRPs when they are canceled
97 * - STATUS_SUCCESS in all cases
99 * - Csq must be non-paged, as the queue is manipulated with a held spinlock
102 Csq
->Type
= IO_TYPE_CSQ
;
103 Csq
->CsqInsertIrp
= CsqInsertIrp
;
104 Csq
->CsqRemoveIrp
= CsqRemoveIrp
;
105 Csq
->CsqPeekNextIrp
= CsqPeekNextIrp
;
106 Csq
->CsqAcquireLock
= CsqAcquireLock
;
107 Csq
->CsqReleaseLock
= CsqReleaseLock
;
108 Csq
->CsqCompleteCanceledIrp
= CsqCompleteCanceledIrp
;
109 Csq
->ReservePointer
= NULL
;
111 return STATUS_SUCCESS
;
115 NTSTATUS NTAPI
IoCsqInitializeEx(PIO_CSQ Csq
,
116 PIO_CSQ_INSERT_IRP_EX CsqInsertIrpEx
,
117 PIO_CSQ_REMOVE_IRP CsqRemoveIrp
,
118 PIO_CSQ_PEEK_NEXT_IRP CsqPeekNextIrp
,
119 PIO_CSQ_ACQUIRE_LOCK CsqAcquireLock
,
120 PIO_CSQ_RELEASE_LOCK CsqReleaseLock
,
121 PIO_CSQ_COMPLETE_CANCELED_IRP CsqCompleteCanceledIrp
)
123 * FUNCTION: Set up a CSQ struct to initialize the queue (extended version)
125 * Csq: Caller-allocated non-paged space for our IO_CSQ to be initialized
126 * CsqInsertIrpEx: Extended insert routine
127 * CsqRemoveIrp: Remove routine
128 * CsqPeekNextIrp: Routine to paeek at the next IRP in queue
129 * CsqAcquireLock: Acquire the queue's lock
130 * CsqReleaseLock: Release the queue's lock
131 * CsqCompleteCanceledIrp: Routine to complete IRPs when they are canceled
133 * - STATUS_SUCCESS in all cases
135 * - Csq must be non-paged, as the queue is manipulated with a held spinlock
138 Csq
->Type
= IO_TYPE_CSQ_EX
;
139 Csq
->CsqInsertIrp
= (PIO_CSQ_INSERT_IRP
)CsqInsertIrpEx
;
140 Csq
->CsqRemoveIrp
= CsqRemoveIrp
;
141 Csq
->CsqPeekNextIrp
= CsqPeekNextIrp
;
142 Csq
->CsqAcquireLock
= CsqAcquireLock
;
143 Csq
->CsqReleaseLock
= CsqReleaseLock
;
144 Csq
->CsqCompleteCanceledIrp
= CsqCompleteCanceledIrp
;
145 Csq
->ReservePointer
= NULL
;
147 return STATUS_SUCCESS
;
151 VOID NTAPI
IoCsqInsertIrp(PIO_CSQ Csq
,
153 PIO_CSQ_IRP_CONTEXT Context
)
155 * FUNCTION: Insert an IRP into the CSQ
157 * Csq: Pointer to the initialized CSQ
158 * Irp: Pointer to the IRP to queue
159 * Context: Context record to track the IRP while queued
161 * - Just passes through to IoCsqInsertIrpEx, with no InsertContext
164 IoCsqInsertIrpEx(Csq
, Irp
, Context
, 0);
168 NTSTATUS NTAPI
IoCsqInsertIrpEx(PIO_CSQ Csq
,
170 PIO_CSQ_IRP_CONTEXT Context
,
173 * FUNCTION: Insert an IRP into the CSQ, with additional tracking context
175 * Csq: Pointer to the initialized CSQ
176 * Irp: Pointer to the IRP to queue
177 * Context: Context record to track the IRP while queued
178 * InsertContext: additional data that is passed through to CsqInsertIrpEx
180 * - Passes the additional context through to the driver-supplied callback,
181 * which can be used with more sophistocated queues
182 * - Marks the IRP pending in all cases
183 * - Guaranteed to not queue a canceled IRP
184 * - This is complicated logic, and is patterend after the Microsoft library.
185 * I'm sure I have gotten the details wrong on a fine point or two, but
186 * basically this works with the MS-supplied samples.
189 NTSTATUS Retval
= STATUS_SUCCESS
;
192 Csq
->CsqAcquireLock(Csq
, &Irql
);
196 /* mark all irps pending -- says so in the cancel sample */
197 IoMarkIrpPending(Irp
);
199 /* set up the context if we have one */
202 Context
->Type
= IO_TYPE_CSQ_IRP_CONTEXT
;
205 Irp
->Tail
.Overlay
.DriverContext
[3] = Context
;
208 Irp
->Tail
.Overlay
.DriverContext
[3] = Csq
;
211 * NOTE! This is very sensitive to order. If you set the cancel routine
212 * *before* you queue the IRP, our cancel routine will get called back for
213 * an IRP that isn't in its queue.
215 * There are three possibilities:
216 * 1) We get an IRP, we queue it, and it is valid the whole way
217 * 2) We get an IRP, and the IO manager cancels it before we're done here
218 * 3) We get an IRP, queue it, and the IO manager cancels it.
222 * When the IO manger receives a request to cancel an IRP, it sets the cancel
223 * bit in the IRP's control byte to TRUE. Then, it looks to see if a cancel
224 * routine is set. If it isn't, the IO manager just returns to the caller.
225 * If there *is* a routine, it gets called.
227 * If we test for cancel first and then set the cancel routine, there is a spot
228 * between test and set that the IO manager can cancel us without our knowledge,
229 * so we miss a cancel request. That is bad.
231 * If we set a routine first and then test for cancel, we race with our completion
232 * routine: We set the routine, the IO Manager sets cancel, we test cancel and find
233 * it is TRUE. Meanwhile the IO manager has called our cancel routine already, so
234 * we can't complete the IRP because it'll rip it out from under the cancel routine.
236 * The IO manager does us a favor though: it nulls out the cancel routine in the IRP
237 * before calling it. Therefore, if we test to see if the cancel routine is NULL
238 * (after we have just set it), that means our own cancel routine is already working
239 * on the IRP, and we can just return quietly. Otherwise, we have to de-queue the
240 * IRP and cancel it ourselves.
242 * We have to go through all of this mess because this API guarantees that we will
243 * never return having left a canceled IRP in the queue.
246 /* Step 1: Queue the IRP */
247 if(Csq
->Type
== IO_TYPE_CSQ
)
248 Csq
->CsqInsertIrp(Csq
, Irp
);
251 PIO_CSQ_INSERT_IRP_EX pCsqInsertIrpEx
= (PIO_CSQ_INSERT_IRP_EX
)Csq
->CsqInsertIrp
;
252 Retval
= pCsqInsertIrpEx(Csq
, Irp
, InsertContext
);
253 if(Retval
!= STATUS_SUCCESS
)
257 /* Step 2: Set our cancel routine */
258 (void)IoSetCancelRoutine(Irp
, IopCsqCancelRoutine
);
260 /* Step 3: Deal with an IRP that is already canceled */
265 * Since we're canceled, see if our cancel routine is already running
266 * If this is NULL, the IO Manager has already called our cancel routine
268 if(!IoSetCancelRoutine(Irp
, NULL
))
271 /* OK, looks like we have to de-queue and complete this ourselves */
272 Csq
->CsqRemoveIrp(Csq
, Irp
);
273 Csq
->CsqCompleteCanceledIrp(Csq
, Irp
);
280 Csq
->CsqReleaseLock(Csq
, Irql
);
286 PIRP NTAPI
IoCsqRemoveIrp(PIO_CSQ Csq
,
287 PIO_CSQ_IRP_CONTEXT Context
)
289 * FUNCTION: Remove anb IRP from the queue
291 * Csq: Queue to remove the IRP from
292 * Context: Context record containing the IRP to be dequeued
294 * - Pointer to an IRP if we found it
296 * - Don't forget that we can be canceled any time up to the point
297 * where we unset our cancel routine
303 Csq
->CsqAcquireLock(Csq
, &Irql
);
307 /* It's possible that this IRP could have been canceled */
313 /* Unset the cancel routine and see if it has already been canceled */
314 if(!IoSetCancelRoutine(Irp
, NULL
))
317 * already gone, return NULL --> NOTE that we cannot touch this IRP *or* the context,
318 * since the context is being simultaneously twiddled by the cancel routine
324 /* This IRP is valid and is ours. Dequeue it, fix it up, and return */
325 Csq
->CsqRemoveIrp(Csq
, Irp
);
327 Context
= (PIO_CSQ_IRP_CONTEXT
)InterlockedExchangePointer(&Irp
->Tail
.Overlay
.DriverContext
[3], NULL
);
329 if(Context
&& Context
->Type
== IO_TYPE_CSQ_IRP_CONTEXT
)
334 Csq
->CsqReleaseLock(Csq
, Irql
);
339 PIRP NTAPI
IoCsqRemoveNextIrp(PIO_CSQ Csq
,
342 * FUNCTION: IoCsqRemoveNextIrp - Removes the next IRP from the queue
344 * Csq: Queue to remove the IRP from
345 * PeekContext: Identifier of the IRP to be removed
347 * Pointer to the IRP that was removed, or NULL if one
350 * - This function is sensitive to yet another race condition.
351 * The basic idea is that we have to return the first IRP that
352 * we get that matches the PeekContext >that is not already canceled<.
353 * Therefore, we have to do a trick similar to the one done in Insert
359 PIO_CSQ_IRP_CONTEXT Context
;
361 Csq
->CsqAcquireLock(Csq
, &Irql
);
363 while((Irp
= Csq
->CsqPeekNextIrp(Csq
, Irp
, PeekContext
)))
366 * If the cancel routine is gone, we're already canceled,
367 * and are spinning on the queue lock in our own cancel
368 * routine. Move on to the next candidate. It'll get
369 * removed by the cance routine.
371 if(!IoSetCancelRoutine(Irp
, NULL
))
374 Csq
->CsqRemoveIrp(Csq
, Irp
);
376 /* Unset the context stuff and return */
377 Context
= (PIO_CSQ_IRP_CONTEXT
)InterlockedExchangePointer(&Irp
->Tail
.Overlay
.DriverContext
[3], NULL
);
379 if(Context
&& Context
->Type
== IO_TYPE_CSQ_IRP_CONTEXT
)
385 Csq
->CsqReleaseLock(Csq
, Irql
);