Best Xunit code snippet using Xunit.Sdk.AsyncManualResetEvent.Set
AsyncReaderWriterLockTests.cs
Source:AsyncReaderWriterLockTests.cs
...97 writeAwaiter.OnCompleted(delegate98 {99 using (writeAwaiter.GetResult())100 {101 writeLockHeld.SetAsync();102 }103 });104 }105 Assert.True(this.asyncLock.IsReadLockHeld, "Lock should be visible.");106 });107 Assert.True(this.asyncLock.IsReadLockHeld);108 }109 Assert.False(this.asyncLock.IsReadLockHeld);110 await writeLockHeld.Task;111 }112 [Fact]113 public async Task HideLocksRevertedOutOfOrder()114 {115 AsyncReaderWriterLock.Suppression suppression;116 using (await this.asyncLock.ReadLockAsync())117 {118 Assert.True(this.asyncLock.IsReadLockHeld);119 suppression = this.asyncLock.HideLocks();120 Assert.False(this.asyncLock.IsReadLockHeld);121 }122 Assert.False(this.asyncLock.IsReadLockHeld);123 suppression.Dispose();124 Assert.False(this.asyncLock.IsReadLockHeld);125 }126 [Fact]127 public void ReleaseDefaultCtorDispose()128 {129 default(AsyncReaderWriterLock.Releaser).Dispose();130 }131 [Fact]132 public void SuppressionDefaultCtorDispose()133 {134 default(AsyncReaderWriterLock.Suppression).Dispose();135 }136 [Fact]137 public void AwaitableDefaultCtorDispose()138 {139 Assert.Throws<InvalidOperationException>(() => default(AsyncReaderWriterLock.Awaitable).GetAwaiter());140 }141 /// <summary>Verifies that continuations of the Completion property's task do not execute in the context of the private lock.</summary>142 [Fact]143 public async Task CompletionContinuationsDoNotDeadlockWithLockClass()144 {145 var continuationFired = new TaskCompletionSource<object?>();146 var releaseContinuation = new TaskCompletionSource<object?>();147 Task? continuation = this.asyncLock.Completion.ContinueWith(148 delegate149 {150 continuationFired.SetAsync();151 releaseContinuation.Task.Wait();152 },153 TaskContinuationOptions.ExecuteSynchronously); // this flag tries to tease out the sync-allowing behavior if it exists.154 var nowait = Task.Run(async delegate155 {156 await continuationFired.Task; // wait for the continuation to fire, and resume on an MTA thread.157 // Now on this separate thread, do something that should require the private lock of the lock class, to ensure it's not a blocking call.158 bool throwaway = this.asyncLock.IsReadLockHeld;159 releaseContinuation.SetResult(null);160 });161 using (await this.asyncLock.ReadLockAsync())162 {163 this.asyncLock.Complete();164 }165 await Task.WhenAll(releaseContinuation.Task, continuation);166 }167 /// <summary>Verifies that continuations of the Completion property's task do not execute synchronously with the last lock holder's Release.</summary>168 [Fact]169 public async Task CompletionContinuationsExecuteAsynchronously()170 {171 var releaseContinuation = new TaskCompletionSource<object?>();172 Task? continuation = this.asyncLock.Completion.ContinueWith(173 delegate174 {175 releaseContinuation.Task.Wait();176 },177 TaskContinuationOptions.ExecuteSynchronously); // this flag tries to tease out the sync-allowing behavior if it exists.178 using (await this.asyncLock.ReadLockAsync())179 {180 this.asyncLock.Complete();181 }182 releaseContinuation.SetResult(null);183 await continuation;184 }185 [Fact]186 public async Task CompleteMethodExecutesContinuationsAsynchronously()187 {188 var releaseContinuation = new TaskCompletionSource<object?>();189 Task continuation = this.asyncLock.Completion.ContinueWith(190 delegate191 {192 releaseContinuation.Task.Wait();193 },194 TaskContinuationOptions.ExecuteSynchronously);195 this.asyncLock.Complete();196 releaseContinuation.SetResult(null);197 await continuation;198 }199 [SkippableFact]200 public async Task NoMemoryLeakForManyLocks()201 {202 if (await this.ExecuteInIsolationAsync())203 {204 // First prime the pump to allocate some fixed cost memory.205 {206 var lck = new AsyncReaderWriterLock();207 using (await lck.ReadLockAsync())208 {209 }210 }211 bool passingAttemptObserved = false;212 for (int attempt = 0; !passingAttemptObserved && attempt < GCAllocationAttempts; attempt++)213 {214 const int iterations = 1000;215 long memory1 = GC.GetTotalMemory(true);216 for (int i = 0; i < iterations; i++)217 {218 var lck = new AsyncReaderWriterLock();219 using (await lck.ReadLockAsync())220 {221 }222 }223 long memory2 = GC.GetTotalMemory(true);224 long allocated = (memory2 - memory1) / iterations;225 this.Logger.WriteLine("Allocated bytes: {0} ({1} allowed)", allocated, MaxGarbagePerLock);226 passingAttemptObserved = allocated <= MaxGarbagePerLock;227 }228 Assert.True(passingAttemptObserved);229 }230 }231#if NETFRAMEWORK232 [Fact, Trait("TestCategory", "FailsInCloudTest")]233 public async Task CallAcrossAppDomainBoundariesWithLock()234 {235 var otherDomain = AppDomain.CreateDomain("test domain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);236 try237 {238 var proxy = (OtherDomainProxy)otherDomain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(OtherDomainProxy).FullName);239 proxy.SomeMethod(AppDomain.CurrentDomain.Id); // verify we can call it first.240 using (await this.asyncLock.ReadLockAsync())241 {242 proxy.SomeMethod(AppDomain.CurrentDomain.Id); // verify we can call it while holding a project lock.243 }244 proxy.SomeMethod(AppDomain.CurrentDomain.Id); // verify we can call it after releasing a project lock.245 }246 finally247 {248 AppDomain.Unload(otherDomain);249 }250 }251#endif252 [Fact]253 public async Task LockStackContainsFlags()254 {255 var asyncLock = new LockDerived();256 var customFlag = (AsyncReaderWriterLock.LockFlags)0x10000;257 var customFlag2 = (AsyncReaderWriterLock.LockFlags)0x20000;258 Assert.False(asyncLock.LockStackContains(customFlag));259 using (await asyncLock.UpgradeableReadLockAsync(customFlag))260 {261 Assert.True(asyncLock.LockStackContains(customFlag));262 Assert.False(asyncLock.LockStackContains(customFlag2));263 using (await asyncLock.WriteLockAsync(customFlag2))264 {265 Assert.True(asyncLock.LockStackContains(customFlag));266 Assert.True(asyncLock.LockStackContains(customFlag2));267 }268 Assert.True(asyncLock.LockStackContains(customFlag));269 Assert.False(asyncLock.LockStackContains(customFlag2));270 }271 Assert.False(asyncLock.LockStackContains(customFlag));272 }273 [Fact]274 public async Task OnLockReleaseCallbacksWithOuterWriteLock()275 {276 var stub = new LockDerived();277 int onExclusiveLockReleasedAsyncInvocationCount = 0;278 stub.OnExclusiveLockReleasedAsyncDelegate = delegate279 {280 onExclusiveLockReleasedAsyncInvocationCount++;281 return Task.FromResult<object?>(null);282 };283 int onUpgradeableReadLockReleasedInvocationCount = 0;284 stub.OnUpgradeableReadLockReleasedDelegate = delegate285 {286 onUpgradeableReadLockReleasedInvocationCount++;287 };288 this.asyncLock = stub;289 using (await this.asyncLock.WriteLockAsync())290 {291 using (await this.asyncLock.WriteLockAsync())292 {293 using (await this.asyncLock.WriteLockAsync())294 {295 using (await this.asyncLock.UpgradeableReadLockAsync())296 {297 Assert.Equal(0, onUpgradeableReadLockReleasedInvocationCount);298 }299 Assert.Equal(0, onUpgradeableReadLockReleasedInvocationCount);300 }301 Assert.Equal(0, onExclusiveLockReleasedAsyncInvocationCount);302 }303 Assert.Equal(0, onExclusiveLockReleasedAsyncInvocationCount);304 }305 Assert.Equal(1, onExclusiveLockReleasedAsyncInvocationCount);306 }307 [Fact]308 public async Task OnLockReleaseCallbacksWithOuterUpgradeableReadLock()309 {310 var stub = new LockDerived();311 int onExclusiveLockReleasedAsyncInvocationCount = 0;312 stub.OnExclusiveLockReleasedAsyncDelegate = delegate313 {314 onExclusiveLockReleasedAsyncInvocationCount++;315 return Task.FromResult<object?>(null);316 };317 int onUpgradeableReadLockReleasedInvocationCount = 0;318 stub.OnUpgradeableReadLockReleasedDelegate = delegate319 {320 onUpgradeableReadLockReleasedInvocationCount++;321 };322 this.asyncLock = stub;323 using (await this.asyncLock.UpgradeableReadLockAsync())324 {325 using (await this.asyncLock.UpgradeableReadLockAsync())326 {327 using (await this.asyncLock.WriteLockAsync())328 {329 Assert.Equal(0, onUpgradeableReadLockReleasedInvocationCount);330 Assert.Equal(0, onExclusiveLockReleasedAsyncInvocationCount);331 }332 Assert.Equal(0, onUpgradeableReadLockReleasedInvocationCount);333 Assert.Equal(1, onExclusiveLockReleasedAsyncInvocationCount);334 }335 Assert.Equal(0, onUpgradeableReadLockReleasedInvocationCount);336 Assert.Equal(1, onExclusiveLockReleasedAsyncInvocationCount);337 }338 Assert.Equal(1, onUpgradeableReadLockReleasedInvocationCount);339 Assert.Equal(1, onExclusiveLockReleasedAsyncInvocationCount);340 }341 [Fact]342 public async Task AwaiterInCallContextGetsRecycled()343 {344 await Task.Run(async delegate345 {346 Task remoteTask;347 var firstLockObserved = new TaskCompletionSource<object?>();348 var secondLockAcquired = new TaskCompletionSource<object?>();349 using (await this.asyncLock.ReadLockAsync())350 {351 remoteTask = Task.Run(async delegate352 {353 Assert.True(this.asyncLock.IsReadLockHeld);354 Task? nowait = firstLockObserved.SetAsync();355 await secondLockAcquired.Task;356 Assert.False(this.asyncLock.IsReadLockHeld, "Some remote call context saw a recycled lock issued to someone else.");357 });358 await firstLockObserved.Task;359 }360 using (await this.asyncLock.ReadLockAsync())361 {362 await secondLockAcquired.SetAsync();363 await remoteTask;364 }365 });366 }367 [Fact]368 public async Task AwaiterInCallContextGetsRecycledTwoDeep()369 {370 await Task.Run(async delegate371 {372 Task remoteTask;373 var lockObservedOnce = new TaskCompletionSource<object?>();374 var nestedLockReleased = new TaskCompletionSource<object?>();375 var lockObservedTwice = new TaskCompletionSource<object?>();376 var secondLockAcquired = new TaskCompletionSource<object?>();377 var secondLockNotSeen = new TaskCompletionSource<object?>();378 using (await this.asyncLock.ReadLockAsync())379 {380 using (await this.asyncLock.ReadLockAsync())381 {382 remoteTask = Task.Run(async delegate383 {384 Assert.True(this.asyncLock.IsReadLockHeld);385 Task? nowait = lockObservedOnce.SetAsync();386 await nestedLockReleased.Task;387 Assert.True(this.asyncLock.IsReadLockHeld);388 nowait = lockObservedTwice.SetAsync();389 await secondLockAcquired.Task;390 Assert.False(this.asyncLock.IsReadLockHeld, "Some remote call context saw a recycled lock issued to someone else.");391 Assert.False(this.asyncLock.IsWriteLockHeld, "Some remote call context saw a recycled lock issued to someone else.");392 nowait = secondLockNotSeen.SetAsync();393 });394 await lockObservedOnce.Task;395 }396 Task? nowait2 = nestedLockReleased.SetAsync();397 await lockObservedTwice.Task;398 }399 using (await this.asyncLock.WriteLockAsync())400 {401 Task? nowait = secondLockAcquired.SetAsync();402 await secondLockNotSeen.Task;403 }404 });405 }406 [Fact, Trait("Stress", "true")]407 public async Task LockStress()408 {409 const int MaxLockAcquisitions = -1;410 const int MaxLockHeldDelay = 0; // 80;411 const int overallTimeout = 4000;412 const int iterationTimeout = overallTimeout;413 int maxWorkers = Environment.ProcessorCount * 4; // we do a lot of awaiting, but still want to flood all cores.414 bool testCancellation = false;415 await this.StressHelper(MaxLockAcquisitions, MaxLockHeldDelay, overallTimeout, iterationTimeout, maxWorkers, testCancellation);416 }417 [Fact, Trait("Stress", "true"), Trait("TestCategory", "FailsInCloudTest")]418 public async Task CancellationStress()419 {420 const int MaxLockAcquisitions = -1;421 const int MaxLockHeldDelay = 0; // 80;422 const int overallTimeout = 4000;423 const int iterationTimeout = 100;424 int maxWorkers = Environment.ProcessorCount * 4; // we do a lot of awaiting, but still want to flood all cores.425 bool testCancellation = true;426 await this.StressHelper(MaxLockAcquisitions, MaxLockHeldDelay, overallTimeout, iterationTimeout, maxWorkers, testCancellation);427 }428 /// <summary>Tests that deadlocks don't occur when acquiring and releasing locks synchronously while async callbacks are defined.</summary>429 [Fact]430 public async Task SynchronousLockReleaseWithCallbacks()431 {432 await Task.Run(async delegate433 {434 Func<Task> yieldingDelegate = async () => { await Task.Yield(); };435 var asyncLock = new LockDerived436 {437 OnBeforeExclusiveLockReleasedAsyncDelegate = yieldingDelegate,438 OnBeforeLockReleasedAsyncDelegate = yieldingDelegate,439 OnExclusiveLockReleasedAsyncDelegate = yieldingDelegate,440 };441 using (await asyncLock.WriteLockAsync())442 {443 }444 using (await asyncLock.UpgradeableReadLockAsync())445 {446 using (await asyncLock.WriteLockAsync())447 {448 }449 }450 using (await asyncLock.WriteLockAsync())451 {452 await Task.Yield();453 }454 using (await asyncLock.UpgradeableReadLockAsync())455 {456 using (await asyncLock.WriteLockAsync())457 {458 await Task.Yield();459 }460 }461 });462 }463 [Fact]464 public async Task IsAnyLockHeldTest()465 {466 var asyncLock = new LockDerived();467 Assert.False(asyncLock.IsAnyLockHeld);468 await Task.Run(async delegate469 {470 Assert.False(asyncLock.IsAnyLockHeld);471 using (await asyncLock.ReadLockAsync())472 {473 Assert.True(asyncLock.IsAnyLockHeld);474 }475 Assert.False(asyncLock.IsAnyLockHeld);476 using (await asyncLock.UpgradeableReadLockAsync())477 {478 Assert.True(asyncLock.IsAnyLockHeld);479 }480 Assert.False(asyncLock.IsAnyLockHeld);481 using (await asyncLock.WriteLockAsync())482 {483 Assert.True(asyncLock.IsAnyLockHeld);484 }485 Assert.False(asyncLock.IsAnyLockHeld);486 });487 }488 [Fact]489 public async Task IsAnyLockHeldReturnsFalseForIncompatibleSyncContexts()490 {491 SynchronizationContext? dispatcher = SingleThreadedTestSynchronizationContext.New();492 var asyncLock = new LockDerived();493 using (await asyncLock.ReadLockAsync())494 {495 Assert.True(asyncLock.IsAnyLockHeld);496 SynchronizationContext.SetSynchronizationContext(dispatcher);497 Assert.False(asyncLock.IsAnyLockHeld);498 }499 }500 [Fact]501 public async Task IsAnyPassiveLockHeldReturnsTrueForIncompatibleSyncContexts()502 {503 SynchronizationContext? dispatcher = SingleThreadedTestSynchronizationContext.New();504 var asyncLock = new LockDerived();505 using (await asyncLock.ReadLockAsync())506 {507 Assert.True(asyncLock.IsAnyPassiveLockHeld);508 SynchronizationContext.SetSynchronizationContext(dispatcher);509 Assert.True(asyncLock.IsAnyPassiveLockHeld);510 }511 }512 [Fact]513 public async Task IsPassiveReadLockHeldReturnsTrueForIncompatibleSyncContexts()514 {515 SynchronizationContext? dispatcher = SingleThreadedTestSynchronizationContext.New();516 using (await this.asyncLock.ReadLockAsync())517 {518 Assert.True(this.asyncLock.IsPassiveReadLockHeld);519 SynchronizationContext.SetSynchronizationContext(dispatcher);520 Assert.True(this.asyncLock.IsPassiveReadLockHeld);521 }522 }523 [Fact]524 public async Task IsPassiveUpgradeableReadLockHeldReturnsTrueForIncompatibleSyncContexts()525 {526 SynchronizationContext? dispatcher = SingleThreadedTestSynchronizationContext.New();527 using (await this.asyncLock.UpgradeableReadLockAsync())528 {529 Assert.True(this.asyncLock.IsPassiveUpgradeableReadLockHeld);530 SynchronizationContext.SetSynchronizationContext(dispatcher);531 Assert.True(this.asyncLock.IsPassiveUpgradeableReadLockHeld);532 }533 }534 [Fact]535 public async Task IsPassiveWriteLockHeldReturnsTrueForIncompatibleSyncContexts()536 {537 SynchronizationContext? dispatcher = SingleThreadedTestSynchronizationContext.New();538 await using (await this.asyncLock.WriteLockAsync())539 {540 Assert.True(this.asyncLock.IsPassiveWriteLockHeld);541 SynchronizationContext.SetSynchronizationContext(dispatcher);542 Assert.True(this.asyncLock.IsPassiveWriteLockHeld);543 }544 }545 [Fact]546 public async Task ReadLockAsyncSimple()547 {548 Assert.False(this.asyncLock.IsReadLockHeld);549 using (await this.asyncLock.ReadLockAsync())550 {551 Assert.True(this.asyncLock.IsAnyLockHeld);552 Assert.True(this.asyncLock.IsReadLockHeld);553 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);554 Assert.False(this.asyncLock.IsWriteLockHeld);555 await Task.Yield();556 Assert.True(this.asyncLock.IsAnyLockHeld);557 Assert.True(this.asyncLock.IsReadLockHeld);558 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);559 Assert.False(this.asyncLock.IsWriteLockHeld);560 }561 Assert.False(this.asyncLock.IsReadLockHeld);562 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);563 Assert.False(this.asyncLock.IsWriteLockHeld);564 }565 [Fact]566 public async Task ReadLockNotIssuedToAllThreads()567 {568 var evt = new ManualResetEventSlim(false);569 var otherThread = Task.Run(delegate570 {571 evt.Wait();572 Assert.False(this.asyncLock.IsReadLockHeld);573 });574 using (await this.asyncLock.ReadLockAsync())575 {576 Assert.True(this.asyncLock.IsReadLockHeld);577 evt.Set();578 await otherThread;579 }580 }581 [Fact]582 public async Task ReadLockImplicitSharing()583 {584 using (await this.asyncLock.ReadLockAsync())585 {586 Assert.True(this.asyncLock.IsReadLockHeld);587 await Task.Run(delegate588 {589 Assert.True(this.asyncLock.IsReadLockHeld);590 });591 Assert.True(this.asyncLock.IsReadLockHeld);592 }593 }594 [Fact]595 public async Task ReadLockImplicitSharingCutOffByParent()596 {597 Task subTask;598 var outerLockReleased = new TaskCompletionSource<object?>();599 using (await this.asyncLock.ReadLockAsync())600 {601 Assert.True(this.asyncLock.IsReadLockHeld);602 var subTaskObservedLock = new TaskCompletionSource<object?>();603 subTask = Task.Run(async delegate604 {605 Assert.True(this.asyncLock.IsReadLockHeld);606 await subTaskObservedLock.SetAsync();607 await outerLockReleased.Task;608 Assert.False(this.asyncLock.IsReadLockHeld);609 });610 await subTaskObservedLock.Task;611 }612 Assert.False(this.asyncLock.IsReadLockHeld);613 await outerLockReleased.SetAsync();614 await subTask;615 }616 /// <summary>Verifies that when a thread that already has inherited an implicit lock explicitly requests a lock, that that lock can outlast the parents lock.</summary>617 [Fact]618 public async Task ReadLockImplicitSharingNotCutOffByParentWhenExplicitlyRetained()619 {620 Task subTask;621 var outerLockReleased = new TaskCompletionSource<object?>();622 using (await this.asyncLock.ReadLockAsync())623 {624 Assert.True(this.asyncLock.IsReadLockHeld);625 var subTaskObservedLock = new TaskCompletionSource<object?>();626 subTask = Task.Run(async delegate627 {628 Assert.True(this.asyncLock.IsReadLockHeld);629 using (await this.asyncLock.ReadLockAsync())630 {631 await subTaskObservedLock.SetAsync();632 await outerLockReleased.Task;633 Assert.True(this.asyncLock.IsReadLockHeld);634 }635 Assert.False(this.asyncLock.IsReadLockHeld);636 });637 await subTaskObservedLock.Task;638 }639 Assert.False(this.asyncLock.IsReadLockHeld);640 await outerLockReleased.SetAsync();641 await subTask;642 }643 [Fact]644 public async Task ConcurrentReaders()645 {646 var reader1HasLock = new ManualResetEventSlim();647 var reader2HasLock = new ManualResetEventSlim();648 await Task.WhenAll(649 Task.Run(async delegate650 {651 using (await this.asyncLock.ReadLockAsync())652 {653 reader1HasLock.Set();654 reader2HasLock.Wait(); // synchronous block to ensure multiple *threads* hold lock.655 }656 }),657 Task.Run(async delegate658 {659 using (await this.asyncLock.ReadLockAsync())660 {661 reader2HasLock.Set();662 reader1HasLock.Wait(); // synchronous block to ensure multiple *threads* hold lock.663 }664 }));665 }666 [Fact]667 public async Task NestedReaders()668 {669 using (await this.asyncLock.ReadLockAsync())670 {671 Assert.True(this.asyncLock.IsReadLockHeld);672 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);673 Assert.False(this.asyncLock.IsWriteLockHeld);674 using (await this.asyncLock.ReadLockAsync())675 {676 Assert.True(this.asyncLock.IsReadLockHeld);677 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);678 Assert.False(this.asyncLock.IsWriteLockHeld);679 using (await this.asyncLock.ReadLockAsync())680 {681 Assert.True(this.asyncLock.IsReadLockHeld);682 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);683 Assert.False(this.asyncLock.IsWriteLockHeld);684 }685 Assert.True(this.asyncLock.IsReadLockHeld);686 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);687 Assert.False(this.asyncLock.IsWriteLockHeld);688 }689 Assert.True(this.asyncLock.IsReadLockHeld);690 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);691 Assert.False(this.asyncLock.IsWriteLockHeld);692 }693 Assert.False(this.asyncLock.IsReadLockHeld);694 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);695 Assert.False(this.asyncLock.IsWriteLockHeld);696 }697 [Fact]698 public async Task DoubleLockReleaseDoesNotReleaseOtherLocks()699 {700 var readLockHeld = new TaskCompletionSource<object?>();701 var writerQueued = new TaskCompletionSource<object?>();702 var writeLockHeld = new TaskCompletionSource<object?>();703 await Task.WhenAll(704 Task.Run(async delegate705 {706 using (AsyncReaderWriterLock.Releaser outerReleaser = await this.asyncLock.ReadLockAsync())707 {708 await readLockHeld.SetAsync();709 await writerQueued.Task;710 using (AsyncReaderWriterLock.Releaser innerReleaser = await this.asyncLock.ReadLockAsync())711 {712 innerReleaser.Dispose(); // doing this here will lead to double-disposal at the close of the using block.713 }714 await Task.Delay(AsyncDelay);715 Assert.False(writeLockHeld.Task.IsCompleted);716 }717 }),718 Task.Run(async delegate719 {720 await readLockHeld.Task;721 AsyncReaderWriterLock.Awaiter? writeAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();722 Assert.False(writeAwaiter.IsCompleted);723 writeAwaiter.OnCompleted(delegate724 {725 using (writeAwaiter.GetResult())726 {727 writeLockHeld.SetAsync();728 }729 });730 await writerQueued.SetAsync();731 }),732 writeLockHeld.Task);733 }734 [StaFact]735 public void ReadLockReleaseOnSta()736 {737 this.LockReleaseTestHelper(this.asyncLock.ReadLockAsync());738 }739#if ISOLATED_TEST_SUPPORT740 [Fact, Trait("GC", "true")]741 public async Task UncontestedTopLevelReadLockAsyncGarbageCheck()742 {743 if (await this.ExecuteInIsolationAsync())744 {745 var cts = new CancellationTokenSource();746 await this.UncontestedTopLevelLocksAllocFreeHelperAsync(() => this.asyncLock.ReadLockAsync(cts.Token), false);747 }748 }749 [Fact, Trait("GC", "true")]750 public async Task NestedReadLockAsyncGarbageCheck()751 {752 if (await this.ExecuteInIsolationAsync())753 {754 await this.NestedLocksAllocFreeHelperAsync(() => this.asyncLock.ReadLockAsync(), false);755 }756 }757#endif758 [StaFact]759 public void LockAsyncThrowsOnGetResultBySta()760 {761 Assert.Equal(ApartmentState.STA, Thread.CurrentThread.GetApartmentState()); // STA required.762 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.ReadLockAsync().GetAwaiter();763 Assert.Throws<InvalidOperationException>(() => awaiter.GetResult()); // throws on an STA thread764 }765 [StaFact]766 public void LockAsyncNotIssuedTillGetResultOnSta()767 {768 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.ReadLockAsync().GetAwaiter();769 Assert.False(this.asyncLock.IsReadLockHeld);770 try771 {772 awaiter.GetResult();773 }774 catch (InvalidOperationException)775 {776 // This exception happens because we invoke it on the STA.777 // But we have to invoke it so that the lock is effectively canceled778 // so the test doesn't hang in Cleanup.779 }780 }781 [Fact]782 public async Task LockAsyncNotIssuedTillGetResultOnMta()783 {784 await Task.Run(delegate785 {786 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.ReadLockAsync().GetAwaiter();787 try788 {789 Assert.False(this.asyncLock.IsReadLockHeld);790 }791 finally792 {793 awaiter.GetResult().Dispose();794 }795 });796 }797 [Fact]798 public async Task AllowImplicitReadLockConcurrency()799 {800 using (await this.asyncLock.ReadLockAsync())801 {802 await this.CheckContinuationsConcurrencyHelper();803 }804 }805 [Fact]806 public async Task ReadLockAsyncYieldsIfSyncContextSet()807 {808 await Task.Run(async delegate809 {810 SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());811 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.ReadLockAsync().GetAwaiter();812 try813 {814 Assert.False(awaiter.IsCompleted);815 }816 catch817 {818 awaiter.GetResult().Dispose(); // avoid test hangs on test failure819 throw;820 }821 var lockAcquired = new TaskCompletionSource<object?>();822 awaiter.OnCompleted(delegate823 {824 using (awaiter.GetResult())825 {826 Assert.Null(SynchronizationContext.Current);827 }828 lockAcquired.SetAsync();829 });830 await lockAcquired.Task;831 });832 }833 [Fact]834 public async Task ReadLockAsyncConcurrent()835 {836 var firstReadLockObtained = new TaskCompletionSource<object?>();837 var secondReadLockObtained = new TaskCompletionSource<object?>();838 await Task.WhenAll(839 Task.Run(async delegate840 {841 using (await this.asyncLock.ReadLockAsync())842 {843 Assert.True(this.asyncLock.IsReadLockHeld);844 await firstReadLockObtained.SetAsync();845 Assert.True(this.asyncLock.IsReadLockHeld);846 await secondReadLockObtained.Task;847 }848 Assert.False(this.asyncLock.IsReadLockHeld);849 }),850 Task.Run(async delegate851 {852 await firstReadLockObtained.Task;853 using (await this.asyncLock.ReadLockAsync())854 {855 Assert.True(this.asyncLock.IsReadLockHeld);856 await secondReadLockObtained.SetAsync();857 Assert.True(this.asyncLock.IsReadLockHeld);858 await firstReadLockObtained.Task;859 }860 Assert.False(this.asyncLock.IsReadLockHeld);861 }));862 }863 [Fact]864 public async Task ReadLockAsyncContention()865 {866 var firstLockObtained = new TaskCompletionSource<object?>();867 await Task.WhenAll(868 Task.Run(async delegate869 {870 using (await this.asyncLock.WriteLockAsync())871 {872 Assert.True(this.asyncLock.IsWriteLockHeld);873 Task? nowait = firstLockObtained.SetAsync();874 await Task.Delay(AsyncDelay); // hold it long enough to ensure our other thread blocks waiting for the read lock.875 Assert.True(this.asyncLock.IsWriteLockHeld);876 }877 Assert.False(this.asyncLock.IsWriteLockHeld);878 }),879 Task.Run(async delegate880 {881 await firstLockObtained.Task;882 using (await this.asyncLock.ReadLockAsync())883 {884 Assert.True(this.asyncLock.IsReadLockHeld);885 await Task.Yield();886 Assert.True(this.asyncLock.IsReadLockHeld);887 }888 Assert.False(this.asyncLock.IsReadLockHeld);889 }));890 }891 [Fact]892 public async Task UpgradeableReadLockAsyncNoUpgrade()893 {894 Assert.False(this.asyncLock.IsReadLockHeld);895 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);896 using (await this.asyncLock.UpgradeableReadLockAsync())897 {898 Assert.False(this.asyncLock.IsReadLockHeld);899 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);900 Assert.False(this.asyncLock.IsWriteLockHeld);901 await Task.Yield();902 Assert.False(this.asyncLock.IsReadLockHeld);903 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);904 Assert.False(this.asyncLock.IsWriteLockHeld);905 }906 Assert.False(this.asyncLock.IsReadLockHeld);907 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);908 Assert.False(this.asyncLock.IsWriteLockHeld);909 }910 [Fact]911 public async Task UpgradeReadLockAsync()912 {913 using (await this.asyncLock.UpgradeableReadLockAsync())914 {915 Assert.False(this.asyncLock.IsWriteLockHeld);916 using (await this.asyncLock.WriteLockAsync())917 {918 await Task.Yield();919 Assert.True(this.asyncLock.IsWriteLockHeld);920 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);921 }922 Assert.False(this.asyncLock.IsWriteLockHeld);923 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);924 }925 }926 /// <summary>Verifies that only one upgradeable read lock can be held at once.</summary>927 [Fact]928 public async Task UpgradeReadLockAsyncMutuallyExclusive()929 {930 var firstUpgradeableReadHeld = new TaskCompletionSource<object?>();931 var secondUpgradeableReadBlocked = new TaskCompletionSource<object?>();932 var secondUpgradeableReadHeld = new TaskCompletionSource<object?>();933 await Task.WhenAll(934 Task.Run(async delegate935 {936 using (await this.asyncLock.UpgradeableReadLockAsync())937 {938 await firstUpgradeableReadHeld.SetAsync();939 await secondUpgradeableReadBlocked.Task;940 }941 }),942 Task.Run(async delegate943 {944 await firstUpgradeableReadHeld.Task;945 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.UpgradeableReadLockAsync().GetAwaiter();946 Assert.False(awaiter.IsCompleted, "Second upgradeable read lock issued while first is still held.");947 awaiter.OnCompleted(delegate948 {949 using (awaiter.GetResult())950 {951 secondUpgradeableReadHeld.SetAsync();952 }953 });954 await secondUpgradeableReadBlocked.SetAsync();955 }),956 secondUpgradeableReadHeld.Task);957 }958 [Fact]959 public async Task UpgradeableReadLockAsyncWithStickyWrite()960 {961 using (await this.asyncLock.UpgradeableReadLockAsync(AsyncReaderWriterLock.LockFlags.StickyWrite))962 {963 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);964 Assert.False(this.asyncLock.IsWriteLockHeld);965 using (await this.asyncLock.WriteLockAsync())966 {967 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);968 Assert.True(this.asyncLock.IsWriteLockHeld);969 }970 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);971 Assert.True(this.asyncLock.IsWriteLockHeld, "StickyWrite flag did not retain the write lock.");972 using (await this.asyncLock.WriteLockAsync())973 {974 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);975 Assert.True(this.asyncLock.IsWriteLockHeld);976 using (await this.asyncLock.WriteLockAsync())977 {978 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);979 Assert.True(this.asyncLock.IsWriteLockHeld);980 }981 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);982 Assert.True(this.asyncLock.IsWriteLockHeld);983 }984 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);985 Assert.True(this.asyncLock.IsWriteLockHeld, "StickyWrite flag did not retain the write lock.");986 }987 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);988 Assert.False(this.asyncLock.IsWriteLockHeld);989 }990 [StaFact]991 public void UpgradeableReadLockAsyncReleaseOnSta()992 {993 this.LockReleaseTestHelper(this.asyncLock.UpgradeableReadLockAsync());994 }995#if ISOLATED_TEST_SUPPORT996 [Fact, Trait("GC", "true")]997 public async Task UncontestedTopLevelUpgradeableReadLockAsyncGarbageCheck()998 {999 if (await this.ExecuteInIsolationAsync())1000 {1001 var cts = new CancellationTokenSource();1002 await this.UncontestedTopLevelLocksAllocFreeHelperAsync(() => this.asyncLock.UpgradeableReadLockAsync(cts.Token), true);1003 }1004 }1005 [Fact, Trait("GC", "true")]1006 public async Task NestedUpgradeableReadLockAsyncGarbageCheck()1007 {1008 if (await this.ExecuteInIsolationAsync())1009 {1010 await this.NestedLocksAllocFreeHelperAsync(() => this.asyncLock.UpgradeableReadLockAsync(), true);1011 }1012 }1013#endif1014 [Fact]1015 public async Task ExclusiveLockReleasedEventsFireOnlyWhenWriteLockReleased()1016 {1017 var asyncLock = new LockDerived();1018 int onBeforeReleaseInvocations = 0;1019 int onReleaseInvocations = 0;1020 asyncLock.OnBeforeExclusiveLockReleasedAsyncDelegate = delegate1021 {1022 onBeforeReleaseInvocations++;1023 return Task.FromResult<object?>(null);1024 };1025 asyncLock.OnExclusiveLockReleasedAsyncDelegate = delegate1026 {1027 onReleaseInvocations++;1028 return Task.FromResult<object?>(null);1029 };1030 using (await asyncLock.WriteLockAsync())1031 {1032 using (await asyncLock.UpgradeableReadLockAsync())1033 {1034 }1035 Assert.Equal(0, onBeforeReleaseInvocations);1036 Assert.Equal(0, onReleaseInvocations);1037 using (await asyncLock.ReadLockAsync())1038 {1039 }1040 Assert.Equal(0, onBeforeReleaseInvocations);1041 Assert.Equal(0, onReleaseInvocations);1042 }1043 Assert.Equal(1, onBeforeReleaseInvocations);1044 Assert.Equal(1, onReleaseInvocations);1045 }1046 [Fact]1047 public async Task ExclusiveLockReleasedEventsFireOnlyWhenWriteLockReleasedWithinUpgradeableRead()1048 {1049 var asyncLock = new LockDerived();1050 int onBeforeReleaseInvocations = 0;1051 int onReleaseInvocations = 0;1052 asyncLock.OnBeforeExclusiveLockReleasedAsyncDelegate = delegate1053 {1054 onBeforeReleaseInvocations++;1055 return Task.FromResult<object?>(null);1056 };1057 asyncLock.OnExclusiveLockReleasedAsyncDelegate = delegate1058 {1059 onReleaseInvocations++;1060 return Task.FromResult<object?>(null);1061 };1062 using (await asyncLock.UpgradeableReadLockAsync())1063 {1064 using (await asyncLock.UpgradeableReadLockAsync())1065 {1066 }1067 Assert.Equal(0, onBeforeReleaseInvocations);1068 Assert.Equal(0, onReleaseInvocations);1069 using (await asyncLock.WriteLockAsync())1070 {1071 }1072 Assert.Equal(1, onBeforeReleaseInvocations);1073 Assert.Equal(1, onReleaseInvocations);1074 }1075 Assert.Equal(1, onBeforeReleaseInvocations);1076 Assert.Equal(1, onReleaseInvocations);1077 }1078 [Fact]1079 public async Task ExclusiveLockReleasedEventsFireOnlyWhenStickyUpgradedLockReleased()1080 {1081 var asyncLock = new LockDerived();1082 int onBeforeReleaseInvocations = 0;1083 int onReleaseInvocations = 0;1084 asyncLock.OnBeforeExclusiveLockReleasedAsyncDelegate = delegate1085 {1086 onBeforeReleaseInvocations++;1087 return Task.FromResult<object?>(null);1088 };1089 asyncLock.OnExclusiveLockReleasedAsyncDelegate = delegate1090 {1091 onReleaseInvocations++;1092 return Task.FromResult<object?>(null);1093 };1094 using (await asyncLock.UpgradeableReadLockAsync(AsyncReaderWriterLock.LockFlags.StickyWrite))1095 {1096 using (await asyncLock.UpgradeableReadLockAsync())1097 {1098 }1099 Assert.Equal(0, onBeforeReleaseInvocations);1100 Assert.Equal(0, onReleaseInvocations);1101 using (await asyncLock.WriteLockAsync())1102 {1103 }1104 Assert.Equal(0, onBeforeReleaseInvocations);1105 Assert.Equal(0, onReleaseInvocations);1106 }1107 Assert.Equal(1, onBeforeReleaseInvocations);1108 Assert.Equal(1, onReleaseInvocations);1109 }1110 [Fact]1111 public async Task OnExclusiveLockReleasedAsyncAcquiresProjectLock()1112 {1113 var innerLockReleased = new AsyncManualResetEvent();1114 var onExclusiveLockReleasedBegun = new AsyncManualResetEvent();1115 var asyncLock = new LockDerived();1116 asyncLock.OnBeforeExclusiveLockReleasedAsyncDelegate = async delegate1117 {1118 using (AsyncReaderWriterLock.Releaser innerReleaser = await asyncLock.WriteLockAsync())1119 {1120 await Task.WhenAny(onExclusiveLockReleasedBegun.WaitAsync(), Task.Delay(AsyncDelay));1121 await innerReleaser.ReleaseAsync();1122 innerLockReleased.Set();1123 }1124 };1125 asyncLock.OnExclusiveLockReleasedAsyncDelegate = async delegate1126 {1127 onExclusiveLockReleasedBegun.Set();1128 await innerLockReleased;1129 };1130 await using (await asyncLock.WriteLockAsync())1131 {1132 }1133 }1134 [Fact]1135 public async Task MitigationAgainstAccidentalUpgradeableReadLockConcurrency()1136 {1137 using (await this.asyncLock.UpgradeableReadLockAsync())1138 {1139 await this.CheckContinuationsConcurrencyHelper();1140 }1141 }1142 [Fact]1143 public async Task MitigationAgainstAccidentalUpgradeableReadLockConcurrencyBeforeFirstYieldSTA()1144 {1145 using (await this.asyncLock.UpgradeableReadLockAsync())1146 {1147 await this.CheckContinuationsConcurrencyBeforeYieldHelper();1148 }1149 }1150 [Fact]1151 public void MitigationAgainstAccidentalUpgradeableReadLockConcurrencyBeforeFirstYieldMTA()1152 {1153 Task.Run(async delegate1154 {1155 using (await this.asyncLock.UpgradeableReadLockAsync())1156 {1157 await this.CheckContinuationsConcurrencyBeforeYieldHelper();1158 }1159 }).GetAwaiter().GetResult();1160 }1161 [Fact]1162 public async Task UpgradeableReadLockAsyncYieldsIfSyncContextSet()1163 {1164 await Task.Run(async delegate1165 {1166 SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());1167 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.UpgradeableReadLockAsync().GetAwaiter();1168 try1169 {1170 Assert.False(awaiter.IsCompleted);1171 }1172 catch1173 {1174 awaiter.GetResult().Dispose(); // avoid test hangs on test failure1175 throw;1176 }1177 var lockAcquired = new TaskCompletionSource<object?>();1178 awaiter.OnCompleted(delegate1179 {1180 using (awaiter.GetResult())1181 {1182 }1183 lockAcquired.SetAsync();1184 });1185 await lockAcquired.Task;1186 });1187 }1188 /// <summary>1189 /// Tests that a common way to accidentally fork an exclusive lock for1190 /// concurrent access gets called out as an error.1191 /// </summary>1192 /// <remarks>1193 /// Test ignored because the tested behavior is incompatible with the1194 /// <see cref="UpgradeableReadLockTraversesAcrossSta"/> and <see cref="WriteLockTraversesAcrossSta"/> tests,1195 /// which are deemed more important.1196 /// </remarks>1197 [Fact(Skip = "Ignored")]1198 public async Task MitigationAgainstAccidentalUpgradeableReadLockForking()1199 {1200 await this.MitigationAgainstAccidentalLockForkingHelper(1201 () => this.asyncLock.UpgradeableReadLockAsync());1202 }1203 [Fact]1204 public async Task UpgradeableReadLockAsyncSimple()1205 {1206 // Get onto an MTA thread so that a lock may be synchronously granted.1207 await Task.Run(async delegate1208 {1209 using (await this.asyncLock.UpgradeableReadLockAsync())1210 {1211 Assert.True(this.asyncLock.IsAnyLockHeld);1212 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);1213 await Task.Yield();1214 Assert.True(this.asyncLock.IsAnyLockHeld);1215 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);1216 }1217 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);1218 using (await this.asyncLock.UpgradeableReadLockAsync(AsyncReaderWriterLock.LockFlags.None))1219 {1220 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);1221 await Task.Yield();1222 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);1223 }1224 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);1225 });1226 }1227 [Fact]1228 public async Task UpgradeableReadLockAsyncContention()1229 {1230 var firstLockObtained = new TaskCompletionSource<object?>();1231 await Task.WhenAll(1232 Task.Run(async delegate1233 {1234 using (await this.asyncLock.WriteLockAsync())1235 {1236 Assert.True(this.asyncLock.IsWriteLockHeld);1237 Task? nowait = firstLockObtained.SetAsync();1238 await Task.Delay(AsyncDelay); // hold it long enough to ensure our other thread blocks waiting for the read lock.1239 Assert.True(this.asyncLock.IsWriteLockHeld);1240 }1241 Assert.False(this.asyncLock.IsWriteLockHeld);1242 }),1243 Task.Run(async delegate1244 {1245 await firstLockObtained.Task;1246 using (await this.asyncLock.UpgradeableReadLockAsync())1247 {1248 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);1249 await Task.Yield();1250 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);1251 }1252 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);1253 }));1254 }1255 [Fact]1256 public void ReleasingUpgradeableReadLockAsyncSynchronouslyClearsSyncContext()1257 {1258 Task.Run(async delegate1259 {1260 Assert.Null(SynchronizationContext.Current);1261 using (await this.asyncLock.UpgradeableReadLockAsync())1262 {1263 Assert.NotNull(SynchronizationContext.Current);1264 }1265 Assert.Null(SynchronizationContext.Current);1266 }).GetAwaiter().GetResult();1267 }1268 [Fact, Trait("TestCategory", "FailsInCloudTest")]1269 public void UpgradeableReadLockAsyncSynchronousReleaseAllowsOtherUpgradeableReaders()1270 {1271 var testComplete = new ManualResetEventSlim(); // deliberately synchronous1272 var firstLockReleased = new AsyncManualResetEvent();1273 var firstLockTask = Task.Run(async delegate1274 {1275 using (await this.asyncLock.UpgradeableReadLockAsync())1276 {1277 }1278 // Synchronously block until the test is complete.1279 firstLockReleased.Set();1280 Assert.True(testComplete.Wait(AsyncDelay));1281 });1282 var secondLockTask = Task.Run(async delegate1283 {1284 await firstLockReleased;1285 using (await this.asyncLock.UpgradeableReadLockAsync())1286 {1287 }1288 });1289 Assert.True(secondLockTask.Wait(TestTimeout));1290 testComplete.Set();1291 Assert.True(firstLockTask.Wait(TestTimeout)); // rethrow any exceptions1292 }1293 [Fact]1294 public async Task WriteLockAsync()1295 {1296 Assert.False(this.asyncLock.IsWriteLockHeld);1297 using (await this.asyncLock.WriteLockAsync())1298 {1299 Assert.False(this.asyncLock.IsReadLockHeld);1300 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);1301 Assert.True(this.asyncLock.IsWriteLockHeld);1302 await Task.Yield();1303 Assert.False(this.asyncLock.IsReadLockHeld);1304 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);1305 Assert.True(this.asyncLock.IsWriteLockHeld);1306 }1307 Assert.False(this.asyncLock.IsReadLockHeld);1308 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);1309 Assert.False(this.asyncLock.IsWriteLockHeld);1310 }1311 [StaFact]1312 public void WriteLockAsyncReleaseOnSta()1313 {1314 this.LockReleaseTestHelper(this.asyncLock.WriteLockAsync());1315 }1316 [Fact]1317 public async Task WriteLockAsyncWhileHoldingUpgradeableReadLockContestedByActiveReader()1318 {1319 var upgradeableLockAcquired = new TaskCompletionSource<object?>();1320 var readLockAcquired = new TaskCompletionSource<object?>();1321 var writeLockRequested = new TaskCompletionSource<object?>();1322 var writeLockAcquired = new TaskCompletionSource<object?>();1323 await Task.WhenAll(1324 Task.Run(async delegate1325 {1326 using (await this.asyncLock.UpgradeableReadLockAsync())1327 {1328 Task? nowait = upgradeableLockAcquired.SetAsync();1329 await readLockAcquired.Task;1330 AsyncReaderWriterLock.Awaiter? upgradeAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();1331 Assert.False(upgradeAwaiter.IsCompleted); // contested lock should not be immediately available.1332 upgradeAwaiter.OnCompleted(delegate1333 {1334 using (upgradeAwaiter.GetResult())1335 {1336 writeLockAcquired.SetAsync();1337 }1338 });1339 nowait = writeLockRequested.SetAsync();1340 await writeLockAcquired.Task;1341 }1342 }),1343 Task.Run(async delegate1344 {1345 await upgradeableLockAcquired.Task;1346 using (await this.asyncLock.ReadLockAsync())1347 {1348 Task? nowait = readLockAcquired.SetAsync();1349 await writeLockRequested.Task;1350 }1351 }));1352 }1353 [Fact]1354 public async Task WriteLockAsyncWhileHoldingUpgradeableReadLockContestedByWaitingWriter()1355 {1356 var upgradeableLockAcquired = new TaskCompletionSource<object?>();1357 var contendingWriteLockRequested = new TaskCompletionSource<object?>();1358 await Task.WhenAll(1359 Task.Run(async delegate1360 {1361 using (await this.asyncLock.UpgradeableReadLockAsync())1362 {1363 Task? nowait = upgradeableLockAcquired.SetAsync();1364 await contendingWriteLockRequested.Task;1365 AsyncReaderWriterLock.Awaiter? upgradeAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();1366 Assert.True(upgradeAwaiter.IsCompleted); // the waiting writer should not have priority of this one.1367 upgradeAwaiter.GetResult().Dispose(); // accept and release the lock.1368 }1369 }),1370 Task.Run(async delegate1371 {1372 await upgradeableLockAcquired.Task;1373 AsyncReaderWriterLock.Awaiter? writeAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();1374 Assert.False(writeAwaiter.IsCompleted);1375 var contestingWriteLockAcquired = new TaskCompletionSource<object?>();1376 writeAwaiter.OnCompleted(delegate1377 {1378 using (writeAwaiter.GetResult())1379 {1380 contestingWriteLockAcquired.SetAsync();1381 }1382 });1383 Task? nowait = contendingWriteLockRequested.SetAsync();1384 await contestingWriteLockAcquired.Task;1385 }));1386 }1387 [Fact]1388 public async Task WriteLockAsyncWhileHoldingUpgradeableReadLockContestedByActiveReaderAndWaitingWriter()1389 {1390 var upgradeableLockAcquired = new TaskCompletionSource<object?>();1391 var readLockAcquired = new TaskCompletionSource<object?>();1392 var contendingWriteLockRequested = new TaskCompletionSource<object?>();1393 var writeLockRequested = new TaskCompletionSource<object?>();1394 var writeLockAcquired = new TaskCompletionSource<object?>();1395 await Task.WhenAll(1396 Task.Run(async delegate1397 {1398 this.Logger.WriteLine("Task 1: Requesting an upgradeable read lock.");1399 using (await this.asyncLock.UpgradeableReadLockAsync())1400 {1401 this.Logger.WriteLine("Task 1: Acquired an upgradeable read lock.");1402 Task? nowait = upgradeableLockAcquired.SetAsync();1403 await Task.WhenAll(readLockAcquired.Task, contendingWriteLockRequested.Task);1404 this.Logger.WriteLine("Task 1: Requesting a write lock.");1405 AsyncReaderWriterLock.Awaiter? upgradeAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();1406 this.Logger.WriteLine("Task 1: Write lock requested.");1407 Assert.False(upgradeAwaiter.IsCompleted); // contested lock should not be immediately available.1408 upgradeAwaiter.OnCompleted(delegate1409 {1410 using (upgradeAwaiter.GetResult())1411 {1412 this.Logger.WriteLine("Task 1: Write lock acquired.");1413 writeLockAcquired.SetAsync();1414 }1415 });1416 nowait = writeLockRequested.SetAsync();1417 this.Logger.WriteLine("Task 1: Waiting for write lock acquisition.");1418 await writeLockAcquired.Task;1419 this.Logger.WriteLine("Task 1: Write lock acquisition complete. Exiting task 1.");1420 }1421 }),1422 Task.Run(async delegate1423 {1424 this.Logger.WriteLine("Task 2: Waiting for upgradeable read lock acquisition in task 1.");1425 await upgradeableLockAcquired.Task;1426 this.Logger.WriteLine("Task 2: Requesting read lock.");1427 using (await this.asyncLock.ReadLockAsync())1428 {1429 this.Logger.WriteLine("Task 2: Acquired read lock.");1430 Task? nowait = readLockAcquired.SetAsync();1431 this.Logger.WriteLine("Task 2: Awaiting write lock request by task 1.");1432 await writeLockRequested.Task;1433 this.Logger.WriteLine("Task 2: Releasing read lock.");1434 }1435 this.Logger.WriteLine("Task 2: Released read lock.");1436 }),1437 Task.Run(async delegate1438 {1439 this.Logger.WriteLine("Task 3: Waiting for upgradeable read lock acquisition in task 1 and read lock acquisition in task 2.");1440 await Task.WhenAll(upgradeableLockAcquired.Task, readLockAcquired.Task);1441 this.Logger.WriteLine("Task 3: Requesting write lock.");1442 AsyncReaderWriterLock.Awaiter? writeAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();1443 this.Logger.WriteLine("Task 3: Write lock requested.");1444 Assert.False(writeAwaiter.IsCompleted);1445 var contestingWriteLockAcquired = new TaskCompletionSource<object?>();1446 writeAwaiter.OnCompleted(delegate1447 {1448 using (writeAwaiter.GetResult())1449 {1450 this.Logger.WriteLine("Task 3: Write lock acquired.");1451 contestingWriteLockAcquired.SetAsync();1452 this.Logger.WriteLine("Task 3: Releasing write lock.");1453 }1454 this.Logger.WriteLine("Task 3: Write lock released.");1455 });1456 Task? nowait = contendingWriteLockRequested.SetAsync();1457 this.Logger.WriteLine("Task 3: Awaiting write lock acquisition.");1458 await contestingWriteLockAcquired.Task;1459 this.Logger.WriteLine("Task 3: Write lock acquisition complete.");1460 }));1461 }1462#if ISOLATED_TEST_SUPPORT1463 [Fact, Trait("GC", "true")]1464 public async Task UncontestedTopLevelWriteLockAsyncGarbageCheck()1465 {1466 if (await this.ExecuteInIsolationAsync())1467 {1468 var cts = new CancellationTokenSource();1469 await this.UncontestedTopLevelLocksAllocFreeHelperAsync(() => this.asyncLock.WriteLockAsync(cts.Token), true);1470 }1471 }1472 [Fact, Trait("GC", "true")]1473 public async Task NestedWriteLockAsyncGarbageCheck()1474 {1475 if (await this.ExecuteInIsolationAsync())1476 {1477 await this.NestedLocksAllocFreeHelperAsync(() => this.asyncLock.WriteLockAsync(), true);1478 }1479 }1480#endif1481 [Fact]1482 public async Task MitigationAgainstAccidentalWriteLockConcurrency()1483 {1484 using (await this.asyncLock.WriteLockAsync())1485 {1486 await this.CheckContinuationsConcurrencyHelper();1487 }1488 }1489 [Fact]1490 public async Task MitigationAgainstAccidentalWriteLockConcurrencyBeforeFirstYieldSTA()1491 {1492 using (await this.asyncLock.WriteLockAsync())1493 {1494 await this.CheckContinuationsConcurrencyBeforeYieldHelper();1495 }1496 }1497 [Fact]1498 public void MitigationAgainstAccidentalWriteLockConcurrencyBeforeFirstYieldMTA()1499 {1500 Task.Run(async delegate1501 {1502 using (await this.asyncLock.WriteLockAsync())1503 {1504 await this.CheckContinuationsConcurrencyBeforeYieldHelper();1505 }1506 }).GetAwaiter().GetResult();1507 }1508 /// <summary>1509 /// Tests that a common way to accidentally fork an exclusive lock for1510 /// concurrent access gets called out as an error.1511 /// </summary>1512 /// <remarks>1513 /// Test ignored because the tested behavior is incompatible with the1514 /// <see cref="UpgradeableReadLockTraversesAcrossSta"/> and <see cref="WriteLockTraversesAcrossSta"/> tests,1515 /// which are deemed more important.1516 /// </remarks>1517 [Fact(Skip = "Ignored")]1518 public async Task MitigationAgainstAccidentalWriteLockForking()1519 {1520 await this.MitigationAgainstAccidentalLockForkingHelper(1521 () => this.asyncLock.WriteLockAsync());1522 }1523 [Fact]1524 public async Task WriteLockAsyncYieldsIfSyncContextSet()1525 {1526 await Task.Run(async delegate1527 {1528 SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());1529 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.WriteLockAsync().GetAwaiter();1530 try1531 {1532 Assert.False(awaiter.IsCompleted);1533 }1534 catch1535 {1536 awaiter.GetResult().Dispose(); // avoid test hangs on test failure1537 throw;1538 }1539 var lockAcquired = new TaskCompletionSource<object?>();1540 awaiter.OnCompleted(delegate1541 {1542 using (awaiter.GetResult())1543 {1544 }1545 lockAcquired.SetAsync();1546 });1547 await lockAcquired.Task;1548 });1549 }1550 [Fact]1551 public void ReleasingWriteLockAsyncSynchronouslyClearsSyncContext()1552 {1553 Task.Run(async delegate1554 {1555 Assert.Null(SynchronizationContext.Current);1556 using (await this.asyncLock.WriteLockAsync())1557 {1558 Assert.NotNull(SynchronizationContext.Current);1559 }1560 Assert.Null(SynchronizationContext.Current);1561 }).GetAwaiter().GetResult();1562 }1563 [Fact]1564 public void WriteLockAsyncSynchronousReleaseAllowsOtherWriters()1565 {1566 var testComplete = new ManualResetEventSlim(); // deliberately synchronous1567 var firstLockReleased = new AsyncManualResetEvent();1568 var firstLockTask = Task.Run(async delegate1569 {1570 using (await this.asyncLock.WriteLockAsync())1571 {1572 }1573 // Synchronously block until the test is complete.1574 firstLockReleased.Set();1575 Assert.True(testComplete.Wait(UnexpectedTimeout));1576 });1577 var secondLockTask = Task.Run(async delegate1578 {1579 await firstLockReleased;1580 using (await this.asyncLock.WriteLockAsync())1581 {1582 }1583 });1584 Assert.True(secondLockTask.Wait(TestTimeout));1585 testComplete.Set();1586 Assert.True(firstLockTask.Wait(TestTimeout)); // rethrow any exceptions1587 }1588 /// <summary>1589 /// Test to verify that we don't block the code to dispose a write lock, when it has been released, and a new write lock was issued right between Release and Dispose.1590 /// That happens in the original implementation, because it shares a same NonConcurrentSynchronizationContext, so a new write lock can take over it, and block the original lock task1591 /// to resume back to the context.1592 /// </summary>1593 [Fact]1594 public void WriteLockDisposingShouldNotBlockByOtherWriters()1595 {1596 var firstLockToRelease = new AsyncManualResetEvent();1597 var firstLockAccquired = new AsyncManualResetEvent();1598 var firstLockToDispose = new AsyncManualResetEvent();1599 var firstLockTask = Task.Run(async delegate1600 {1601 using (AsyncReaderWriterLock.Releaser firstLock = await this.asyncLock.WriteLockAsync())1602 {1603 firstLockAccquired.Set();1604 await firstLockToRelease.WaitAsync();1605 await firstLock.ReleaseAsync();1606 // Wait for the second lock to be issued1607 await firstLockToDispose.WaitAsync();1608 }1609 });1610 var secondLockReleased = new TaskCompletionSource<int>();1611 var secondLockTask = Task.Run(async delegate1612 {1613 await firstLockAccquired.WaitAsync();1614 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.WriteLockAsync().GetAwaiter();1615 Assert.False(awaiter.IsCompleted);1616 awaiter.OnCompleted(() =>1617 {1618 try1619 {1620 using (AsyncReaderWriterLock.Releaser access = awaiter.GetResult())1621 {1622 firstLockToDispose.Set();1623 // We must block the thread synchronously, so it won't release the NonConcurrentSynchronizationContext until the first lock is completely disposed.1624 firstLockTask.Wait(TestTimeout * 2);1625 }1626 }1627 catch (Exception ex)1628 {1629 secondLockReleased.TrySetException(ex);1630 }1631 secondLockReleased.TrySetResult(0);1632 });1633 firstLockToRelease.Set();1634 // clean up logic1635 await firstLockTask;1636 await secondLockReleased.Task;1637 });1638 Assert.True(secondLockTask.Wait(TestTimeout)); // rethrow any exceptions1639 Assert.False(secondLockReleased.Task.IsFaulted);1640 }1641 [Fact]1642 public async Task WriteLockAsyncSimple()1643 {1644 // Get onto an MTA thread so that a lock may be synchronously granted.1645 await Task.Run(async delegate1646 {1647 using (await this.asyncLock.WriteLockAsync())1648 {1649 Assert.True(this.asyncLock.IsAnyLockHeld);1650 Assert.True(this.asyncLock.IsWriteLockHeld);1651 await Task.Yield();1652 Assert.True(this.asyncLock.IsAnyLockHeld);1653 Assert.True(this.asyncLock.IsWriteLockHeld);1654 }1655 Assert.False(this.asyncLock.IsWriteLockHeld);1656 using (await this.asyncLock.WriteLockAsync(AsyncReaderWriterLock.LockFlags.None))1657 {1658 Assert.True(this.asyncLock.IsWriteLockHeld);1659 await Task.Yield();1660 Assert.True(this.asyncLock.IsWriteLockHeld);1661 }1662 Assert.False(this.asyncLock.IsWriteLockHeld);1663 });1664 }1665 [Fact]1666 public async Task WriteLockAsyncContention()1667 {1668 var firstLockObtained = new TaskCompletionSource<object?>();1669 await Task.WhenAll(1670 Task.Run(async delegate1671 {1672 using (await this.asyncLock.WriteLockAsync())1673 {1674 Assert.True(this.asyncLock.IsWriteLockHeld);1675 Task? nowait = firstLockObtained.SetAsync();1676 await Task.Delay(AsyncDelay); // hold it long enough to ensure our other thread blocks waiting for the read lock.1677 Assert.True(this.asyncLock.IsWriteLockHeld);1678 }1679 Assert.False(this.asyncLock.IsWriteLockHeld);1680 }),1681 Task.Run(async delegate1682 {1683 await firstLockObtained.Task;1684 using (await this.asyncLock.WriteLockAsync())1685 {1686 Assert.True(this.asyncLock.IsWriteLockHeld);1687 await Task.Yield();1688 Assert.True(this.asyncLock.IsWriteLockHeld);1689 }1690 Assert.False(this.asyncLock.IsWriteLockHeld);1691 }));1692 }1693 /// <summary>Verifies that reads and upgradeable reads can run concurrently.</summary>1694 [Fact]1695 public async Task UpgradeableReadAvailableWithExistingReaders()1696 {1697 var readerHasLock = new TaskCompletionSource<object?>();1698 var upgradeableReaderHasLock = new TaskCompletionSource<object?>();1699 await Task.WhenAll(1700 Task.Run(async delegate1701 {1702 using (await this.asyncLock.ReadLockAsync())1703 {1704 await readerHasLock.SetAsync();1705 await upgradeableReaderHasLock.Task;1706 }1707 }),1708 Task.Run(async delegate1709 {1710 await readerHasLock.Task;1711 using (await this.asyncLock.UpgradeableReadLockAsync())1712 {1713 await upgradeableReaderHasLock.SetAsync();1714 }1715 }));1716 }1717 /// <summary>Verifies that reads and upgradeable reads can run concurrently.</summary>1718 [Fact]1719 public async Task ReadAvailableWithExistingUpgradeableReader()1720 {1721 var readerHasLock = new TaskCompletionSource<object?>();1722 var upgradeableReaderHasLock = new TaskCompletionSource<object?>();1723 await Task.WhenAll(1724 Task.Run(async delegate1725 {1726 await upgradeableReaderHasLock.Task;1727 using (await this.asyncLock.ReadLockAsync())1728 {1729 await readerHasLock.SetAsync();1730 }1731 }),1732 Task.Run(async delegate1733 {1734 using (await this.asyncLock.UpgradeableReadLockAsync())1735 {1736 await upgradeableReaderHasLock.SetAsync();1737 await readerHasLock.Task;1738 }1739 }));1740 }1741 /// <summary>Verifies that an upgradeable reader can obtain write access even while a writer is waiting for a lock.</summary>1742 [Fact]1743 public async Task UpgradeableReaderCanUpgradeWhileWriteRequestWaiting()1744 {1745 var upgradeableReadHeld = new TaskCompletionSource<object?>();1746 var upgradeableReadUpgraded = new TaskCompletionSource<object?>();1747 var writeRequestPending = new TaskCompletionSource<object?>();1748 var writeLockObtained = new TaskCompletionSource<object?>();1749 await Task.WhenAll(1750 Task.Run(async delegate1751 {1752 using (await this.asyncLock.UpgradeableReadLockAsync())1753 {1754 await upgradeableReadHeld.SetAsync();1755 await writeRequestPending.Task;1756 using (await this.asyncLock.WriteLockAsync())1757 {1758 Assert.False(writeLockObtained.Task.IsCompleted, "The upgradeable read should have received its write lock first.");1759 this.PrintHangReport();1760 await upgradeableReadUpgraded.SetAsync();1761 }1762 }1763 }),1764 Task.Run(async delegate1765 {1766 await upgradeableReadHeld.Task;1767 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.WriteLockAsync().GetAwaiter();1768 Assert.False(awaiter.IsCompleted, "We shouldn't get a write lock when an upgradeable read is held.");1769 awaiter.OnCompleted(delegate1770 {1771 using (AsyncReaderWriterLock.Releaser releaser = awaiter.GetResult())1772 {1773 writeLockObtained.SetAsync();1774 }1775 });1776 await writeRequestPending.SetAsync();1777 await writeLockObtained.Task;1778 }));1779 }1780 /// <summary>Verifies that an upgradeable reader blocks for upgrade while other readers release their locks.</summary>1781 [Fact]1782 public async Task UpgradeableReaderWaitsForExistingReadersToExit()1783 {1784 var readerHasLock = new TaskCompletionSource<object?>();1785 var upgradeableReaderWaitingForUpgrade = new TaskCompletionSource<object?>();1786 var upgradeableReaderHasUpgraded = new TaskCompletionSource<object?>();1787 var writeLockHasReleased = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);1788 await Task.WhenAll(1789 Task.Run(async delegate1790 {1791 using (await this.asyncLock.UpgradeableReadLockAsync())1792 {1793 await readerHasLock.Task;1794 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.WriteLockAsync().GetAwaiter();1795 Assert.False(awaiter.IsCompleted, "The upgradeable read lock should not be upgraded while readers still have locks.");1796 awaiter.OnCompleted(delegate1797 {1798 using (awaiter.GetResult())1799 {1800 upgradeableReaderHasUpgraded.SetAsync();1801 }1802 writeLockHasReleased.SetResult(null);1803 });1804 Assert.False(upgradeableReaderHasUpgraded.Task.IsCompleted);1805 await upgradeableReaderWaitingForUpgrade.SetAsync();1806 await writeLockHasReleased.Task;1807 }1808 }),1809 Task.Run(async delegate1810 {1811 using (await this.asyncLock.ReadLockAsync())1812 {1813 await readerHasLock.SetAsync();1814 await upgradeableReaderWaitingForUpgrade.Task;1815 }1816 }),1817 upgradeableReaderHasUpgraded.Task);1818 }1819 /// <summary>Verifies that read lock requests are not serviced until any writers have released their locks.</summary>1820 [Fact]1821 public async Task ReadersWaitForWriter()1822 {1823 var readerHasLock = new TaskCompletionSource<object?>();1824 var writerHasLock = new TaskCompletionSource<object?>();1825 await Task.WhenAll(1826 Task.Run(async delegate1827 {1828 await writerHasLock.Task;1829 using (await this.asyncLock.ReadLockAsync())1830 {1831 await readerHasLock.SetAsync();1832 }1833 }),1834 Task.Run(async delegate1835 {1836 using (await this.asyncLock.WriteLockAsync())1837 {1838 await writerHasLock.SetAsync();1839 await Task.Delay(AsyncDelay);1840 Assert.False(readerHasLock.Task.IsCompleted, "Reader was issued lock while writer still had lock.");1841 }1842 }));1843 }1844 /// <summary>Verifies that write lock requests are not serviced until all existing readers have released their locks.</summary>1845 [Fact]1846 public async Task WriterWaitsForReaders()1847 {1848 var readerHasLock = new TaskCompletionSource<object?>();1849 var writerHasLock = new TaskCompletionSource<object?>();1850 await Task.WhenAll(1851 Task.Run(async delegate1852 {1853 using (await this.asyncLock.ReadLockAsync())1854 {1855 await readerHasLock.SetAsync();1856 await Task.Delay(AsyncDelay);1857 Assert.False(writerHasLock.Task.IsCompleted, "Writer was issued lock while reader still had lock.");1858 }1859 }),1860 Task.Run(async delegate1861 {1862 await readerHasLock.Task;1863 using (await this.asyncLock.WriteLockAsync())1864 {1865 await writerHasLock.SetAsync();1866 Assert.True(this.asyncLock.IsWriteLockHeld);1867 }1868 }));1869 }1870 /// <summary>Verifies that if a read lock is open, and a writer is waiting for a lock, that no new top-level read locks will be issued.</summary>1871 [Fact]1872 public async Task NewReadersWaitForWaitingWriters()1873 {1874 var readLockHeld = new TaskCompletionSource<object?>();1875 var writerWaitingForLock = new TaskCompletionSource<object?>();1876 var newReaderWaiting = new TaskCompletionSource<object?>();1877 var writerLockHeld = new TaskCompletionSource<object?>();1878 var newReaderLockHeld = new TaskCompletionSource<object?>();1879 await Task.WhenAll(1880 Task.Run(async delegate1881 {1882 this.Logger.WriteLine("About to wait for first read lock.");1883 using (await this.asyncLock.ReadLockAsync())1884 {1885 this.Logger.WriteLine("First read lock now held, and waiting for second reader to get blocked.");1886 await readLockHeld.SetAsync();1887 await newReaderWaiting.Task;1888 this.Logger.WriteLine("Releasing first read lock.");1889 }1890 this.Logger.WriteLine("First read lock released.");1891 }),1892 Task.Run(async delegate1893 {1894 await readLockHeld.Task;1895 AsyncReaderWriterLock.Awaiter? writeAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();1896 Assert.False(writeAwaiter.IsCompleted, "The writer should not be issued a lock while a read lock is held.");1897 this.Logger.WriteLine("Write lock in queue.");1898 writeAwaiter.OnCompleted(delegate1899 {1900 using (writeAwaiter.GetResult())1901 {1902 try1903 {1904 this.Logger.WriteLine("Write lock issued.");1905 Assert.False(newReaderLockHeld.Task.IsCompleted, "Read lock should not be issued till after the write lock is released.");1906 writerLockHeld.SetResult(null); // must not be the asynchronous Set() extension method since we use it as a flag to check ordering later.1907 }1908 catch (Exception ex)1909 {1910 writerLockHeld.SetException(ex);1911 }1912 }1913 });1914 await writerWaitingForLock.SetAsync();1915 }),1916 Task.Run(async delegate1917 {1918 await writerWaitingForLock.Task;1919 AsyncReaderWriterLock.Awaiter? readAwaiter = this.asyncLock.ReadLockAsync().GetAwaiter();1920 Assert.False(readAwaiter.IsCompleted, "The new reader should not be issued a lock while a write lock is pending.");1921 this.Logger.WriteLine("Second reader in queue.");1922 readAwaiter.OnCompleted(delegate1923 {1924 try1925 {1926 this.Logger.WriteLine("Second read lock issued.");1927 using (readAwaiter.GetResult())1928 {1929 Assert.True(writerLockHeld.Task.IsCompleted);1930 newReaderLockHeld.SetAsync();1931 }1932 }1933 catch (Exception ex)1934 {1935 newReaderLockHeld.SetException(ex);1936 }1937 });1938 await newReaderWaiting.SetAsync();1939 }),1940 readLockHeld.Task,1941 writerWaitingForLock.Task,1942 newReaderWaiting.Task,1943 writerLockHeld.Task,1944 newReaderLockHeld.Task);1945 }1946 [Fact]1947 public async Task NoDeadlockByBlockingReaderLocks()1948 {1949 var taskContext = new JoinableTaskContext();1950 var taskCollection = new JoinableTaskCollection(taskContext);1951 JoinableTaskFactory taskFactory = taskContext.CreateFactory(taskCollection);1952 var lockService = new ReaderWriterLockWithFastDeadlockCheck(taskContext);1953 var firstReadLockHeld = new TaskCompletionSource<object?>();1954 var writerWaitingForLock = new TaskCompletionSource<object?>();1955 var writeLockReleased = new TaskCompletionSource<object?>();1956 JoinableTask computationTask = taskFactory.RunAsync(async delegate1957 {1958 await writerWaitingForLock.Task;1959 this.Logger.WriteLine("About to wait for second read lock.");1960 using (await lockService.ReadLockAsync())1961 {1962 this.Logger.WriteLine("Second read lock now held.");1963 await Task.Yield();1964 this.Logger.WriteLine("Releasing second read lock.");1965 }1966 this.Logger.WriteLine("Second read lock released.");1967 });1968 await Task.WhenAll(1969 Task.Run(delegate1970 {1971 return taskFactory.RunAsync(async delegate1972 {1973 this.Logger.WriteLine("About to wait for first read lock.");1974 using (await lockService.ReadLockAsync())1975 {1976 this.Logger.WriteLine("First read lock now held, and waiting a task requiring second read lock.");1977 await firstReadLockHeld.SetAsync();1978 await computationTask;1979 this.Logger.WriteLine("Releasing first read lock.");1980 }1981 });1982 }),1983 Task.Run(async delegate1984 {1985 await firstReadLockHeld.Task;1986 AsyncReaderWriterLock.Awaiter? writeAwaiter = lockService.WriteLockAsync().GetAwaiter();1987 Assert.False(writeAwaiter.IsCompleted, "The writer should not be issued a lock while a read lock is held.");1988 this.Logger.WriteLine("Write lock in queue.");1989 writeAwaiter.OnCompleted(delegate1990 {1991 using (writeAwaiter.GetResult())1992 {1993 this.Logger.WriteLine("Write lock issued.");1994 }1995 writeLockReleased.SetResult(null);1996 });1997 await writerWaitingForLock.SetAsync();1998 }),1999 computationTask.Task,2000 firstReadLockHeld.Task,2001 writerWaitingForLock.Task,2002 writeLockReleased.Task);2003 }2004 [Fact]2005 public async Task NoDeadlockByBlockingUpgradeableReaderLocks()2006 {2007 var taskContext = new JoinableTaskContext();2008 var taskCollection = new JoinableTaskCollection(taskContext);2009 JoinableTaskFactory taskFactory = taskContext.CreateFactory(taskCollection);2010 var lockService = new ReaderWriterLockWithFastDeadlockCheck(taskContext);2011 var firstReadLockHeld = new TaskCompletionSource<object?>();2012 var writerWaitingForLock = new TaskCompletionSource<object?>();2013 var writeLockReleased = new TaskCompletionSource<object?>();2014 JoinableTask computationTask = taskFactory.RunAsync(async delegate2015 {2016 await writerWaitingForLock.Task;2017 this.Logger.WriteLine("About to wait for second read lock.");2018 using (await lockService.ReadLockAsync())2019 {2020 this.Logger.WriteLine("Second read lock now held.");2021 await Task.Yield();2022 this.Logger.WriteLine("Releasing second read lock.");2023 }2024 this.Logger.WriteLine("Second read lock released.");2025 });2026 await Task.WhenAll(2027 Task.Run(delegate2028 {2029 return taskFactory.RunAsync(async delegate2030 {2031 this.Logger.WriteLine("About to wait for first read lock.");2032 using (await lockService.UpgradeableReadLockAsync())2033 {2034 this.Logger.WriteLine("First upgradable read lock now held, and waiting a task requiring second read lock.");2035 await firstReadLockHeld.SetAsync();2036 await computationTask;2037 this.Logger.WriteLine("Releasing upgradable read lock.");2038 }2039 });2040 }),2041 Task.Run(async delegate2042 {2043 await firstReadLockHeld.Task;2044 AsyncReaderWriterLock.Awaiter? writeAwaiter = lockService.WriteLockAsync().GetAwaiter();2045 Assert.False(writeAwaiter.IsCompleted, "The writer should not be issued a lock while a read lock is held.");2046 this.Logger.WriteLine("Write lock in queue.");2047 writeAwaiter.OnCompleted(delegate2048 {2049 using (writeAwaiter.GetResult())2050 {2051 this.Logger.WriteLine("Write lock issued.");2052 }2053 writeLockReleased.SetResult(null);2054 });2055 await writerWaitingForLock.SetAsync();2056 }),2057 firstReadLockHeld.Task,2058 writerWaitingForLock.Task,2059 writeLockReleased.Task);2060 await computationTask.Task;2061 }2062 [Fact]2063 public async Task NoDeadlockByBlockingSequenceReaderLocks()2064 {2065 var taskContext = new JoinableTaskContext();2066 var taskCollection = new JoinableTaskCollection(taskContext);2067 JoinableTaskFactory? taskFactory = taskContext.CreateFactory(taskCollection);2068 var lockService = new ReaderWriterLockWithFastDeadlockCheck(taskContext);2069 var firstLockHeld = new TaskCompletionSource<object?>();2070 var writerWaitingForLock = new TaskCompletionSource<object?>();2071 var writeLockReleased = new TaskCompletionSource<object?>();2072 var computationTasks = new JoinableTask[4];2073 for (int i = 0; i < computationTasks.Length; i++)2074 {2075 computationTasks[i] = CreateReaderLockTask(taskFactory, lockService, writerWaitingForLock.Task, i, i > 0 ? computationTasks[i - 1] : null);2076 }2077 JoinableTask unrelatedTask = taskFactory.RunAsync(async delegate2078 {2079 await writerWaitingForLock.Task;2080 this.Logger.WriteLine("About to wait for unrelated read lock.");2081 using (await lockService.ReadLockAsync())2082 {2083 this.Logger.WriteLine("unrelated read lock now held.");2084 await Task.Yield();2085 this.Logger.WriteLine("Releasing unrelated read lock.");2086 }2087 this.Logger.WriteLine("unrelated read lock released.");2088 });2089 await Task.WhenAll(2090 Task.Run(delegate2091 {2092 return taskFactory.RunAsync(async delegate2093 {2094 this.Logger.WriteLine("About to wait for first read lock.");2095 using (await lockService.ReadLockAsync())2096 {2097 this.Logger.WriteLine("first read lock now held, and waiting a task requiring related read lock.");2098 await firstLockHeld.SetAsync();2099 await computationTasks[computationTasks.Length - 1];2100 this.Logger.WriteLine("Releasing first read lock.");2101 }2102 });2103 }),2104 Task.Run(async delegate2105 {2106 await firstLockHeld.Task;2107 AsyncReaderWriterLock.Awaiter? writeAwaiter = lockService.WriteLockAsync().GetAwaiter();2108 Assert.False(writeAwaiter.IsCompleted, "The writer should not be issued a lock while a read lock is held.");2109 this.Logger.WriteLine("Write lock in queue.");2110 writeAwaiter.OnCompleted(delegate2111 {2112 using (writeAwaiter.GetResult())2113 {2114 this.Logger.WriteLine("Write lock issued.");2115 Assert.False(unrelatedTask.IsCompleted, "Unrelated reader lock should not be issued.");2116 }2117 writeLockReleased.SetResult(null);2118 });2119 await writerWaitingForLock.SetAsync();2120 }),2121 firstLockHeld.Task,2122 writerWaitingForLock.Task,2123 unrelatedTask.Task,2124 writeLockReleased.Task);2125 await Task.WhenAll(computationTasks.Select(t => t.Task));2126 JoinableTask CreateReaderLockTask(JoinableTaskFactory taskFactory, AsyncReaderWriterLock lockService, Task initTask, int sequence, JoinableTask? previousTask)2127 {2128 return taskFactory.RunAsync(async delegate2129 {2130 await initTask;2131 this.Logger.WriteLine($"About to wait for read lock {sequence}.");2132 using (await lockService.ReadLockAsync())2133 {2134 this.Logger.WriteLine($"Related read lock {sequence} now held.");2135 await Task.Yield();2136 this.Logger.WriteLine($"Releasing related read lock {sequence}.");2137 }2138 if (previousTask is not null)2139 {2140 await previousTask;2141 }2142 this.Logger.WriteLine($"Read lock {sequence} released.");2143 });2144 }2145 }2146 /// <summary>Verifies proper behavior when multiple read locks are held, and both read and write locks are in the queue, and a read lock is released.</summary>2147 [Fact]2148 public async Task ManyReadersBlockWriteAndSubsequentReadRequest()2149 {2150 var firstReaderAcquired = new TaskCompletionSource<object?>();2151 var secondReaderAcquired = new TaskCompletionSource<object?>();2152 var writerWaiting = new TaskCompletionSource<object?>();2153 var thirdReaderWaiting = new TaskCompletionSource<object?>();2154 var releaseFirstReader = new TaskCompletionSource<object?>();2155 var releaseSecondReader = new TaskCompletionSource<object?>();2156 var writeAcquired = new TaskCompletionSource<object?>();2157 var thirdReadAcquired = new TaskCompletionSource<object?>();2158 await Task.WhenAll(2159 Task.Run(async delegate2160 { // FIRST READER2161 using (await this.asyncLock.ReadLockAsync())2162 {2163 Task? nowait = firstReaderAcquired.SetAsync();2164 await releaseFirstReader.Task;2165 }2166 }),2167 Task.Run(async delegate2168 { // SECOND READER2169 using (await this.asyncLock.ReadLockAsync())2170 {2171 Task? nowait = secondReaderAcquired.SetAsync();2172 await releaseSecondReader.Task;2173 }2174 }),2175 Task.Run(async delegate2176 { // WRITER2177 await Task.WhenAll(firstReaderAcquired.Task, secondReaderAcquired.Task);2178 AsyncReaderWriterLock.Awaiter? writeAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();2179 Assert.False(writeAwaiter.IsCompleted);2180 writeAwaiter.OnCompleted(delegate2181 {2182 using (writeAwaiter.GetResult())2183 {2184 writeAcquired.SetAsync();2185 Assert.False(thirdReadAcquired.Task.IsCompleted);2186 }2187 });2188 Task? nowait = writerWaiting.SetAsync();2189 await writeAcquired.Task;2190 }),2191 Task.Run(async delegate2192 { // THIRD READER2193 await writerWaiting.Task;2194 AsyncReaderWriterLock.Awaiter? readAwaiter = this.asyncLock.ReadLockAsync().GetAwaiter();2195 Assert.False(readAwaiter.IsCompleted, "Third reader should not have been issued a new top-level lock while writer is in the queue.");2196 readAwaiter.OnCompleted(delegate2197 {2198 using (readAwaiter.GetResult())2199 {2200 thirdReadAcquired.SetAsync();2201 Assert.True(writeAcquired.Task.IsCompleted);2202 }2203 });2204 Task? nowait = thirdReaderWaiting.SetAsync();2205 await thirdReadAcquired.Task;2206 }),2207 Task.Run(async delegate2208 { // Coordinator2209 await thirdReaderWaiting.Task;2210 Task? nowait = releaseFirstReader.SetAsync();2211 nowait = releaseSecondReader.SetAsync();2212 }));2213 }2214 /// <summary>Verifies that if a read lock is open, and a writer is waiting for a lock, that nested read locks will still be issued.</summary>2215 [Fact]2216 public async Task NestedReadersStillIssuedLocksWhileWaitingWriters()2217 {2218 var readerLockHeld = new TaskCompletionSource<object?>();2219 var writerQueued = new TaskCompletionSource<object?>();2220 var readerNestedLockHeld = new TaskCompletionSource<object?>();2221 var writerLockHeld = new TaskCompletionSource<object?>();2222 await Task.WhenAll(2223 Task.Run(async delegate2224 {2225 using (await this.asyncLock.ReadLockAsync())2226 {2227 await readerLockHeld.SetAsync();2228 await writerQueued.Task;2229 using (await this.asyncLock.ReadLockAsync())2230 {2231 Assert.False(writerLockHeld.Task.IsCompleted);2232 await readerNestedLockHeld.SetAsync();2233 }2234 }2235 }),2236 Task.Run(async delegate2237 {2238 await readerLockHeld.Task;2239 AsyncReaderWriterLock.Awaiter? writerAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();2240 Assert.False(writerAwaiter.IsCompleted);2241 writerAwaiter.OnCompleted(delegate2242 {2243 using (writerAwaiter.GetResult())2244 {2245 writerLockHeld.SetAsync();2246 }2247 });2248 await writerQueued.SetAsync();2249 }),2250 readerNestedLockHeld.Task,2251 writerLockHeld.Task);2252 }2253 /// <summary>Verifies that an upgradeable reader can 'downgrade' to a standard read lock without releasing the overall lock.</summary>2254 [Fact]2255 public async Task DowngradeUpgradeableReadToNormalRead()2256 {2257 var firstUpgradeableReadHeld = new TaskCompletionSource<object?>();2258 var secondUpgradeableReadHeld = new TaskCompletionSource<object?>();2259 await Task.WhenAll(2260 Task.Run(async delegate2261 {2262 using (AsyncReaderWriterLock.Releaser upgradeableReader = await this.asyncLock.UpgradeableReadLockAsync())2263 {2264 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);2265 await firstUpgradeableReadHeld.SetAsync();2266 using (AsyncReaderWriterLock.Releaser standardReader = await this.asyncLock.ReadLockAsync())2267 {2268 Assert.True(this.asyncLock.IsReadLockHeld);2269 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld);2270 // Give up the upgradeable reader lock right away.2271 // This allows another upgradeable reader to obtain that kind of lock.2272 // Since we're *also* holding a (non-upgradeable) read lock, we're not letting writers in.2273 upgradeableReader.Dispose();2274 Assert.True(this.asyncLock.IsReadLockHeld);2275 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld);2276 // Ensure that the second upgradeable read lock is now obtainable.2277 await secondUpgradeableReadHeld.Task;2278 }2279 }2280 }),2281 Task.Run(async delegate2282 {2283 await firstUpgradeableReadHeld.Task;2284 using (await this.asyncLock.UpgradeableReadLockAsync())2285 {2286 await secondUpgradeableReadHeld.SetAsync();2287 }2288 }));2289 }2290 [Fact]2291 public async Task MitigationAgainstAccidentalExclusiveLockConcurrency()2292 {2293 using (await this.asyncLock.UpgradeableReadLockAsync())2294 {2295 using (await this.asyncLock.WriteLockAsync())2296 {2297 await this.CheckContinuationsConcurrencyHelper();2298 using (await this.asyncLock.WriteLockAsync())2299 {2300 await this.CheckContinuationsConcurrencyHelper();2301 }2302 await this.CheckContinuationsConcurrencyHelper();2303 }2304 await this.CheckContinuationsConcurrencyHelper();2305 }2306 using (await this.asyncLock.WriteLockAsync())2307 {2308 await this.CheckContinuationsConcurrencyHelper();2309 using (await this.asyncLock.WriteLockAsync())2310 {2311 await this.CheckContinuationsConcurrencyHelper();2312 }2313 await this.CheckContinuationsConcurrencyHelper();2314 }2315 }2316 [Fact]2317 public async Task UpgradedReadWithSyncContext()2318 {2319 var contestingReadLockAcquired = new TaskCompletionSource<object?>();2320 var writeLockWaiting = new TaskCompletionSource<object?>();2321 await Task.WhenAll(2322 Task.Run(async delegate2323 {2324 using (await this.asyncLock.UpgradeableReadLockAsync())2325 {2326 await contestingReadLockAcquired.Task;2327 AsyncReaderWriterLock.Awaiter? writeAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();2328 Assert.False(writeAwaiter.IsCompleted);2329 var nestedLockAcquired = new TaskCompletionSource<object?>();2330 writeAwaiter.OnCompleted(async delegate2331 {2332 using (writeAwaiter.GetResult())2333 {2334 using (await this.asyncLock.UpgradeableReadLockAsync())2335 {2336 Task? nowait2 = nestedLockAcquired.SetAsync();2337 }2338 }2339 });2340 Task? nowait = writeLockWaiting.SetAsync();2341 await nestedLockAcquired.Task;2342 }2343 }),2344 Task.Run(async delegate2345 {2346 using (await this.asyncLock.ReadLockAsync())2347 {2348 Task? nowait = contestingReadLockAcquired.SetAsync();2349 await writeLockWaiting.Task;2350 }2351 }));2352 }2353 [Fact]2354 public async Task PrecancelledReadLockAsyncRequest()2355 {2356 await Task.Run(delegate2357 { // get onto an MTA2358 var cts = new CancellationTokenSource();2359 cts.Cancel();2360 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.ReadLockAsync(cts.Token).GetAwaiter();2361 Assert.True(awaiter.IsCompleted);2362 try2363 {2364 awaiter.GetResult();2365 Assert.True(false, "Expected OperationCanceledException not thrown.");2366 }2367 catch (OperationCanceledException)2368 {2369 }2370 });2371 }2372 [StaFact]2373 public void PrecancelledWriteLockAsyncRequestOnSTA()2374 {2375 var cts = new CancellationTokenSource();2376 cts.Cancel();2377 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.WriteLockAsync(cts.Token).GetAwaiter();2378 Assert.True(awaiter.IsCompleted);2379 try2380 {2381 awaiter.GetResult();2382 Assert.True(false, "Expected OperationCanceledException not thrown.");2383 }2384 catch (OperationCanceledException)2385 {2386 }2387 }2388 [Fact]2389 public async Task CancelPendingLock()2390 {2391 var firstWriteHeld = new TaskCompletionSource<object?>();2392 var cancellationTestConcluded = new TaskCompletionSource<object?>();2393 await Task.WhenAll(2394 Task.Run(async delegate2395 {2396 using (await this.asyncLock.WriteLockAsync())2397 {2398 await firstWriteHeld.SetAsync();2399 await cancellationTestConcluded.Task;2400 }2401 }),2402 Task.Run(async delegate2403 {2404 await firstWriteHeld.Task;2405 var cts = new CancellationTokenSource();2406 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.WriteLockAsync(cts.Token).GetAwaiter();2407 Assert.False(awaiter.IsCompleted);2408 awaiter.OnCompleted(delegate2409 {2410 try2411 {2412 awaiter.GetResult();2413 cancellationTestConcluded.SetException(new ThrowsException(typeof(OperationCanceledException)));2414 }2415 catch (OperationCanceledException)2416 {2417 cancellationTestConcluded.SetAsync();2418 }2419 });2420 cts.Cancel();2421 }));2422 }2423 [Fact]2424 public async Task CancelPendingLockFollowedByAnotherLock()2425 {2426 var firstWriteHeld = new TaskCompletionSource<object?>();2427 var releaseWriteLock = new TaskCompletionSource<object?>();2428 var cancellationTestConcluded = new TaskCompletionSource<object?>();2429 var readerConcluded = new TaskCompletionSource<object?>();2430 await Task.WhenAll(2431 Task.Run(async delegate2432 {2433 using (await this.asyncLock.WriteLockAsync())2434 {2435 await firstWriteHeld.SetAsync();2436 await releaseWriteLock.Task;2437 }2438 }),2439 Task.Run(async delegate2440 {2441 await firstWriteHeld.Task;2442 var cts = new CancellationTokenSource();2443 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.WriteLockAsync(cts.Token).GetAwaiter();2444 Assert.False(awaiter.IsCompleted);2445 awaiter.OnCompleted(delegate2446 {2447 try2448 {2449 awaiter.GetResult();2450 cancellationTestConcluded.SetException(new ThrowsException(typeof(OperationCanceledException)));2451 }2452 catch (OperationCanceledException)2453 {2454 cancellationTestConcluded.SetAsync();2455 }2456 });2457 cts.Cancel();2458 // Pend another lock request. Make it a read lock this time.2459 // The point of this test is to ensure that the canceled (Write) awaiter doesn't get2460 // reused as a read awaiter while it is still in the writer queue.2461 await cancellationTestConcluded.Task;2462 Assert.False(this.asyncLock.IsWriteLockHeld);2463 Assert.False(this.asyncLock.IsReadLockHeld);2464 AsyncReaderWriterLock.Awaiter? readLockAwaiter = this.asyncLock.ReadLockAsync().GetAwaiter();2465 readLockAwaiter.OnCompleted(delegate2466 {2467 using (readLockAwaiter.GetResult())2468 {2469 Assert.True(this.asyncLock.IsReadLockHeld);2470 Assert.False(this.asyncLock.IsWriteLockHeld);2471 }2472 Assert.False(this.asyncLock.IsReadLockHeld);2473 Assert.False(this.asyncLock.IsWriteLockHeld);2474 readerConcluded.SetAsync();2475 });2476 Task? nowait = releaseWriteLock.SetAsync();2477 await readerConcluded.Task;2478 }));2479 }2480 [Fact]2481 public async Task CancelNonImpactfulToIssuedLocks()2482 {2483 var cts = new CancellationTokenSource();2484 using (await this.asyncLock.WriteLockAsync(cts.Token))2485 {2486 Assert.True(this.asyncLock.IsWriteLockHeld);2487 cts.Cancel();2488 Assert.True(this.asyncLock.IsWriteLockHeld);2489 }2490 Assert.False(this.asyncLock.IsWriteLockHeld);2491 }2492 [StaFact]2493 public async Task CancelJustBeforeIsCompletedNoLeak()2494 {2495 var lockAwaitFinished = new TaskCompletionSource<object?>();2496 var cts = new CancellationTokenSource();2497 AsyncReaderWriterLock.Awaitable awaitable = this.asyncLock.UpgradeableReadLockAsync(cts.Token);2498 AsyncReaderWriterLock.Awaiter? awaiter = awaitable.GetAwaiter();2499 cts.Cancel();2500 if (awaiter.IsCompleted)2501 {2502 // The lock should not be issued on an STA thread2503 Assert.ThrowsAny<OperationCanceledException>(() => awaiter.GetResult().Dispose());2504 await lockAwaitFinished.SetAsync();2505 }2506 else2507 {2508 awaiter.OnCompleted(delegate2509 {2510 try2511 {2512 awaiter.GetResult().Dispose();2513 Assert.Equal(ApartmentState.MTA, Thread.CurrentThread.GetApartmentState());2514 }2515 catch (OperationCanceledException)2516 {2517 }2518 lockAwaitFinished.SetAsync();2519 });2520 }2521 await lockAwaitFinished.Task.WithTimeout(UnexpectedTimeout);2522 // No lock is leaked2523 using (await this.asyncLock.UpgradeableReadLockAsync())2524 {2525 Assert.Equal(ApartmentState.MTA, Thread.CurrentThread.GetApartmentState());2526 }2527 }2528 [Fact]2529 public async Task CancelJustAfterIsCompleted()2530 {2531 var lockAwaitFinished = new TaskCompletionSource<object?>();2532 var testCompleted = new TaskCompletionSource<object?>();2533 var readlockTask = Task.Run(async delegate2534 {2535 using (await this.asyncLock.ReadLockAsync())2536 {2537 await lockAwaitFinished.SetAsync();2538 await testCompleted.Task;2539 }2540 });2541 await lockAwaitFinished.Task;2542 var cts = new CancellationTokenSource();2543 AsyncReaderWriterLock.Awaitable awaitable = this.asyncLock.WriteLockAsync(cts.Token);2544 AsyncReaderWriterLock.Awaiter? awaiter = awaitable.GetAwaiter();2545 Assert.False(awaiter.IsCompleted, "The lock should not be issued until read lock is released.");2546 cts.Cancel();2547 awaiter.OnCompleted(delegate2548 {2549 Assert.ThrowsAny<OperationCanceledException>(() => awaiter.GetResult().Dispose());2550 testCompleted.SetAsync();2551 });2552 await readlockTask.WithTimeout(UnexpectedTimeout);2553 }2554 [Fact]2555 public async Task CancelWriteLockUnblocksReadLocks()2556 {2557 var firstReadLockAcquired = new AsyncManualResetEvent();2558 var firstReadLockToRelease = new AsyncManualResetEvent();2559 var firstReadLockTask = Task.Run(async () =>2560 {2561 using (await this.asyncLock.ReadLockAsync())2562 {2563 firstReadLockAcquired.Set();2564 await firstReadLockToRelease.WaitAsync();2565 }2566 });2567 await firstReadLockAcquired.WaitAsync();2568 var cancellationSource = new CancellationTokenSource();2569 AsyncReaderWriterLock.Awaiter? writeLockAwaiter = this.asyncLock.WriteLockAsync(cancellationSource.Token).GetAwaiter();2570 Assert.False(writeLockAwaiter.IsCompleted);2571 writeLockAwaiter.OnCompleted(delegate2572 {2573 try2574 {2575 writeLockAwaiter.GetResult();2576 }2577 catch (OperationCanceledException)2578 {2579 }2580 });2581 AsyncReaderWriterLock.Awaiter? readLockAwaiter = this.asyncLock.ReadLockAsync().GetAwaiter();2582 var secondReadLockAcquired = new AsyncManualResetEvent();2583 Assert.False(readLockAwaiter.IsCompleted);2584 readLockAwaiter.OnCompleted(delegate2585 {2586 using (readLockAwaiter.GetResult())2587 {2588 secondReadLockAcquired.Set();2589 }2590 });2591 cancellationSource.Cancel();2592 await secondReadLockAcquired.WaitAsync();2593 firstReadLockToRelease.Set();2594 await firstReadLockTask;2595 }2596 [Fact]2597 public async Task CancelWriteLockUnblocksUpgradeableReadLocks()2598 {2599 var firstReadLockAcquired = new AsyncManualResetEvent();2600 var firstReadLockToRelease = new AsyncManualResetEvent();2601 var firstReadLockTask = Task.Run(async () =>2602 {2603 using (await this.asyncLock.ReadLockAsync())2604 {2605 firstReadLockAcquired.Set();2606 await firstReadLockToRelease.WaitAsync();2607 }2608 });2609 await firstReadLockAcquired.WaitAsync();2610 var cancellationSource = new CancellationTokenSource();2611 AsyncReaderWriterLock.Awaiter? writeLockAwaiter = this.asyncLock.WriteLockAsync(cancellationSource.Token).GetAwaiter();2612 Assert.False(writeLockAwaiter.IsCompleted);2613 writeLockAwaiter.OnCompleted(delegate2614 {2615 try2616 {2617 writeLockAwaiter.GetResult();2618 }2619 catch (OperationCanceledException)2620 {2621 }2622 });2623 AsyncReaderWriterLock.Awaiter? upgradeableReadLockAwaiter = this.asyncLock.UpgradeableReadLockAsync().GetAwaiter();2624 var upgradeableReadLockAcquired = new AsyncManualResetEvent();2625 Assert.False(upgradeableReadLockAwaiter.IsCompleted);2626 upgradeableReadLockAwaiter.OnCompleted(delegate2627 {2628 using (upgradeableReadLockAwaiter.GetResult())2629 {2630 upgradeableReadLockAcquired.Set();2631 }2632 });2633 cancellationSource.Cancel();2634 await upgradeableReadLockAcquired.WaitAsync();2635 firstReadLockToRelease.Set();2636 await firstReadLockTask;2637 }2638 [Fact]2639 public async Task CancelWriteLockDoesNotUnblocksReadLocksIncorrectly()2640 {2641 var firstWriteLockAcquired = new AsyncManualResetEvent();2642 var firstWriteLockToRelease = new AsyncManualResetEvent();2643 var firstCancellationSource = new CancellationTokenSource();2644 var firstWriteLockTask = Task.Run(async () =>2645 {2646 using (await this.asyncLock.WriteLockAsync(firstCancellationSource.Token))2647 {2648 firstWriteLockAcquired.Set();2649 await firstWriteLockToRelease.WaitAsync();2650 }2651 });2652 await firstWriteLockAcquired.WaitAsync();2653 var cancellationSource = new CancellationTokenSource();2654 AsyncReaderWriterLock.Awaiter? writeLockAwaiter = this.asyncLock.WriteLockAsync(cancellationSource.Token).GetAwaiter();2655 Assert.False(writeLockAwaiter.IsCompleted);2656 writeLockAwaiter.OnCompleted(delegate2657 {2658 try2659 {2660 writeLockAwaiter.GetResult();2661 }2662 catch (OperationCanceledException)2663 {2664 }2665 });2666 AsyncReaderWriterLock.Awaiter? readLockAwaiter = this.asyncLock.ReadLockAsync().GetAwaiter();2667 var readLockAcquired = new AsyncManualResetEvent();2668 Assert.False(readLockAwaiter.IsCompleted);2669 readLockAwaiter.OnCompleted(delegate2670 {2671 using (readLockAwaiter.GetResult())2672 {2673 readLockAcquired.Set();2674 }2675 });2676 cancellationSource.Cancel();2677 firstCancellationSource.Cancel();2678 Assert.False(readLockAcquired.WaitAsync().Wait(AsyncDelay));2679 firstWriteLockToRelease.Set();2680 await firstWriteLockAcquired;2681 await readLockAcquired.WaitAsync();2682 }2683 [StaFact]2684 public void CompleteBlocksNewTopLevelLocksSTA()2685 {2686 Assert.Equal(ApartmentState.STA, Thread.CurrentThread.GetApartmentState()); // test requires STA2687 this.asyncLock.Complete();2688 // Exceptions should always be thrown via the awaitable result rather than synchronously thrown2689 // so that we meet expectations of C# async methods.2690 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.ReadLockAsync().GetAwaiter();2691 Assert.True(awaiter.IsCompleted);2692 try2693 {2694 awaiter.GetResult();2695 Assert.True(false, "Expected exception not thrown.");2696 }2697 catch (InvalidOperationException)2698 {2699 }2700 }2701 [Fact]2702 public async Task CompleteBlocksNewTopLevelLocksMTA()2703 {2704 this.asyncLock.Complete();2705 await Task.Run(delegate2706 {2707 // Exceptions should always be thrown via the awaitable result rather than synchronously thrown2708 // so that we meet expectations of C# async methods.2709 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.ReadLockAsync().GetAwaiter();2710 Assert.True(awaiter.IsCompleted);2711 try2712 {2713 awaiter.GetResult();2714 Assert.True(false, "Expected exception not thrown.");2715 }2716 catch (InvalidOperationException)2717 {2718 }2719 });2720 }2721 [Fact]2722 public async Task CompleteDoesNotBlockNestedLockRequests()2723 {2724 using (await this.asyncLock.ReadLockAsync())2725 {2726 this.asyncLock.Complete();2727 Assert.False(this.asyncLock.Completion.IsCompleted, "Lock shouldn't be completed while there are open locks.");2728 using (await this.asyncLock.ReadLockAsync())2729 {2730 }2731 Assert.False(this.asyncLock.Completion.IsCompleted, "Lock shouldn't be completed while there are open locks.");2732 }2733 await this.asyncLock.Completion; // ensure that Completion transitions to completed as a result of releasing all locks.2734 }2735 [Fact]2736 public async Task CompleteAllowsPreviouslyQueuedLockRequests()2737 {2738 var firstLockAcquired = new TaskCompletionSource<object?>();2739 var secondLockQueued = new TaskCompletionSource<object?>();2740 var completeSignaled = new TaskCompletionSource<object?>();2741 var secondLockAcquired = new TaskCompletionSource<object?>();2742 await Task.WhenAll(2743 Task.Run(async delegate2744 {2745 using (await this.asyncLock.WriteLockAsync())2746 {2747 this.Logger.WriteLine("First write lock acquired.");2748 await firstLockAcquired.SetAsync();2749 await completeSignaled.Task;2750 Assert.False(this.asyncLock.Completion.IsCompleted);2751 }2752 }),2753 Task.Run(async delegate2754 {2755 try2756 {2757 await firstLockAcquired.Task;2758 AsyncReaderWriterLock.Awaiter? secondWriteAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();2759 Assert.False(secondWriteAwaiter.IsCompleted);2760 this.Logger.WriteLine("Second write lock request pended.");2761 secondWriteAwaiter.OnCompleted(delegate2762 {2763 using (secondWriteAwaiter.GetResult())2764 {2765 this.Logger.WriteLine("Second write lock acquired.");2766 secondLockAcquired.SetAsync();2767 Assert.False(this.asyncLock.Completion.IsCompleted);2768 }2769 });2770 await secondLockQueued.SetAsync();2771 }2772 catch (Exception ex)2773 {2774 secondLockAcquired.TrySetException(ex);2775 }2776 }),2777 Task.Run(async delegate2778 {2779 await secondLockQueued.Task;2780 this.Logger.WriteLine("Calling Complete() method.");2781 this.asyncLock.Complete();2782 await completeSignaled.SetAsync();2783 }),2784 secondLockAcquired.Task);2785 await this.asyncLock.Completion;2786 }2787 [Fact]2788 public async Task OnBeforeExclusiveLockReleasedAsyncSimpleSyncHandler()2789 {2790 var asyncLock = new LockDerived();2791 int callbackInvoked = 0;2792 asyncLock.OnBeforeExclusiveLockReleasedAsyncDelegate = delegate2793 {2794 callbackInvoked++;2795 Assert.True(asyncLock.IsWriteLockHeld);2796 return Task.CompletedTask;2797 };2798 using (await asyncLock.WriteLockAsync())2799 {2800 }2801 Assert.Equal(1, callbackInvoked);2802 }2803 [Fact]2804 public async Task OnBeforeExclusiveLockReleasedAsyncNestedLockSyncHandler()2805 {2806 var asyncLock = new LockDerived();2807 int callbackInvoked = 0;2808 asyncLock.OnBeforeExclusiveLockReleasedAsyncDelegate = async delegate2809 {2810 Assert.True(asyncLock.IsWriteLockHeld);2811 using (await asyncLock.ReadLockAsync())2812 {2813 Assert.True(asyncLock.IsWriteLockHeld);2814 using (await asyncLock.WriteLockAsync())2815 { // this should succeed because our caller has a write lock.2816 Assert.True(asyncLock.IsWriteLockHeld);2817 }2818 }2819 callbackInvoked++;2820 };2821 using (await asyncLock.WriteLockAsync())2822 {2823 }2824 Assert.Equal(1, callbackInvoked);2825 }2826 [Fact]2827 public async Task OnBeforeExclusiveLockReleasedAsyncSimpleAsyncHandler()2828 {2829 var asyncLock = new LockDerived();2830 var callbackCompleted = new TaskCompletionSource<object?>();2831 asyncLock.OnBeforeExclusiveLockReleasedAsyncDelegate = async delegate2832 {2833 try2834 {2835 Assert.True(asyncLock.IsWriteLockHeld);2836 await Task.Yield();2837 Assert.True(asyncLock.IsWriteLockHeld);2838 callbackCompleted.SetResult(null);2839 }2840 catch (Exception ex)2841 {2842 callbackCompleted.SetException(ex);2843 }2844 };2845 using (await asyncLock.WriteLockAsync())2846 {2847 }2848 await callbackCompleted.Task;2849 }2850 [Fact]2851 public async Task OnBeforeExclusiveLockReleasedAsyncReadLockAcquiringAsyncHandler()2852 {2853 var asyncLock = new LockDerived();2854 var callbackCompleted = new TaskCompletionSource<object?>();2855 asyncLock.OnBeforeExclusiveLockReleasedAsyncDelegate = async delegate2856 {2857 try2858 {2859 Assert.True(asyncLock.IsWriteLockHeld);2860 using (await asyncLock.ReadLockAsync())2861 {2862 Assert.True(asyncLock.IsWriteLockHeld);2863 Assert.True(asyncLock.IsReadLockHeld);2864 await Task.Yield();2865 Assert.True(asyncLock.IsWriteLockHeld);2866 Assert.True(asyncLock.IsReadLockHeld);2867 }2868 callbackCompleted.SetResult(null);2869 }2870 catch (Exception ex)2871 {2872 callbackCompleted.SetException(ex);2873 }2874 };2875 using (await asyncLock.WriteLockAsync())2876 {2877 }2878 await callbackCompleted.Task;2879 }2880 [Fact]2881 public async Task OnBeforeExclusiveLockReleasedAsyncNestedWriteLockAsyncHandler()2882 {2883 var asyncLock = new LockDerived();2884 var callbackCompleted = new TaskCompletionSource<object?>();2885 bool outermostExecuted = false;2886 asyncLock.OnBeforeExclusiveLockReleasedAsyncDelegate = async delegate2887 {2888 try2889 {2890 // Only do our deed on the outermost lock release -- not the one we take.2891 if (!outermostExecuted)2892 {2893 outermostExecuted = true;2894 Assert.True(asyncLock.IsWriteLockHeld);2895 using (await asyncLock.WriteLockAsync())2896 {2897 await Task.Yield();2898 using (await asyncLock.ReadLockAsync())2899 {2900 Assert.True(asyncLock.IsWriteLockHeld);2901 using (await asyncLock.WriteLockAsync())2902 {2903 Assert.True(asyncLock.IsWriteLockHeld);2904 await Task.Yield();2905 Assert.True(asyncLock.IsWriteLockHeld);2906 }2907 }2908 }2909 callbackCompleted.SetResult(null);2910 }2911 }2912 catch (Exception ex)2913 {2914 callbackCompleted.SetException(ex);2915 }2916 };2917 using (await asyncLock.WriteLockAsync())2918 {2919 }2920 await callbackCompleted.Task;2921 }2922 [Fact]2923 public async Task OnBeforeExclusiveLockReleasedAsyncWriteLockWrapsBaseMethod()2924 {2925 var callbackCompleted = new AsyncManualResetEvent();2926 var asyncLock = new LockDerivedWriteLockAroundOnBeforeExclusiveLockReleased();2927 using (await asyncLock.WriteLockAsync())2928 {2929 asyncLock.OnBeforeWriteLockReleased(async delegate2930 {2931 await Task.Yield();2932 });2933 }2934 await asyncLock.OnBeforeExclusiveLockReleasedAsyncInvoked.WaitAsync();2935 }2936 [Fact]2937 public async Task OnBeforeExclusiveLockReleasedAsyncWriteLockReleaseAsync()2938 {2939 var asyncLock = new LockDerivedWriteLockAroundOnBeforeExclusiveLockReleased();2940 await using (await asyncLock.WriteLockAsync())2941 {2942 }2943 }2944 [Fact]2945 public async Task OnBeforeExclusiveLockReleasedAsyncReadLockReleaseAsync()2946 {2947 var asyncLock = new LockDerivedReadLockAroundOnBeforeExclusiveLockReleased();2948 await using (await asyncLock.WriteLockAsync())2949 {2950 }2951 }2952 [Fact]2953 public async Task OnBeforeWriteLockReleasedNullArgument()2954 {2955 using (await this.asyncLock.WriteLockAsync())2956 {2957 Assert.Throws<ArgumentNullException>(() => this.asyncLock.OnBeforeWriteLockReleased(null!));2958 }2959 }2960 [Fact]2961 public async Task OnBeforeWriteLockReleasedSingle()2962 {2963 var afterWriteLock = new TaskCompletionSource<object?>();2964 using (await this.asyncLock.WriteLockAsync())2965 {2966 this.asyncLock.OnBeforeWriteLockReleased(async delegate2967 {2968 try2969 {2970 Assert.True(this.asyncLock.IsWriteLockHeld);2971 afterWriteLock.SetResult(null);2972 await Task.Yield();2973 Assert.True(this.asyncLock.IsWriteLockHeld);2974 }2975 catch (Exception ex)2976 {2977 afterWriteLock.SetException(ex);2978 }2979 });2980 Assert.False(afterWriteLock.Task.IsCompleted);2981 // Set Complete() this early to verify that callbacks can fire even after Complete() is called.2982 this.asyncLock.Complete();2983 }2984 await afterWriteLock.Task;2985 await this.asyncLock.Completion;2986 }2987 [Fact]2988 public async Task OnBeforeWriteLockReleasedMultiple()2989 {2990 var afterWriteLock1 = new TaskCompletionSource<object?>();2991 var afterWriteLock2 = new TaskCompletionSource<object?>();2992 using (await this.asyncLock.WriteLockAsync())2993 {2994 this.asyncLock.OnBeforeWriteLockReleased(async delegate2995 {2996 try2997 {2998 Assert.True(this.asyncLock.IsWriteLockHeld);2999 afterWriteLock1.SetResult(null);3000 await Task.Yield();3001 Assert.True(this.asyncLock.IsWriteLockHeld);3002 }3003 catch (Exception ex)3004 {3005 afterWriteLock1.SetException(ex);3006 }3007 });3008 this.asyncLock.OnBeforeWriteLockReleased(async delegate3009 {3010 try3011 {3012 Assert.True(this.asyncLock.IsWriteLockHeld);3013 afterWriteLock2.SetResult(null);3014 await Task.Yield();3015 Assert.True(this.asyncLock.IsWriteLockHeld);3016 }3017 catch (Exception ex)3018 {3019 afterWriteLock2.SetException(ex);3020 }3021 });3022 Assert.False(afterWriteLock1.Task.IsCompleted);3023 Assert.False(afterWriteLock2.Task.IsCompleted);3024 }3025 this.asyncLock.Complete();3026 await afterWriteLock1.Task;3027 await afterWriteLock2.Task;3028 await this.asyncLock.Completion;3029 }3030 [Fact]3031 public async Task OnBeforeWriteLockReleasedNestedCallbacks()3032 {3033 var callback1 = new TaskCompletionSource<object?>();3034 var callback2 = new TaskCompletionSource<object?>();3035 using (await this.asyncLock.WriteLockAsync())3036 {3037 this.asyncLock.OnBeforeWriteLockReleased(async delegate3038 {3039 Assert.True(this.asyncLock.IsWriteLockHeld);3040 await Task.Yield();3041 Assert.True(this.asyncLock.IsWriteLockHeld);3042 await callback1.SetAsync();3043 // Now within a callback, let's pretend we made some change that caused another callback to register.3044 this.asyncLock.OnBeforeWriteLockReleased(async delegate3045 {3046 Assert.True(this.asyncLock.IsWriteLockHeld);3047 await Task.Yield();3048 Assert.True(this.asyncLock.IsWriteLockHeld);3049 await callback2.SetAsync();3050 });3051 });3052 // Set Complete() this early to verify that callbacks can fire even after Complete() is called.3053 this.asyncLock.Complete();3054 }3055 await callback2.Task;3056 await this.asyncLock.Completion;3057 }3058 [Fact]3059 public async Task OnBeforeWriteLockReleasedDelegateThrows()3060 {3061 var afterWriteLock = new TaskCompletionSource<object?>();3062 var exceptionToThrow = new ApplicationException();3063 try3064 {3065 using (await this.asyncLock.WriteLockAsync())3066 {3067 this.asyncLock.OnBeforeWriteLockReleased(delegate3068 {3069 afterWriteLock.SetResult(null);3070 throw exceptionToThrow;3071 });3072 Assert.False(afterWriteLock.Task.IsCompleted);3073 this.asyncLock.Complete();3074 }3075 Assert.True(false, "Expected exception not thrown.");3076 }3077 catch (AggregateException ex)3078 {3079 Assert.Same(exceptionToThrow, ex.Flatten().InnerException);3080 }3081 Assert.False(this.asyncLock.IsWriteLockHeld);3082 await afterWriteLock.Task;3083 await this.asyncLock.Completion;3084 }3085 [Fact]3086 public async Task OnBeforeWriteLockReleasedWithUpgradedWrite()3087 {3088 var callbackFired = new TaskCompletionSource<object?>();3089 using (await this.asyncLock.UpgradeableReadLockAsync())3090 {3091 using (await this.asyncLock.WriteLockAsync())3092 {3093 this.asyncLock.OnBeforeWriteLockReleased(async delegate3094 {3095 Assert.True(this.asyncLock.IsWriteLockHeld);3096 await Task.Yield();3097 Assert.True(this.asyncLock.IsWriteLockHeld);3098 await callbackFired.SetAsync();3099 });3100 }3101 Assert.True(callbackFired.Task.IsCompleted, "This should have completed synchronously with releasing the write lock.");3102 }3103 }3104 [Fact]3105 public async Task OnBeforeWriteLockReleasedWithNestedStickyUpgradedWrite()3106 {3107 var callbackFired = new TaskCompletionSource<object?>();3108 using (await this.asyncLock.UpgradeableReadLockAsync())3109 {3110 using (await this.asyncLock.UpgradeableReadLockAsync(AsyncReaderWriterLock.LockFlags.StickyWrite))3111 {3112 using (await this.asyncLock.WriteLockAsync())3113 {3114 this.asyncLock.OnBeforeWriteLockReleased(async delegate3115 {3116 Assert.True(this.asyncLock.IsWriteLockHeld);3117 await callbackFired.SetAsync();3118 await Task.Yield();3119 Assert.True(this.asyncLock.IsWriteLockHeld);3120 });3121 }3122 Assert.False(callbackFired.Task.IsCompleted, "This shouldn't have run yet because the upgradeable read lock bounding the write lock is a sticky one.");3123 }3124 Assert.True(callbackFired.Task.IsCompleted, "This should have completed synchronously with releasing the upgraded sticky upgradeable read lock.");3125 }3126 }3127 [Fact]3128 public async Task OnBeforeWriteLockReleasedWithStickyUpgradedWrite()3129 {3130 var callbackBegin = new TaskCompletionSource<object?>();3131 var callbackEnding = new TaskCompletionSource<object?>();3132 var releaseCallback = new TaskCompletionSource<object?>();3133 using (await this.asyncLock.UpgradeableReadLockAsync(AsyncReaderWriterLock.LockFlags.StickyWrite))3134 {3135 using (await this.asyncLock.WriteLockAsync())3136 {3137 this.asyncLock.OnBeforeWriteLockReleased(async delegate3138 {3139 await callbackBegin.SetAsync();3140 Assert.True(this.asyncLock.IsWriteLockHeld);3141 await Task.Delay(AsyncDelay);3142 Assert.True(this.asyncLock.IsWriteLockHeld);3143 // Technically this callback should be able to complete asynchronously3144 // with respect to the thread that released the lock, but for now that3145 // feature is disabled to keep things a bit sane (it also gives us a3146 // listener if one of the exclusive lock callbacks throw an exception).3147 ////await releaseCallback.Task;3148 callbackEnding.SetResult(null); // don't use Set() extension method because that's asynchronous, and we measure this to verify ordered behavior.3149 });3150 }3151 Assert.False(callbackBegin.Task.IsCompleted, "This shouldn't have run yet because the upgradeable read lock bounding the write lock is a sticky one.");3152 }3153 // This next assert is commented out because (again), the lock's current behavior is to3154 // synchronously block when releasing locks, even if it's arguably not necessary.3155 ////Assert.False(callbackEnding.Task.IsCompleted, "This should have completed asynchronously because no read lock remained after the sticky upgraded read lock was released.");3156 Assert.True(callbackEnding.Task.IsCompleted, "Once this fails, uncomment the previous assert and the await earlier in the test if it's intended behavior.");3157 await releaseCallback.SetAsync();3158 // Because the callbacks are fired asynchronously, we must wait for it to settle before allowing the test to finish3159 // to avoid a false failure from the Cleanup method.3160 this.asyncLock.Complete();3161 await this.asyncLock.Completion;3162 Assert.True(callbackEnding.Task.IsCompleted, "The completion task should not have completed until the callbacks had completed.");3163 }3164 [Fact]3165 public async Task OnBeforeWriteLockReleasedWithStickyUpgradedWriteWithNestedLocks()3166 {3167 var asyncLock = new LockDerived3168 {3169 OnExclusiveLockReleasedAsyncDelegate = async delegate3170 {3171 await Task.Yield();3172 },3173 };3174 var releaseCallback = new TaskCompletionSource<object?>();3175 using (await asyncLock.UpgradeableReadLockAsync(AsyncReaderWriterLock.LockFlags.StickyWrite))3176 {3177 using (await asyncLock.WriteLockAsync())3178 {3179 asyncLock.OnBeforeWriteLockReleased(async delegate3180 {3181 // For this test, we deliberately do not yield before3182 // requesting first lock because we're racing to request a lock3183 // while the reenterConcurrencyPrepRunning field is "true".3184 Assert.True(asyncLock.IsWriteLockHeld);3185 using (await asyncLock.ReadLockAsync())3186 {3187 using (await asyncLock.WriteLockAsync())3188 {3189 }3190 }3191 using (await asyncLock.UpgradeableReadLockAsync())3192 {3193 }3194 using (await asyncLock.WriteLockAsync())3195 {3196 }3197 await Task.Yield();3198 Assert.True(asyncLock.IsWriteLockHeld);3199 // Technically this callback should be able to complete asynchronously3200 // with respect to the thread that released the lock, but for now that3201 // feature is disabled to keep things a bit sane (it also gives us a3202 // listener if one of the exclusive lock callbacks throw an exception).3203 ////await releaseCallback.Task;3204 ////Assert.True(asyncLock.IsWriteLockHeld);3205 });3206 }3207 }3208 await releaseCallback.SetAsync();3209 // Because the callbacks are fired asynchronously, we must wait for it to settle before allowing the test to finish3210 // to avoid a false failure from the Cleanup method.3211 asyncLock.Complete();3212 await asyncLock.Completion;3213 }3214 [Fact]3215 public void OnBeforeWriteLockReleasedWithoutAnyLock()3216 {3217 Assert.Throws<InvalidOperationException>(() =>3218 {3219 this.asyncLock.OnBeforeWriteLockReleased(delegate3220 {3221 return Task.FromResult<object?>(null);3222 });3223 });3224 }3225 [Fact]3226 public async Task OnBeforeWriteLockReleasedInReadlock()3227 {3228 using (await this.asyncLock.ReadLockAsync())3229 {3230 Assert.Throws<InvalidOperationException>(() =>3231 {3232 this.asyncLock.OnBeforeWriteLockReleased(delegate3233 {3234 return Task.FromResult<object?>(null);3235 });3236 });3237 }3238 }3239 [Fact]3240 public async Task OnBeforeWriteLockReleasedCallbackFiresSynchronouslyWithoutPrivateLockHeld()3241 {3242 var callbackFired = new TaskCompletionSource<object?>();3243 var writeLockRequested = new TaskCompletionSource<object?>();3244 await Task.WhenAll(3245 Task.Run(async delegate3246 {3247 using (await this.asyncLock.UpgradeableReadLockAsync())3248 {3249 using (await this.asyncLock.WriteLockAsync())3250 {3251 // Set up a callback that will deadlock if a private lock is held (so the test will fail3252 // to identify the misuse of the lock).3253 this.asyncLock.OnBeforeWriteLockReleased(async delegate3254 {3255 Assert.True(this.asyncLock.IsWriteLockHeld);3256 await Task.Yield();3257 // If a private lock were held, now that we're on a different thread this should deadlock.3258 Assert.True(this.asyncLock.IsWriteLockHeld);3259 // And if that weren't enough, we can hold this while another thread tries to get a lock.3260 // They should immediately get a "not available" flag, but if they block due to a private3261 // lock behind held while this callback executes, then we'll deadlock.3262 await callbackFired.SetAsync();3263 await writeLockRequested.Task;3264 });3265 }3266 Assert.True(callbackFired.Task.IsCompleted, "This should have completed synchronously with releasing the write lock.");3267 }3268 }),3269 Task.Run(async delegate3270 {3271 await callbackFired.Task;3272 try3273 {3274 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.WriteLockAsync().GetAwaiter();3275 Assert.False(awaiter.IsCompleted);3276 await writeLockRequested.SetAsync();3277 }3278 catch (Exception ex)3279 {3280 writeLockRequested.SetException(ex);3281 }3282 }));3283 }3284 [StaFact]3285 public void OnBeforeWriteLockReleasedCallbackNeverInvokedOnSTA()3286 {3287 TestUtilities.Run(async delegate3288 {3289 var callbackCompleted = new TaskCompletionSource<object?>();3290 AsyncReaderWriterLock.Releaser releaser = default(AsyncReaderWriterLock.Releaser);3291 var staScheduler = TaskScheduler.FromCurrentSynchronizationContext();3292 var nowait = Task.Run(async delegate3293 {3294 using (await this.asyncLock.UpgradeableReadLockAsync())3295 {3296 using (releaser = await this.asyncLock.WriteLockAsync())3297 {3298 this.asyncLock.OnBeforeWriteLockReleased(async delegate3299 {3300 try3301 {3302 Assert.Equal(ApartmentState.MTA, Thread.CurrentThread.GetApartmentState());3303 await Task.Yield();3304 Assert.Equal(ApartmentState.MTA, Thread.CurrentThread.GetApartmentState());3305 await callbackCompleted.SetAsync();3306 }3307 catch (Exception ex)3308 {3309 callbackCompleted.SetException(ex);3310 }3311 });3312 // Transition to an STA thread prior to calling Release (the point of this test).3313 await staScheduler;3314 }3315 }3316 });3317 await callbackCompleted.Task;3318 });3319 }3320 /// <summary>3321 /// Test for when the write queue is NOT empty when a write lock is released on an STA to a (non-sticky)3322 /// upgradeable read lock and a synchronous callback is to be invoked.3323 /// </summary>3324 [StaFact]3325 public async Task OnBeforeWriteLockReleasedToUpgradeableReadOnStaWithCallbacksAndWaitingWriter()3326 {3327 TestUtilities.Run(async delegate3328 {3329 var firstWriteHeld = new TaskCompletionSource<object?>();3330 var callbackCompleted = new TaskCompletionSource<object?>();3331 var secondWriteLockQueued = new TaskCompletionSource<object?>();3332 var secondWriteLockHeld = new TaskCompletionSource<object?>();3333 AsyncReaderWriterLock.Releaser releaser = default(AsyncReaderWriterLock.Releaser);3334 var staScheduler = TaskScheduler.FromCurrentSynchronizationContext();3335 await Task.WhenAll(3336 Task.Run(async delegate3337 {3338 using (await this.asyncLock.UpgradeableReadLockAsync())3339 {3340 using (releaser = await this.asyncLock.WriteLockAsync())3341 {3342 await firstWriteHeld.SetAsync();3343 this.asyncLock.OnBeforeWriteLockReleased(async delegate3344 {3345 await callbackCompleted.SetAsync();3346 });3347 await secondWriteLockQueued.Task;3348 // Transition to an STA thread prior to calling Release (the point of this test).3349 await staScheduler;3350 }3351 }3352 }),3353 Task.Run(async delegate3354 {3355 await firstWriteHeld.Task;3356 AsyncReaderWriterLock.Awaiter? writerAwaiter = this.asyncLock.WriteLockAsync().GetAwaiter();3357 Assert.False(writerAwaiter.IsCompleted);3358 writerAwaiter.OnCompleted(delegate3359 {3360 using (writerAwaiter.GetResult())3361 {3362 try3363 {3364 Assert.True(callbackCompleted.Task.IsCompleted);3365 secondWriteLockHeld.SetAsync();3366 }3367 catch (Exception ex)3368 {3369 secondWriteLockHeld.SetException(ex);3370 }3371 }3372 });3373 await secondWriteLockQueued.SetAsync();3374 }),3375 callbackCompleted.Task,3376 secondWriteLockHeld.Task);3377 });3378 this.asyncLock.Complete();3379 await this.asyncLock.Completion;3380 }3381 [Fact]3382 public async Task OnBeforeWriteLockReleasedAndReenterConcurrency()3383 {3384 var stub = new LockDerived();3385 var beforeWriteLockReleasedTaskSource = new TaskCompletionSource<object?>();3386 var exclusiveLockReleasedTaskSource = new TaskCompletionSource<object?>();3387 stub.OnExclusiveLockReleasedAsyncDelegate = () => exclusiveLockReleasedTaskSource.Task;3388 using (AsyncReaderWriterLock.Releaser releaser = await stub.WriteLockAsync())3389 {3390 stub.OnBeforeWriteLockReleased(() => beforeWriteLockReleasedTaskSource.Task);3391 Task? releaseTask = releaser.ReleaseAsync();3392 beforeWriteLockReleasedTaskSource.SetResult(null);3393 exclusiveLockReleasedTaskSource.SetResult(null);3394 await releaseTask;3395 }3396 }3397 /// <summary>Verifies that locks requested on STA threads will marshal to an MTA.</summary>3398 [StaFact]3399 public async Task StaLockRequestsMarshalToMTA()3400 {3401 var testComplete = new TaskCompletionSource<object?>();3402 Thread staThread = new Thread((ThreadStart)delegate3403 {3404 try3405 {3406 AsyncReaderWriterLock.Awaitable awaitable = this.asyncLock.ReadLockAsync();3407 AsyncReaderWriterLock.Awaiter? awaiter = awaitable.GetAwaiter();3408 Assert.False(awaiter.IsCompleted, "The lock should not be issued on an STA thread.");3409 awaiter.OnCompleted(delegate3410 {3411 Assert.Equal(ApartmentState.MTA, Thread.CurrentThread.GetApartmentState());3412 awaiter.GetResult().Dispose();3413 testComplete.SetAsync();3414 });3415 testComplete.Task.Wait();3416 }3417 catch (Exception ex)3418 {3419 testComplete.TrySetException(ex);3420 }3421 });3422 staThread.SetApartmentState(ApartmentState.STA);3423 staThread.Start();3424 await testComplete.Task;3425 }3426 /// <summary>Verifies that when an MTA holding a lock traverses (via CallContext) to an MTA that the MTA DOES appear to hold a lock.</summary>3427 [Fact]3428 public async Task MtaLockSharedWithMta()3429 {3430 using (await this.asyncLock.ReadLockAsync())3431 {3432 await Task.Run(() => Assert.True(this.asyncLock.IsReadLockHeld, "MTA should be told it holds a read lock."));3433 }3434 }3435 /// <summary>Verifies that when an MTA holding a lock traverses (via CallContext) to an STA that the STA does not appear to hold a lock.</summary>3436 [SkippableFact]3437 public async Task MtaLockNotSharedWithSta()3438 {3439 Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));3440 using (await this.asyncLock.ReadLockAsync())3441 {3442 var testComplete = new TaskCompletionSource<object?>();3443 Thread staThread = new Thread((ThreadStart)delegate3444 {3445 try3446 {3447 Assert.False(this.asyncLock.IsReadLockHeld, "STA should not be told it holds a read lock.");3448 testComplete.SetAsync();3449 }3450 catch (Exception ex)3451 {3452 testComplete.TrySetException(ex);3453 }3454 });3455 staThread.SetApartmentState(ApartmentState.STA);3456 staThread.Start();3457 await testComplete.Task;3458 }3459 }3460 /// <summary>Verifies that when an MTA holding a lock traverses (via CallContext) to an STA that the STA will be able to access the same lock by marshaling back to an MTA.</summary>3461 [SkippableFact]3462 public async Task ReadLockTraversesAcrossSta()3463 {3464 Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));3465 using (await this.asyncLock.ReadLockAsync())3466 {3467 var testComplete = new TaskCompletionSource<object?>();3468 Thread staThread = new Thread((ThreadStart)delegate3469 {3470 try3471 {3472 Assert.False(this.asyncLock.IsReadLockHeld, "STA should not be told it holds a read lock.");3473 Thread mtaThread = new Thread((ThreadStart)delegate3474 {3475 try3476 {3477 Assert.True(this.asyncLock.IsReadLockHeld, "MTA thread couldn't access lock across STA.");3478 testComplete.SetAsync();3479 }3480 catch (Exception ex)3481 {3482 testComplete.TrySetException(ex);3483 }3484 });3485 mtaThread.SetApartmentState(ApartmentState.MTA);3486 mtaThread.Start();3487 }3488 catch (Exception ex)3489 {3490 testComplete.TrySetException(ex);3491 }3492 });3493 staThread.SetApartmentState(ApartmentState.STA);3494 staThread.Start();3495 await testComplete.Task;3496 }3497 }3498 /// <summary>Verifies that when an MTA holding a lock traverses (via CallContext) to an STA that the STA will be able to access the same lock by requesting it and moving back to an MTA.</summary>3499 [SkippableFact]3500 public async Task UpgradeableReadLockTraversesAcrossSta()3501 {3502 Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));3503 using (await this.asyncLock.UpgradeableReadLockAsync())3504 {3505 var testComplete = new TaskCompletionSource<object?>();3506 Thread staThread = new Thread((ThreadStart)delegate3507 {3508 try3509 {3510 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld, "STA should not be told it holds an upgradeable read lock.");3511 Task.Run(async delegate3512 {3513 try3514 {3515 using (await this.asyncLock.UpgradeableReadLockAsync())3516 {3517 Assert.True(this.asyncLock.IsUpgradeableReadLockHeld, "MTA thread couldn't access lock across STA.");3518 }3519 testComplete.SetAsync().Forget();3520 }3521 catch (Exception ex)3522 {3523 testComplete.TrySetException(ex);3524 }3525 });3526 }3527 catch (Exception ex)3528 {3529 testComplete.TrySetException(ex);3530 }3531 });3532 staThread.SetApartmentState(ApartmentState.STA);3533 staThread.Start();3534 await testComplete.Task;3535 }3536 }3537 /// <summary>Verifies that when an MTA holding a lock traverses (via CallContext) to an STA that the STA will be able to access the same lock by requesting it and moving back to an MTA.</summary>3538 [SkippableFact]3539 public async Task WriteLockTraversesAcrossSta()3540 {3541 Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));3542 using (await this.asyncLock.WriteLockAsync())3543 {3544 var testComplete = new TaskCompletionSource<object?>();3545 Thread staThread = new Thread((ThreadStart)delegate3546 {3547 try3548 {3549 Assert.False(this.asyncLock.IsWriteLockHeld, "STA should not be told it holds an upgradeable read lock.");3550 Task.Run(async delegate3551 {3552 try3553 {3554 using (await this.asyncLock.WriteLockAsync())3555 {3556 Assert.True(this.asyncLock.IsWriteLockHeld, "MTA thread couldn't access lock across STA.");3557 }3558 testComplete.SetAsync().Forget();3559 }3560 catch (Exception ex)3561 {3562 testComplete.TrySetException(ex);3563 }3564 });3565 }3566 catch (Exception ex)3567 {3568 testComplete.TrySetException(ex);3569 }3570 });3571 staThread.SetApartmentState(ApartmentState.STA);3572 staThread.Start();3573 await testComplete.Task;3574 }3575 }3576 [Fact]3577 public async Task NestedLocksScenarios()3578 {3579 // R = Reader, U = non-sticky Upgradeable reader, S = Sticky upgradeable reader, W = Writer3580 var scenarios = new Dictionary<string, bool>3581 {3582 { "RU", false }, // false means this lock sequence should throw at the last step.3583 { "RS", false },3584 { "RW", false },3585 // Legal simple nested locks3586 { "RRR", true },3587 { "UUU", true },3588 { "SSS", true },3589 { "WWW", true },3590 // Legal interleaved nested locks3591 { "WRW", true },3592 { "UW", true },3593 { "UWW", true },3594 { "URW", true },3595 { "UWR", true },3596 { "UURRWW", true },3597 { "WUW", true },3598 { "WRRUW", true },3599 { "SW", true },3600 { "USW", true },3601 { "WSWRU", true },3602 { "WSRW", true },3603 { "SUSURWR", true },3604 { "USUSRWR", true },3605 };3606 foreach (KeyValuePair<string, bool> scenario in scenarios)3607 {3608 this.Logger.WriteLine("Testing {1} scenario: {0}", scenario.Key, scenario.Value ? "valid" : "invalid");3609 await this.NestedLockHelper(scenario.Key, scenario.Value);3610 }3611 }3612 [Fact]3613 public async Task AmbientLockReflectsCurrentLock()3614 {3615 var asyncLock = new LockDerived();3616 using (await asyncLock.UpgradeableReadLockAsync())3617 {3618 Assert.True(asyncLock.AmbientLockInternal.IsUpgradeableReadLock);3619 using (await asyncLock.WriteLockAsync())3620 {3621 Assert.True(asyncLock.AmbientLockInternal.IsWriteLock);3622 }3623 Assert.True(asyncLock.AmbientLockInternal.IsUpgradeableReadLock);3624 }3625 }3626 [Fact]3627 public async Task WriteLockForksAndAsksForReadLock()3628 {3629 using (TestUtilities.DisableAssertionDialog())3630 {3631 using (await this.asyncLock.WriteLockAsync())3632 {3633 await Task.Run(async delegate3634 {3635 // This throws because it's a dangerous pattern for a read lock to fork off3636 // from a write lock. For instance, if this Task.Run hadn't had an "await"3637 // in front of it (which the lock can't have insight into), then this would3638 // represent concurrent execution within what should be an exclusive lock.3639 // Also, if the read lock out-lived the nesting write lock, we'd have real3640 // trouble on our hands as it's impossible to prepare resources for concurrent3641 // access (as the exclusive lock gets released) while an existing read lock is held.3642 await Assert.ThrowsAsync<InvalidOperationException>(async delegate3643 {3644 using (await this.asyncLock.ReadLockAsync())3645 {3646 }3647 });3648 });3649 }3650 }3651 }3652 [Fact]3653 public async Task WriteNestsReadWithWriteReleasedFirst()3654 {3655 await Assert.ThrowsAsync<InvalidOperationException>(async delegate3656 {3657 using (TestUtilities.DisableAssertionDialog())3658 {3659 var readLockAcquired = new AsyncManualResetEvent();3660 var readLockReleased = new AsyncManualResetEvent();3661 var writeLockCallbackBegun = new AsyncManualResetEvent();3662 var asyncLock = new LockDerived();3663 this.asyncLock = asyncLock;3664 Task readerTask;3665 using (AsyncReaderWriterLock.Releaser access = await this.asyncLock.WriteLockAsync())3666 {3667 asyncLock.OnExclusiveLockReleasedAsyncDelegate = async delegate3668 {3669 writeLockCallbackBegun.Set();3670 // Stay in the critical region until the read lock has been released.3671 await readLockReleased;3672 };3673 // Kicking off a concurrent task while holding a write lock is not allowed3674 // unless the original execution awaits that task. Otherwise, it introduces3675 // concurrency in what is supposed to be an exclusive lock.3676 // So what we're doing here is Bad, but it's how we get the repro.3677 readerTask = Task.Run(async delegate3678 {3679 try3680 {3681 using (await this.asyncLock.ReadLockAsync())3682 {3683 readLockAcquired.Set();3684 // Hold the read lock until the lock class has entered the3685 // critical region called reenterConcurrencyPrep.3686 await writeLockCallbackBegun;3687 }3688 }3689 finally3690 {3691 // Signal the read lock is released. Actually, it may not have been3692 // (if a bug causes the read lock release call to throw and the lock gets3693 // orphaned), but we want to avoid a deadlock in the test itself.3694 // If an exception was thrown, the test will still fail because we rethrow it.3695 readLockAcquired.Set();3696 readLockReleased.Set();3697 }3698 });3699 // Don't release the write lock until the nested read lock has been acquired.3700 await readLockAcquired;3701 }3702 // Wait for, and rethrow any exceptions from our child task.3703 await readerTask;3704 }3705 });3706 }3707 [Fact]3708 public async Task WriteNestsReadWithWriteReleasedFirstWithoutTaskRun()3709 {3710 using (TestUtilities.DisableAssertionDialog())3711 {3712 var readLockReleased = new AsyncManualResetEvent();3713 var writeLockCallbackBegun = new AsyncManualResetEvent();3714 var asyncLock = new LockDerived();3715 this.asyncLock = asyncLock;3716 Task writeLockReleaseTask;3717 AsyncReaderWriterLock.Releaser writerLock = await this.asyncLock.WriteLockAsync();3718 asyncLock.OnExclusiveLockReleasedAsyncDelegate = async delegate3719 {3720 writeLockCallbackBegun.Set();3721 // Stay in the critical region until the read lock has been released.3722 await readLockReleased;3723 };3724 AsyncReaderWriterLock.Releaser readerLock = await this.asyncLock.ReadLockAsync();3725 // This is really unnatural, to release a lock without awaiting it.3726 // In fact I daresay we could call it illegal.3727 // It is critical for the repro that code either execute concurrently3728 // or that we don't await while releasing this lock.3729 writeLockReleaseTask = writerLock.ReleaseAsync();3730 // Hold the read lock until the lock class has entered the3731 // critical region called reenterConcurrencyPrep.3732 Task completingTask = await Task.WhenAny(writeLockCallbackBegun.WaitAsync(), writeLockReleaseTask);3733 try3734 {3735 await completingTask; // observe any exception.3736 Assert.True(false, "Expected exception not thrown.");3737 }3738 catch (CriticalErrorException ex)3739 {3740 Assert.True(asyncLock.CriticalErrorDetected, "The lock should have raised a critical error.");3741 Assert.IsType<InvalidOperationException>(ex.InnerException);3742 return; // the test is over3743 }3744 // The rest of this never executes, but serves to illustrate the anti-pattern that lock users3745 // may try to use, that this test verifies the lock detects and throws exceptions about.3746 await readerLock.ReleaseAsync();3747 readLockReleased.Set();3748 await writerLock.ReleaseAsync();3749 // Wait for, and rethrow any exceptions from our child task.3750 await writeLockReleaseTask;3751 }3752 }3753 [Fact]3754 public void SetLockDataWithoutLock()3755 {3756 var lck = new LockDerived();3757 Assert.Throws<InvalidOperationException>(() => lck.SetLockData(null));3758 }3759 [Fact]3760 public void GetLockDataWithoutLock()3761 {3762 var lck = new LockDerived();3763 Assert.Null(lck.GetLockData());3764 }3765 [Fact]3766 public async Task SetLockDataNoLock()3767 {3768 var lck = new LockDerived();3769 using (await lck.WriteLockAsync())3770 {3771 lck.SetLockData(null);3772 Assert.Null(lck.GetLockData());3773 var value1 = new object();3774 lck.SetLockData(value1);3775 Assert.Equal(value1, lck.GetLockData());3776 using (await lck.WriteLockAsync())3777 {3778 Assert.Null(lck.GetLockData());3779 var value2 = new object();3780 lck.SetLockData(value2);3781 Assert.Equal(value2, lck.GetLockData());3782 }3783 Assert.Equal(value1, lck.GetLockData());3784 }3785 }3786 [Fact]3787 public async Task DisposeWhileExclusiveLockContextCaptured()3788 {3789 var signal = new AsyncManualResetEvent();3790 Task helperTask;3791 using (await this.asyncLock.WriteLockAsync())3792 {3793 helperTask = this.DisposeWhileExclusiveLockContextCaptured_HelperAsync(signal);3794 }3795 signal.Set();3796 this.asyncLock.Dispose();3797 await helperTask;3798 }3799 [Fact]3800 public void GetHangReportSimple()3801 {3802 IHangReportContributor reportContributor = this.asyncLock;3803 HangReportContribution? report = reportContributor.GetHangReport();3804 Assert.NotNull(report);3805 Assert.NotNull(report.Content);3806 Assert.NotNull(report.ContentType);3807 Assert.NotNull(report.ContentName);3808 this.Logger.WriteLine(report.Content);3809 }3810 [Fact]3811 public async Task GetHangReportWithReleasedNestingOuterLock()3812 {3813 using (AsyncReaderWriterLock.Releaser lock1 = await this.asyncLock.ReadLockAsync())3814 {3815 using (AsyncReaderWriterLock.Releaser lock2 = await this.asyncLock.ReadLockAsync())3816 {3817 using (AsyncReaderWriterLock.Releaser lock3 = await this.asyncLock.ReadLockAsync())3818 {3819 await lock1.ReleaseAsync();3820 this.PrintHangReport();3821 }3822 }3823 }3824 }3825 [Fact]3826 public async Task GetHangReportWithReleasedNestingMiddleLock()3827 {3828 using (AsyncReaderWriterLock.Releaser lock1 = await this.asyncLock.ReadLockAsync())3829 {3830 using (AsyncReaderWriterLock.Releaser lock2 = await this.asyncLock.ReadLockAsync())3831 {3832 using (AsyncReaderWriterLock.Releaser lock3 = await this.asyncLock.ReadLockAsync())3833 {3834 await lock2.ReleaseAsync();3835 this.PrintHangReport();3836 }3837 }3838 }3839 }3840 [Fact]3841 public async Task GetHangReportWithWriteLockUpgradeWaiting()3842 {3843 var readLockObtained = new AsyncManualResetEvent();3844 var hangReportComplete = new AsyncManualResetEvent();3845 var writerTask = Task.Run(async delegate3846 {3847 using (await this.asyncLock.UpgradeableReadLockAsync())3848 {3849 await readLockObtained;3850 AsyncReaderWriterLock.Awaiter? writeWaiter = this.asyncLock.WriteLockAsync().GetAwaiter();3851 writeWaiter.OnCompleted(delegate3852 {3853 writeWaiter.GetResult().Dispose();3854 });3855 this.PrintHangReport();3856 hangReportComplete.Set();3857 }3858 });3859 using (AsyncReaderWriterLock.Releaser lock1 = await this.asyncLock.ReadLockAsync())3860 {3861 using (AsyncReaderWriterLock.Releaser lock2 = await this.asyncLock.ReadLockAsync())3862 {3863 readLockObtained.Set();3864 await hangReportComplete;3865 }3866 }3867 await writerTask;3868 }3869 [Fact]3870 public async Task ReadLockAsync_Await_CapturesExecutionContext()3871 {3872 var asyncLocal = new Microsoft.VisualStudio.Threading.AsyncLocal<string>();3873 asyncLocal.Value = "expected";3874 using (AsyncReaderWriterLock.Releaser lck = await this.asyncLock.ReadLockAsync())3875 {3876 Assert.Equal("expected", asyncLocal.Value);3877 }3878 }3879 [Fact]3880 public async Task ReadLockAsync_OnCompleted_CapturesExecutionContext()3881 {3882 var asyncLocal = new Microsoft.VisualStudio.Threading.AsyncLocal<string>();3883 asyncLocal.Value = "expected";3884 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.ReadLockAsync().GetAwaiter();3885 Assumes.False(awaiter.IsCompleted);3886 var testResultSource = new TaskCompletionSource<object?>();3887 awaiter.OnCompleted(delegate3888 {3889 try3890 {3891 using (awaiter.GetResult())3892 {3893 Assert.Equal("expected", asyncLocal.Value);3894 testResultSource.SetResult(null);3895 }3896 }3897 catch (Exception ex)3898 {3899 testResultSource.SetException(ex);3900 }3901 finally3902 {3903 }3904 });3905 await testResultSource.Task;3906 }3907 [Fact]3908 public async Task ReadLockAsync_UnsafeOnCompleted_DoesNotCaptureExecutionContext()3909 {3910 var asyncLocal = new Microsoft.VisualStudio.Threading.AsyncLocal<string>();3911 asyncLocal.Value = "expected";3912 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.ReadLockAsync().GetAwaiter();3913 Assumes.False(awaiter.IsCompleted);3914 var testResultSource = new TaskCompletionSource<object?>();3915 awaiter.UnsafeOnCompleted(delegate3916 {3917 try3918 {3919 using (awaiter.GetResult())3920 {3921 Assert.Null(asyncLocal.Value);3922 testResultSource.SetResult(null);3923 }3924 }3925 catch (Exception ex)3926 {3927 testResultSource.SetException(ex);3928 }3929 finally3930 {3931 }3932 });3933 await testResultSource.Task;3934 }3935 [Fact]3936 public async Task ReadLockAsync_UseTaskScheduler()3937 {3938 var asyncLock = new AsyncReaderWriterLockWithSpecialScheduler();3939 Assumes.Equals(0, asyncLock.StartedTaskCount);3940 // A reader lock issued immediately will not be rescheduled.3941 using (await asyncLock.ReadLockAsync())3942 {3943 }3944 Assumes.Equals(0, asyncLock.StartedTaskCount);3945 var writeLockObtained = new AsyncManualResetEvent();3946 var readLockObtained = new AsyncManualResetEvent();3947 var writeLockToRelease = new AsyncManualResetEvent();3948 var writeLockTask = Task.Run(async () =>3949 {3950 using (await asyncLock.WriteLockAsync())3951 {3952 // Write lock is not scheduled through the read lock scheduler.3953 Assumes.Equals(0, asyncLock.StartedTaskCount);3954 writeLockObtained.Set();3955 await writeLockToRelease.WaitAsync();3956 }3957 });3958 await writeLockObtained.WaitAsync();3959 var readLockTask = Task.Run(async () =>3960 {3961 using (await asyncLock.ReadLockAsync())3962 {3963 // Newly issued read lock is using the task scheduler.3964 Assumes.Equals(1, asyncLock.StartedTaskCount);3965 readLockObtained.Set();3966 }3967 });3968 await asyncLock.ScheduleSemaphore.WaitAsync();3969 writeLockToRelease.Set();3970 await writeLockTask;3971 await readLockTask;3972 Assumes.Equals(1, asyncLock.StartedTaskCount);3973 }3974 [Fact]3975 public void Disposable()3976 {3977 IDisposable disposable = this.asyncLock;3978 disposable.Dispose();3979 }3980 private async Task DisposeWhileExclusiveLockContextCaptured_HelperAsync(AsyncManualResetEvent signal)3981 {3982 await signal;3983 await Task.Yield();3984 }3985 private void PrintHangReport()3986 {3987 IHangReportContributor reportContributor = this.asyncLock;3988 HangReportContribution? report = reportContributor.GetHangReport();3989 this.Logger.WriteLine(report.Content);3990 }3991 private void LockReleaseTestHelper(AsyncReaderWriterLock.Awaitable initialLock)3992 {3993 TestUtilities.Run(async delegate3994 {3995 var staScheduler = TaskScheduler.FromCurrentSynchronizationContext();3996 var initialLockHeld = new TaskCompletionSource<object?>();3997 var secondLockInQueue = new TaskCompletionSource<object?>();3998 var secondLockObtained = new TaskCompletionSource<object?>();3999 await Task.WhenAll(4000 Task.Run(async delegate4001 {4002 using (await initialLock)4003 {4004 await initialLockHeld.SetAsync();4005 await secondLockInQueue.Task;4006 await staScheduler;4007 }4008 }),4009 Task.Run(async delegate4010 {4011 await initialLockHeld.Task;4012 AsyncReaderWriterLock.Awaiter? awaiter = this.asyncLock.WriteLockAsync().GetAwaiter();4013 Assert.False(awaiter.IsCompleted);4014 awaiter.OnCompleted(delegate4015 {4016 using (awaiter.GetResult())4017 {4018 try4019 {4020 Assert.Equal(ApartmentState.MTA, Thread.CurrentThread.GetApartmentState());4021 secondLockObtained.SetAsync();4022 }4023 catch (Exception ex)4024 {4025 secondLockObtained.SetException(ex);4026 }4027 }4028 });4029 await secondLockInQueue.SetAsync();4030 }),4031 secondLockObtained.Task);4032 });4033 }4034 private Task UncontestedTopLevelLocksAllocFreeHelperAsync(Func<AsyncReaderWriterLock.Awaitable> locker, bool yieldingLock)4035 {4036 // Get on an MTA thread so that locks do not necessarily yield.4037 return Task.Run(async delegate4038 {4039 // First prime the pump to allocate some fixed cost memory.4040 using (await locker())4041 {4042 }4043 // This test is rather rough. So we're willing to try it a few times in order to observe the desired value.4044 bool passingAttemptObserved = false;4045 for (int attempt = 0; !passingAttemptObserved && attempt < GCAllocationAttempts; attempt++)4046 {4047 const int iterations = 1000;4048 long memory1 = GC.GetTotalMemory(true);4049 for (int i = 0; i < iterations; i++)4050 {4051 using (await locker())4052 {4053 }4054 }4055 long memory2 = GC.GetTotalMemory(false);4056 long allocated = (memory2 - memory1) / iterations;4057 long allowed = 300 + MaxGarbagePerLock + (yieldingLock ? MaxGarbagePerYield : 0);4058 this.Logger.WriteLine("Allocated bytes: {0} ({1} allowed)", allocated, allowed);4059 passingAttemptObserved = allocated <= allowed;4060 }4061 Assert.True(passingAttemptObserved);4062 });4063 }4064 private Task NestedLocksAllocFreeHelperAsync(Func<AsyncReaderWriterLock.Awaitable> locker, bool yieldingLock)4065 {4066 // Get on an MTA thread so that locks do not necessarily yield.4067 return Task.Run(async delegate4068 {4069 // First prime the pump to allocate some fixed cost memory.4070 using (await locker())4071 {4072 using (await locker())4073 {4074 using (await locker())4075 {4076 }4077 }4078 }4079 // This test is rather rough. So we're willing to try it a few times in order to observe the desired value.4080 bool passingAttemptObserved = false;4081 for (int attempt = 0; !passingAttemptObserved && attempt < GCAllocationAttempts; attempt++)4082 {4083 const int iterations = 1000;4084 long memory1 = GC.GetTotalMemory(true);4085 for (int i = 0; i < iterations; i++)4086 {4087 using (await locker())4088 {4089 using (await locker())4090 {4091 using (await locker())4092 {4093 }4094 }4095 }4096 }4097 long memory2 = GC.GetTotalMemory(false);4098 long allocated = (memory2 - memory1) / iterations;4099 const int NestingLevel = 3;4100 long allowed = (MaxGarbagePerLock * NestingLevel) + (yieldingLock ? MaxGarbagePerYield : 0);4101 this.Logger.WriteLine("Allocated bytes: {0} ({1} allowed)", allocated, allowed);4102 passingAttemptObserved = allocated <= allowed;4103 }4104 Assert.True(passingAttemptObserved);4105 });4106 }4107 private Task UncontestedTopLevelLocksAllocFreeHelperAsync(Func<AsyncReaderWriterLock.Releaser> locker)4108 {4109 // Get on an MTA thread so that locks do not necessarily yield.4110 return Task.Run(delegate4111 {4112 // First prime the pump to allocate some fixed cost memory.4113 using (locker())4114 {4115 }4116 // This test is rather rough. So we're willing to try it a few times in order to observe the desired value.4117 bool passingAttemptObserved = false;4118 for (int attempt = 0; !passingAttemptObserved && attempt < GCAllocationAttempts; attempt++)4119 {4120 const int iterations = 1000;4121 long memory1 = GC.GetTotalMemory(true);4122 for (int i = 0; i < iterations; i++)4123 {4124 using (locker())4125 {4126 }4127 }4128 long memory2 = GC.GetTotalMemory(false);4129 long allocated = (memory2 - memory1) / iterations;4130 this.Logger.WriteLine("Allocated bytes: {0}", allocated);4131 passingAttemptObserved = allocated <= MaxGarbagePerLock;4132 }4133 Assert.True(passingAttemptObserved);4134 });4135 }4136 private Task NestedLocksAllocFreeHelperAsync(Func<AsyncReaderWriterLock.Releaser> locker)4137 {4138 // Get on an MTA thread so that locks do not necessarily yield.4139 return Task.Run(delegate4140 {4141 // First prime the pump to allocate some fixed cost memory.4142 using (locker())4143 {4144 using (locker())4145 {4146 using (locker())4147 {4148 }4149 }4150 }4151 // This test is rather rough. So we're willing to try it a few times in order to observe the desired value.4152 bool passingAttemptObserved = false;4153 for (int attempt = 0; !passingAttemptObserved && attempt < GCAllocationAttempts; attempt++)4154 {4155 const int iterations = 1000;4156 long memory1 = GC.GetTotalMemory(true);4157 for (int i = 0; i < iterations; i++)4158 {4159 using (locker())4160 {4161 using (locker())4162 {4163 using (locker())4164 {4165 }4166 }4167 }4168 }4169 long memory2 = GC.GetTotalMemory(false);4170 long allocated = (memory2 - memory1) / iterations;4171 this.Logger.WriteLine("Allocated bytes: {0}", allocated);4172 const int NestingLevel = 3;4173 passingAttemptObserved = allocated <= MaxGarbagePerLock * NestingLevel;4174 }4175 Assert.True(passingAttemptObserved);4176 });4177 }4178 private async Task NestedLockHelper(string lockScript, bool validScenario)4179 {4180 Assert.False(this.asyncLock.IsReadLockHeld, "IsReadLockHeld not expected value.");4181 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld, "IsUpgradeableReadLockHeld not expected value.");4182 Assert.False(this.asyncLock.IsWriteLockHeld, "IsWriteLockHeld not expected value.");4183 var lockStack = new Stack<AsyncReaderWriterLock.Releaser>(lockScript.Length);4184 int readers = 0, nonStickyUpgradeableReaders = 0, stickyUpgradeableReaders = 0, writers = 0;4185 try4186 {4187 bool success = true;4188 for (int i = 0; i < lockScript.Length; i++)4189 {4190 char lockTypeChar = lockScript[i];4191 AsyncReaderWriterLock.Awaitable asyncLock;4192 try4193 {4194 switch (lockTypeChar)4195 {4196 case ReadChar:4197 asyncLock = this.asyncLock.ReadLockAsync();4198 readers++;4199 break;4200 case UpgradeableReadChar:4201 asyncLock = this.asyncLock.UpgradeableReadLockAsync();4202 nonStickyUpgradeableReaders++;4203 break;4204 case StickyUpgradeableReadChar:4205 asyncLock = this.asyncLock.UpgradeableReadLockAsync(AsyncReaderWriterLock.LockFlags.StickyWrite);4206 stickyUpgradeableReaders++;4207 break;4208 case WriteChar:4209 asyncLock = this.asyncLock.WriteLockAsync();4210 writers++;4211 break;4212 default:4213 throw new ArgumentOutOfRangeException(nameof(lockScript), "Unexpected lock type character '" + lockTypeChar + "'.");4214 }4215 lockStack.Push(await asyncLock);4216 success = true;4217 }4218 catch (InvalidOperationException)4219 {4220 if (i < lockScript.Length - 1)4221 {4222 // A failure prior to the last lock in the sequence is always a failure.4223 throw;4224 }4225 success = false;4226 }4227 AssertEx.Equal(readers > 0, this.asyncLock.IsReadLockHeld, "IsReadLockHeld not expected value at step {0}.", i + 1);4228 AssertEx.Equal(nonStickyUpgradeableReaders + stickyUpgradeableReaders > 0, this.asyncLock.IsUpgradeableReadLockHeld, "IsUpgradeableReadLockHeld not expected value at step {0}.", i + 1);4229 AssertEx.Equal(writers > 0, this.asyncLock.IsWriteLockHeld, "IsWriteLockHeld not expected value at step {0}.", i + 1);4230 }4231 AssertEx.Equal(success, validScenario, "Scenario validity unexpected.");4232 int readersRemaining = readers;4233 int nonStickyUpgradeableReadersRemaining = nonStickyUpgradeableReaders;4234 int stickyUpgradeableReadersRemaining = stickyUpgradeableReaders;4235 int writersRemaining = writers;4236 int countFrom = lockScript.Length - 1;4237 if (!validScenario)4238 {4239 countFrom--;4240 }4241 for (int i = countFrom; i >= 0; i--)4242 {4243 char lockTypeChar = lockScript[i];4244 lockStack.Pop().Dispose();4245 switch (lockTypeChar)4246 {4247 case ReadChar:4248 readersRemaining--;4249 break;4250 case UpgradeableReadChar:4251 nonStickyUpgradeableReadersRemaining--;4252 break;4253 case StickyUpgradeableReadChar:4254 stickyUpgradeableReadersRemaining--;4255 break;4256 case WriteChar:4257 writersRemaining--;4258 break;4259 default:4260 throw new ArgumentOutOfRangeException(nameof(lockScript), "Unexpected lock type character '" + lockTypeChar + "'.");4261 }4262 AssertEx.Equal(readersRemaining > 0, this.asyncLock.IsReadLockHeld, "IsReadLockHeld not expected value at step -{0}.", i + 1);4263 AssertEx.Equal(nonStickyUpgradeableReadersRemaining + stickyUpgradeableReadersRemaining > 0, this.asyncLock.IsUpgradeableReadLockHeld, "IsUpgradeableReadLockHeld not expected value at step -{0}.", i + 1);4264 AssertEx.Equal(writersRemaining > 0 || (stickyUpgradeableReadersRemaining > 0 && writers > 0), this.asyncLock.IsWriteLockHeld, "IsWriteLockHeld not expected value at step -{0}.", i + 1);4265 }4266 }4267 catch4268 {4269 while (lockStack.Count > 0)4270 {4271 lockStack.Pop().Dispose();4272 }4273 throw;4274 }4275 Assert.False(this.asyncLock.IsReadLockHeld, "IsReadLockHeld not expected value.");4276 Assert.False(this.asyncLock.IsUpgradeableReadLockHeld, "IsUpgradeableReadLockHeld not expected value.");4277 Assert.False(this.asyncLock.IsWriteLockHeld, "IsWriteLockHeld not expected value.");4278 }4279 private async Task CheckContinuationsConcurrencyHelper()4280 {4281 bool hasReadLock = this.asyncLock.IsReadLockHeld;4282 bool hasUpgradeableReadLock = this.asyncLock.IsUpgradeableReadLockHeld;4283 bool hasWriteLock = this.asyncLock.IsWriteLockHeld;4284 bool concurrencyExpected = !(hasWriteLock || hasUpgradeableReadLock);4285 TimeSpan signalAndWaitDelay = concurrencyExpected ? UnexpectedTimeout : TimeSpan.FromMilliseconds(AsyncDelay / 2);4286 var barrier = new Barrier(2); // we use a *synchronous* style Barrier since we are deliberately measuring multi-thread concurrency4287 Func<Task> worker = async delegate4288 {4289 await Task.Yield();4290 Assert.Equal(hasReadLock, this.asyncLock.IsReadLockHeld);4291 Assert.Equal(hasUpgradeableReadLock, this.asyncLock.IsUpgradeableReadLockHeld);4292 Assert.Equal(hasWriteLock, this.asyncLock.IsWriteLockHeld);4293 AssertEx.Equal(concurrencyExpected, barrier.SignalAndWait(signalAndWaitDelay), "Actual vs. expected ({0}) concurrency did not match.", concurrencyExpected);4294 await Task.Yield(); // this second yield is useful to check that the magic works across multiple continuations.4295 Assert.Equal(hasReadLock, this.asyncLock.IsReadLockHeld);4296 Assert.Equal(hasUpgradeableReadLock, this.asyncLock.IsUpgradeableReadLockHeld);4297 Assert.Equal(hasWriteLock, this.asyncLock.IsWriteLockHeld);4298 AssertEx.Equal(concurrencyExpected, barrier.SignalAndWait(signalAndWaitDelay), "Actual vs. expected ({0}) concurrency did not match.", concurrencyExpected);4299 };4300 var asyncFuncs = new Func<Task>[] { worker, worker };4301 // This idea of kicking off lots of async tasks and then awaiting all of them is a common4302 // pattern in async code. The async lock should protect against the continuations accidentally4303 // running concurrently, thereby forking the write lock across multiple threads.4304 await Task.WhenAll(asyncFuncs.Select(f => f())).ConfigureAwaitForAggregateException(false);4305 }4306 private async Task CheckContinuationsConcurrencyBeforeYieldHelper()4307 {4308 bool hasReadLock = this.asyncLock.IsReadLockHeld;4309 bool hasUpgradeableReadLock = this.asyncLock.IsUpgradeableReadLockHeld;4310 bool hasWriteLock = this.asyncLock.IsWriteLockHeld;4311 bool concurrencyExpected = !(hasWriteLock || hasUpgradeableReadLock);4312 bool primaryCompleted = false;4313 Func<Task> worker = async delegate4314 {4315 await Task.Yield();4316 // By the time this continuation executes,4317 // our caller should have already yielded after signaling the barrier.4318 Assert.True(primaryCompleted);4319 Assert.Equal(hasReadLock, this.asyncLock.IsReadLockHeld);4320 Assert.Equal(hasUpgradeableReadLock, this.asyncLock.IsUpgradeableReadLockHeld);4321 Assert.Equal(hasWriteLock, this.asyncLock.IsWriteLockHeld);4322 };4323 Task? workerTask = worker();4324 Thread.Sleep(AsyncDelay); // give the worker plenty of time to execute if it's going to. (we don't expect it)4325 Assert.False(workerTask.IsCompleted);4326 Assert.Equal(hasReadLock, this.asyncLock.IsReadLockHeld);4327 Assert.Equal(hasUpgradeableReadLock, this.asyncLock.IsUpgradeableReadLockHeld);4328 Assert.Equal(hasWriteLock, this.asyncLock.IsWriteLockHeld);4329 // *now* wait for the worker in a yielding fashion.4330 primaryCompleted = true;4331 await workerTask;4332 }4333 private async Task StressHelper(int maxLockAcquisitions, int maxLockHeldDelay, int overallTimeout, int iterationTimeout, int maxWorkers, bool testCancellation)4334 {4335 var overallCancellation = new CancellationTokenSource(overallTimeout);4336 const int MaxDepth = 5;4337 int lockAcquisitions = 0;4338 while (!overallCancellation.IsCancellationRequested)4339 {4340 // Construct a cancellation token that is canceled when either the overall or the iteration timeout has expired.4341 var cancellation = CancellationTokenSource.CreateLinkedTokenSource(4342 overallCancellation.Token,4343 new CancellationTokenSource(iterationTimeout).Token);4344 CancellationToken token = testCancellation ? cancellation.Token : CancellationToken.None;4345 Func<Task> worker = async delegate4346 {4347 var random = new Random();4348 var lockStack = new Stack<AsyncReaderWriterLock.Releaser>(MaxDepth);4349 while (testCancellation || !cancellation.Token.IsCancellationRequested)4350 {4351 string log = string.Empty;4352 Assert.False(this.asyncLock.IsReadLockHeld || this.asyncLock.IsUpgradeableReadLockHeld || this.asyncLock.IsWriteLockHeld);4353 int depth = random.Next(MaxDepth) + 1;4354 int kind = random.Next(3);4355 try4356 {4357 switch (kind)4358 {4359 case 0: // read4360 while (depth-- > 0)4361 {4362 log += ReadChar;4363 lockStack.Push(await this.asyncLock.ReadLockAsync(token));4364 }4365 break;4366 case 1: // upgradeable read4367 log += UpgradeableReadChar;4368 lockStack.Push(await this.asyncLock.UpgradeableReadLockAsync(token));4369 depth--;4370 while (depth-- > 0)4371 {4372 switch (random.Next(3))4373 {4374 case 0:4375 log += ReadChar;4376 lockStack.Push(await this.asyncLock.ReadLockAsync(token));4377 break;4378 case 1:4379 log += UpgradeableReadChar;4380 lockStack.Push(await this.asyncLock.UpgradeableReadLockAsync(token));4381 break;4382 case 2:4383 log += WriteChar;4384 lockStack.Push(await this.asyncLock.WriteLockAsync(token));4385 break;4386 }4387 }4388 break;4389 case 2: // write4390 log += WriteChar;4391 lockStack.Push(await this.asyncLock.WriteLockAsync(token));4392 depth--;4393 while (depth-- > 0)4394 {4395 switch (random.Next(3))4396 {4397 case 0:4398 log += ReadChar;4399 lockStack.Push(await this.asyncLock.ReadLockAsync(token));4400 break;4401 case 1:4402 log += UpgradeableReadChar;4403 lockStack.Push(await this.asyncLock.UpgradeableReadLockAsync(token));4404 break;4405 case 2:4406 log += WriteChar;4407 lockStack.Push(await this.asyncLock.WriteLockAsync(token));4408 break;4409 }4410 }4411 break;4412 }4413 await Task.Delay(random.Next(maxLockHeldDelay));4414 }4415 finally4416 {4417 log += " ";4418 while (lockStack.Count > 0)4419 {4420 if (Interlocked.Increment(ref lockAcquisitions) > maxLockAcquisitions && maxLockAcquisitions > 0)4421 {4422 cancellation.Cancel();4423 }4424 AsyncReaderWriterLock.Releaser releaser = lockStack.Pop();4425 log += '_';4426 releaser.Dispose();4427 }4428 }4429 }4430 };4431 await Task.Run(async delegate4432 {4433 var workers = new Task[maxWorkers];4434 for (int i = 0; i < workers.Length; i++)4435 {4436 workers[i] = Task.Run(() => worker(), cancellation.Token);4437 Task? nowait = workers[i].ContinueWith(_ => cancellation.Cancel(), TaskContinuationOptions.OnlyOnFaulted);4438 }4439 try4440 {4441 await Task.WhenAll(workers);4442 }4443 catch (OperationCanceledException)4444 {4445 }4446 finally4447 {4448 this.Logger.WriteLine("Stress tested {0} lock acquisitions.", lockAcquisitions);4449 }4450 });4451 }4452 }4453 private async Task MitigationAgainstAccidentalLockForkingHelper(Func<AsyncReaderWriterLock.Awaitable> locker)4454 {4455 Action<bool> test = successExpected =>4456 {4457 try4458 {4459 bool dummy = this.asyncLock.IsReadLockHeld;4460 if (!successExpected)4461 {4462 Assert.True(false, "Expected exception not thrown.");4463 }4464 }4465 catch (Exception ex)4466 {4467 if (ex is XunitException || ex is CriticalErrorException)4468 {4469 throw;4470 }4471 }4472 try4473 {4474 bool dummy = this.asyncLock.IsUpgradeableReadLockHeld;4475 if (!successExpected)4476 {4477 Assert.True(false, "Expected exception not thrown.");4478 }4479 }4480 catch (Exception ex)4481 {4482 if (ex is XunitException || ex is CriticalErrorException)4483 {4484 throw;4485 }4486 }4487 try4488 {4489 bool dummy = this.asyncLock.IsWriteLockHeld;4490 if (!successExpected)4491 {4492 Assert.True(false, "Expected exception not thrown.");4493 }4494 }4495 catch (Exception ex)4496 {4497 if (ex is XunitException || ex is CriticalErrorException)4498 {4499 throw;4500 }4501 }4502 };4503 Func<Task> helper = async delegate4504 {4505 test(false);4506 AsyncReaderWriterLock.Releaser releaser = default(AsyncReaderWriterLock.Releaser);4507 try4508 {4509 releaser = await locker();4510 Assert.True(false, "Expected exception not thrown.");4511 }4512 catch (Exception ex)4513 {4514 if (ex is XunitException || ex is CriticalErrorException)4515 {4516 throw;4517 }4518 }4519 finally4520 {4521 releaser.Dispose();4522 }4523 };4524 using (TestUtilities.DisableAssertionDialog())4525 {4526 using (await locker())4527 {4528 test(true);4529 await Task.Run(helper);4530 using (await this.asyncLock.ReadLockAsync())4531 {4532 await Task.Run(helper);4533 }4534 }4535 }4536 }4537#if NETFRAMEWORK4538 private class OtherDomainProxy : MarshalByRefObject4539 {4540 internal void SomeMethod(int callingAppDomainId)4541 {4542 AssertEx.NotEqual(callingAppDomainId, AppDomain.CurrentDomain.Id, "AppDomain boundaries not crossed.");4543 }4544 }4545#endif4546 private class LockDerived : AsyncReaderWriterLock4547 {4548 internal bool CriticalErrorDetected { get; set; }4549 internal Func<Task>? OnBeforeExclusiveLockReleasedAsyncDelegate { get; set; }4550 internal Func<Task>? OnExclusiveLockReleasedAsyncDelegate { get; set; }4551 internal Func<Task>? OnBeforeLockReleasedAsyncDelegate { get; set; }4552 internal Action? OnUpgradeableReadLockReleasedDelegate { get; set; }4553 internal new bool IsAnyLockHeld4554 {4555 get { return base.IsAnyLockHeld; }4556 }4557 internal InternalLockHandle AmbientLockInternal4558 {4559 get4560 {4561 LockHandle ambient = this.AmbientLock;4562 return new InternalLockHandle(ambient.IsUpgradeableReadLock, ambient.IsWriteLock);4563 }4564 }4565 internal void SetLockData(object? data)4566 {4567 LockHandle lck = this.AmbientLock;4568 lck.Data = data;4569 }4570 internal object? GetLockData()4571 {4572 return this.AmbientLock.Data;4573 }4574 internal bool LockStackContains(LockFlags flags)4575 {4576 return this.LockStackContains(flags, this.AmbientLock);4577 }4578 protected override void OnUpgradeableReadLockReleased()4579 {4580 base.OnUpgradeableReadLockReleased();4581 this.OnUpgradeableReadLockReleasedDelegate?.Invoke();4582 }4583 protected override async Task OnBeforeExclusiveLockReleasedAsync()4584 {4585 await Task.Yield();4586 await base.OnBeforeExclusiveLockReleasedAsync();4587 if (this.OnBeforeExclusiveLockReleasedAsyncDelegate is object)4588 {4589 await this.OnBeforeExclusiveLockReleasedAsyncDelegate();4590 }4591 }4592 protected override async Task OnExclusiveLockReleasedAsync()4593 {4594 await base.OnExclusiveLockReleasedAsync();4595 if (this.OnExclusiveLockReleasedAsyncDelegate is object)4596 {4597 await this.OnExclusiveLockReleasedAsyncDelegate();4598 }4599 }4600 protected override async Task OnBeforeLockReleasedAsync(bool exclusiveLockRelease, LockHandle releasingLock)4601 {4602 await base.OnBeforeLockReleasedAsync(exclusiveLockRelease, releasingLock);4603 if (this.OnBeforeLockReleasedAsyncDelegate is object)4604 {4605 await this.OnBeforeLockReleasedAsyncDelegate();4606 }4607 }4608 /// <summary>4609 /// We override this to cause test failures instead of crashing the test runner.4610 /// </summary>4611 protected override Exception OnCriticalFailure(Exception ex)4612 {4613 this.CriticalErrorDetected = true;4614 doNotWaitForLockCompletionAtTestCleanup = true; // we expect this to corrupt the lock.4615 throw new CriticalErrorException(ex);4616 }4617 internal readonly struct InternalLockHandle4618 {4619 internal InternalLockHandle(bool upgradeableRead, bool write)4620 {4621 this.IsUpgradeableReadLock = upgradeableRead;4622 this.IsWriteLock = write;4623 }4624 internal bool IsUpgradeableReadLock { get; }4625 internal bool IsWriteLock { get; }4626 }4627 }4628 private class LockDerivedWriteLockAroundOnBeforeExclusiveLockReleased : AsyncReaderWriterLock4629 {4630 internal AsyncAutoResetEvent OnBeforeExclusiveLockReleasedAsyncInvoked = new AsyncAutoResetEvent();4631 protected override async Task OnBeforeExclusiveLockReleasedAsync()4632 {4633 using (await this.WriteLockAsync())4634 {4635 await base.OnBeforeExclusiveLockReleasedAsync();4636 }4637 this.OnBeforeExclusiveLockReleasedAsyncInvoked.Set();4638 }4639 }4640 private class LockDerivedReadLockAroundOnBeforeExclusiveLockReleased : AsyncReaderWriterLock4641 {4642 protected override async Task OnBeforeExclusiveLockReleasedAsync()4643 {4644 using (await this.ReadLockAsync())4645 {4646 }4647 }4648 }4649 private class ReaderWriterLockWithFastDeadlockCheck : AsyncReaderWriterLock4650 {4651 public ReaderWriterLockWithFastDeadlockCheck(JoinableTaskContext joinableTaskContext)4652 : base(joinableTaskContext)4653 {4654 }4655 protected override TimeSpan DeadlockCheckTimeout { get; } = TimeSpan.FromMilliseconds(50);4656 }4657 private class AsyncReaderWriterLockWithSpecialScheduler : AsyncReaderWriterLock4658 {4659 private readonly SpecialTaskScheduler scheduler;4660 public AsyncReaderWriterLockWithSpecialScheduler()4661 {4662 this.scheduler = new SpecialTaskScheduler(this.ScheduleSemaphore);4663 }4664 public int StartedTaskCount => this.scheduler.StartedTaskCount;4665 public SemaphoreSlim ScheduleSemaphore { get; } = new SemaphoreSlim(0);4666 protected override bool IsUnsupportedSynchronizationContext4667 {4668 get4669 {4670 if (SynchronizationContext.Current == this.scheduler.SynchronizationContext)4671 {4672 return false;4673 }4674 return base.IsUnsupportedSynchronizationContext;4675 }4676 }4677 protected override TaskScheduler GetTaskSchedulerForReadLockRequest()4678 {4679 return this.scheduler;4680 }4681 protected class SpecialTaskScheduler : TaskScheduler4682 {4683 private readonly SemaphoreSlim schedulerSemaphore;4684 private int startedTaskCount;4685 public SpecialTaskScheduler(SemaphoreSlim schedulerSemaphore)4686 {4687 this.schedulerSemaphore = schedulerSemaphore;4688 this.SynchronizationContext = new SpecialSynchorizationContext();4689 }4690 public int StartedTaskCount => this.startedTaskCount;4691 public SynchronizationContext SynchronizationContext { get; }4692 protected override void QueueTask(Task task)4693 {4694 ThreadPool.QueueUserWorkItem(4695 s =>4696 {4697 var tuple = (Tuple<SpecialTaskScheduler, Task>)s!;4698 Interlocked.Increment(ref tuple.Item1.startedTaskCount);4699 SynchronizationContext? originalContext = SynchronizationContext.Current;4700 SynchronizationContext.SetSynchronizationContext(this.SynchronizationContext);4701 tuple.Item1.TryExecuteTask(tuple.Item2);4702 SynchronizationContext.SetSynchronizationContext(originalContext);4703 },4704 new Tuple<SpecialTaskScheduler, Task>(this, task));4705 this.schedulerSemaphore.Release();4706 }4707 protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)4708 {4709 return false;4710 }4711 protected override IEnumerable<Task> GetScheduledTasks()4712 {4713 throw new NotSupportedException();4714 }4715 /// <summary>4716 /// A customized synchroization context to verify that the schedule can use one.4717 /// </summary>4718 private class SpecialSynchorizationContext : SynchronizationContext4719 {4720 }4721 }4722 }4723 private class SelfPreservingSynchronizationContext : SynchronizationContext4724 {4725 public override void Post(SendOrPostCallback d, object? state)4726 {4727 Task.Run(delegate4728 {4729 SynchronizationContext.SetSynchronizationContext(this);4730 d(state);4731 });4732 }4733 }4734 private class CriticalErrorException : Exception4735 {4736 public CriticalErrorException(Exception innerException)4737 : base(innerException?.Message, innerException)4738 {4739 }4740 }4741}...
SubscriptionsFailover.cs
Source:SubscriptionsFailover.cs
...166 var subscription = store.Subscriptions.GetSubscriptionWorker<User>(new SubscriptionWorkerOptions(subscriptionId)167 {168 TimeToWaitBeforeConnectionRetry = TimeSpan.FromSeconds(5)169 });170 subscription.AfterAcknowledgment += b => { reachedMaxDocCountMre.Set(); return Task.CompletedTask; };171 GC.KeepAlive(subscription.Run(x => { }));172 Assert.True(await reachedMaxDocCountMre.WaitAsync(TimeSpan.FromSeconds(60)));173 foreach (var ravenServer in Servers)174 {175 using (ravenServer.ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))176 using (context.OpenReadTransaction())177 {178 Assert.NotNull(ravenServer.ServerStore.Cluster.Read(context, SubscriptionState.GenerateSubscriptionItemKeyName(defaultDatabase, subscriptionId.ToString())));179 }180 }181 await subscription.DisposeAsync();182 var deleteResult = store.Maintenance.Server.Send(new DeleteDatabasesOperation(defaultDatabase, hardDelete: true));183 foreach (var ravenServer in Servers)184 {185 await ravenServer.ServerStore.WaitForCommitIndexChange(RachisConsensus.CommitIndexModification.GreaterOrEqual, deleteResult.RaftCommandIndex + nodesAmount).WaitWithTimeout(TimeSpan.FromSeconds(60));186 }187 await Task.Delay(2000);188 foreach (var ravenServer in Servers)189 {190 using (ravenServer.ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))191 using (context.OpenReadTransaction())192 {193 Assert.Null(ravenServer.ServerStore.Cluster.Read(context, SubscriptionState.GenerateSubscriptionItemKeyName(defaultDatabase, subscriptionId.ToString())));194 }195 }196 }197 }198 [Fact]199 public async Task SetMentorToSubscriptionWithFailover()200 {201 const int nodesAmount = 5;202 var (_, leader) = await CreateRaftCluster(nodesAmount);203 var defaultDatabase = GetDatabaseName();204 await CreateDatabaseInCluster(defaultDatabase, nodesAmount, leader.WebUrl).ConfigureAwait(false);205 string mentor = "C";206 using (var store = new DocumentStore207 {208 Urls = new[] { leader.WebUrl },209 Database = defaultDatabase210 }.Initialize())211 {212 var usersCountInAck = new List<User>();213 var reachedMaxDocCountInAckMre = new AsyncManualResetEvent();214 var usersCountInBatch = new List<User>();215 var reachedMaxDocCountInBatchMre = new AsyncManualResetEvent();216 await GenerateDocuments(store);217 (var subscription, var subsTask) = await CreateAndInitiateSubscription(store, defaultDatabase, usersCountInAck, reachedMaxDocCountInAckMre, usersCountInBatch, reachedMaxDocCountInBatchMre, 20, mentor: mentor);218 Assert.True(await Task.WhenAny(subsTask, reachedMaxDocCountInBatchMre.WaitAsync(_reasonableWaitTime)).WaitWithoutExceptionAsync(_reasonableWaitTime), $"1. Reached in batch {usersCountInBatch.Count}/10");219 Assert.True(await Task.WhenAny(subsTask, reachedMaxDocCountInAckMre.WaitAsync(_reasonableWaitTime)).WaitWithoutExceptionAsync(_reasonableWaitTime), $"1. Reached in ack {usersCountInAck.Count}/10");220 Assert.False(subsTask.IsFaulted, subsTask?.Exception?.ToString());221 usersCountInBatch.Clear();222 reachedMaxDocCountInAckMre.Reset();223 usersCountInAck.Clear();224 reachedMaxDocCountInBatchMre.Reset();225 await KillServerWhereSubscriptionWorks(defaultDatabase, subscription.SubscriptionName);226 await GenerateDocuments(store);227 Assert.True(await Task.WhenAny(subsTask, reachedMaxDocCountInBatchMre.WaitAsync(_reasonableWaitTime)).WaitWithoutExceptionAsync(_reasonableWaitTime), $"2. Reached in batch {usersCountInBatch.Count}/10");228 Assert.True(await Task.WhenAny(subsTask, reachedMaxDocCountInAckMre.WaitAsync(_reasonableWaitTime)).WaitWithoutExceptionAsync(_reasonableWaitTime), $"2. Reached in ack {usersCountInAck.Count}/10");229 Assert.False(subsTask.IsFaulted, subsTask?.Exception?.ToString());230 usersCountInBatch.Clear();231 reachedMaxDocCountInAckMre.Reset();232 usersCountInAck.Clear();233 reachedMaxDocCountInBatchMre.Reset();234 await KillServerWhereSubscriptionWorks(defaultDatabase, subscription.SubscriptionName);235 await GenerateDocuments(store);236 Assert.True(await Task.WhenAny(subsTask, reachedMaxDocCountInBatchMre.WaitAsync(_reasonableWaitTime)).WaitWithoutExceptionAsync(_reasonableWaitTime), $"3. Reached in batch {usersCountInBatch.Count}/10");237 Assert.True(await Task.WhenAny(subsTask, reachedMaxDocCountInAckMre.WaitAsync(_reasonableWaitTime)).WaitWithoutExceptionAsync(_reasonableWaitTime), $"3. Reached in ack {usersCountInAck.Count}/10");238 Assert.False(subsTask.IsFaulted, subsTask?.Exception?.ToString());239 }240 }241 [MultiplatformTheory(RavenArchitecture.AllX64)]242 [InlineData(3)]243 [InlineData(5)]244 public async Task DistributedRevisionsSubscription(int nodesAmount)245 {246 var uniqueRevisions = new HashSet<string>();247 var uniqueDocs = new HashSet<string>();248 var (_, leader) = await CreateRaftCluster(nodesAmount).ConfigureAwait(false);249 var defaultDatabase = GetDatabaseName();250 await CreateDatabaseInCluster(defaultDatabase, nodesAmount, leader.WebUrl).ConfigureAwait(false);251 using (var store = new DocumentStore252 {253 Urls = new[] { leader.WebUrl },254 Database = defaultDatabase255 }.Initialize())256 {257 await SetupRevisions(leader, defaultDatabase).ConfigureAwait(false);258 var reachedMaxDocCountMre = new AsyncManualResetEvent();259 var ackSent = new AsyncManualResetEvent();260 var continueMre = new AsyncManualResetEvent();261 await GenerateDistributedRevisionsDataAsync(defaultDatabase);262 var subscriptionId = await store.Subscriptions.CreateAsync<Revision<User>>().ConfigureAwait(false);263 var docsCount = 0;264 var revisionsCount = 0;265 var expectedRevisionsCount = 0;266 SubscriptionWorker<Revision<User>> subscription = null;267 int i;268 for (i = 0; i < 10; i++)269 {270 subscription = store.Subscriptions.GetSubscriptionWorker<Revision<User>>(new SubscriptionWorkerOptions(subscriptionId)271 {272 MaxErroneousPeriod = nodesAmount == 5 ? TimeSpan.FromSeconds(15) : TimeSpan.FromSeconds(5),273 MaxDocsPerBatch = 1,274 TimeToWaitBeforeConnectionRetry = TimeSpan.FromMilliseconds(100)275 });276 subscription.AfterAcknowledgment += async b =>277 {278 Assert.True(await continueMre.WaitAsync(TimeSpan.FromSeconds(60)));279 try280 {281 if (revisionsCount == expectedRevisionsCount)282 {283 continueMre.Reset();284 ackSent.Set();285 }286 Assert.True(await continueMre.WaitAsync(TimeSpan.FromSeconds(60)));287 }288 catch (Exception)289 {290 }291 };292 var started = new AsyncManualResetEvent();293 var task = subscription.Run(b =>294 {295 started.Set();296 HandleSubscriptionBatch(nodesAmount, b, uniqueDocs, ref docsCount, uniqueRevisions, reachedMaxDocCountMre, ref revisionsCount);297 });298 var cont = task.ContinueWith(t =>299 {300 reachedMaxDocCountMre.SetException(t.Exception);301 ackSent.SetException(t.Exception);302 }, TaskContinuationOptions.OnlyOnFaulted);303 await Task.WhenAny(task, started.WaitAsync(TimeSpan.FromSeconds(60)));304 if (started.IsSet)305 break;306 Assert.IsType<SubscriptionDoesNotExistException>(task.Exception.InnerException);307 subscription.Dispose();308 }309 Assert.NotEqual(i, 10);310 expectedRevisionsCount = nodesAmount + 2;311 continueMre.Set();312 Assert.True(await ackSent.WaitAsync(_reasonableWaitTime).ConfigureAwait(false), $"Doc count is {docsCount} with revisions {revisionsCount}/{expectedRevisionsCount} (1st assert)");313 ackSent.Reset(true);314 var disposedTag = await KillServerWhereSubscriptionWorks(defaultDatabase, subscription.SubscriptionName).ConfigureAwait(false);315 await WaitForResponsibleNodeToChange(defaultDatabase, subscription.SubscriptionName, disposedTag);316 continueMre.Set();317 expectedRevisionsCount += 2;318 Assert.True(await ackSent.WaitAsync(_reasonableWaitTime).ConfigureAwait(false), $"Doc count is {docsCount} with revisions {revisionsCount}/{expectedRevisionsCount} (2nd assert)");319 ackSent.Reset(true);320 continueMre.Set();321 expectedRevisionsCount = (int)Math.Pow(nodesAmount, 2);322 if (nodesAmount == 5)323 {324 var secondDisposedTag = await KillServerWhereSubscriptionWorks(defaultDatabase, subscription.SubscriptionName).ConfigureAwait(false);325 await WaitForResponsibleNodeToChange(defaultDatabase, subscription.SubscriptionName, secondDisposedTag);326 }327 Assert.True(await reachedMaxDocCountMre.WaitAsync(_reasonableWaitTime).ConfigureAwait(false), $"Doc count is {docsCount} with revisions {revisionsCount}/{expectedRevisionsCount} (3rd assert)");328 }329 }330 [MultiplatformFact(RavenArchitecture.AllX86)]331 public async Task DistributedRevisionsSubscription32Bit()332 {333 await DistributedRevisionsSubscription(3);334 }335 private static void HandleSubscriptionBatch(int nodesAmount, SubscriptionBatch<Revision<User>> b, HashSet<string> uniqueDocs, ref int docsCount, HashSet<string> uniqueRevisions,336 AsyncManualResetEvent reachedMaxDocCountMre, ref int revisionsCount)337 {338 foreach (var item in b.Items)339 {340 var x = item.Result;341 try342 {343 if (x == null)344 {345 }346 else if (x.Previous == null)347 {348 if (uniqueDocs.Add(x.Current.Id))349 docsCount++;350 if (uniqueRevisions.Add(x.Current.Name))351 revisionsCount++;352 }353 else if (x.Current == null)354 {355 }356 else357 {358 if (x.Current.Age > x.Previous.Age)359 {360 if (uniqueRevisions.Add(x.Current.Name))361 revisionsCount++;362 }363 }364 if (docsCount == nodesAmount && revisionsCount == Math.Pow(nodesAmount, 2))365 reachedMaxDocCountMre.Set();366 }367 catch (Exception)368 {369 }370 }371 }372 private async Task SetupRevisions(RavenServer server, string defaultDatabase)373 {374 using (var context = JsonOperationContext.ShortTermSingleUse())375 {376 var configuration = new RevisionsConfiguration377 {378 Default = new RevisionsCollectionConfiguration379 {380 Disabled = false,381 MinimumRevisionsToKeep = 10,382 },383 Collections = new Dictionary<string, RevisionsCollectionConfiguration>384 {385 ["Users"] = new RevisionsCollectionConfiguration386 {387 Disabled = false,388 MinimumRevisionsToKeep = 10389 }390 }391 };392 var documentDatabase = await server.ServerStore.DatabasesLandlord.TryGetOrCreateResourceStore(defaultDatabase);393 var res = await documentDatabase.ServerStore.ModifyDatabaseRevisions(context,394 defaultDatabase,395 DocumentConventions.Default.Serialization.DefaultConverter.ToBlittable(configuration, context), Guid.NewGuid().ToString());396 foreach (var s in Servers)// need to wait for it on all servers397 {398 documentDatabase = await s.ServerStore.DatabasesLandlord.TryGetOrCreateResourceStore(defaultDatabase);399 await documentDatabase.RachisLogIndexNotifications.WaitForIndexNotification(res.Item1, s.ServerStore.Engine.OperationTimeout);400 }401 }402 }403 private async Task GenerateDistributedRevisionsDataAsync(string defaultDatabase)404 {405 IReadOnlyList<ServerNode> nodes;406 using (var store = new DocumentStore407 {408 Urls = new[] { Servers[0].WebUrl },409 Database = defaultDatabase410 }.Initialize())411 {412 await store.GetRequestExecutor().UpdateTopologyAsync(new RequestExecutor.UpdateTopologyParameters(new ServerNode413 {414 Url = store.Urls[0],415 Database = defaultDatabase,416 })417 {418 TimeoutInMs = Timeout.Infinite419 });420 nodes = store.GetRequestExecutor().TopologyNodes;421 }422 var rnd = new Random(1);423 for (var index = 0; index < Servers.Count; index++)424 {425 var curVer = 0;426 foreach (var server in Servers.OrderBy(x => rnd.Next()))427 {428 using (var curStore = new DocumentStore429 {430 Urls = new[] { server.WebUrl },431 Database = defaultDatabase,432 Conventions = new DocumentConventions433 {434 DisableTopologyUpdates = true435 }436 }.Initialize())437 {438 var curDocName = $"user {index} revision {curVer}";439 using (var session = (DocumentSession)curStore.OpenSession())440 {441 if (curVer == 0)442 {443 session.Store(new User444 {445 Name = curDocName,446 Age = curVer,447 Id = $"users/{index}"448 }, $"users/{index}");449 }450 else451 {452 var user = session.Load<User>($"users/{index}");453 user.Age = curVer;454 user.Name = curDocName;455 session.Store(user, $"users/{index}");456 }457 session.SaveChanges();458 Assert.True(459 AsyncHelpers.RunSync(() => WaitForDocumentInClusterAsync<User>(nodes, "users/" + index, x => x.Name == curDocName, _reasonableWaitTime))460 );461 }462 }463 curVer++;464 }465 }466 }467 private async Task<(SubscriptionWorker<User> worker, Task subsTask)> CreateAndInitiateSubscription(IDocumentStore store, string defaultDatabase, List<User> usersCountInAck, 468 AsyncManualResetEvent reachedMaxDocCountInAckMre, List<User> usersCountInBatch, AsyncManualResetEvent reachedMaxDocCountInBatchMre, int batchSize, int numberOfConnections = 1, string mentor = null)469 {470 var proggress = new SubscriptionProggress()471 {472 MaxId = 0473 };474 var subscriptionName = await store.Subscriptions.CreateAsync<User>(options: new SubscriptionCreationOptions475 {476 MentorNode = mentor477 }).ConfigureAwait(false);478 SubscriptionWorker<User> subscription;479 List<Task> subTasks = new();480 int connections = numberOfConnections;481 do482 {483 subscription = store.Subscriptions.GetSubscriptionWorker<User>(new SubscriptionWorkerOptions(subscriptionName)484 {485 TimeToWaitBeforeConnectionRetry = TimeSpan.FromMilliseconds(500), 486 MaxDocsPerBatch = batchSize,487 Strategy = numberOfConnections > 1 ? SubscriptionOpeningStrategy.Concurrent : SubscriptionOpeningStrategy.OpenIfFree488 });489 subscription.AfterAcknowledgment += b =>490 {491 try492 {493 foreach (var item in b.Items)494 {495 var x = item.Result;496 int curId = 0;497 var afterSlash = x.Id.Substring(x.Id.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1);498 curId = int.Parse(afterSlash.Substring(0, afterSlash.Length - 2));499 Assert.True(curId >= proggress.MaxId);500 usersCountInAck.Add(x);501 proggress.MaxId = curId;502 }503 504 if (usersCountInAck.Count == 10)505 {506 reachedMaxDocCountInAckMre.Set();507 }508 }509 catch (Exception)510 {511 }512 return Task.CompletedTask;513 };514 515 subTasks.Add(subscription.Run(batch =>516 {517 foreach (var item in batch.Items)518 {519 usersCountInBatch.Add(item.Result);520 if (usersCountInBatch.Count == 10)521 {522 reachedMaxDocCountInBatchMre.Set();523 }524 }525 }));526 connections--;527 } while (connections > 0);528 var subscripitonState = await store.Subscriptions.GetSubscriptionStateAsync(subscriptionName, store.Database);529 var getDatabaseTopologyCommand = new GetDatabaseRecordOperation(defaultDatabase);530 var record = await store.Maintenance.Server.SendAsync(getDatabaseTopologyCommand).ConfigureAwait(false);531 foreach (var server in Servers.Where(s => record.Topology.RelevantFor(s.ServerStore.NodeTag)))532 {533 await server.ServerStore.Cluster.WaitForIndexNotification(subscripitonState.SubscriptionId).ConfigureAwait(false);534 }535 if (mentor != null)536 {537 Assert.Equal(mentor, record.Topology.WhoseTaskIsIt(RachisState.Follower, subscripitonState, null));538 }539 540 //await Task.WhenAny(task, Task.Delay(_reasonableWaitTime)).ConfigureAwait(false);541 return (subscription, Task.WhenAll(subTasks));542 }543 private async Task<string> KillServerWhereSubscriptionWorks(string defaultDatabase, string subscriptionName)544 {545 var sp = Stopwatch.StartNew();546 try547 {548 while (sp.ElapsedMilliseconds < _reasonableWaitTime.TotalMilliseconds)549 {550 string tag = null;551 var someServer = Servers.First(x => x.Disposed == false);552 using (someServer.ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))553 using (context.OpenReadTransaction())554 {555 var databaseRecord = someServer.ServerStore.Cluster.ReadDatabase(context, defaultDatabase);556 var db = await someServer.ServerStore.DatabasesLandlord.TryGetOrCreateResourceStore(defaultDatabase).ConfigureAwait(false);557 var subscriptionState = db.SubscriptionStorage.GetSubscriptionFromServerStore(subscriptionName);558 tag = databaseRecord.Topology.WhoseTaskIsIt(someServer.ServerStore.Engine.CurrentState, subscriptionState, null);559 }560 if (tag == null)561 {562 await Task.Delay(100);563 continue;564 }565 var server = Servers.First(x => x.ServerStore.NodeTag == tag);566 if (server.Disposed)567 {568 await Task.Delay(100);569 continue;570 }571 await DisposeServerAndWaitForFinishOfDisposalAsync(server);572 return tag;573 }574 return null;575 }576 finally577 {578 Assert.True(sp.ElapsedMilliseconds < _reasonableWaitTime.TotalMilliseconds);579 }580 }581 private async Task WaitForResponsibleNodeToChange(string database, string subscriptionName, string responsibleNode)582 {583 var sp = Stopwatch.StartNew();584 try585 {586 while (sp.ElapsedMilliseconds < _reasonableWaitTime.TotalMilliseconds)587 {588 string tag;589 var someServer = Servers.First(x => x.Disposed == false);590 using (someServer.ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))591 using (context.OpenReadTransaction())592 {593 var databaseRecord = someServer.ServerStore.Cluster.ReadDatabase(context, database);594 var db = await someServer.ServerStore.DatabasesLandlord.TryGetOrCreateResourceStore(database).ConfigureAwait(false);595 var subscriptionState = db.SubscriptionStorage.GetSubscriptionFromServerStore(subscriptionName);596 tag = databaseRecord.Topology.WhoseTaskIsIt(someServer.ServerStore.Engine.CurrentState, subscriptionState, null);597 }598 if (tag == null || tag == responsibleNode)599 {600 await Task.Delay(333);601 continue;602 }603 return;604 }605 }606 finally607 {608 Assert.True(sp.ElapsedMilliseconds < _reasonableWaitTime.TotalMilliseconds);609 }610 }611 private static async Task GenerateDocuments(IDocumentStore store)612 {613 using (var session = store.OpenAsyncSession())614 {615 for (int i = 0; i < 10; i++)616 {617 await session.StoreAsync(new User()618 {619 Name = "John" + i620 })621 .ConfigureAwait(false);622 }623 await session.SaveChangesAsync().ConfigureAwait(false);624 }625 }626 [Fact]627 public async Task SubscriptionWorkerShouldNotFailoverToErroredNodes()628 {629 var cluster = await CreateRaftCluster(numberOfNodes: 3);630 using (var store = GetDocumentStore(new Options631 {632 ReplicationFactor = 3,633 Server = cluster.Leader,634 DeleteDatabaseOnDispose = false635 }))636 {637 Servers.ForEach(x => x.ForTestingPurposesOnly().GatherVerboseDatabaseDisposeInformation = true);638 var mre = new AsyncManualResetEvent();639 using (var subscriptionManager = new DocumentSubscriptions(store))640 {641 var reqEx = store.GetRequestExecutor();642 var name = subscriptionManager.Create(new SubscriptionCreationOptions<User>());643 var subs = await SubscriptionFailoverWithWaitingChains.GetSubscription(name, store.Database, cluster.Nodes);644 Assert.NotNull(subs);645 await WaitForRaftIndexToBeAppliedOnClusterNodes(subs.SubscriptionId, cluster.Nodes);646 await ActionWithLeader(async l => await WaitForResponsibleNode(l.ServerStore, store.Database, name, toBecomeNull: false));647 Assert.True(WaitForValue(() => reqEx.Topology != null, true));648 var topology = reqEx.Topology;649 var serverNode1 = topology.Nodes[0];650 await reqEx.UpdateTopologyAsync(new RequestExecutor.UpdateTopologyParameters(serverNode1) { TimeoutInMs = 10_000 });651 var node1 = Servers.First(x => x.WebUrl.Equals(serverNode1.Url, StringComparison.InvariantCultureIgnoreCase));652 var (_, __, disposedTag) = await DisposeServerAndWaitForFinishOfDisposalAsync(node1);653 var serverNode2 = topology.Nodes[1];654 var node2 = Servers.First(x => x.WebUrl.Equals(serverNode2.Url, StringComparison.InvariantCultureIgnoreCase));655 var (_, ___, disposedTag2) = await DisposeServerAndWaitForFinishOfDisposalAsync(node2);656 var onlineServer = cluster.Nodes.Single(x => x.ServerStore.NodeTag != disposedTag && x.ServerStore.NodeTag != disposedTag2).ServerStore;657 await WaitForResponsibleNode(onlineServer, store.Database, name, toBecomeNull: true);658 using (reqEx.ContextPool.AllocateOperationContext(out var context))659 {660 var command = new KillOperationCommand(-int.MaxValue);661 await Assert.ThrowsAsync<RavenException>(async () => await reqEx.ExecuteAsync(command, context));662 }663 var redirects = new Dictionary<string, string>();664 var subscription = store.Subscriptions.GetSubscriptionWorker(new SubscriptionWorkerOptions(name)665 {666 TimeToWaitBeforeConnectionRetry = TimeSpan.FromMilliseconds(16),667 MaxErroneousPeriod = TimeSpan.FromMinutes(5)668 });669 subscription.OnSubscriptionConnectionRetry += ex =>670 {671 if (string.IsNullOrEmpty(subscription.CurrentNodeTag))672 return;673 redirects[subscription.CurrentNodeTag] = ex.ToString();674 mre.Set();675 };676 var expectedTag = onlineServer.NodeTag;677 _ = subscription.Run(x => { });678 Assert.True(await mre.WaitAsync(TimeSpan.FromSeconds(60)), $"Could not redirect to alive node in time{Environment.NewLine}Redirects:{Environment.NewLine}{string.Join(Environment.NewLine, redirects.Select(x => $"Tag: {x.Key}, Exception: {x.Value}").ToList())}");679 Assert.True(redirects.Keys.Contains(expectedTag), $"Could not find '{expectedTag}' in Redirects:{Environment.NewLine}{string.Join(Environment.NewLine, redirects.Select(x => $"Tag: {x.Key}, Exception: {x.Value}").ToList())}");680 Assert.False(redirects.Keys.Contains(disposedTag), $"Found disposed '{disposedTag}' in Redirects:{Environment.NewLine}{string.Join(Environment.NewLine, redirects.Select(x => $"Tag: {x.Key}, Exception: {x.Value}").ToList())}");681 Assert.False(redirects.Keys.Contains(disposedTag2), $"Found disposed '{disposedTag2}' in Redirects:{Environment.NewLine}{string.Join(Environment.NewLine, redirects.Select(x => $"Tag: {x.Key}, Exception: {x.Value}").ToList())}");682 Assert.Equal(1, redirects.Count);683 }684 }685 }686 private async Task WaitForResponsibleNode(ServerStore online, string dbName, string subscriptionName, bool toBecomeNull = false)687 {688 var sp = Stopwatch.StartNew();...
SubscriptionsFailoverStress.cs
Source:SubscriptionsFailoverStress.cs
...241 {242 var batchProccessed = new AsyncManualResetEvent();243 var task = subscription.Run(async a =>244 {245 batchProccessed.Set();246 await DisposeServerAndWaitForFinishOfDisposalAsync(leader);247 });248 await GenerateDocuments(store);249 Assert.True(await batchProccessed.WaitAsync(_reasonableWaitTime));250 Assert.True(await ThrowsAsync<SubscriptionInvalidStateException>(task).WaitWithTimeout(TimeSpan.FromSeconds(120)).ConfigureAwait(false));251 }252 }253 }254 [Fact]255 public async Task SubscriptionShouldNotFailIfLeaderIsDownButItStillHasEnoughTimeToRetry()256 {257 const int nodesAmount = 2;258 var (_, leader) = await CreateRaftCluster(nodesAmount, shouldRunInMemory: false);259 var indexLeader = Servers.FindIndex(x => x == leader);260 var defaultDatabase = "SubscriptionShouldNotFailIfLeaderIsDownButItStillHasEnoughTimeToRetry";261 await CreateDatabaseInCluster(defaultDatabase, nodesAmount, leader.WebUrl).ConfigureAwait(false);262 string mentor = Servers.First(x => x.ServerStore.NodeTag != x.ServerStore.LeaderTag).ServerStore.NodeTag;263 using (var store = new DocumentStore264 {265 Urls = new[] { leader.WebUrl },266 Database = defaultDatabase267 }.Initialize())268 {269 var subscriptionName = await store.Subscriptions.CreateAsync<User>(options: new SubscriptionCreationOptions270 {271 MentorNode = mentor272 }).ConfigureAwait(false);273 var subscripitonState = await store.Subscriptions.GetSubscriptionStateAsync(subscriptionName, store.Database);274 var getDatabaseTopologyCommand = new GetDatabaseRecordOperation(defaultDatabase);275 var record = await store.Maintenance.Server.SendAsync(getDatabaseTopologyCommand).ConfigureAwait(false);276 foreach (var server in Servers.Where(s => record.Topology.RelevantFor(s.ServerStore.NodeTag)))277 {278 await server.ServerStore.Cluster.WaitForIndexNotification(subscripitonState.SubscriptionId).ConfigureAwait(false);279 }280 if (mentor != null)281 {282 Assert.Equal(mentor, record.Topology.WhoseTaskIsIt(RachisState.Follower, subscripitonState, null));283 }284 using (var subscription = store.Subscriptions.GetSubscriptionWorker<User>(new SubscriptionWorkerOptions(subscriptionName)285 {286 TimeToWaitBeforeConnectionRetry = TimeSpan.FromMilliseconds(500),287 MaxDocsPerBatch = 20,288 MaxErroneousPeriod = TimeSpan.FromSeconds(120)289 }))290 {291 var batchProccessed = new AsyncManualResetEvent();292 var subscriptionRetryBegins = new AsyncManualResetEvent();293 var batchedAcked = new AsyncManualResetEvent();294 var disposedOnce = false;295 subscription.AfterAcknowledgment += x =>296 {297 batchedAcked.Set();298 return Task.CompletedTask;299 };300 (string DataDirectory, string Url, string NodeTag) result = default;301 var task = subscription.Run(batch =>302 {303 if (disposedOnce == false)304 {305 disposedOnce = true;306 subscription.OnSubscriptionConnectionRetry += x => subscriptionRetryBegins.SetAndResetAtomically();307 result = DisposeServerAndWaitForFinishOfDisposal(leader);308 }309 batchProccessed.SetAndResetAtomically();310 });311 await GenerateDocuments(store);312 Assert.True(await batchProccessed.WaitAsync(_reasonableWaitTime));313 Assert.True(await subscriptionRetryBegins.WaitAsync(TimeSpan.FromSeconds(30)));314 Assert.True(await subscriptionRetryBegins.WaitAsync(TimeSpan.FromSeconds(30)));315 Assert.True(await subscriptionRetryBegins.WaitAsync(TimeSpan.FromSeconds(30)));316 leader = Servers[indexLeader] =317 GetNewServer(new ServerCreationOptions318 {319 CustomSettings = new Dictionary<string, string>320 {321 {RavenConfiguration.GetKey(x => x.Core.PublicServerUrl), result.Url},322 {RavenConfiguration.GetKey(x => x.Core.ServerUrls), result.Url}323 },324 RunInMemory = false,325 DeletePrevious = false,326 DataDirectory = result.DataDirectory327 });328 Assert.True(await batchProccessed.WaitAsync(TimeSpan.FromSeconds(60)));329 Assert.True(await batchedAcked.WaitAsync(TimeSpan.FromSeconds(60)));330 }331 }332 }333 private static async Task ThrowsAsync<T>(Task task) where T : Exception...
TestUtilities.cs
Source:TestUtilities.cs
...13using Xunit;14using Xunit.Abstractions;15internal static class TestUtilities16{17 internal static Task SetAsync(this TaskCompletionSource<object?> tcs)18 {19 return Task.Run(() => tcs.TrySetResult(null));20 }21 /// <summary>22 /// Runs an asynchronous task synchronously, using just the current thread to execute continuations.23 /// </summary>24 internal static void Run(Func<Task> func)25 {26 if (func is null)27 {28 throw new ArgumentNullException(nameof(func));29 }30 SynchronizationContext? prevCtx = SynchronizationContext.Current;31 try32 {33 SynchronizationContext? syncCtx = SingleThreadedTestSynchronizationContext.New();34 SynchronizationContext.SetSynchronizationContext(syncCtx);35 Task? t = func();36 if (t is null)37 {38 throw new InvalidOperationException();39 }40 SingleThreadedTestSynchronizationContext.IFrame? frame = SingleThreadedTestSynchronizationContext.NewFrame();41 t.ContinueWith(_ => { frame.Continue = false; }, TaskScheduler.Default);42 SingleThreadedTestSynchronizationContext.PushFrame(syncCtx, frame);43 t.GetAwaiter().GetResult();44 }45 finally46 {47 SynchronizationContext.SetSynchronizationContext(prevCtx);48 }49 }50 /// <summary>51 /// Executes the specified function on multiple threads simultaneously.52 /// </summary>53 /// <typeparam name="T">The type of the value returned by the specified function.</typeparam>54 /// <param name="action">The function to invoke concurrently.</param>55 /// <param name="concurrency">The level of concurrency.</param>56 internal static T[] ConcurrencyTest<T>(Func<T> action, int concurrency = -1)57 {58 Requires.NotNull(action, nameof(action));59 if (concurrency == -1)60 {61 concurrency = Environment.ProcessorCount;62 }63 Skip.If(Environment.ProcessorCount < concurrency, $"The test machine does not have enough CPU cores to exercise a concurrency level of {concurrency}");64 // We use a barrier to guarantee that all threads are fully ready to65 // execute the provided function at precisely the same time.66 // The barrier will unblock all of them together.67 using (var barrier = new Barrier(concurrency))68 {69 var tasks = new Task<T>[concurrency];70 for (int i = 0; i < tasks.Length; i++)71 {72 tasks[i] = Task.Run(delegate73 {74 barrier.SignalAndWait();75 return action();76 });77 }78 Task.WaitAll(tasks);79 return tasks.Select(t => t.Result).ToArray();80 }81 }82 internal static DebugAssertionRevert DisableAssertionDialog()83 {84#if NETFRAMEWORK85 DefaultTraceListener? listener = Debug.Listeners.OfType<DefaultTraceListener>().FirstOrDefault();86 if (listener is object)87 {88 listener.AssertUiEnabled = false;89 }90#else91 Trace.Listeners.Clear();92#endif93 return default(DebugAssertionRevert);94 }95 internal static void CompleteSynchronously(this JoinableTaskFactory factory, JoinableTaskCollection collection, Task task)96 {97 Requires.NotNull(factory, nameof(factory));98 Requires.NotNull(collection, nameof(collection));99 Requires.NotNull(task, nameof(task));100 factory.Run(async delegate101 {102 using (collection.Join())103 {104 await task;105 }106 });107 }108 /// <summary>109 /// Forces an awaitable to yield, setting signals after the continuation has been pended and when the continuation has begun execution.110 /// </summary>111 /// <param name="baseAwaiter">The awaiter to extend.</param>112 /// <param name="yieldingSignal">The signal to set after the continuation has been pended.</param>113 /// <param name="resumingSignal">The signal to set when the continuation has been invoked.</param>114 /// <returns>A new awaitable.</returns>115 internal static YieldAndNotifyAwaitable YieldAndNotify(this INotifyCompletion baseAwaiter, AsyncManualResetEvent? yieldingSignal = null, AsyncManualResetEvent? resumingSignal = null)116 {117 Requires.NotNull(baseAwaiter, nameof(baseAwaiter));118 return new YieldAndNotifyAwaitable(baseAwaiter, yieldingSignal, resumingSignal);119 }120 /// <summary>121 /// Flood the threadpool with requests that will just block the threads122 /// until the returned value is disposed of.123 /// </summary>124 /// <returns>A value to dispose of to unblock the threadpool.</returns>125 /// <remarks>126 /// This can provide a unique technique for influencing execution order127 /// of synchronous code vs. async code.128 /// </remarks>129 internal static IDisposable StarveThreadpool()130 {131 ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);132 var disposalTokenSource = new CancellationTokenSource();133 var unblockThreadpool = new ManualResetEventSlim();134 for (int i = 0; i < workerThreads; i++)135 {136 Task.Run(137 () => unblockThreadpool.Wait(disposalTokenSource.Token),138 disposalTokenSource.Token);139 }140 return new DisposalAction(disposalTokenSource.Cancel);141 }142 /// <summary>143 /// Executes the specified test method in its own process, offering maximum isolation from ambient noise from other threads144 /// and GC.145 /// </summary>146 /// <param name="testClass">The instance of the test class containing the method to be run in isolation.</param>147 /// <param name="testMethodName">The name of the test method.</param>148 /// <param name="logger">An optional logger to forward any <see cref="ITestOutputHelper"/> output to from the isolated test runner.</param>149 /// <returns>150 /// A task whose result is <c>true</c> if test execution is already isolated and should therefore proceed with the body of the test,151 /// or <c>false</c> after the isolated instance of the test has completed execution.152 /// </returns>153 /// <exception cref="Xunit.Sdk.XunitException">Thrown if the isolated test result is a Failure.</exception>154 /// <exception cref="SkipException">Thrown if on a platform that we do not yet support test isolation on.</exception>155 internal static Task<bool> ExecuteInIsolationAsync(object testClass, string testMethodName, ITestOutputHelper logger)156 {157 Requires.NotNull(testClass, nameof(testClass));158 return ExecuteInIsolationAsync(testClass.GetType().FullName!, testMethodName, logger);159 }160 /// <summary>161 /// Executes the specified test method in its own process, offering maximum isolation from ambient noise from other threads162 /// and GC.163 /// </summary>164 /// <param name="testClassName">The full name of the test class.</param>165 /// <param name="testMethodName">The name of the test method.</param>166 /// <param name="logger">An optional logger to forward any <see cref="ITestOutputHelper"/> output to from the isolated test runner.</param>167 /// <returns>168 /// A task whose result is <c>true</c> if test execution is already isolated and should therefore proceed with the body of the test,169 /// or <c>false</c> after the isolated instance of the test has completed execution.170 /// </returns>171 /// <exception cref="Xunit.Sdk.XunitException">Thrown if the isolated test result is a Failure.</exception>172 /// <exception cref="SkipException">Thrown if on a platform that we do not yet support test isolation on.</exception>173#pragma warning disable CA1801 // Review unused parameters174 internal static Task<bool> ExecuteInIsolationAsync(string testClassName, string testMethodName, ITestOutputHelper logger)175#pragma warning restore CA1801 // Review unused parameters176 {177 Requires.NotNullOrEmpty(testClassName, nameof(testClassName));178 Requires.NotNullOrEmpty(testMethodName, nameof(testMethodName));179#if NETFRAMEWORK180 const string testHostProcessName = "IsolatedTestHost.exe";181 if (Process.GetCurrentProcess().ProcessName == Path.GetFileNameWithoutExtension(testHostProcessName))182 {183 return TplExtensions.TrueTask;184 }185 var startInfo = new ProcessStartInfo(186 testHostProcessName,187 AssemblyCommandLineArguments(188 Assembly.GetExecutingAssembly().Location,189 testClassName,190 testMethodName))191 {192 RedirectStandardError = logger is object,193 RedirectStandardOutput = logger is object,194 CreateNoWindow = true,195 UseShellExecute = false,196 };197 Process isolatedTestProcess = new Process198 {199 StartInfo = startInfo,200 EnableRaisingEvents = true,201 };202 var processExitCode = new TaskCompletionSource<IsolatedTestHost.ExitCode>();203 isolatedTestProcess.Exited += (s, e) =>204 {205 processExitCode.SetResult((IsolatedTestHost.ExitCode)isolatedTestProcess.ExitCode);206 };207 if (logger is object)208 {209 isolatedTestProcess.OutputDataReceived += (s, e) => logger.WriteLine(e.Data ?? string.Empty);210 isolatedTestProcess.ErrorDataReceived += (s, e) => logger.WriteLine(e.Data ?? string.Empty);211 }212 Assert.True(isolatedTestProcess.Start());213 if (logger is object)214 {215 isolatedTestProcess.BeginOutputReadLine();216 isolatedTestProcess.BeginErrorReadLine();217 }218 return processExitCode.Task.ContinueWith(219 t =>220 {221 switch (t.Result)222 {223 case IsolatedTestHost.ExitCode.TestSkipped:224 throw new SkipException("Test skipped. See output of isolated task for details.");225 case IsolatedTestHost.ExitCode.TestPassed:226 default:227 Assert.Equal(IsolatedTestHost.ExitCode.TestPassed, t.Result);228 break;229 }230 return false;231 },232 TaskScheduler.Default);233#else234 return Task.FromException<bool>(new SkipException("Test isolation is not yet supported on this platform."));235#endif236 }237 /// <summary>238 /// Wait on a task without possibly inlining it to the current thread.239 /// </summary>240 /// <param name="task">The task to wait on.</param>241 /// <param name="throwOriginalException"><c>true</c> to throw the original (inner) exception when the <paramref name="task"/> faults; <c>false</c> to throw <see cref="AggregateException"/>.</param>242 /// <exception cref="AggregateException">Thrown if <paramref name="task"/> completes in a faulted state if <paramref name="throwOriginalException"/> is <c>false</c>.</exception>243 internal static void WaitWithoutInlining(this Task task, bool throwOriginalException)244 {245 Requires.NotNull(task, nameof(task));246 if (!task.IsCompleted)247 {248 // Waiting on a continuation of a task won't ever inline the predecessor (in .NET 4.x anyway).249 Task? continuation = task.ContinueWith(t => { }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);250 continuation.Wait();251 }252 // Rethrow the exception if the task faulted.253 if (throwOriginalException)254 {255 task.GetAwaiter().GetResult();256 }257 else258 {259 task.Wait();260 }261 }262 /// <summary>263 /// Wait on a task without possibly inlining it to the current thread and returns its result.264 /// </summary>265 /// <typeparam name="T">The type of result returned from the <paramref name="task"/>.</typeparam>266 /// <param name="task">The task to wait on.</param>267 /// <param name="throwOriginalException"><c>true</c> to throw the original (inner) exception when the <paramref name="task"/> faults; <c>false</c> to throw <see cref="AggregateException"/>.</param>268 /// <returns>The result of the <see cref="Task{T}"/>.</returns>269 /// <exception cref="AggregateException">Thrown if <paramref name="task"/> completes in a faulted state if <paramref name="throwOriginalException"/> is <c>false</c>.</exception>270 internal static T GetResultWithoutInlining<T>(this Task<T> task, bool throwOriginalException = true)271 {272 WaitWithoutInlining(task, throwOriginalException);273 return task.Result;274 }275 private static string AssemblyCommandLineArguments(params string[] args) => string.Join(" ", args.Select(a => $"\"{a}\""));276 internal readonly struct YieldAndNotifyAwaitable277 {278 private readonly INotifyCompletion baseAwaiter;279 private readonly AsyncManualResetEvent? yieldingSignal;280 private readonly AsyncManualResetEvent? resumingSignal;281 internal YieldAndNotifyAwaitable(INotifyCompletion baseAwaiter, AsyncManualResetEvent? yieldingSignal, AsyncManualResetEvent? resumingSignal)282 {283 Requires.NotNull(baseAwaiter, nameof(baseAwaiter));284 this.baseAwaiter = baseAwaiter;285 this.yieldingSignal = yieldingSignal;286 this.resumingSignal = resumingSignal;287 }288 public YieldAndNotifyAwaiter GetAwaiter()289 {290 return new YieldAndNotifyAwaiter(this.baseAwaiter, this.yieldingSignal, this.resumingSignal);291 }292 }293 internal readonly struct YieldAndNotifyAwaiter : INotifyCompletion294 {295 private readonly INotifyCompletion baseAwaiter;296 private readonly AsyncManualResetEvent? yieldingSignal;297 private readonly AsyncManualResetEvent? resumingSignal;298 internal YieldAndNotifyAwaiter(INotifyCompletion baseAwaiter, AsyncManualResetEvent? yieldingSignal, AsyncManualResetEvent? resumingSignal)299 {300 Requires.NotNull(baseAwaiter, nameof(baseAwaiter));301 this.baseAwaiter = baseAwaiter;302 this.yieldingSignal = yieldingSignal;303 this.resumingSignal = resumingSignal;304 }305 public bool IsCompleted306 {307 get { return false; }308 }309 public void OnCompleted(Action continuation)310 {311 YieldAndNotifyAwaiter that = this;312 this.baseAwaiter.OnCompleted(delegate313 {314 if (that.resumingSignal is object)315 {316 that.resumingSignal.Set();317 }318 continuation();319 });320 if (this.yieldingSignal is object)321 {322 this.yieldingSignal.Set();323 }324 }325 public void GetResult()326 {327 }328 }329 internal readonly struct DebugAssertionRevert : IDisposable330 {331 public void Dispose()332 {333#if NETFRAMEWORK334 DefaultTraceListener? listener = Debug.Listeners.OfType<DefaultTraceListener>().FirstOrDefault();335 if (listener is object)336 {...
SubscriptionFailoverWIthWaitingChains.cs
Source:SubscriptionFailoverWIthWaitingChains.cs
...96 using var subsWorker = store.Subscriptions.GetSubscriptionWorker<User>(new Raven.Client.Documents.Subscriptions.SubscriptionWorkerOptions(subsId)97 {98 TimeToWaitBeforeConnectionRetry = TimeSpan.FromMilliseconds(16)99 });100 HashSet<string> redirects = new HashSet<string>();101 var mre = new ManualResetEvent(false);102 var processedItems = new List<string>();103 subsWorker.AfterAcknowledgment += batch =>104 {105 foreach (var item in batch.Items)106 processedItems.Add(item.Result.Name);107 mre.Set();108 return Task.CompletedTask;109 };110 subsWorker.OnSubscriptionConnectionRetry += ex =>111 {112 redirects.Add(subsWorker.CurrentNodeTag);113 };114 _ = subsWorker.Run(x => { });115 List<string> toggledNodes = new List<string>();116 var toggleCount = Math.Round(clusterSize * 0.51);117 string previousResponsibleNode = string.Empty;118 for (int i = 0; i < toggleCount; i++)119 {120 string currentResponsibleNode = string.Empty;121 await ActionWithLeader(async l =>122 {123 currentResponsibleNode = await GetResponsibleNodeAndCompareWithPrevious(l, databaseName, previousResponsibleNode, subsId);124 });125 toggledNodes.Add(currentResponsibleNode);126 previousResponsibleNode = currentResponsibleNode;127 var node = cluster.Nodes.FirstOrDefault(x => x.ServerStore.NodeTag == currentResponsibleNode);128 Assert.NotNull(node);129 if (i != 0)130 mre.Reset();131 using (var session = store.OpenSession())132 {133 session.Store(new User134 {135 Name = namesList[i]136 });137 session.SaveChanges();138 }139 Assert.True(mre.WaitOne(TimeSpan.FromSeconds(15)), "no ack");140 var res = await DisposeServerAndWaitForFinishOfDisposalAsync(node);141 Assert.Equal(currentResponsibleNode, res.NodeTag);142 }143 Assert.True(redirects.Count >= toggleCount, $"redirects count : {redirects.Count}, leaderNodeTag: {cluster.Leader.ServerStore.NodeTag}, missing: {string.Join(", ", cluster.Nodes.Select(x => x.ServerStore.NodeTag).Except(redirects))}, offline: {string.Join(", ", toggledNodes)}");144 Assert.Equal(namesList.Count, processedItems.Count);145 for (int i = 0; i < namesList.Count; i++)146 {147 Assert.Equal(namesList[i], processedItems[i]);148 }149 }150 }151 private static async Task<string> GetResponsibleNodeAndCompareWithPrevious(RavenServer l, string databaseName, string previousResponsibleNode, string subsId)152 {153 string currentResponsibleNode = string.Empty;154 var responsibleTime = Debugger.IsAttached || PlatformDetails.Is32Bits ? 300_000 : 30_000;155 var documentDatabase = await l.ServerStore.DatabasesLandlord.TryGetOrCreateResourceStore(databaseName);156 var sp = Stopwatch.StartNew();157 while (previousResponsibleNode == currentResponsibleNode || string.IsNullOrEmpty(currentResponsibleNode))158 {159 using (documentDatabase.ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))160 using (context.OpenReadTransaction())161 {162 currentResponsibleNode = documentDatabase.SubscriptionStorage.GetResponsibleNode(context, subsId);163 }164 await Task.Delay(1000);165 Assert.True(sp.ElapsedMilliseconds < responsibleTime, $"Could not get the subscription ResponsibleNode in responsible time '{responsibleTime}'");166 }167 return currentResponsibleNode;168 }169 [Fact]170 public async Task SubscriptionShouldReconnectOnExceptionInTcpListener()171 {172 using var server = GetNewServer(new ServerCreationOptions173 {174 RunInMemory = false,175 RegisterForDisposal = false,176 });177 using (var store = GetDocumentStore(new Options178 {179 Server = server,180 ModifyDocumentStore = s => s.Conventions.ReadBalanceBehavior = Raven.Client.Http.ReadBalanceBehavior.RoundRobin,181 }))182 {183 var mre = new AsyncManualResetEvent();184 using (var session = store.OpenSession())185 {186 session.Store(new User());187 session.SaveChanges();188 }189 var subsId = await store.Subscriptions.CreateAsync<User>();190 using var subsWorker = store.Subscriptions.GetSubscriptionWorker<User>(new SubscriptionWorkerOptions(subsId)191 {192 TimeToWaitBeforeConnectionRetry = TimeSpan.FromMilliseconds(16)193 });194 subsWorker.OnSubscriptionConnectionRetry += ex =>195 {196 Assert.NotNull(ex);197 198 if (ex is AggregateException ae)199 Assert.True(ae.InnerExceptions.Count(e => e.GetType() == typeof(IOException) || e.GetType() == typeof(EndOfStreamException)) > 0);200 else201 Assert.True(ex.GetType() == typeof(IOException) || ex.GetType() == typeof(EndOfStreamException));202 mre.Set();203 };204 server.ForTestingPurposesOnly().ThrowExceptionInListenToNewTcpConnection = true;205 try206 {207 var task = subsWorker.Run(x => { });208 Assert.True(await mre.WaitAsync(TimeSpan.FromSeconds(30)));209 }210 finally211 {212 server.ForTestingPurposesOnly().ThrowExceptionInListenToNewTcpConnection = false;213 }214 }215 }216 internal static async Task ContinuouslyGenerateDocsInternal(int DocsBatchSize, DocumentStore store, CancellationToken token)...
AsyncTrackingSyncContext.cs
Source:AsyncTrackingSyncContext.cs
...30 /// https://github.com/xunit/xunit/blob/master/src/xunit.execution/Sdk/MaxConcurrencySyncContext.cs31 /// </summary>32 public class AsyncTrackingSyncContext : SynchronizationContext33 {34 public static AsyncTrackingSyncContext Setup()35 {36 var synchronizationContext = new AsyncTrackingSyncContext();37 synchronizationContext.SetCurrentAsSynchronizationContext();38 return synchronizationContext;39 }40 [SecuritySafeCritical]41 void SetCurrentAsSynchronizationContext() => SetSynchronizationContext(this);42 readonly AsyncManualResetEvent @event = new(true);43 readonly SynchronizationContext innerContext;44 int operationCount;45 private AsyncTrackingSyncContext()46 {47 innerContext = Current ?? new SynchronizationContext();48 }49 public override void OperationCompleted()50 {51 var result = Interlocked.Decrement(ref operationCount);52 if (result == 0)53 @event.Set();54 innerContext.OperationCompleted();55 }56 public override void OperationStarted()57 {58 Interlocked.Increment(ref operationCount);59 @event.Reset();60 innerContext.OperationStarted();61 }62 public override void Post(SendOrPostCallback d, object state)63 {64 // The call to Post() may be the state machine signaling that an exception is65 // about to be thrown, so we make sure the operation count gets incremented66 // before the Task.Run, and then decrement the count when the operation is done.67 OperationStarted();68 try69 {70 innerContext.Post(_ =>71 {72 try73 {74 RunOnSyncContext(d, state);75 }76 catch { }77 finally78 {79 OperationCompleted();80 }81 }, null);82 }83 catch { }84 }85 public override void Send(SendOrPostCallback d, object state)86 {87 try88 {89 innerContext.Send(_ => RunOnSyncContext(d, state), null);90 }91 catch { }92 }93 [SecuritySafeCritical]94 void RunOnSyncContext(SendOrPostCallback callback, object state)95 {96 var oldSyncContext = Current;97 SetSynchronizationContext(this);98 callback(state);99 SetSynchronizationContext(oldSyncContext);100 }101 /// <summary>102 /// Returns a task which is signaled when all outstanding operations are complete.103 /// </summary>104 public async Task WaitForCompletionAsync()105 {106 await @event.WaitAsync();107 }108 }109}...
AsyncTestSyncContext.cs
Source:AsyncTestSyncContext.cs
...25 public override void OperationCompleted()26 {27 var result = Interlocked.Decrement(ref operationCount);28 if (result == 0)29 @event.Set();30 }31 /// <inheritdoc/>32 public override void OperationStarted()33 {34 Interlocked.Increment(ref operationCount);35 @event.Reset();36 }37 /// <inheritdoc/>38 public override void Post(SendOrPostCallback d, object state)39 {40 // The call to Post() may be the state machine signaling that an exception is41 // about to be thrown, so we make sure the operation count gets incremented42 // before the Task.Run, and then decrement the count when the operation is done.43 OperationStarted();...
AsyncManualResetEvent.cs
Source:AsyncManualResetEvent.cs
...7 volatile TaskCompletionSource<bool> taskCompletionSource = new();8 public AsyncManualResetEvent(bool signaled = false)9 {10 if (signaled)11 taskCompletionSource.TrySetResult(true);12 }13 public bool IsSet14 {15 get { return taskCompletionSource.Task.IsCompleted; }16 }17 public Task WaitAsync()18 {19 return taskCompletionSource.Task;20 }21 public void Set()22 {23 taskCompletionSource.TrySetResult(true);24 }25 public void Reset()26 {27 if (IsSet)28 taskCompletionSource = new TaskCompletionSource<bool>();29 }30 }31}...
Set
Using AI Code Generation
1using System.Threading.Tasks;2using Xunit;3{4 {5 public async Task Test1()6 {7 var amre = new Xunit.Sdk.AsyncManualResetEvent();8 amre.Set();9 await amre.WaitAsync();10 }11 }12}13using System.Threading.Tasks;14using Xunit;15{16 {17 public async Task Test1()18 {19 var amre = new System.Threading.AsyncManualResetEvent();20 amre.Set();21 await amre.WaitAsync();22 }23 }24}25using System.Threading.Tasks;26using Xunit;27{28 {29 public async Task Test1()30 {31 var amre = new System.Threading.Tasks.AsyncManualResetEvent();32 amre.Set();33 await amre.WaitAsync();34 }35 }36}37using System.Threading.Tasks;38using Xunit;39{40 {41 public async Task Test1()42 {43 var amre = new System.Threading.Tasks.AsyncManualResetEvent();44 amre.Set();45 await amre.WaitAsync();46 }47 }48}49using System.Threading.Tasks;50using Xunit;51{52 {53 public async Task Test1()54 {55 var amre = new System.Threading.Tasks.AsyncManualResetEvent();56 amre.Set();57 await amre.WaitAsync();58 }59 }60}61using System.Threading.Tasks;62using Xunit;63{64 {65 public async Task Test1()66 {
Set
Using AI Code Generation
1using System;2using System.Threading.Tasks;3using Xunit;4{5 {6 public async Task TestMethod1()7 {8 var amre = new Xunit.Sdk.AsyncManualResetEvent();9 var task = Task.Run(async () =>10 {11 await Task.Delay(1000);12 amre.Set();13 });14 await amre.WaitAsync();15 }16 }17}18using System;19using System.Threading;20using System.Threading.Tasks;21{22 {23 private readonly TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();24 public AsyncManualResetEvent()25 {26 tcs.TrySetResult(false);27 }28 public AsyncManualResetEvent(bool initialState)29 {30 tcs.TrySetResult(initialState);31 }32 {33 {34 return tcs.Task.IsCompleted && tcs.Task.Result;35 }36 }37 public void Set()38 {39 tcs.TrySetResult(true);40 }41 public void Reset()42 {43 tcs.TrySetResult(false);44 }45 public Task WaitAsync()46 {47 return WaitAsync(CancellationToken.None
Set
Using AI Code Generation
1using Xunit;2using Xunit.Sdk;3using System.Threading.Tasks;4using System.Threading;5{6 {7 public async Task Test1()8 {9 var amre = new AsyncManualResetEvent();10 var task = Task.Run(() =>11 {12 Thread.Sleep(100);13 amre.Set();14 });15 await amre.WaitAsync();16 }17 }18}19Thanks for your response. I have tried to use the AsyncManualResetEvent class from Xunit.Sdk, but it seems that the Set() method is not available. I have added the following code to my test project:20using Xunit;21using Xunit.Sdk;22using System.Threading.Tasks;23using System.Threading;24{25 {26 public async Task Test1()27 {28 var amre = new AsyncManualResetEvent();29 var task = Task.Run(() =>30 {31 Thread.Sleep(100);32 amre.Set();33 });34 await amre.WaitAsync();35 }36 }37}38Error CS1061 'AsyncManualResetEvent' does not contain a definition for 'Set' and no accessible extension method 'Set' accepting a first argument of type 'AsyncManualResetEvent' could be found (are you missing a using directive or an assembly reference?) Test C:\Users\jdoe\source\repos\Test\Test\Test.cs 21 Active39using Xunit;40using Xunit.Sdk;41using System.Threading.Tasks;42using System.Threading;43{44 {45 public async Task Test1()46 {47 var amre = new AsyncManualResetEvent();48 var task = Task.Run(() =>49 {50 Thread.Sleep(100);51 amre.Set();52 });53 await amre.WaitAsync();54 }55 }56}57Error CS1061 'AsyncManualResetEvent' does not contain a definition for 'Set' and no accessible extension method 'Set' accepting a first argument of type 'AsyncManualResetEvent' could be found (are you missing a using directive or an assembly reference?) Test C:\Users\jdoe\source\repos\Test\Test\Test.cs 21 Active
Set
Using AI Code Generation
1{2 public async Task TestMethod1()3 {4 var mre = new AsyncManualResetEvent();5 var t = Task.Run(() => mre.WaitAsync());6 mre.Set();7 await t;8 }9}10{11 public async Task TestMethod1()12 {13 var mre = new AsyncManualResetEvent();14 mre.Set();15 mre.Reset();16 var t = Task.Run(() => mre.WaitAsync());17 Assert.False(t.IsCompleted);18 }19}20{21 public async Task TestMethod1()22 {23 var mre = new AsyncManualResetEvent();24 var t = Task.Run(() => mre.WaitAsync());25 mre.Set();26 await t;27 }28}29{30 public async Task TestMethod1()31 {32 var mre = new AsyncManualResetEvent();33 var cts = new CancellationTokenSource();34 var t = Task.Run(() => mre.WaitAsync(cts.Token));35 cts.Cancel();36 await Assert.ThrowsAsync<OperationCanceledException>(() => t);37 }38}39{40 public async Task TestMethod1()41 {42 var mre = new AsyncManualResetEvent();43 var t = Task.Run(() => mre.WaitAsync(TimeSpan.FromMilliseconds(100)));44 await Assert.ThrowsAsync<TimeoutException>(() => t);45 }46}
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!