diff --git a/src/Microsoft.VisualStudio.Threading/IllegalSemaphoreUsageException.cs b/src/Microsoft.VisualStudio.Threading/IllegalSemaphoreUsageException.cs new file mode 100644 index 000000000..1660c1905 --- /dev/null +++ b/src/Microsoft.VisualStudio.Threading/IllegalSemaphoreUsageException.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.Threading +{ + using System; + using System.Globalization; + + /// + /// Exception which is thrown when the contract of a is violated. + /// + public class IllegalSemaphoreUsageException : InvalidOperationException + { + /// + /// Initializes a new instance of the class. + /// + public IllegalSemaphoreUsageException(string message) + : base(message) + { + } + } +} diff --git a/src/Microsoft.VisualStudio.Threading/ReentrantSemaphore.cs b/src/Microsoft.VisualStudio.Threading/ReentrantSemaphore.cs index a8e4c8954..d6b97eb20 100644 --- a/src/Microsoft.VisualStudio.Threading/ReentrantSemaphore.cs +++ b/src/Microsoft.VisualStudio.Threading/ReentrantSemaphore.cs @@ -7,6 +7,7 @@ namespace Microsoft.VisualStudio.Threading using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Globalization; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -759,7 +760,7 @@ public override async Task ExecuteAsync(Func operation, CancellationToken // When the semaphore faults, we will drain and throw for awaiting tasks one by one. this.faulted = true; #pragma warning disable CA2219 // Do not raise exceptions in finally clauses - throw Verify.FailOperation(Strings.SemaphoreStackNestingViolated, ReentrancyMode.Stack); + throw new IllegalSemaphoreUsageException(string.Format(CultureInfo.CurrentCulture, Strings.SemaphoreStackNestingViolated, ReentrantSemaphore.ReentrancyMode.Stack)); #pragma warning restore CA2219 // Do not raise exceptions in finally clauses } } @@ -873,7 +874,7 @@ public override async ValueTask ExecuteAsync(Func> operation, // When the semaphore faults, we will drain and throw for awaiting tasks one by one. this.faulted = true; #pragma warning disable CA2219 // Do not raise exceptions in finally clauses - throw Verify.FailOperation(Strings.SemaphoreStackNestingViolated, ReentrancyMode.Stack); + throw new IllegalSemaphoreUsageException(string.Format(CultureInfo.CurrentCulture, Strings.SemaphoreStackNestingViolated, ReentrantSemaphore.ReentrancyMode.Stack)); #pragma warning restore CA2219 // Do not raise exceptions in finally clauses } } @@ -901,7 +902,7 @@ protected override void ThrowIfFaulted() { if (this.faulted) { - throw Verify.FailOperation(Strings.SemaphoreMisused); + throw new SemaphoreFaultedException(); } } } diff --git a/src/Microsoft.VisualStudio.Threading/SemaphoreFaultedException.cs b/src/Microsoft.VisualStudio.Threading/SemaphoreFaultedException.cs new file mode 100644 index 000000000..d9412344c --- /dev/null +++ b/src/Microsoft.VisualStudio.Threading/SemaphoreFaultedException.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.Threading +{ + using System; + + /// + /// Exception thrown when a is in a faulted state. + /// + public class SemaphoreFaultedException : InvalidOperationException + { + /// + /// Initializes a new instance of the class. + /// + public SemaphoreFaultedException() + : base(Strings.SemaphoreMisused) + { + } + } +} diff --git a/src/Microsoft.VisualStudio.Threading/net472/PublicAPI.Unshipped.txt b/src/Microsoft.VisualStudio.Threading/net472/PublicAPI.Unshipped.txt index 860bd7636..e85d3c718 100644 --- a/src/Microsoft.VisualStudio.Threading/net472/PublicAPI.Unshipped.txt +++ b/src/Microsoft.VisualStudio.Threading/net472/PublicAPI.Unshipped.txt @@ -1,4 +1,8 @@ Microsoft.VisualStudio.Threading.AsyncReaderWriterLock.AsyncReaderWriterLock(Microsoft.VisualStudio.Threading.JoinableTaskContext? joinableTaskContext, bool captureDiagnostics = false) -> void Microsoft.VisualStudio.Threading.AsyncReaderWriterResourceLock.AsyncReaderWriterResourceLock(Microsoft.VisualStudio.Threading.JoinableTaskContext? joinableTaskContext, bool captureDiagnostics) -> void Microsoft.VisualStudio.Threading.JoinableTaskContext.IsMainThreadMaybeBlocked() -> bool +Microsoft.VisualStudio.Threading.SemaphoreFaultedException +Microsoft.VisualStudio.Threading.SemaphoreFaultedException.SemaphoreFaultedException() -> void +Microsoft.VisualStudio.Threading.IllegalSemaphoreUsageException +Microsoft.VisualStudio.Threading.IllegalSemaphoreUsageException.IllegalSemaphoreUsageException(string! message) -> void virtual Microsoft.VisualStudio.Threading.AsyncReaderWriterLock.DeadlockCheckTimeout.get -> System.TimeSpan diff --git a/src/Microsoft.VisualStudio.Threading/netcoreapp3.1/PublicAPI.Unshipped.txt b/src/Microsoft.VisualStudio.Threading/netcoreapp3.1/PublicAPI.Unshipped.txt index 860bd7636..e85d3c718 100644 --- a/src/Microsoft.VisualStudio.Threading/netcoreapp3.1/PublicAPI.Unshipped.txt +++ b/src/Microsoft.VisualStudio.Threading/netcoreapp3.1/PublicAPI.Unshipped.txt @@ -1,4 +1,8 @@ Microsoft.VisualStudio.Threading.AsyncReaderWriterLock.AsyncReaderWriterLock(Microsoft.VisualStudio.Threading.JoinableTaskContext? joinableTaskContext, bool captureDiagnostics = false) -> void Microsoft.VisualStudio.Threading.AsyncReaderWriterResourceLock.AsyncReaderWriterResourceLock(Microsoft.VisualStudio.Threading.JoinableTaskContext? joinableTaskContext, bool captureDiagnostics) -> void Microsoft.VisualStudio.Threading.JoinableTaskContext.IsMainThreadMaybeBlocked() -> bool +Microsoft.VisualStudio.Threading.SemaphoreFaultedException +Microsoft.VisualStudio.Threading.SemaphoreFaultedException.SemaphoreFaultedException() -> void +Microsoft.VisualStudio.Threading.IllegalSemaphoreUsageException +Microsoft.VisualStudio.Threading.IllegalSemaphoreUsageException.IllegalSemaphoreUsageException(string! message) -> void virtual Microsoft.VisualStudio.Threading.AsyncReaderWriterLock.DeadlockCheckTimeout.get -> System.TimeSpan diff --git a/src/Microsoft.VisualStudio.Threading/netstandard2.0/PublicAPI.Unshipped.txt b/src/Microsoft.VisualStudio.Threading/netstandard2.0/PublicAPI.Unshipped.txt index 860bd7636..e85d3c718 100644 --- a/src/Microsoft.VisualStudio.Threading/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.VisualStudio.Threading/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,4 +1,8 @@ Microsoft.VisualStudio.Threading.AsyncReaderWriterLock.AsyncReaderWriterLock(Microsoft.VisualStudio.Threading.JoinableTaskContext? joinableTaskContext, bool captureDiagnostics = false) -> void Microsoft.VisualStudio.Threading.AsyncReaderWriterResourceLock.AsyncReaderWriterResourceLock(Microsoft.VisualStudio.Threading.JoinableTaskContext? joinableTaskContext, bool captureDiagnostics) -> void Microsoft.VisualStudio.Threading.JoinableTaskContext.IsMainThreadMaybeBlocked() -> bool +Microsoft.VisualStudio.Threading.SemaphoreFaultedException +Microsoft.VisualStudio.Threading.SemaphoreFaultedException.SemaphoreFaultedException() -> void +Microsoft.VisualStudio.Threading.IllegalSemaphoreUsageException +Microsoft.VisualStudio.Threading.IllegalSemaphoreUsageException.IllegalSemaphoreUsageException(string! message) -> void virtual Microsoft.VisualStudio.Threading.AsyncReaderWriterLock.DeadlockCheckTimeout.get -> System.TimeSpan diff --git a/test/Microsoft.VisualStudio.Threading.Tests/ReentrantSemaphoreTestBase.cs b/test/Microsoft.VisualStudio.Threading.Tests/ReentrantSemaphoreTestBase.cs index 7b05eced7..d5a9482b8 100644 --- a/test/Microsoft.VisualStudio.Threading.Tests/ReentrantSemaphoreTestBase.cs +++ b/test/Microsoft.VisualStudio.Threading.Tests/ReentrantSemaphoreTestBase.cs @@ -533,18 +533,18 @@ public void Stack_ViolationCaughtAtBothSites() // Release the outer one first. This should throw because the inner one hasn't been released yet. release1.Set(); - await Assert.ThrowsAsync(() => operation1); + await Assert.ThrowsAsync(() => operation1); // Verify that the semaphore is in a faulted state. - Assert.Throws(() => this.semaphore.CurrentCount); + Assert.Throws(() => this.semaphore.CurrentCount); // Release the nested one last, which should similarly throw because its parent is already released. release2.Set(); - await Assert.ThrowsAsync(() => operation2); + await Assert.ThrowsAsync(() => operation2); // Verify that the semaphore is still in a faulted state, and will reject new calls. - Assert.Throws(() => this.semaphore.CurrentCount); - await Assert.ThrowsAsync(() => this.semaphore.ExecuteAsync(() => Task.CompletedTask)); + Assert.Throws(() => this.semaphore.CurrentCount); + await Assert.ThrowsAsync(() => this.semaphore.ExecuteAsync(() => Task.CompletedTask)); }); } @@ -691,12 +691,12 @@ public void Stack_FaultedSemaphoreDrains() Task? pendingSemaphoreTask = semaphore.ExecuteAsync(() => Task.CompletedTask); releaser1.Set(); - await Assert.ThrowsAsync(() => outerFaultySemaphoreTask).WithCancellation(this.TimeoutToken); - await Assert.ThrowsAsync(() => pendingSemaphoreTask).WithCancellation(this.TimeoutToken); + await Assert.ThrowsAsync(() => outerFaultySemaphoreTask).WithCancellation(this.TimeoutToken); + await Assert.ThrowsAsync(() => pendingSemaphoreTask).WithCancellation(this.TimeoutToken); releaser1.Set(); - await Assert.ThrowsAsync(() => innerFaulterSemaphoreTask).WithCancellation(this.TimeoutToken); - await Assert.ThrowsAsync(() => semaphore.ExecuteAsync(() => Task.CompletedTask)).WithCancellation(this.TimeoutToken); + await Assert.ThrowsAsync(() => innerFaulterSemaphoreTask).WithCancellation(this.TimeoutToken); + await Assert.ThrowsAsync(() => semaphore.ExecuteAsync(() => Task.CompletedTask)).WithCancellation(this.TimeoutToken); }); }