Process Hacker
queuedlock.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * queued lock
4  *
5  * Copyright (C) 2010-2011 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /*
24  * Queued lock, a.k.a. push lock (kernel-mode) or slim reader-writer lock
25  * (user-mode).
26  *
27  * The queued lock is:
28  * * Around 10% faster than the fast lock.
29  * * Only the size of a pointer.
30  * * Low on resource usage (no additional kernel objects are
31  * created for blocking).
32  *
33  * The usual flags are used for contention-free
34  * acquire/release. When there is contention, stack-based
35  * wait blocks are chained. The first wait block contains
36  * the shared owners count which is decremented by
37  * shared releasers.
38  *
39  * Naturally these wait blocks would be chained
40  * in FILO order, but list optimization is done for two purposes:
41  * * Finding the last wait block (where the shared owners
42  * count is stored). This is implemented by the Last pointer.
43  * * Unblocking the wait blocks in FIFO order. This is
44  * implemented by the Previous pointer.
45  *
46  * The optimization is incremental - each optimization run
47  * will stop at the first optimized wait block. Any needed
48  * optimization is completed just before waking waiters.
49  *
50  * The waiters list/chain has the following restrictions:
51  * * At any time wait blocks may be pushed onto the list.
52  * * While waking waiters, the list may not be traversed
53  * nor optimized.
54  * * When there are multiple shared owners, shared releasers
55  * may traverse the list (to find the last wait block).
56  * This is not an issue because waiters wouldn't be woken
57  * until there are no more shared owners.
58  * * List optimization may be done at any time except for
59  * when someone else is waking waiters. This is controlled
60  * by the traversing bit.
61  *
62  * The traversing bit has the following rules:
63  * * The list may be optimized only after the traversing bit
64  * is set, checking that it wasn't set already.
65  * If it was set, it would indicate that someone else is
66  * optimizing the list or waking waiters.
67  * * Before waking waiters the traversing bit must be set.
68  * If it was set already, just clear the owned bit.
69  * * If during list optimization the owned bit is detected
70  * to be cleared, the function begins waking waiters. This
71  * is because the owned bit is cleared when a releaser
72  * fails to set the traversing bit.
73  *
74  * Blocking is implemented through a process-wide keyed event.
75  * A spin count is also used before blocking on the keyed
76  * event.
77  *
78  * Queued locks can act as condition variables, with
79  * wait, pulse and pulse all support. Waiters are released
80  * in FIFO order.
81  *
82  * Queued locks can act as wake events. These are designed
83  * for tiny one-bit locks which share a single event to block
84  * on. Spurious wake-ups are a part of normal operation.
85  */
86 
87 #include <phbase.h>
88 #include <phintrnl.h>
89 
91  _Inout_ PPH_QUEUED_LOCK QueuedLock,
92  _In_ ULONG_PTR Value
93  );
94 
96  _Inout_ PPH_QUEUED_LOCK QueuedLock,
97  _In_ ULONG_PTR Value
98  );
99 
101  _Inout_ PPH_QUEUED_LOCK QueuedLock,
102  _In_ ULONG_PTR Value,
103  _In_ BOOLEAN IgnoreOwned,
104  _In_ BOOLEAN WakeAll
105  );
106 
107 static HANDLE PhQueuedLockKeyedEventHandle;
108 static ULONG PhQueuedLockSpinCount = 2000;
109 
111  VOID
112  )
113 {
114  if (!NT_SUCCESS(NtCreateKeyedEvent(
115  &PhQueuedLockKeyedEventHandle,
116  KEYEDEVENT_ALL_ACCESS,
117  NULL,
118  0
119  )))
120  return FALSE;
121 
123  PhQueuedLockSpinCount = 4000;
124  else
125  PhQueuedLockSpinCount = 0;
126 
127  return TRUE;
128 }
129 
160 FORCEINLINE BOOLEAN PhpPushQueuedWaitBlock(
161  _Inout_ PPH_QUEUED_LOCK QueuedLock,
162  _In_ ULONG_PTR Value,
163  _In_ BOOLEAN Exclusive,
164  _Out_ PPH_QUEUED_WAIT_BLOCK WaitBlock,
165  _Out_ PBOOLEAN Optimize,
166  _Out_ PULONG_PTR NewValue,
167  _Out_ PULONG_PTR CurrentValue
168  )
169 {
170  ULONG_PTR newValue;
171  BOOLEAN optimize;
172 
173  WaitBlock->Previous = NULL; // set later by optimization
174  optimize = FALSE;
175 
176  if (Exclusive)
178  else
179  WaitBlock->Flags = PH_QUEUED_WAITER_SPINNING;
180 
181  if (Value & PH_QUEUED_LOCK_WAITERS)
182  {
183  // We're not the first waiter.
184 
185  WaitBlock->Last = NULL; // set later by optimization
186  WaitBlock->Next = PhGetQueuedLockWaitBlock(Value);
187  WaitBlock->SharedOwners = 0;
188 
189  // Push our wait block onto the list.
190  // Set the traversing bit because we'll be optimizing the list.
191  newValue = ((ULONG_PTR)WaitBlock) | (Value & PH_QUEUED_LOCK_FLAGS) |
193 
194  if (!(Value & PH_QUEUED_LOCK_TRAVERSING))
195  optimize = TRUE;
196  }
197  else
198  {
199  // We're the first waiter.
200 
201  WaitBlock->Last = WaitBlock; // indicate that this is the last wait block
202 
203  if (Exclusive)
204  {
205  // We're the first waiter. Save the shared owners count.
206  WaitBlock->SharedOwners = (ULONG)PhGetQueuedLockSharedOwners(Value);
207 
208  if (WaitBlock->SharedOwners > 1)
209  {
210  newValue = ((ULONG_PTR)WaitBlock) | PH_QUEUED_LOCK_OWNED |
211  PH_QUEUED_LOCK_WAITERS | PH_QUEUED_LOCK_MULTIPLE_SHARED;
212  }
213  else
214  {
215  newValue = ((ULONG_PTR)WaitBlock) | PH_QUEUED_LOCK_OWNED |
217  }
218  }
219  else
220  {
221  // We're waiting in shared mode, which means there can't
222  // be any shared owners (otherwise we would've acquired
223  // the lock already).
224 
225  WaitBlock->SharedOwners = 0;
226 
227  newValue = ((ULONG_PTR)WaitBlock) | PH_QUEUED_LOCK_OWNED |
229  }
230  }
231 
232  *Optimize = optimize;
233  *CurrentValue = newValue;
234 
235  newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
236  (PVOID *)&QueuedLock->Value,
237  (PVOID)newValue,
238  (PVOID)Value
239  );
240 
241  *NewValue = newValue;
242 
243  return newValue == Value;
244 }
245 
259  _In_ ULONG_PTR Value
260  )
261 {
262  PPH_QUEUED_WAIT_BLOCK waitBlock;
263  PPH_QUEUED_WAIT_BLOCK lastWaitBlock;
264 
265  waitBlock = PhGetQueuedLockWaitBlock(Value);
266 
267  // Traverse the list until we find the last wait block.
268  // The Last pointer should be set by list optimization,
269  // allowing us to skip all, if not most of the wait blocks.
270 
271  while (TRUE)
272  {
273  lastWaitBlock = waitBlock->Last;
274 
275  if (lastWaitBlock)
276  {
277  // Follow the Last pointer. This can mean two
278  // things: the pointer was set by list optimization,
279  // or this wait block is actually the last wait block
280  // (set when it was pushed onto the list).
281  waitBlock = lastWaitBlock;
282  break;
283  }
284 
285  waitBlock = waitBlock->Next;
286  }
287 
288  return waitBlock;
289 }
290 
299  _Inout_ PPH_QUEUED_WAIT_BLOCK WaitBlock,
300  _In_ BOOLEAN Spin,
301  _In_opt_ PLARGE_INTEGER Timeout
302  )
303 {
304  NTSTATUS status;
305  ULONG i;
306 
307  if (Spin)
308  {
309  PHLIB_INC_STATISTIC(QlBlockSpins);
310 
311  for (i = PhQueuedLockSpinCount; i != 0; i--)
312  {
313  if (!(*(volatile ULONG *)&WaitBlock->Flags & PH_QUEUED_WAITER_SPINNING))
314  return STATUS_SUCCESS;
315 
316  YieldProcessor();
317  }
318  }
319 
320  if (_interlockedbittestandreset((PLONG)&WaitBlock->Flags, PH_QUEUED_WAITER_SPINNING_SHIFT))
321  {
322  PHLIB_INC_STATISTIC(QlBlockWaits);
323 
324  status = NtWaitForKeyedEvent(
325  PhQueuedLockKeyedEventHandle,
326  WaitBlock,
327  FALSE,
328  Timeout
329  );
330 
331  // If an error occurred (timeout is not an error), raise an exception
332  // as it is nearly impossible to recover from this situation.
333  if (!NT_SUCCESS(status))
334  PhRaiseStatus(status);
335  }
336  else
337  {
338  status = STATUS_SUCCESS;
339  }
340 
341  return status;
342 }
343 
354  _Inout_ PPH_QUEUED_WAIT_BLOCK WaitBlock
355  )
356 {
357  NTSTATUS status;
358 
359  if (!_interlockedbittestandreset((PLONG)&WaitBlock->Flags, PH_QUEUED_WAITER_SPINNING_SHIFT))
360  {
361  if (!NT_SUCCESS(status = NtReleaseKeyedEvent(
362  PhQueuedLockKeyedEventHandle,
363  WaitBlock,
364  FALSE,
365  NULL
366  )))
367  PhRaiseStatus(status);
368  }
369 }
370 
383  _Inout_ PPH_QUEUED_LOCK QueuedLock,
384  _In_ ULONG_PTR Value,
385  _In_ BOOLEAN IgnoreOwned
386  )
387 {
388  ULONG_PTR value;
389  ULONG_PTR newValue;
390  PPH_QUEUED_WAIT_BLOCK waitBlock;
391  PPH_QUEUED_WAIT_BLOCK firstWaitBlock;
392  PPH_QUEUED_WAIT_BLOCK lastWaitBlock;
393  PPH_QUEUED_WAIT_BLOCK previousWaitBlock;
394 
395  value = Value;
396 
397  while (TRUE)
398  {
399  assert(value & PH_QUEUED_LOCK_TRAVERSING);
400 
401  if (!IgnoreOwned && !(value & PH_QUEUED_LOCK_OWNED))
402  {
403  // Someone has requested that we wake waiters.
404  PhpfWakeQueuedLock(QueuedLock, value);
405  break;
406  }
407 
408  // Perform the optimization.
409 
410  waitBlock = PhGetQueuedLockWaitBlock(value);
411  firstWaitBlock = waitBlock;
412 
413  while (TRUE)
414  {
415  lastWaitBlock = waitBlock->Last;
416 
417  if (lastWaitBlock)
418  {
419  // Save a pointer to the last wait block in
420  // the first wait block and stop optimizing.
421  //
422  // We don't need to continue setting Previous
423  // pointers because the last optimization run
424  // would have set them already.
425 
426  firstWaitBlock->Last = lastWaitBlock;
427  break;
428  }
429 
430  previousWaitBlock = waitBlock;
431  waitBlock = waitBlock->Next;
432  waitBlock->Previous = previousWaitBlock;
433  }
434 
435  // Try to clear the traversing bit.
436 
437  newValue = value - PH_QUEUED_LOCK_TRAVERSING;
438 
439  if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
440  (PVOID *)&QueuedLock->Value,
441  (PVOID)newValue,
442  (PVOID)value
443  )) == value)
444  break;
445 
446  // Either someone pushed a wait block onto the list
447  // or someone released ownership. In either case we
448  // need to go back.
449 
450  value = newValue;
451  }
452 }
453 
464  _Inout_ PPH_QUEUED_LOCK QueuedLock,
465  _In_ ULONG_PTR Value
466  )
467 {
468  PhpOptimizeQueuedLockListEx(QueuedLock, Value, FALSE);
469 }
470 
483  _Inout_ PPH_QUEUED_LOCK QueuedLock,
484  _In_ ULONG_PTR Value,
485  _In_ BOOLEAN IgnoreOwned,
486  _In_ BOOLEAN WakeAll
487  )
488 {
489  ULONG_PTR value;
490  ULONG_PTR newValue;
491  PPH_QUEUED_WAIT_BLOCK waitBlock;
492  PPH_QUEUED_WAIT_BLOCK firstWaitBlock;
493  PPH_QUEUED_WAIT_BLOCK lastWaitBlock;
494  PPH_QUEUED_WAIT_BLOCK previousWaitBlock;
495 
496  value = Value;
497 
498  while (TRUE)
499  {
500  // If there are multiple shared owners, no one is going
501  // to wake waiters since the lock would still be owned.
502  // Also if there are multiple shared owners they may be
503  // traversing the list. While that is safe when
504  // done concurrently with list optimization, we may be
505  // removing and waking waiters.
506  assert(!(value & PH_QUEUED_LOCK_MULTIPLE_SHARED));
507  assert(IgnoreOwned || (value & PH_QUEUED_LOCK_TRAVERSING));
508 
509  // There's no point in waking a waiter if the lock
510  // is owned. Clear the traversing bit.
511  while (!IgnoreOwned && (value & PH_QUEUED_LOCK_OWNED))
512  {
513  newValue = value - PH_QUEUED_LOCK_TRAVERSING;
514 
515  if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
516  (PVOID *)&QueuedLock->Value,
517  (PVOID)newValue,
518  (PVOID)value
519  )) == value)
520  return NULL;
521 
522  value = newValue;
523  }
524 
525  // Finish up any needed optimization (setting the
526  // Previous pointers) while finding the last wait
527  // block.
528 
529  waitBlock = PhGetQueuedLockWaitBlock(value);
530  firstWaitBlock = waitBlock;
531 
532  while (TRUE)
533  {
534  lastWaitBlock = waitBlock->Last;
535 
536  if (lastWaitBlock)
537  {
538  waitBlock = lastWaitBlock;
539  break;
540  }
541 
542  previousWaitBlock = waitBlock;
543  waitBlock = waitBlock->Next;
544  waitBlock->Previous = previousWaitBlock;
545  }
546 
547  // Unlink the relevant wait blocks and clear the
548  // traversing bit before we wake waiters.
549 
550  if (
551  !WakeAll &&
552  (waitBlock->Flags & PH_QUEUED_WAITER_EXCLUSIVE) &&
553  (previousWaitBlock = waitBlock->Previous)
554  )
555  {
556  // We have an exclusive waiter and there are
557  // multiple waiters.
558  // We'll only be waking this waiter.
559 
560  // Unlink the wait block from the list.
561  // Although other wait blocks may have their
562  // Last pointers set to this wait block,
563  // the algorithm to find the last wait block
564  // will stop here. Likewise the Next pointers
565  // are never followed beyond this point, so
566  // we don't need to clear those.
567  firstWaitBlock->Last = previousWaitBlock;
568 
569  // Make sure we only wake this waiter.
570  waitBlock->Previous = NULL;
571 
572  if (!IgnoreOwned)
573  {
574  // Clear the traversing bit.
575  _InterlockedExchangeAddPointer((PLONG_PTR)&QueuedLock->Value, -(LONG_PTR)PH_QUEUED_LOCK_TRAVERSING);
576  }
577 
578  break;
579  }
580  else
581  {
582  // We're waking an exclusive waiter and there
583  // is only one waiter, or we are waking
584  // a shared waiter and possibly others.
585 
586  newValue = 0;
587 
588  if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
589  (PVOID *)&QueuedLock->Value,
590  (PVOID)newValue,
591  (PVOID)value
592  )) == value)
593  break;
594 
595  // Someone changed the lock (acquired it or
596  // pushed a wait block).
597 
598  value = newValue;
599  }
600  }
601 
602  return waitBlock;
603 }
604 
617  _Inout_ PPH_QUEUED_LOCK QueuedLock,
618  _In_ ULONG_PTR Value
619  )
620 {
621  PPH_QUEUED_WAIT_BLOCK waitBlock;
622  PPH_QUEUED_WAIT_BLOCK previousWaitBlock;
623 
624  waitBlock = PhpPrepareToWakeQueuedLock(QueuedLock, Value, FALSE, FALSE);
625 
626  // Wake waiters.
627 
628  while (waitBlock)
629  {
630  previousWaitBlock = waitBlock->Previous;
631  PhpUnblockQueuedWaitBlock(waitBlock);
632  waitBlock = previousWaitBlock;
633  }
634 }
635 
652  _Inout_ PPH_QUEUED_LOCK QueuedLock,
653  _In_ ULONG_PTR Value,
654  _In_ BOOLEAN IgnoreOwned,
655  _In_ BOOLEAN WakeAll
656  )
657 {
658  PPH_QUEUED_WAIT_BLOCK waitBlock;
659  PPH_QUEUED_WAIT_BLOCK previousWaitBlock;
660 
661  waitBlock = PhpPrepareToWakeQueuedLock(QueuedLock, Value, IgnoreOwned, WakeAll);
662 
663  // Wake waiters.
664 
665  while (waitBlock)
666  {
667  previousWaitBlock = waitBlock->Previous;
668  PhpUnblockQueuedWaitBlock(waitBlock);
669  waitBlock = previousWaitBlock;
670  }
671 }
672 
679  _Inout_ PPH_QUEUED_LOCK QueuedLock
680  )
681 {
682  ULONG_PTR value;
683  ULONG_PTR newValue;
684  ULONG_PTR currentValue;
685  BOOLEAN optimize;
686  PH_QUEUED_WAIT_BLOCK waitBlock;
687 
688  value = QueuedLock->Value;
689 
690  while (TRUE)
691  {
692  if (!(value & PH_QUEUED_LOCK_OWNED))
693  {
694  if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
695  (PVOID *)&QueuedLock->Value,
696  (PVOID)(value + PH_QUEUED_LOCK_OWNED),
697  (PVOID)value
698  )) == value)
699  break;
700  }
701  else
702  {
704  QueuedLock,
705  value,
706  TRUE,
707  &waitBlock,
708  &optimize,
709  &newValue,
710  &currentValue
711  ))
712  {
713  if (optimize)
714  PhpfOptimizeQueuedLockList(QueuedLock, currentValue);
715 
716  PHLIB_INC_STATISTIC(QlAcquireExclusiveBlocks);
717  PhpBlockOnQueuedWaitBlock(&waitBlock, TRUE, NULL);
718  }
719  }
720 
721  value = newValue;
722  }
723 }
724 
731  _Inout_ PPH_QUEUED_LOCK QueuedLock
732  )
733 {
734  ULONG_PTR value;
735  ULONG_PTR newValue;
736  ULONG_PTR currentValue;
737  BOOLEAN optimize;
738  PH_QUEUED_WAIT_BLOCK waitBlock;
739 
740  value = QueuedLock->Value;
741 
742  while (TRUE)
743  {
744  // We can't acquire if there are waiters for two reasons:
745  //
746  // We want to prioritize exclusive acquires over shared acquires.
747  // There's currently no fast, safe way of finding the last wait
748  // block and incrementing the shared owners count here.
749  if (
750  !(value & PH_QUEUED_LOCK_WAITERS) &&
751  (!(value & PH_QUEUED_LOCK_OWNED) || (PhGetQueuedLockSharedOwners(value) > 0))
752  )
753  {
754  newValue = (value + PH_QUEUED_LOCK_SHARED_INC) | PH_QUEUED_LOCK_OWNED;
755 
756  if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
757  (PVOID *)&QueuedLock->Value,
758  (PVOID)newValue,
759  (PVOID)value
760  )) == value)
761  break;
762  }
763  else
764  {
766  QueuedLock,
767  value,
768  FALSE,
769  &waitBlock,
770  &optimize,
771  &newValue,
772  &currentValue
773  ))
774  {
775  if (optimize)
776  PhpfOptimizeQueuedLockList(QueuedLock, currentValue);
777 
778  PHLIB_INC_STATISTIC(QlAcquireSharedBlocks);
779  PhpBlockOnQueuedWaitBlock(&waitBlock, TRUE, NULL);
780  }
781  }
782 
783  value = newValue;
784  }
785 }
786 
793  _Inout_ PPH_QUEUED_LOCK QueuedLock
794  )
795 {
796  ULONG_PTR value;
797  ULONG_PTR newValue;
798  ULONG_PTR currentValue;
799 
800  value = QueuedLock->Value;
801 
802  while (TRUE)
803  {
804  assert(value & PH_QUEUED_LOCK_OWNED);
805  assert((value & PH_QUEUED_LOCK_WAITERS) || (PhGetQueuedLockSharedOwners(value) == 0));
806 
807  if ((value & (PH_QUEUED_LOCK_WAITERS | PH_QUEUED_LOCK_TRAVERSING)) != PH_QUEUED_LOCK_WAITERS)
808  {
809  // There are no waiters or someone is traversing the list.
810  //
811  // If there are no waiters, we're simply releasing ownership.
812  // If someone is traversing the list, clearing the owned bit
813  // is a signal for them to wake waiters.
814 
815  newValue = value - PH_QUEUED_LOCK_OWNED;
816 
817  if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
818  (PVOID *)&QueuedLock->Value,
819  (PVOID)newValue,
820  (PVOID)value
821  )) == value)
822  break;
823  }
824  else
825  {
826  // We need to wake waiters and no one is traversing the list.
827  // Try to set the traversing bit and wake waiters.
828 
829  newValue = value - PH_QUEUED_LOCK_OWNED + PH_QUEUED_LOCK_TRAVERSING;
830  currentValue = newValue;
831 
832  if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
833  (PVOID *)&QueuedLock->Value,
834  (PVOID)newValue,
835  (PVOID)value
836  )) == value)
837  {
838  PhpfWakeQueuedLock(QueuedLock, currentValue);
839  break;
840  }
841  }
842 
843  value = newValue;
844  }
845 }
846 
853  _Inout_ PPH_QUEUED_LOCK QueuedLock
854  )
855 {
856  ULONG_PTR value;
857  ULONG_PTR newValue;
858  ULONG_PTR currentValue;
859  PPH_QUEUED_WAIT_BLOCK waitBlock;
860 
861  value = QueuedLock->Value;
862 
863  while (!(value & PH_QUEUED_LOCK_WAITERS))
864  {
865  assert(value & PH_QUEUED_LOCK_OWNED);
866  assert((value & PH_QUEUED_LOCK_WAITERS) || (PhGetQueuedLockSharedOwners(value) > 0));
867 
868  if (PhGetQueuedLockSharedOwners(value) > 1)
869  newValue = value - PH_QUEUED_LOCK_SHARED_INC;
870  else
871  newValue = 0;
872 
873  if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
874  (PVOID *)&QueuedLock->Value,
875  (PVOID)newValue,
876  (PVOID)value
877  )) == value)
878  return;
879 
880  value = newValue;
881  }
882 
883  if (value & PH_QUEUED_LOCK_MULTIPLE_SHARED)
884  {
885  // Unfortunately we have to find the last wait block and
886  // decrement the shared owners count.
887  waitBlock = PhpFindLastQueuedWaitBlock(value);
888 
889  if ((ULONG)_InterlockedDecrement((PLONG)&waitBlock->SharedOwners) > 0)
890  return;
891  }
892 
893  while (TRUE)
894  {
895  if (value & PH_QUEUED_LOCK_TRAVERSING)
896  {
898 
899  if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
900  (PVOID *)&QueuedLock->Value,
901  (PVOID)newValue,
902  (PVOID)value
903  )) == value)
904  break;
905  }
906  else
907  {
908  newValue = (value & ~(PH_QUEUED_LOCK_OWNED | PH_QUEUED_LOCK_MULTIPLE_SHARED)) |
910  currentValue = newValue;
911 
912  if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer(
913  (PVOID *)&QueuedLock->Value,
914  (PVOID)newValue,
915  (PVOID)value
916  )) == value)
917  {
918  PhpfWakeQueuedLock(QueuedLock, currentValue);
919  break;
920  }
921  }
922 
923  value = newValue;
924  }
925 }
926 
936  _Inout_ PPH_QUEUED_LOCK QueuedLock
937  )
938 {
939  ULONG_PTR value;
940  ULONG_PTR newValue;
941 
942  value = QueuedLock->Value;
943 
944  if (
945  !(value & PH_QUEUED_LOCK_WAITERS) ||
946  (value & PH_QUEUED_LOCK_TRAVERSING) ||
947  (value & PH_QUEUED_LOCK_OWNED)
948  )
949  return;
950 
951  newValue = value + PH_QUEUED_LOCK_TRAVERSING;
952 
953  if ((ULONG_PTR)_InterlockedCompareExchangePointer(
954  (PVOID *)&QueuedLock->Value,
955  (PVOID)newValue,
956  (PVOID)value
957  ) == value)
958  {
959  PhpfWakeQueuedLock(QueuedLock, newValue);
960  }
961 }
962 
975  _Inout_ PPH_QUEUED_LOCK QueuedLock,
976  _In_ ULONG_PTR Value
977  )
978 {
979  ULONG_PTR newValue;
980 
981  newValue = Value + PH_QUEUED_LOCK_TRAVERSING;
982 
983  if ((ULONG_PTR)_InterlockedCompareExchangePointer(
984  (PVOID *)&QueuedLock->Value,
985  (PVOID)newValue,
986  (PVOID)Value
987  ) == Value)
988  {
989  PhpfWakeQueuedLock(QueuedLock, newValue);
990  }
991 }
992 
1002  _Inout_ PPH_QUEUED_LOCK Condition
1003  )
1004 {
1005  if (Condition->Value & PH_QUEUED_LOCK_WAITERS)
1006  PhpfWakeQueuedLockEx(Condition, Condition->Value, TRUE, FALSE);
1007 }
1008 
1018  _Inout_ PPH_QUEUED_LOCK Condition
1019  )
1020 {
1021  if (Condition->Value & PH_QUEUED_LOCK_WAITERS)
1022  PhpfWakeQueuedLockEx(Condition, Condition->Value, TRUE, TRUE);
1023 }
1024 
1036  _Inout_ PPH_QUEUED_LOCK Condition,
1037  _Inout_ PPH_QUEUED_LOCK Lock,
1038  _In_opt_ PLARGE_INTEGER Timeout
1039  )
1040 {
1041  ULONG_PTR value;
1042  ULONG_PTR currentValue;
1043  PH_QUEUED_WAIT_BLOCK waitBlock;
1044  BOOLEAN optimize;
1045 
1046  value = Condition->Value;
1047 
1048  while (TRUE)
1049  {
1051  Condition,
1052  value,
1053  TRUE,
1054  &waitBlock,
1055  &optimize,
1056  &value,
1057  &currentValue
1058  ))
1059  {
1060  if (optimize)
1061  {
1062  PhpOptimizeQueuedLockListEx(Condition, currentValue, TRUE);
1063  }
1064 
1066 
1067  PhpBlockOnQueuedWaitBlock(&waitBlock, FALSE, NULL);
1068 
1069  // Don't use the inline variant; it is extremely likely
1070  // that the lock is still owned.
1072 
1073  break;
1074  }
1075  }
1076 }
1077 
1087  _Inout_ PPH_QUEUED_LOCK Condition,
1088  _Inout_ PVOID Lock,
1089  _In_ ULONG Flags,
1090  _In_opt_ PLARGE_INTEGER Timeout
1091  )
1092 {
1093  ULONG_PTR value;
1094  ULONG_PTR currentValue;
1095  PH_QUEUED_WAIT_BLOCK waitBlock;
1096  BOOLEAN optimize;
1097 
1098  value = Condition->Value;
1099 
1100  while (TRUE)
1101  {
1103  Condition,
1104  value,
1105  TRUE,
1106  &waitBlock,
1107  &optimize,
1108  &value,
1109  &currentValue
1110  ))
1111  {
1112  if (optimize)
1113  {
1114  PhpOptimizeQueuedLockListEx(Condition, currentValue, TRUE);
1115  }
1116 
1117  switch (Flags & PH_CONDITION_WAIT_LOCK_TYPE_MASK)
1118  {
1120  if (!(Flags & PH_CONDITION_WAIT_SHARED))
1122  else
1124  break;
1127  break;
1129  if (!(Flags & PH_CONDITION_WAIT_SHARED))
1131  else
1133  break;
1134  }
1135 
1136  if (!(Flags & PH_CONDITION_WAIT_SPIN))
1137  {
1138  PhpBlockOnQueuedWaitBlock(&waitBlock, FALSE, NULL);
1139  }
1140  else
1141  {
1142  PhpBlockOnQueuedWaitBlock(&waitBlock, TRUE, NULL);
1143  }
1144 
1145  switch (Flags & PH_CONDITION_WAIT_LOCK_TYPE_MASK)
1146  {
1148  if (!(Flags & PH_CONDITION_WAIT_SHARED))
1150  else
1152  break;
1155  break;
1157  if (!(Flags & PH_CONDITION_WAIT_SHARED))
1159  else
1161  break;
1162  }
1163 
1164  break;
1165  }
1166  }
1167 }
1168 
1180  _Inout_ PPH_QUEUED_LOCK WakeEvent,
1181  _Out_ PPH_QUEUED_WAIT_BLOCK WaitBlock
1182  )
1183 {
1184  PPH_QUEUED_WAIT_BLOCK value;
1185  PPH_QUEUED_WAIT_BLOCK newValue;
1186 
1187  WaitBlock->Flags = PH_QUEUED_WAITER_SPINNING;
1188 
1189  value = (PPH_QUEUED_WAIT_BLOCK)WakeEvent->Value;
1190 
1191  while (TRUE)
1192  {
1193  WaitBlock->Next = value;
1194 
1195  if ((newValue = _InterlockedCompareExchangePointer(
1196  (PVOID *)&WakeEvent->Value,
1197  WaitBlock,
1198  value
1199  )) == value)
1200  break;
1201 
1202  value = newValue;
1203  }
1204 }
1205 
1214  _Inout_ PPH_QUEUED_LOCK WakeEvent,
1215  _Inout_opt_ PPH_QUEUED_WAIT_BLOCK WaitBlock
1216  )
1217 {
1218  PPH_QUEUED_WAIT_BLOCK waitBlock;
1219  PPH_QUEUED_WAIT_BLOCK nextWaitBlock;
1220 
1221  // Pop all waiters and unblock them.
1222 
1223  waitBlock = _InterlockedExchangePointer((PVOID *)&WakeEvent->Value, NULL);
1224 
1225  while (waitBlock)
1226  {
1227  nextWaitBlock = waitBlock->Next;
1228  PhpUnblockQueuedWaitBlock(waitBlock);
1229  waitBlock = nextWaitBlock;
1230  }
1231 
1232  if (WaitBlock)
1233  {
1234  // We're cancelling a wait; the thread called this function instead
1235  // of PhfWaitForWakeEvent. This will remove all waiters from
1236  // the list. However, we may not have popped and unblocked the
1237  // cancelled wait block ourselves. Another thread may have popped all
1238  // waiters but not unblocked them yet at this point:
1239  //
1240  // 1. This thread: calls PhfQueueWakeEvent.
1241  // 2. This thread: code determines that the wait should be cancelled.
1242  // 3. Other thread: calls PhfSetWakeEvent and pops our wait block off.
1243  // It hasn't unblocked any wait blocks yet.
1244  // 4. This thread: calls PhfSetWakeEvent. Since all wait blocks have
1245  // been popped, we don't do anything. The caller function exits,
1246  // making our wait block invalid.
1247  // 5. Other thread: tries to unblock our wait block. Anything could
1248  // happen, since our caller already returned.
1249  //
1250  // The solution is to (always) wait for an unblock. Note that the check below
1251  // for the spinning flag is not required, but it is a small optimization.
1252  // If the wait block has been unblocked (i.e. the spinning flag is cleared),
1253  // then there's no danger.
1254 
1255  if (WaitBlock->Flags & PH_QUEUED_WAITER_SPINNING)
1256  PhpBlockOnQueuedWaitBlock(WaitBlock, FALSE, NULL);
1257  }
1258 }
1259 
1275  _Inout_ PPH_QUEUED_LOCK WakeEvent,
1276  _Inout_ PPH_QUEUED_WAIT_BLOCK WaitBlock,
1277  _In_ BOOLEAN Spin,
1278  _In_opt_ PLARGE_INTEGER Timeout
1279  )
1280 {
1281  NTSTATUS status;
1282 
1283  status = PhpBlockOnQueuedWaitBlock(WaitBlock, Spin, Timeout);
1284 
1285  if (status != STATUS_SUCCESS)
1286  {
1287  // Probably a timeout. There's no way of unlinking
1288  // the wait block safely, so just wake everyone.
1289  PhSetWakeEvent(WakeEvent, WaitBlock);
1290  }
1291 
1292  return status;
1293 }