public async Task Uncontested(int initialCount) { this.lck = new AsyncSemaphore(initialCount); var releasers = new AsyncSemaphore.Releaser[initialCount]; for (int i = 0; i < 5; i++) { // Fill the semaphore to its capacity for (int j = 0; j < initialCount; j++) { releasers[j] = await this.lck.EnterAsync(); } var releaseSequence = Enumerable.Range(0, initialCount); // We'll test both releasing in FIFO and LIFO order. if (i % 2 == 0) { releaseSequence = releaseSequence.Reverse(); } foreach (int j in releaseSequence) { releasers[j].Dispose(); } } }
public async Task CancelledRequestIsImmediate() { AsyncSemaphore.Releaser releaser1 = await this.lck.EnterAsync(); // With the semaphore fully occupied, issue a cancelable request. var cts = new CancellationTokenSource(); Task <AsyncSemaphore.Releaser>?releaser2Task = this.lck.EnterAsync(cts.Token); Assert.False(releaser2Task.IsCompleted); // Also pend a 3rd request. Task <AsyncSemaphore.Releaser>?releaser3Task = this.lck.EnterAsync(); Assert.False(releaser3Task.IsCompleted); // Cancel the second user cts.Cancel(); OperationCanceledException?oce = await Assert.ThrowsAnyAsync <OperationCanceledException>(() => releaser2Task).WithCancellation(this.TimeoutToken); Assert.Equal(cts.Token, oce.CancellationToken); // Verify that the 3rd user still hasn't gotten in. Assert.Equal(0, this.lck.CurrentCount); Assert.False(releaser3Task.IsCompleted); // Now have the first user exit the semaphore and verify that the 3rd user gets in. releaser1.Dispose(); await releaser3Task.WithCancellation(this.TimeoutToken); }
public async Task CurrentCount() { const int initialCapacity = 3; var sem = new AsyncSemaphore(initialCapacity); Assert.Equal(initialCapacity, sem.CurrentCount); var releasers = new AsyncSemaphore.Releaser[initialCapacity]; for (int i = 0; i < initialCapacity; i++) { releasers[i] = await sem.EnterAsync(); Assert.Equal(initialCapacity - (i + 1), sem.CurrentCount); } // After requesting another beyond its capacity, it should still report 0. var extraReleaser = sem.EnterAsync(); Assert.Equal(0, sem.CurrentCount); for (int i = 0; i < initialCapacity; i++) { releasers[i].Dispose(); Assert.Equal(i, sem.CurrentCount); } extraReleaser.Result.Dispose(); Assert.Equal(initialCapacity, sem.CurrentCount); }
public async Task NoLeakForContestedRequests_ThatAreEventuallyAdmitted(int contestingNumbers) { var sem = new AsyncSemaphore(1); var releasers = new Task <AsyncSemaphore.Releaser> [contestingNumbers]; await this.CheckGCPressureAsync( async delegate { AsyncSemaphore.Releaser blockingReleaser = await sem.EnterAsync(); for (int i = 0; i < releasers.Length; i++) { releasers[i] = sem.EnterAsync(); } // Now let the first one in. blockingReleaser.Dispose(); // Now dispose the first one, and each one afterward as it is let in. for (int i = 0; i < releasers.Length; i++) { (await releasers[i]).Dispose(); } }, maxBytesAllocated : -1, iterations : 5); }
public async Task TooManyReleases_SameStruct() { AsyncSemaphore.Releaser releaser = await this.lck.EnterAsync(); releaser.Dispose(); releaser.Dispose(); Assert.Equal(2, this.lck.CurrentCount); }
public async Task TooManyReleases_CopyOfStruct_OverInitialCount() { AsyncSemaphore.Releaser releaser = await this.lck.EnterAsync(); AsyncSemaphore.Releaser releaserCopy = releaser; releaser.Dispose(); Assert.Equal(1, this.lck.CurrentCount); releaserCopy.Dispose(); Assert.Equal(2, this.lck.CurrentCount); }
public async Task SemaphoreAwaitersAreQueued() { AsyncSemaphore.Releaser holder = await this.lck.EnterAsync(); const int waiterCount = 5; var cts = new CancellationTokenSource[waiterCount]; var waiters = new Task <AsyncSemaphore.Releaser> [waiterCount]; for (int i = 0; i < waiterCount; i++) { cts[i] = new CancellationTokenSource(); waiters[i] = this.lck.EnterAsync(cts[i].Token); } Assert.All(waiters, waiter => Assert.False(waiter.IsCompleted)); const int canceledWaiterIndex = 2; cts[canceledWaiterIndex].Cancel(); await Assert.ThrowsAnyAsync <OperationCanceledException>(() => waiters[canceledWaiterIndex]).WithCancellation(this.TimeoutToken); for (int i = 0; i < waiterCount; i++) { Assert.Equal(i == canceledWaiterIndex, waiters[i].IsCompleted); } holder.Dispose(); for (int i = 0; i < waiterCount; i++) { if (i == canceledWaiterIndex) { continue; } // Assert that all subsequent waiters have not yet entered the semaphore. Assert.All(waiters.Skip(i + 1), w => Assert.True(w == waiters[canceledWaiterIndex] || !w.IsCompleted)); // Now accept and exit the semaphore. using (await waiters[i].WithCancellation(this.TimeoutToken)) { // We got the semaphore and will release it. } } }
public async Task NoLeakForUncontestedRequests(int initialCapacity) { var sem = new AsyncSemaphore(initialCapacity); var releasers = new AsyncSemaphore.Releaser[initialCapacity]; await this.CheckGCPressureAsync( async delegate { for (int i = 0; i < releasers.Length; i++) { releasers[i] = await sem.EnterAsync(); } for (int i = 0; i < releasers.Length; i++) { releasers[i].Dispose(); } }, maxBytesAllocated : -1, iterations : 5); }
public async Task TooManyReleases_CopyOfStruct() { var sem = new AsyncSemaphore(2); AsyncSemaphore.Releaser releaser1 = await sem.EnterAsync(); AsyncSemaphore.Releaser releaser2 = await sem.EnterAsync(); // Assigning the releaser struct to another local variable copies it. AsyncSemaphore.Releaser releaser2Copy = releaser2; // Dispose of each copy of the releaser. // The double-release is undetectable. The semaphore should be back at capacity 2. releaser2.Dispose(); Assert.Equal(1, sem.CurrentCount); releaser2Copy.Dispose(); Assert.Equal(2, sem.CurrentCount); releaser1.Dispose(); Assert.Equal(3, sem.CurrentCount); }