This function releases a queued spin lock without restoring the IRQL.


VOID FASTCALL KeReleaseInStackQueuedSpinLockFromDpcLevel (KLOCK_QUEUE_HANDLE *LockHandle);


The LockHandle argument is the address of an opaque context structure that is re-presented from the lock’s acquisition.


The KeReleaseInStackQueuedSpinLockFromDpcLevel function assumes that the IRQL is at least DISPATCH_LEVEL. The context structure and the spin lock that it refers to must be in non-paged memory.


The KeReleaseInStackQueuedSpinLockFromDpcLevel function is exported by name from the kernel in version 5.1 and higher.

Documentation Status

The KeReleaseInStackQueuedSpinLockFromDpcLevel function is documented.

Annotations on the declaration in WDM.H since the Windows Driver Kit (WDK) for Windows 7 have DISATCH_LEVEL as the required IRQL, not as the minimum requirement.


Since the KeReleaseInStackQueuedSpinLockFromDpcLevel does not change the IRQL, it ignores the OldIrql member in the KLOCK_QUEUE_HANDLE. Only the LockQueue is meaningful. It is assumed to be unchanged from how the kernel left it when the caller acquired the lock.

Inlined Common Code

The function looks to be an inlining of an internal routine whose dependence only on the KSPIN_LOCK_QUEUE is explicit:

VOID FASTCALL KeReleaseQueuedSpinLockFromDpcLevel (KSPIN_LOCK_QUEUE *);

This might pass unmentioned as an implementation detail except that this internal routine is the common code for releasing a queued spin lock through other exported functions, notably KeReleaseQueuedSpinLock, the notes for which refer here for the details.

General Mechanism

If the Next member in the caller’s KSPIN_LOCK_QUEUE is not NULL, it is the address of the KSPIN_LOCK_QUEUE that represents the lock’s next owner. The processor that this next owner is running on is spinning on a loop inside some such function as KeAcquireInStackQueuedSpinLockAtDpcLevel. Its entry to that function will have set the LOCK_QUEUE_WAIT bit in the Lock member of its KSPIN_LOCK_QUEUE. Its spin loop checks repeatedly for this bit to get cleared. Releasing the lock is as simple as clearing this bit! The next owner exits its spin loop and returns from its acquisition function as the lock’s new owner.

The complication to this general case is when the Next member in the caller’s KSPIN_LOCK_QUEUE is NULL. No processor is yet waiting for the lock but it can be that a processor has started trying to acquire the lock but not yet have got appended to the queue. This will show in the lock itself. If there truly is no processor to transfer ownership to, then the lock will hold the address of the caller’s KSPIN_LOCK_QUEUE and releasing the lock is as simple as clearing the lock to NULL. If a lock cmpxchg instruction to do this shows instead that the lock holds the address of some other KSPIN_LOCK_QUEUE, then the caller surmises that the queue is in the midst of being extended and it waits in a spin loop of its own until the Next member in its KSPIN_LOCK_QUEUE is not NULL. Then it can proceed as for the general case.

If the function transfers ownership, it clears the Next member in the caller’s KSPIN_LOCK_QUEUE. (If the lock returns to being unowned, then this member must have been NULL already.) This resetting allows that the caller’s KSPIN_LOCK_QUEUE can be reused, without further preparation, for another cycle of acquiring and releasing the same lock.