Wondering about reading Irp->Cancel without holding global Cancel spinlock

Cancel Logic in Windows Drivers page 8 states this is correct code when queueing an IRP with a driver-supplied lock:

if (Irp->Cancel && 
    IoSetCancelRoutine (Irp, NULL)) {

And it does so without holding the global Cancel spinlock.

But The Truth About Cancel - IRP Cancel Operations (Part II) states:

however testing the Irp->Cancel flag only makes sense if you also hold the global Cancel spinlock before the test and until after the IRP is queued. Otherwise you might as well look at any random memory location and decide based on the value of the 6th bit.

Now the aforementioned "Cancel Logic in Windows Drivers" document on page 9 states:

The interlocked sequence in IoSetCancelRoutine ensures that the value of Irp->Cancel is read before the Cancel routine is removed.

Is that why it's safe to read Irp->Cancel without holding the global Cancel spinlock?

Both of those docs are old and obsolete, but yes, it is safe to call IoSetCancelRoutine without holding the global cancel spinlock.

Either use cancel safe queues for legacy wdm drivers or use wdf, and then you do not have to puzzle over even more legacy IoSetCancelRoutine nonsense.

Thanks Mark but I was really wondering whether it's safe to read the Irp->Cancel flag without holding the global Cancel spinlock. That OSR article I linked says no.

That article is ancient and wrong.
Anyway, the answer is no.

lock;
x = irp->cancel;
unlock;

and
x = irp->cancel;

are equivalent. If you read the value of Irp->cancel with the cancel spinlock held, the value can change as soon as you release the cancel spinlock.

This is correct:

In drivers that manage their own queues of IRPs and use driver-supplied spin locks to synchronize queue access, the driver routines do not need to acquire the cancel spin lock before calling IoSetCancelRoutine.

1 Like

You're not showing the entire code snippet:

KeAcquireSpinLock(&devExtension->QueueLock, &oldIrql);
IoSetCancelRoutine(Irp, CsampCancelIrp);

if (Irp->Cancel &&
    IoSetCancelRoutine(Irp, NULL)) {

    //
    // The IRP has already been canceled, but the I/O 
    // Manager has not yet called the Cancel routine. 
    status = STATUS_CANCELLED;
    // 
    // Complete the IRP later, after releasing the lock.
    //
} else {
    IoMarkIrpPending(Irp);
    InsertTailList(&devExtension->PendingIrpQueue,
    &Irp->Tail.Overlay.ListEntry);
    status = STATUS_PENDING;
}

KeReleaseSpinLock(&devExtension->QueueLock, oldIrql);

In this context the check of Irp->Cancel is safe because it's done after a cancel routine is established. To understand why there's some important bits of information to know about the I/O Manager's cancel processing:

  1. Grab the cancel spinlock
  2. Set Irp->Cancel to TRUE
  3. Clear the cancel routine with IoSetCancelRoutine(NULL)
  4. Call the cancel routine if there was one
  5. Once Irp->Cancel is set to TRUE it never resets to FALSE

So in your dispatch entry point you do the following:

  1. Call IoSetCancelRoutine to set your cancel routine
  2. Check to see if Irp->Cancel is TRUE. If not you're good to go
  3. If Irp->Cancel is TRUE it means the IRP was canceled either before or after you set your cancel routine. So, you call IoSetCancelRoutine(NULL) to interlock yourself against the I/O Manager's cancel processing. If it returns NULL you lost the race, if it returns your cancel routine you "won" the race and have to cancel the IRP yourself.

Note that IoCsqInsertIrp does all this for you...

1 Like

Thanks Scott. In my dispatch entry routine, if Irp->Cancel is FALSE, can I conditionally either complete the IRP because the desired data is available OR pend the IRP because the desired data is NOT available?

I'm not sure what you're worried about. If Irp->Cancel is FALSE, then everything is normal and you should do whatever you need to do.

I’m worried that Irp->Cancel could flip from FALSE to TRUE at any moment.

The only time you have to worry about that is when you queue an irp for deferred processing.
If you use cancel safe queues then the required operations are correctly performed for you.

1 Like

You need to follow the processing outlined in that document. Or just use a cancel safe queue and stop worrying about this (the CSQ package was added to the XP DDK to solve this exact confusion)

Thanks Mark, Scott, and Tim. I would like to learn and use cancel safe queues but my modifications need to be done soon. Plus, this driver code I've inherited has a lot of other problems exposed by driver verifier that thankfully I've been able to fix. I think I'm starting to get how the I/O Manager and a driver race when it comes to cancelation.

This is a bit of an exaggeration, but the Irp->Cancel flag is basically just a suggestion. It says that some entity above you would like to have the IRP cancelled. You are not, in the most literal sense, required to do anything about it, although to be a good neighbor, you should complete it with a canceled status when it is convenient to do so. But you don't have to take immediate action. If you miss the signal, it's no biggie, you'll catch it later.

1 Like

Thank you Tim. I was also thinking Irp->Cancel was just a suggestion because dispatch routines that do not set IRP cancel routines simply ignore Irp->Cancel.