ReactOS 0.4.17-dev-672-gf9943c7
ExPushLock.c
Go to the documentation of this file.
1/*
2 * PROJECT: ReactOS kernel tests
3 * LICENSE: MIT (https://spdx.org/licenses/MIT)
4 * PURPOSE: Push lock tests
5 * COPYRIGHT: Copyright 2026 Gleb Surikov <glebs.surikovs@gmail.com>
6 */
7
8#include <kmt_test.h>
9
10#define PUSH_LOCK_TIMEOUT_MS 5000
11#define PUSH_LOCK_POLL_INTERVAL_MS 1
12
13#define PUSH_LOCK_RELATIVE_TIMEOUT(Milliseconds) (-((LONGLONG)(Milliseconds) * 10 * 1000))
14
15#define PUSH_LOCK_MAX_WAITERS 4
16
17#define PUSH_LOCK_RACE_ITERATIONS 64
18#define PUSH_LOCK_CONTENTION_THREADS 6
19#define PUSH_LOCK_CONTENTION_ITERATIONS 2048
20
21#define PUSH_LOCK_CHECKSUM_XOR 0xA5A5A5A5UL
22
23typedef enum PUSH_LOCK_MODE
24{
28
30{
34
36{
41
43{
45
48 volatile LONG Violations;
49
53
54 /* These fields form one protected value. Writers deliberately update the
55 fields separately so that a reader can detect any overlap with a writer. */
56 volatile ULONG Sequence;
58 volatile ULONG Checksum;
60
62{
74
75C_ASSERT(sizeof(EX_PUSH_LOCK) == sizeof(ULONG_PTR));
76
81{
83
85
89 FALSE,
90 &Timeout);
91}
92
94VOID
96{
97 LARGE_INTEGER Delay;
98
100
102}
103
104static
107 _In_ PEX_PUSH_LOCK PushLock)
108{
110
111 /* Use a cmpxchg with identical xchg and comparand values as an
112 atomic read of the complete push lock word. A zero value remains zero,
113 and a nonzero value never matches the comparand, so the operation doesn't
114 modify the lock. */
115 Value.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, NULL, NULL);
116
117 return Value;
118}
119
120/*
121 * This checks only relationships that are valid for every externally visible
122 * push lock state. In particular, an unlocked lock may still have Waiting set
123 * while a selected waiter is becoming ready.
124 */
125static
129{
130 ULONG_PTR WaitBlockAddress;
131
132 if (Value.Waking && !Value.Waiting)
133 {
134 return FALSE;
135 }
136
137 if (Value.MultipleShared && (!Value.Locked || !Value.Waiting))
138 {
139 return FALSE;
140 }
141
142 if (!Value.Waiting && (Value.Waking || Value.MultipleShared))
143 {
144 return FALSE;
145 }
146
147 if (!Value.Locked && !Value.Waiting && Value.Value != 0)
148 {
149 return FALSE;
150 }
151
152 /* When Waiting is set, the upper bits contain the address of the newest wait
153 block while the low bits retain the push lock flags */
154 if (Value.Waiting)
155 {
156 WaitBlockAddress = Value.Value & ~EX_PUSH_LOCK_PTR_BITS;
157 if (WaitBlockAddress == 0)
158 {
159 return FALSE;
160 }
161 }
162
163 return TRUE;
164}
165
167VOID
170{
171 InterlockedIncrement(&State->Violations);
172}
173
174static
175VOID
178{
180 {
182 }
183}
184
185static
186VOID
188 _Inout_ PEX_PUSH_LOCK PushLock,
190{
192 {
194 }
195 else
196 {
197 ExfAcquirePushLockShared(PushLock);
198 }
199}
200
201static
202VOID
204 _Inout_ PEX_PUSH_LOCK PushLock,
206 _In_ PUSH_LOCK_RELEASE_KIND ReleaseKind)
207{
208 if (ReleaseKind == PushLockReleaseGeneric)
209 {
210 ExfReleasePushLock(PushLock);
211 }
212 else if (Mode == PushLockModeExclusive)
213 {
215 }
216 else
217 {
218 ExfReleasePushLockShared(PushLock);
219 }
220}
221
222/*
223 * The counters below are independent of the push lock implementation. They
224 * detect overlapping writers and any reader that enters while a writer owns
225 * the protected region.
226 */
227static
228VOID
232{
233 LONG Count;
234
236 {
237 Count = InterlockedIncrement(&State->ActiveWriters);
238 if (Count != 1 || State->ActiveReaders != 0)
239 {
241 }
242
243 InterlockedIncrement(&State->ExclusiveAcquisitions);
244 }
245 else
246 {
247 /* Check on both sides of the reader increment. The second check detects a
248 writer that entered after the first load but before this reader published
249 itself. */
250 if (State->ActiveWriters != 0)
251 {
253 }
254
255 InterlockedIncrement(&State->ActiveReaders);
256
257 if (State->ActiveWriters != 0)
258 {
260 }
261
262 InterlockedIncrement(&State->SharedAcquisitions);
263 }
264}
265
266static
267VOID
271{
272 LONG Count;
273
275 {
276 Count = InterlockedDecrement(&State->ActiveWriters);
277 if (Count != 0)
278 {
280 }
281 }
282 else
283 {
284 Count = InterlockedDecrement(&State->ActiveReaders);
285 if (Count < 0)
286 {
288 }
289 }
290}
291
292static
293VOID
296{
297 ULONG Sequence;
298 ULONG SequenceInverse;
299 ULONG Checksum;
300
301 /* A reader must observe all 3 fields from the same completed writer
302 update. Any mixture of old and new fields indicates overlap with an
303 x owner. */
304 Sequence = State->Sequence;
305 SequenceInverse = State->SequenceInverse;
306 Checksum = State->Checksum;
307
308 if (SequenceInverse != ~Sequence ||
309 Checksum != (Sequence ^ PUSH_LOCK_CHECKSUM_XOR))
310 {
312 }
313}
314
315static
316VOID
319{
320 ULONG Sequence;
321
322 /* Publish an intentionally inconsistent value while the update is in
323 progress. A reader entering concurrently is likely to observe
324 either an invalid inverse or an invalid checksum. */
325 Sequence = State->Sequence + 1;
326 State->Sequence = Sequence;
327 /* Keep the 3 stores ordered. N.B. The barriers aren't intended to replace
328 the synchronization supplied by the push lock. */
330 State->SequenceInverse = ~Sequence;
332 State->Checksum = Sequence ^ PUSH_LOCK_CHECKSUM_XOR;
333}
334
335static
336VOID
339{
340 RtlZeroMemory(State, sizeof(*State));
341 State->SequenceInverse = ~(ULONG)0;
342 State->Checksum = PUSH_LOCK_CHECKSUM_XOR;
343}
344
345static
346VOID
351 _In_ PUSH_LOCK_RELEASE_KIND ReleaseKind)
352{
353 RtlZeroMemory(Context, sizeof(*Context));
354
355 Context->State = State;
356 Context->Mode = Mode;
357 Context->ReleaseKind = ReleaseKind;
358
363}
364
365/*
366 * A controlled waiter acquires once and then remains in the protected region
367 * until the test releases it. This makes queue construction and waking
368 * observable without relying on timing after acquisition.
369 */
370static
371VOID
372NTAPI
375{
379
382
383 /* ReadyEvent reports only that the thread is running. It may still be held
384 behind StartGate and hasn't yet attempted to acquire the push lock. */
385 KeSetEvent(&Context->ReadyEvent, IO_NO_INCREMENT, FALSE);
386
387 if (Context->StartGate != NULL)
388 {
390 Executive,
392 FALSE,
393 NULL);
394 if (Status != STATUS_SUCCESS)
395 {
398 return;
399 }
400 }
401
403
404 PushLockAcquire(&State->Lock, Context->Mode);
406
407 /* Publish acquisition only after updating the ownership counters,
408 so the controlling thread can inspect a consistent protected
409 state after this event is signaled */
410 KeSetEvent(&Context->AcquiredEvent, IO_NO_INCREMENT, FALSE);
411
412 /* Remain inside the protected region until the test has inspected the lock
413 word and waiter chain for this acquisition */
414 Status = KeWaitForSingleObject(&Context->ReleaseEvent,
415 Executive,
417 FALSE,
418 NULL);
419 if (Status != STATUS_SUCCESS)
420 {
422 }
423
425 PushLockRelease(&State->Lock, Context->Mode, Context->ReleaseKind);
426
428
430}
431
432static
437{
439
441 if (*Thread == NULL)
442 {
443 ok(FALSE, "Could not create push-lock test thread\n");
444 return FALSE;
445 }
446
447 /* Don't examine the queue until the worker has started.
448 N.B. ReadyEvent doesn't imply that acquisition or queue
449 insertion has completed. */
450 Status = PushLockWaitForEvent(&Context->ReadyEvent);
452
453 return Status == STATUS_SUCCESS;
454}
455
456static
457VOID
461{
463
464 if (Thread == NULL)
465 {
466 return;
467 }
468
469 KeSetEvent(&Context->ReleaseEvent, IO_NO_INCREMENT, FALSE);
470
471 Status = PushLockWaitForEvent(&Context->DoneEvent);
473
475}
476
477/*
478 * Wait blocks are inserted in the newest-first manner. Once list optimization completes,
479 * Head->Last points to the oldest waiter and Previous links point toward newer
480 * waiters. The oldest block's Next field isn't a list terminator and isn't
481 * inspected.
482 *
483 * ExpectedNewestFirst describes the expected waiter modes starting at the
484 * wait block stored in the push lock and ending with the oldest waiter.
485 *
486 * The lock must remain owned while the chain is examined. InProgress is
487 * returned while an expected waiter hasn't yet been inserted or while list
488 * optimization is still in progress.
489 */
490static
493 _In_ PEX_PUSH_LOCK PushLock,
494 _In_reads_(ExpectedCount) const PUSH_LOCK_MODE *ExpectedNewestFirst,
495 _In_ ULONG ExpectedCount,
496 _Out_opt_ PEX_PUSH_LOCK_WAIT_BLOCK *OldestWaitBlock)
497{
499 EX_PUSH_LOCK CurrentValue;
501 PUSH_LOCK_MODE ActualNewestFirst[PUSH_LOCK_MAX_WAITERS];
506 ULONG ActualCount;
507 ULONG Index;
508 ULONG SeenIndex;
509 LONG Flags;
510
511 if (ExpectedCount == 0 || ExpectedCount > PUSH_LOCK_MAX_WAITERS)
512 {
514 }
515
516 Value = PushLockReadValue(PushLock);
517
519 {
521 }
522
523 /* The tests keep an owner in place while constructing the queue.
524 Waiting may still be clear until the expected waiter is inserted,
525 and Waking remains set while the list links are being optimized. */
526 if (!Value.Locked)
527 {
529 }
530
531 if (!Value.Waiting || Value.Waking)
532 {
534 }
535
537 if (!MmIsAddressValid(Head))
538 {
540 }
541
542 Last = Head->Last;
543 if (Last == NULL)
544 goto CheckForConcurrentChange;
545
546 if (((ULONG_PTR)Last & EX_PUSH_LOCK_PTR_BITS) != 0 || !MmIsAddressValid(Last))
547 {
549 }
550
551 RtlZeroMemory(Seen, sizeof(Seen));
552
553 Current = Head;
554 Previous = NULL;
555 ActualCount = 0;
556
557 for (Index = 0; Index < PUSH_LOCK_MAX_WAITERS; Index++)
558 {
559 if (Current == NULL)
560 {
562 }
563
564 if (!MmIsAddressValid(Current))
565 {
567 }
568
569 if (((ULONG_PTR)Current & EX_PUSH_LOCK_PTR_BITS) != 0)
570 {
572 }
573
574 for (SeenIndex = 0; SeenIndex < ActualCount; SeenIndex++)
575 {
576 if (Seen[SeenIndex] == Current)
577 {
579 }
580 }
581
582 Seen[ActualCount] = Current;
583
584 /* Previous links are built by ExpOptimizePushLockList. If the
585 push lock value changed while examining them, retry after the
586 concurrent insertion/optimization completes. */
587 if (Current->Previous != Previous)
588 {
589 goto CheckForConcurrentChange;
590 }
591
592 Flags = Current->Flags;
593
594 /* WAIT is the wake/sleep handshake bit and may already be clear.
595 EXCLUSIVE records the acquisition mode. No other flag bits are
596 valid for these wait blocks. */
598 {
600 }
601
602 ActualNewestFirst[ActualCount] = Flags & EX_PUSH_LOCK_FLAGS_EXCLUSIVE
605
606 ActualCount++;
607
608 if (Current == Last)
609 {
610 break;
611 }
612
613 Previous = Current;
614 Current = Current->Next;
615 }
616
617 if (Current != Last)
619
620 /* Make sure the lock word didn't change while the non-atomic wait block
621 links were being examined */
622 CurrentValue = PushLockReadValue(PushLock);
623 if (CurrentValue.Value != Value.Value)
624 {
626 }
627
628 /* A shorter chain means that not all expected waiters have been queued yet.
629 Additional waiters are invalid because the tests construct the queue in
630 controlled steps. */
631 if (ActualCount < ExpectedCount)
632 {
634 }
635
636 if (ActualCount > ExpectedCount)
637 {
639 }
640
641 for (Index = 0; Index < ExpectedCount; Index++)
642 {
643 /* EXCLUSIVE is the persistent mode bit, so the stable chain must
644 match the acquisition modes requested by the test */
645 if (ActualNewestFirst[Index] != ExpectedNewestFirst[Index])
646 {
648 }
649 }
650
651 if (OldestWaitBlock != NULL)
652 {
653 *OldestWaitBlock = Last;
654 }
655
657
658CheckForConcurrentChange:
659
660 CurrentValue = PushLockReadValue(PushLock);
661 if (CurrentValue.Value != Value.Value)
662 {
664 }
665
667}
668
669/*
670 * ExpectedNewestFirst describes the expected mode of each waiter,
671 * starting with the wait block encoded in the push lock and ending
672 * with the oldest wait block.
673 */
674static
677 _In_ PEX_PUSH_LOCK PushLock,
678 _In_reads_(ExpectedCount) const PUSH_LOCK_MODE *ExpectedNewestFirst,
679 _In_ ULONG ExpectedCount,
680 _Out_opt_ PEX_PUSH_LOCK_WAIT_BLOCK *OldestWaitBlock)
681{
683 ULONG Elapsed;
684
685 /* Queue insertion and list optimization are separate operations. Poll until
686 the waiter chain reaches the backward linked form required by the
687 structural checks below. */
688 for (Elapsed = 0;
689 Elapsed < PUSH_LOCK_TIMEOUT_MS;
691 {
692 ChainState = PushLockValidateWaitChain(PushLock,
693 ExpectedNewestFirst,
694 ExpectedCount,
695 OldestWaitBlock);
696
697 if (ChainState == PushLockWaitChainStable)
698 {
699 return TRUE;
700 }
701
702 if (ChainState == PushLockWaitChainInvalid)
703 {
704 return FALSE;
705 }
706
708 }
709
710 return FALSE;
711}
712
713/* Verify the exact lock word encodings used by uncontended operations. */
714static
715VOID
717 _In_ PUSH_LOCK_RELEASE_KIND ReleaseKind)
718{
721 ULONG Count;
722
724 ok_eq_ulongptr(State.Lock.Value, 0);
725
727
728 /* An uncontended exclusive acquisition is represented by the Locked bit alone */
731
732 PushLockRelease(&State.Lock, PushLockModeExclusive, ReleaseKind);
734
735 /* In the uncontended shared form, the lock word contains Locked plus one
736 EX_PUSH_LOCK_SHARE_INC for each shared acquisition */
737 for (Count = 1; Count <= 4; Count++)
738 {
742 }
743
744 /* Verify every intermediate shared count, including the transition from the
745 final shared owner back to the zero lock word */
746 for (Count = 4; Count > 0; Count--)
747 {
748 PushLockRelease(&State.Lock, PushLockModeShared, ReleaseKind);
750
751 if (Count == 1)
752 {
753 ok_eq_ulongptr(Value.Value, 0);
754 }
755 else
756 {
757 ok_eq_ulongptr(Value.Value,
759 }
760 }
761
763}
764
765/*
766 * Queue one exclusive waiter followed by two shared waiters. The oldest
767 * exclusive waiter must be selected alone. After it releases, the two readers
768 * may acquire together.
769 */
770static
771VOID
773{
774 static const PUSH_LOCK_MODE Modes[] = {
778 };
779 static const PUSH_LOCK_MODE ExpectedOne[] = {PushLockModeExclusive};
780 static const PUSH_LOCK_MODE ExpectedTwo[] = {
783 };
784 static const PUSH_LOCK_MODE ExpectedThree[] = {
788 };
789 static const PUSH_LOCK_MODE ExpectedReaders[] = {
792 };
793 const PUSH_LOCK_MODE *Expected[] = {
794 ExpectedOne,
795 ExpectedTwo,
796 ExpectedThree
797 };
800 PKTHREAD Threads[RTL_NUMBER_OF(Modes)] = {NULL};
803 ULONG Index;
804
806 Started = 0;
807
808 /* Hold the lock while constructing the waiter chain. Starting each waiter
809 separately makes the newest-to-oldest order deterministic. */
810
812
814
815 /* The resulting chains are:
816 o exclusive
817 o shared -> exclusive
818 o shared -> shared -> exclusive
819 where the leftmost entry is the newest waiter */
820
821 for (Index = 0; Index < RTL_NUMBER_OF(Modes); Index++)
822 {
824 &State,
825 Modes[Index],
827
829 {
830 if (Threads[Index] != NULL)
831 {
832 Started++;
833 }
834 break;
835 }
836
837 Started++;
838
840 "Wait chain did not stabilize after waiter %lu\n", Index);
841 }
842
843 /* The oldest waiter is exclusive, so releasing the owner must select only
844 that waiter and leave both shared waiters queued */
846
848
850 {
851 Status = PushLockWaitForEvent(&Contexts[0].AcquiredEvent);
853
854 ok_eq_long(KeReadStateEvent(&Contexts[1].AcquiredEvent), 0);
855 ok_eq_long(KeReadStateEvent(&Contexts[2].AcquiredEvent), 0);
856
857 /* Once the x waiter owns the lock, the two newer s waiters must
858 still form the complete remaining wait chain */
859 ok(PushLockWaitForStableWaitChain(&State.Lock, ExpectedReaders,
860 RTL_NUMBER_OF(ExpectedReaders), NULL),
861 "Reader wait chain is invalid while the writer owns the lock\n");
862
863 /* Releasing the oldest x waiter permits the remaining s batch to acquire together */
865 Threads[0] = NULL;
866
867 Status = PushLockWaitForEvent(&Contexts[1].AcquiredEvent);
869 Status = PushLockWaitForEvent(&Contexts[2].AcquiredEvent);
871
872 ok_eq_long(State.ActiveReaders, 2);
873 ok_eq_long(State.ActiveWriters, 0);
876 }
877
878 for (Index = 0; Index < Started; Index++)
879 {
880 if (Threads[Index] != NULL)
881 {
883 Threads[Index] = NULL;
884 }
885 }
886
887 ok_eq_long(State.ActiveReaders, 0);
888 ok_eq_long(State.ActiveWriters, 0);
889 ok_eq_long(State.Violations, 0);
891}
892
893/*
894 * When an x waiter is queued behind several s acquisitions, the
895 * oldest wait block stores the number of outstanding shares.
896 * A newer s waiter must remain queued until the x waiter has acquired and
897 * released the lock.
898 */
899static
900VOID
902 _In_ PUSH_LOCK_RELEASE_KIND ReleaseKind)
903{
904 /*
905 * shared
906 * shared
907 * shared
908 * |
909 * v
910 * exclusive waiter <- oldest, ShareCount = 3
911 * |
912 * v
913 * shared waiter <- newest
914 * |
915 * v
916 * release share ShareCount = 2, nobody wakes
917 * release share ShareCount = 1, nobody wakes
918 * release share ShareCount = 0
919 * |
920 * v
921 * exclusive wakes shared MUST remain queued
922 * |
923 * v
924 * exclusive releases
925 * |
926 * v
927 * shared wakes
928 */
929 static const PUSH_LOCK_MODE ExpectedExclusive[] = {PushLockModeExclusive};
930 static const PUSH_LOCK_MODE ExpectedBoth[] = {PushLockModeShared, PushLockModeExclusive};
931 static const PUSH_LOCK_MODE ExpectedShared[] = {PushLockModeShared};
933 PUSH_LOCK_THREAD_CONTEXT ExclusiveContext;
934 PUSH_LOCK_THREAD_CONTEXT SharedContext;
935 PKTHREAD ExclusiveThread;
936 PKTHREAD SharedThread;
937 PEX_PUSH_LOCK_WAIT_BLOCK OldestWaitBlock;
941 ULONG Count;
942
944 ExclusiveThread = NULL;
945 SharedThread = NULL;
946 OldestWaitBlock = NULL;
947
948 PushLockInitializeThreadContext(&ExclusiveContext,
949 &State,
952
953 PushLockInitializeThreadContext(&SharedContext,
954 &State,
957
959
960 /* 3 shared acquisitions force the 1st x waiter to use
961 MultipleShared and store the outstanding count in its wait block */
962 for (Count = 0; Count < 3; Count++)
963 {
965 }
966
967 if (!PushLockStartControlledThread(&ExclusiveContext, &ExclusiveThread))
968 {
969 goto Cleanup;
970 }
971
973 ExpectedExclusive,
974 RTL_NUMBER_OF(ExpectedExclusive),
975 &OldestWaitBlock);
976 ok(Success, "Exclusive waiter did not reach a stable wait state\n");
977 if (!Success)
978 {
979 goto Cleanup;
980 }
981
982 /* Queue an s waiter after the x waiter. Since Waiting is already set,
983 it must queue instead of joining the current s owners */
984 if (!PushLockStartControlledThread(&SharedContext, &SharedThread))
985 {
986 goto Cleanup;
987 }
988
990 ExpectedBoth,
991 RTL_NUMBER_OF(ExpectedBoth),
992 &OldestWaitBlock);
993 ok(Success, "Exclusive/shared wait chain did not stabilize\n");
994 if (!Success)
995 {
996 goto Cleanup;
997 }
998
1000 ok(Value.Locked, "Push lock is not locked\n");
1001 ok(Value.Waiting, "Push lock has no waiters\n");
1002 ok(!Value.Waking, "Push lock is waking\n");
1003 ok(Value.MultipleShared, "MultipleShared is not set\n");
1004
1005 if (OldestWaitBlock != NULL)
1006 {
1007 ok_eq_long(OldestWaitBlock->ShareCount, 3);
1008 ok(OldestWaitBlock->Flags & EX_PUSH_LOCK_FLAGS_EXCLUSIVE, "Oldest waiter is not exclusive\n");
1009 }
1010
1011 /* The first 2 releases only decrement the saved shared count.
1012 Neither waiter may acquire while an existing s owner remains. */
1013 for (Count = 3; Count > 1; Count--)
1014 {
1015 PushLockRelease(&State.Lock, PushLockModeShared, ReleaseKind);
1016
1017 if (OldestWaitBlock != NULL)
1018 {
1019 ok_eq_long(OldestWaitBlock->ShareCount, Count - 1);
1020 }
1021
1022 Value = PushLockReadValue(&State.Lock);
1023 ok(Value.Locked, "Push lock is not locked while shares remain\n");
1024 ok(Value.Waiting, "Push lock has no waiters while shares remain\n");
1025 ok(Value.MultipleShared, "MultipleShared is not set while shares remain\n");
1026
1027 ok_eq_long(KeReadStateEvent(&ExclusiveContext.AcquiredEvent), 0);
1028 ok_eq_long(KeReadStateEvent(&SharedContext.AcquiredEvent), 0);
1029 }
1030
1031 /* The final s release must select the oldest x waiter.
1032 The newer s waiter must remain queued behind it. */
1033 PushLockRelease(&State.Lock, PushLockModeShared, ReleaseKind);
1034
1036
1037 Status = PushLockWaitForEvent(&ExclusiveContext.AcquiredEvent);
1039
1040 if (Status != STATUS_SUCCESS)
1041 {
1042 /* Allow either waiter to finish if the expected handoff failed.
1043 Signaling both avoids making cleanup depend on which one acquired. */
1044 KeSetEvent(&ExclusiveContext.ReleaseEvent, IO_NO_INCREMENT, FALSE);
1045 KeSetEvent(&SharedContext.ReleaseEvent, IO_NO_INCREMENT, FALSE);
1046
1047 PushLockReleaseAndFinishThread(ExclusiveThread, &ExclusiveContext);
1048 PushLockReleaseAndFinishThread(SharedThread, &SharedContext);
1049 return;
1050 }
1051
1052 ok_eq_long(KeReadStateEvent(&SharedContext.AcquiredEvent), 0);
1053 ok_eq_long(State.ActiveWriters, 1);
1054 ok_eq_long(State.ActiveReaders, 0);
1055
1056 /* While the x waiter owns the lock, the newer s waiter must still be
1057 the complete remaining wait chain */
1059 ExpectedShared,
1060 RTL_NUMBER_OF(ExpectedShared),
1061 NULL);
1062 ok(Success, "Shared waiter did not remain queued behind the exclusive owner\n");
1063
1064 /* Releasing the x owner now permits the remaining s waiter to acquire */
1065 PushLockReleaseAndFinishThread(ExclusiveThread, &ExclusiveContext);
1066 ExclusiveThread = NULL;
1067
1068 Status = PushLockWaitForEvent(&SharedContext.AcquiredEvent);
1070
1071 if (Status == STATUS_SUCCESS)
1072 {
1073 ok_eq_long(State.ActiveWriters, 0);
1074 ok_eq_long(State.ActiveReaders, 1);
1075 }
1076
1077 PushLockReleaseAndFinishThread(SharedThread, &SharedContext);
1078 SharedThread = NULL;
1079
1080 ok_eq_long(State.ActiveReaders, 0);
1081 ok_eq_long(State.ActiveWriters, 0);
1082 ok_eq_long(State.Violations, 0);
1084
1085 return;
1086
1087Cleanup:
1088
1089 /* Let any successfully started waiter leave immediately after acquiring,
1090 regardless of how far queue construction progressed */
1091 if (ExclusiveThread != NULL)
1092 {
1093 KeSetEvent(&ExclusiveContext.ReleaseEvent, IO_NO_INCREMENT, FALSE);
1094 }
1095
1096 if (SharedThread != NULL)
1097 {
1098 KeSetEvent(&SharedContext.ReleaseEvent, IO_NO_INCREMENT, FALSE);
1099 }
1100
1101 for (Count = 0; Count < 3; Count++)
1102 {
1103 PushLockRelease(&State.Lock, PushLockModeShared, ReleaseKind);
1104 }
1105
1107
1108 PushLockReleaseAndFinishThread(ExclusiveThread, &ExclusiveContext);
1109 PushLockReleaseAndFinishThread(SharedThread, &SharedContext);
1110}
1111
1112/*
1113 * TLA counter example: 1 waiter is already queued while another waiter is
1114 * allowed to arrive as the final shared owner releases.
1115 * A black box test can't stop the release routine between its load and cmpxchg,
1116 * so this only stresses the window...
1117 */
1118static
1119VOID
1121{
1122 static const PUSH_LOCK_MODE ExpectedOldest[] = {PushLockModeExclusive};
1124 PUSH_LOCK_THREAD_CONTEXT OldestContext;
1125 PUSH_LOCK_THREAD_CONTEXT NewestContext;
1126 PKTHREAD OldestThread;
1127 PKTHREAD NewestThread;
1128 KEVENT StartGate;
1129 PUSH_LOCK_RELEASE_KIND ReleaseKind;
1130 ULONG Iteration;
1131
1132 for (Iteration = 0; Iteration < PUSH_LOCK_RACE_ITERATIONS; Iteration++)
1133 {
1135 OldestThread = NULL;
1136 NewestThread = NULL;
1138
1141
1142 PushLockInitializeThreadContext(&OldestContext,
1143 &State,
1146 if (!PushLockStartControlledThread(&OldestContext, &OldestThread))
1147 {
1150 PushLockReleaseAndFinishThread(OldestThread, &OldestContext);
1151 continue;
1152 }
1153
1154 ok(PushLockWaitForStableWaitChain(&State.Lock, ExpectedOldest,
1155 RTL_NUMBER_OF(ExpectedOldest), NULL),
1156 "Oldest waiter did not stabilize at iteration %lu\n",
1157 Iteration);
1158
1159 PushLockInitializeThreadContext(&NewestContext,
1160 &State,
1163 NewestContext.StartGate = &StartGate;
1164
1165 if (!PushLockStartControlledThread(&NewestContext, &NewestThread))
1166 {
1167 KeSetEvent(&StartGate, IO_NO_INCREMENT, FALSE);
1170 PushLockReleaseAndFinishThread(OldestThread, &OldestContext);
1171 PushLockReleaseAndFinishThread(NewestThread, &NewestContext);
1172 continue;
1173 }
1174
1175 /* Don't hold either waiter after acquisition. The test is interested in
1176 completion of the entire queue, not in inspecting an intermediate owner... */
1177 KeSetEvent(&OldestContext.ReleaseEvent, IO_NO_INCREMENT, FALSE);
1178 KeSetEvent(&NewestContext.ReleaseEvent, IO_NO_INCREMENT, FALSE);
1179
1180 /* Exercise the same arrival window through both the s specific and
1181 generic release */
1182 ReleaseKind = (Iteration & 1)
1185
1186 /* Make the new waiter ready immediately before the final shared release.
1187 The scheduler may insert it before or during the release. This can't
1188 force the exact load/CAS interleaving, but repeatedly exposes the
1189 implementation to the execution found by the tla */
1190 KeSetEvent(&StartGate, IO_NO_INCREMENT, FALSE);
1191 PushLockRelease(&State.Lock, PushLockModeShared, ReleaseKind);
1192
1194
1195 PushLockReleaseAndFinishThread(OldestThread, &OldestContext);
1196 PushLockReleaseAndFinishThread(NewestThread, &NewestContext);
1197
1198 ok_eq_long(KeReadStateEvent(&OldestContext.AcquiredEvent), 1);
1199 ok_eq_long(KeReadStateEvent(&NewestContext.AcquiredEvent), 1);
1200 ok_eq_long(State.Violations, 0);
1202 }
1203}
1204
1205/*
1206 * The contention workers use fixed roles and a deterministic release pattern.
1207 * 2 writers and 4 readers start together, repeatedly validate protected data,
1208 * adn use both the generic and mode specific releases.
1209 */
1210static
1211VOID
1212NTAPI
1215{
1218 PUSH_LOCK_RELEASE_KIND ReleaseKind;
1220 ULONG Iteration;
1221
1223 State = Context->State;
1224
1225 KeSetEvent(&Context->ReadyEvent, IO_NO_INCREMENT, FALSE);
1226
1228 Executive,
1229 KernelMode,
1230 FALSE,
1231 NULL);
1232 if (Status != STATUS_SUCCESS)
1233 {
1235 KeSetEvent(&Context->DoneEvent, IO_NO_INCREMENT, FALSE);
1236 return;
1237 }
1238
1239 for (Iteration = 0; Iteration < Context->Iterations; Iteration++)
1240 {
1241 /* Alternate release per worker and iteration so every role uses
1242 both the generic and mode specific release functions */
1243 ReleaseKind = ((Iteration + Context->Index) & 1)
1246
1248
1249 PushLockAcquire(&State->Lock, Context->Mode);
1250 /* The ownership counters and protected value checks validate the
1251 exclusion contract without relying on the internal push lock fields */
1253
1254 if (Context->Mode == PushLockModeExclusive)
1255 {
1257 }
1258 else
1259 {
1261 }
1262
1264 PushLockRelease(&State->Lock, Context->Mode, ReleaseKind);
1265
1267
1268 InterlockedIncrement(&State->CompletedOperations);
1269
1270 /* Occasionally sample the encoded lock state and yield to increase useful nterleavings */
1271 if ((Iteration & 0x3f) == 0)
1272 {
1275 }
1276 }
1277
1278 KeSetEvent(&Context->DoneEvent, IO_NO_INCREMENT, FALSE);
1279}
1280
1281static
1282VOID
1284{
1288 KEVENT StartGate;
1290 ULONG Index;
1291 ULONG Started;
1292 ULONG WriterCount;
1293 ULONG ReaderCount;
1294
1297
1298 Started = 0;
1299 WriterCount = 0;
1300 ReaderCount = 0;
1301
1303 {
1305
1306 /* Threads 0 and 3 are writers; the remaining 4 are readers */
1308
1310 {
1311 WriterCount++;
1312 }
1313 else
1314 {
1315 ReaderCount++;
1316 }
1317
1319 &State,
1320 Mode,
1322 Contexts[Index].StartGate = &StartGate;
1323 Contexts[Index].Index = Index;
1325
1327 &Contexts[Index]);
1328 if (Threads[Index] == NULL)
1329 {
1330 ok(FALSE, "Could not create contention thread %lu\n", Index);
1331 break;
1332 }
1333
1334 Started++;
1335
1336 Status = PushLockWaitForEvent(&Contexts[Index].ReadyEvent);
1338
1339 if (Status != STATUS_SUCCESS)
1340 {
1341 break;
1342 }
1343 }
1344
1345 /* Release all successfully created workers together so their first
1346 acquisitions contend on the same initially unlocked push lock */
1347 KeSetEvent(&StartGate, IO_NO_INCREMENT, FALSE);
1348
1349 for (Index = 0; Index < Started; Index++)
1350 {
1353 KmtFinishThread(Threads[Index], NULL);
1354 }
1355
1356 ok_eq_long(State.ActiveReaders, 0);
1357 ok_eq_long(State.ActiveWriters, 0);
1358 ok_eq_long(State.Violations, 0);
1359 ok_eq_long(State.CompletedOperations,
1361
1363 {
1364 ok_eq_long(State.ExclusiveAcquisitions,
1365 (LONG)(WriterCount * PUSH_LOCK_CONTENTION_ITERATIONS));
1366 ok_eq_long(State.SharedAcquisitions,
1367 (LONG)(ReaderCount * PUSH_LOCK_CONTENTION_ITERATIONS));
1368 }
1369
1371}
1372
1373START_TEST(ExPushLock)
1374{
1377
1379
1382
1385}
static VOID TestPushLockSharedOwnerDrain(_In_ PUSH_LOCK_RELEASE_KIND ReleaseKind)
Definition: ExPushLock.c:901
#define PUSH_LOCK_CONTENTION_ITERATIONS
Definition: ExPushLock.c:19
struct PUSH_LOCK_TEST_STATE * PPUSH_LOCK_TEST_STATE
static VOID TestPushLockWaiterSelection(VOID)
Definition: ExPushLock.c:772
static VOID PushLockEnterProtectedRegion(_Inout_ PPUSH_LOCK_TEST_STATE State, _In_ PUSH_LOCK_MODE Mode)
Definition: ExPushLock.c:229
static PUSH_LOCK_WAIT_CHAIN_STATE PushLockValidateWaitChain(_In_ PEX_PUSH_LOCK PushLock, _In_reads_(ExpectedCount) const PUSH_LOCK_MODE *ExpectedNewestFirst, _In_ ULONG ExpectedCount, _Out_opt_ PEX_PUSH_LOCK_WAIT_BLOCK *OldestWaitBlock)
Definition: ExPushLock.c:492
static VOID TestPushLockUncontended(_In_ PUSH_LOCK_RELEASE_KIND ReleaseKind)
Definition: ExPushLock.c:716
static VOID PushLockReleaseAndFinishThread(_In_opt_ PKTHREAD Thread, _Inout_ PPUSH_LOCK_THREAD_CONTEXT Context)
Definition: ExPushLock.c:458
static EX_PUSH_LOCK PushLockReadValue(_In_ PEX_PUSH_LOCK PushLock)
Definition: ExPushLock.c:106
static VOID TestPushLockContention(VOID)
Definition: ExPushLock.c:1283
static BOOLEAN PushLockStartControlledThread(_Inout_ PPUSH_LOCK_THREAD_CONTEXT Context, _Out_ PKTHREAD *Thread)
Definition: ExPushLock.c:434
#define PUSH_LOCK_MAX_WAITERS
Definition: ExPushLock.c:15
#define PUSH_LOCK_CONTENTION_THREADS
Definition: ExPushLock.c:18
PUSH_LOCK_RELEASE_KIND
Definition: ExPushLock.c:30
@ PushLockReleaseSpecific
Definition: ExPushLock.c:31
@ PushLockReleaseGeneric
Definition: ExPushLock.c:32
static VOID PushLockSampleState(_Inout_ PPUSH_LOCK_TEST_STATE State)
Definition: ExPushLock.c:176
static VOID PushLockReadProtectedValue(_Inout_ PPUSH_LOCK_TEST_STATE State)
Definition: ExPushLock.c:294
struct PUSH_LOCK_THREAD_CONTEXT * PPUSH_LOCK_THREAD_CONTEXT
static VOID PushLockInitializeThreadContext(_Out_ PPUSH_LOCK_THREAD_CONTEXT Context, _Inout_ PPUSH_LOCK_TEST_STATE State, _In_ PUSH_LOCK_MODE Mode, _In_ PUSH_LOCK_RELEASE_KIND ReleaseKind)
Definition: ExPushLock.c:347
static VOID PushLockWriteProtectedValue(_Inout_ PPUSH_LOCK_TEST_STATE State)
Definition: ExPushLock.c:317
FORCEINLINE NTSTATUS PushLockWaitForEvent(_In_ PKEVENT Event)
Definition: ExPushLock.c:79
static VOID PushLockAcquire(_Inout_ PEX_PUSH_LOCK PushLock, _In_ PUSH_LOCK_MODE Mode)
Definition: ExPushLock.c:187
#define PUSH_LOCK_RELATIVE_TIMEOUT(Milliseconds)
Definition: ExPushLock.c:13
static VOID NTAPI PushLockContentionThread(_In_ PVOID Parameter)
Definition: ExPushLock.c:1213
#define PUSH_LOCK_RACE_ITERATIONS
Definition: ExPushLock.c:17
static VOID PushLockLeaveProtectedRegion(_Inout_ PPUSH_LOCK_TEST_STATE State, _In_ PUSH_LOCK_MODE Mode)
Definition: ExPushLock.c:268
static VOID TestPushLockWaiterArrivalDuringRelease(VOID)
Definition: ExPushLock.c:1120
static BOOLEAN PushLockValueIsPlausible(_In_ EX_PUSH_LOCK Value)
Definition: ExPushLock.c:127
static VOID PushLockInitializeState(_Out_ PPUSH_LOCK_TEST_STATE State)
Definition: ExPushLock.c:337
static VOID PushLockRelease(_Inout_ PEX_PUSH_LOCK PushLock, _In_ PUSH_LOCK_MODE Mode, _In_ PUSH_LOCK_RELEASE_KIND ReleaseKind)
Definition: ExPushLock.c:203
FORCEINLINE VOID PushLockRecordViolation(_Inout_ PPUSH_LOCK_TEST_STATE State)
Definition: ExPushLock.c:168
PUSH_LOCK_MODE
Definition: ExPushLock.c:24
@ PushLockModeShared
Definition: ExPushLock.c:25
@ PushLockModeExclusive
Definition: ExPushLock.c:26
static VOID NTAPI PushLockControlledThread(_In_ PVOID Parameter)
Definition: ExPushLock.c:373
#define PUSH_LOCK_CHECKSUM_XOR
Definition: ExPushLock.c:21
#define PUSH_LOCK_TIMEOUT_MS
Definition: ExPushLock.c:10
FORCEINLINE VOID PushLockDelay(VOID)
Definition: ExPushLock.c:95
#define PUSH_LOCK_POLL_INTERVAL_MS
Definition: ExPushLock.c:11
static BOOLEAN PushLockWaitForStableWaitChain(_In_ PEX_PUSH_LOCK PushLock, _In_reads_(ExpectedCount) const PUSH_LOCK_MODE *ExpectedNewestFirst, _In_ ULONG ExpectedCount, _Out_opt_ PEX_PUSH_LOCK_WAIT_BLOCK *OldestWaitBlock)
Definition: ExPushLock.c:676
PUSH_LOCK_WAIT_CHAIN_STATE
Definition: ExPushLock.c:36
@ PushLockWaitChainInProgress
Definition: ExPushLock.c:37
@ PushLockWaitChainInvalid
Definition: ExPushLock.c:39
@ PushLockWaitChainStable
Definition: ExPushLock.c:38
BOOLEAN Expected
#define EX_PUSH_LOCK_PTR_BITS
Definition: Object.c:34
#define EX_PUSH_LOCK_SHARE_INC
Definition: Object.c:33
#define EX_PUSH_LOCK_LOCK
Definition: Object.c:29
#define RTL_NUMBER_OF(x)
Definition: RtlRegistry.c:12
@ Started
Definition: acpisys.h:14
unsigned char BOOLEAN
Definition: actypes.h:127
#define ok_eq_hex(value, expected)
Definition: apitest.h:134
#define ok_eq_long(value, expected)
Definition: apitest.h:119
#define ok_eq_ulongptr(value, expected)
Definition: apitest.h:128
#define InterlockedIncrement
Definition: armddk.h:53
#define InterlockedDecrement
Definition: armddk.h:52
#define ok(value,...)
Definition: atltest.h:57
#define START_TEST(x)
Definition: atltest.h:75
LONG NTSTATUS
Definition: precomp.h:26
#define NULL
Definition: types.h:112
#define TRUE
Definition: types.h:120
#define FALSE
Definition: types.h:117
static const WCHAR Cleanup[]
Definition: register.c:80
#define KeWaitForSingleObject(pEvt, foo, a, b, c)
Definition: env_spec_w32.h:478
#define KeInitializeEvent(pEvt, foo, foo2)
Definition: env_spec_w32.h:477
#define KeSetEvent(pEvt, foo, foo2)
Definition: env_spec_w32.h:476
#define KeDelayExecutionThread(mode, foo, t)
Definition: env_spec_w32.h:484
@ Success
Definition: eventcreate.c:712
LONG NTAPI KeReadStateEvent(IN PKEVENT Event)
Definition: eventobj.c:121
_In_opt_ PFILE_OBJECT _In_opt_ PETHREAD Thread
Definition: fltkernel.h:2653
Status
Definition: gdiplustypes.h:24
_In_ ULONG Mode
Definition: hubbusif.h:303
#define InterlockedCompareExchangePointer
Definition: interlocked.h:144
#define C_ASSERT(e)
Definition: intsafe.h:73
#define KeLeaveCriticalRegion()
Definition: ke_x.h:119
#define KeEnterCriticalRegion()
Definition: ke_x.h:88
PKTHREAD KmtStartThread(IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext OPTIONAL)
VOID KmtFinishThread(IN PKTHREAD Thread OPTIONAL, IN PKEVENT Event OPTIONAL)
static PFLT_CONTEXT_REGISTRATION Contexts
BOOLEAN NTAPI MmIsAddressValid(IN PVOID VirtualAddress)
Definition: mmsup.c:174
* PEX_PUSH_LOCK_WAIT_BLOCK
Definition: extypes.h:672
#define EX_PUSH_LOCK_FLAGS_EXCLUSIVE
Definition: extypes.h:162
#define EX_PUSH_LOCK_FLAGS_WAIT
Definition: extypes.h:164
#define KernelMode
Definition: asm.h:38
#define _In_reads_(s)
Definition: no_sal2.h:168
#define _Out_opt_
Definition: no_sal2.h:214
#define _Inout_
Definition: no_sal2.h:162
#define _Out_
Definition: no_sal2.h:160
#define _In_
Definition: no_sal2.h:158
#define _In_opt_
Definition: no_sal2.h:212
int Count
Definition: noreturn.cpp:7
@ NotificationEvent
long LONG
Definition: pedump.c:60
static ULONG Timeout
Definition: ping.c:61
VOID FASTCALL ExfReleasePushLock(PEX_PUSH_LOCK PushLock)
Definition: pushlock.c:810
VOID FASTCALL ExfReleasePushLockShared(PEX_PUSH_LOCK PushLock)
Definition: pushlock.c:972
VOID FASTCALL ExfReleasePushLockExclusive(PEX_PUSH_LOCK PushLock)
Definition: pushlock.c:1123
VOID FASTCALL ExfAcquirePushLockExclusive(PEX_PUSH_LOCK PushLock)
Definition: pushlock.c:471
VOID FASTCALL ExfAcquirePushLockShared(PEX_PUSH_LOCK PushLock)
Definition: pushlock.c:645
#define YieldProcessor
Definition: ke.h:48
FORCEINLINE VOID KeMemoryBarrier(VOID)
Definition: ke.h:58
#define STATUS_SUCCESS
Definition: shellext.h:65
_In_ PVOID Context
Definition: storport.h:2269
volatile ULONG Sequence
Definition: ExPushLock.c:56
volatile LONG SharedAcquisitions
Definition: ExPushLock.c:50
volatile ULONG Checksum
Definition: ExPushLock.c:58
volatile LONG ActiveReaders
Definition: ExPushLock.c:46
EX_PUSH_LOCK Lock
Definition: ExPushLock.c:44
volatile LONG ActiveWriters
Definition: ExPushLock.c:47
volatile LONG ExclusiveAcquisitions
Definition: ExPushLock.c:51
volatile LONG CompletedOperations
Definition: ExPushLock.c:52
volatile ULONG SequenceInverse
Definition: ExPushLock.c:57
volatile LONG Violations
Definition: ExPushLock.c:48
PPUSH_LOCK_TEST_STATE State
Definition: ExPushLock.c:63
PUSH_LOCK_RELEASE_KIND ReleaseKind
Definition: ExPushLock.c:65
PUSH_LOCK_MODE Mode
Definition: ExPushLock.c:64
State(char *beg, char *end)
ULONG_PTR Value
Definition: extypes.h:636
static const VBE_MODE Modes[VBE_MODE_COUNT]
Definition: vbe.c:189
#define NTAPI
Definition: typedefs.h:36
#define RtlZeroMemory(Destination, Length)
Definition: typedefs.h:262
uint32_t ULONG_PTR
Definition: typedefs.h:65
uint32_t ULONG
Definition: typedefs.h:59
LONGLONG QuadPart
Definition: typedefs.h:114
_In_ WDFCOLLECTION _In_ ULONG Index
_Must_inspect_result_ _In_ WDFKEY _In_ PCUNICODE_STRING _Out_opt_ PUSHORT _Inout_opt_ PUNICODE_STRING Value
Definition: wdfregistry.h:413
#define FORCEINLINE
Definition: wdftypes.h:67
_Must_inspect_result_ _In_ ULONG Flags
Definition: wsk.h:170
#define IO_NO_INCREMENT
Definition: iotypes.h:598
@ Executive
Definition: ketypes.h:467
_Inout_opt_ PVOID Parameter
Definition: rtltypes.h:336