diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 7cd940f242fc8b..95a963f1037872 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -18,10 +18,6 @@ true $(IntermediateOutputPath)ILLink.Descriptors.xml $(MSBuildThisFileDirectory)src\ILLink\ - - true - true - true @@ -303,6 +299,12 @@ + + + + + + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index b29bf304f3811b..a48ff67a654fa8 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -145,6 +145,8 @@ private void StartCallback() /// public static void SpinWait(int iterations) { + if (Thread.IsSingleThreaded) return; + if (iterations < SpinWaitCoopThreshold) { SpinWaitInternal(iterations); diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props index 1a07f0aa7c1225..4f7e9114d968ce 100644 --- a/src/coreclr/clr.featuredefines.props +++ b/src/coreclr/clr.featuredefines.props @@ -5,6 +5,8 @@ false false true + true + true @@ -15,6 +17,13 @@ true true true + true + + + true + false + false + false @@ -56,6 +65,8 @@ $(DefineConstants);FEATURE_INTERPRETER $(DefineConstants);FEATURE_PORTABLE_ENTRYPOINTS $(DefineConstants);FEATURE_PORTABLE_HELPERS + $(DefineConstants);FEATURE_WASM_MANAGED_THREADS + $(DefineConstants);FEATURE_SINGLE_THREADED $(DefineConstants);PROFILING_SUPPORTED diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 8bd3953dc82d12..30c1cd022a9d9c 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -232,7 +232,8 @@ private static bool GetLinqExpressionsBuiltWithIsInterpretingOnly() // heavily on Reflection.Emit public static bool IsXmlDsigXsltTransformSupported => !PlatformDetection.IsInAppContainer && IsReflectionEmitSupported; - public static bool IsPreciseGcSupported => !IsMonoRuntime; + public static bool IsPreciseGcSupported => !IsMonoRuntime + && !IsBrowser; // TODO-WASM: https://github.com/dotnet/runtime/issues/114096 public static bool IsRareEnumsSupported => !IsNativeAot; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Xml/src/XmlDocumentDecryptor.cs b/src/libraries/Microsoft.Extensions.Configuration.Xml/src/XmlDocumentDecryptor.cs index 24f88d5166de9a..da0690e56b2ec3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Xml/src/XmlDocumentDecryptor.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Xml/src/XmlDocumentDecryptor.cs @@ -103,7 +103,7 @@ public XmlReader CreateDecryptingXmlReader(Stream input, XmlReaderSettings? sett protected virtual XmlReader DecryptDocumentAndCreateXmlReader(XmlDocument document) { #if !NETSTANDARD2_1 && !NETSTANDARD2_0 && !NETFRAMEWORK // TODO remove with https://github.com/dotnet/runtime/pull/107185 - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); + if (OperatingSystem.IsWasi() || OperatingSystem.IsBrowser()) throw new PlatformNotSupportedException(); #else #pragma warning disable CA1416 #endif diff --git a/src/libraries/Microsoft.Extensions.Hosting.Systemd/src/SystemdNotifier.cs b/src/libraries/Microsoft.Extensions.Hosting.Systemd/src/SystemdNotifier.cs index a27a7c9154e675..791eda2a8413af 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.Systemd/src/SystemdNotifier.cs +++ b/src/libraries/Microsoft.Extensions.Hosting.Systemd/src/SystemdNotifier.cs @@ -42,7 +42,7 @@ public void Notify(ServiceState state) } #if !NETSTANDARD2_1 && !NETSTANDARD2_0 && !NETFRAMEWORK // TODO remove with https://github.com/dotnet/runtime/pull/107185 - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); + if (OperatingSystem.IsWasi() || OperatingSystem.IsBrowser()) throw new PlatformNotSupportedException(); #else #pragma warning disable CA1416 #endif diff --git a/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionCancellationTests.cs b/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionCancellationTests.cs index acba0d0f485ce8..a5c48680d803a1 100644 --- a/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionCancellationTests.cs +++ b/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionCancellationTests.cs @@ -8,9 +8,10 @@ namespace System.Collections.Concurrent.Tests { + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public class BlockingCollectionCancellationTests { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void InternalCancellation_CompleteAdding_Negative() { BlockingCollection coll1 = new BlockingCollection(); @@ -26,7 +27,7 @@ public static void InternalCancellation_CompleteAdding_Negative() } //This tests that Take/TryTake wake up correctly if CompleteAdding() is called while waiting - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void InternalCancellation_WakingUp() { for (int test = 0; test < 2; test++) @@ -60,7 +61,7 @@ public static void InternalCancellation_WakingUp() } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void ExternalCancel_Negative() { BlockingCollection bc = new BlockingCollection(); //empty collection. @@ -96,7 +97,7 @@ public static void ExternalCancel_Negative() cs.Token); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void ExternalCancel_AddToAny() { for (int test = 0; test < 3; test++) diff --git a/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs b/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs index 893784a05e0361..d9c202b4f9df05 100644 --- a/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs +++ b/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs @@ -13,9 +13,10 @@ namespace System.Collections.Concurrent.Tests { /// The class that contains the unit tests of the BlockingCollection. + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public class BlockingCollectionTests { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TestBasicScenarios() { BlockingCollection bc = new BlockingCollection(3); @@ -53,7 +54,7 @@ public static void TestBasicScenarios() /// BlockingCollection throws InvalidOperationException when calling CompleteAdding even after adding and taking all elements /// /// - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TestBugFix544259() { int count = 8; @@ -88,7 +89,7 @@ public static void TestBugFix544259() // Since the change to wait as part of CTS.Dispose, the ODE no longer occurs // but we keep the test as a good example of how cleanup of linkedCTS must be carefully handled // to prevent users of the source CTS mistakenly calling methods on disposed targets. - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TestBugFix626345() { const int noOfProducers = 1; @@ -152,7 +153,7 @@ public static void TestBugFix626345() /// /// Making sure if TryTakeFromAny succeeds, it returns the correct index /// - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TestBugFix914998() { var producer1 = new BlockingCollection(); @@ -252,7 +253,7 @@ public static void TestAddTake_Longrunning(int numOfAdds, int numOfTakes, int bo /// present in the collection. /// Number of producer threads. /// Number of elements added per thread. - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Theory] [InlineData(2, 1024)] [InlineData(8, 512)] public static void TestConcurrentAdd(int numOfThreads, int numOfElementsPerThread) @@ -291,7 +292,7 @@ public static void TestConcurrentAdd(int numOfThreads, int numOfElementsPerThrea /// are consumed by consumers with no element lost nor consumed more than once. /// Total number of producer and consumer threads. /// Number of elements to Add/Take per thread. - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Theory] [InlineData(8, 1024)] public static void TestConcurrentAddTake(int numOfThreads, int numOfElementsPerThread) { @@ -534,7 +535,7 @@ public static void Test7_CompleteAdding() Assert.Equal(0, counter); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void Test7_ConcurrentAdd_CompleteAdding() { BlockingCollection blockingCollection = ConstructBlockingCollection(); @@ -741,7 +742,7 @@ public static void TestAddAnyTakeAny_Longrunning(int numOfAdds, int numOfTakes, /// are consumed by consumers with no element lost nor consumed more than once. /// Total number of producer and consumer threads. /// Number of elements to Add/Take per thread. - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Theory] [InlineData(4, 2048, 2, 64)] [OuterLoop] private static void TestConcurrentAddAnyTakeAny(int numOfThreads, int numOfElementsPerThread, int numOfCollections, int boundOfCollections) diff --git a/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj b/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj index 173fae9740b234..fdf895c25448ff 100644 --- a/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj +++ b/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj @@ -11,6 +11,8 @@ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) true $(DefineConstants);FEATURE_WASM_MANAGED_THREADS + true + $(DefineConstants);FEATURE_SINGLE_THREADED diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs index 9f3dc2674aab52..48b88769467f51 100644 --- a/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs +++ b/src/libraries/System.Linq.Parallel/src/System/Linq/ParallelEnumerable.cs @@ -18,6 +18,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq.Parallel; +using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; @@ -64,9 +65,11 @@ public static class ParallelEnumerable // When running in single partition mode, PLINQ operations will occur on a single partition and will not // be executed in parallel, but will retain PLINQ semantics (exceptions wrapped as aggregates, etc). -#if !FEATURE_WASM_MANAGED_THREADS - [System.Runtime.Versioning.SupportedOSPlatformGuard("browser")] - internal static bool SinglePartitionMode => OperatingSystem.IsBrowser() || OperatingSystem.IsWasi(); + + [SupportedOSPlatformGuard("browser")] + [SupportedOSPlatformGuard("wasi")] +#if FEATURE_SINGLE_THREADED + internal static bool SinglePartitionMode => true; #else internal static bool SinglePartitionMode => false; #endif diff --git a/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs b/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs index d0fa5bea4a4a5d..5d13e583323003 100644 --- a/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs +++ b/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs @@ -67,7 +67,7 @@ public async Task ReceiveHelloWithContextTakeover() Assert.Equal("Hello", Encoding.UTF8.GetString(buffer.Span)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public async Task SendHelloWithContextTakeover() { WebSocketTestStream stream = new(); @@ -164,7 +164,7 @@ public async Task ReceiveHelloWithoutContextTakeover() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public async Task SendHelloWithoutContextTakeover() { WebSocketTestStream stream = new(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs index 11c712fc68d6c1..08b49c3dedf763 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs @@ -35,15 +35,15 @@ public readonly struct ProcessCpuUsage public TimeSpan TotalTime => UserTime + PrivilegedTime; } - public static int ProcessorCount { get; } = GetProcessorCount(); - /// /// Gets whether the current machine has only a single processor. /// -#if !FEATURE_SINGLE_THREADED - internal static bool IsSingleProcessor => ProcessorCount == 1; -#else +#if FEATURE_SINGLE_THREADED internal const bool IsSingleProcessor = true; + public static int ProcessorCount => 1; +#else + internal static bool IsSingleProcessor => ProcessorCount == 1; + public static int ProcessorCount { get; } = GetProcessorCount(); #endif private static volatile sbyte s_privilegedProcess; diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs index 4a4c00efd30137..66184e0dfdcc1d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs @@ -216,15 +216,16 @@ internal Task BeginReadInternal( // thread if it does a second IO request until the first one completes. SemaphoreSlim semaphore = EnsureAsyncActiveSemaphoreInitialized(); Task? semaphoreTask = null; - if (serializeAsynchronously) + + // The synchronous path is emulating legacy behavior. + // Drop the emulation for IsSingleThreaded to avoid throwing. + if (Thread.IsSingleThreaded || serializeAsynchronously) { semaphoreTask = semaphore.WaitAsync(); } else { -#pragma warning disable CA1416 // Validate platform compatibility, issue: https://github.com/dotnet/runtime/issues/44543 semaphore.Wait(); -#pragma warning restore CA1416 } // Create the task to asynchronously do a Read. This task serves both @@ -490,15 +491,16 @@ internal Task BeginWriteInternal( // thread if it does a second IO request until the first one completes. SemaphoreSlim semaphore = EnsureAsyncActiveSemaphoreInitialized(); Task? semaphoreTask = null; - if (serializeAsynchronously) + + // The synchronous path is emulating legacy behavior. + // Drop the emulation for IsSingleThreaded to avoid throwing. + if (Thread.IsSingleThreaded || serializeAsynchronously) { semaphoreTask = semaphore.WaitAsync(); // kick off the asynchronous wait, but don't block } else { -#pragma warning disable CA1416 // Validate platform compatibility, issue: https://github.com/dotnet/runtime/issues/44543 semaphore.Wait(); // synchronously wait here -#pragma warning restore CA1416 } // Create the task to asynchronously do a Write. This task serves both diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/TextWriter.cs b/src/libraries/System.Private.CoreLib/src/System/IO/TextWriter.cs index 59d5a46fcd7e61..8f879a79a6701d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/TextWriter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/TextWriter.cs @@ -883,11 +883,9 @@ public static TextWriter Synchronized(TextWriter writer) { ArgumentNullException.ThrowIfNull(writer); -#if (!TARGET_BROWSER && !TARGET_WASI) || FEATURE_WASM_MANAGED_THREADS - return writer is SyncTextWriter ? writer : new SyncTextWriter(writer); -#else - return writer; -#endif + return Thread.IsSingleThreaded || writer is SyncTextWriter + ? writer + : new SyncTextWriter(writer); } internal sealed class SyncTextWriter : TextWriter, IDisposable diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs index 584df1d63b99d9..2ac672238f74d9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @@ -362,7 +362,7 @@ internal static void ResetThreadPoolThread(Thread currentThread) [Conditional("DEBUG")] internal static void CheckThreadPoolAndContextsAreDefault() { - Debug.Assert(!Thread.IsThreadStartSupported || Thread.CurrentThread.IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads + Debug.Assert(Thread.IsSingleThreaded || Thread.CurrentThread.IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads Debug.Assert(Thread.CurrentThread._executionContext == null, "ThreadPool thread not on Default ExecutionContext."); Debug.Assert(Thread.CurrentThread._synchronizationContext == null, "ThreadPool thread not on Default SynchronizationContext."); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs index e43baa789d3e75..db99731ec0d950 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs @@ -519,6 +519,8 @@ internal int TryEnterSlow(int timeoutMs, int currentThreadId) goto Locked; } + Thread.ThrowIfSingleThreaded(); + // Lock was not acquired and a waiter was registered. All following paths need to unregister the waiter, including // exceptional paths. try diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs index 73464549ff9dea..89ca032236e7a4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs @@ -37,10 +37,6 @@ public bool Wait(int timeoutMs) { Debug.Assert(timeoutMs >= -1); -#if FEATURE_WASM_MANAGED_THREADS - Thread.AssureBlockingPossible(); -#endif - // Try one-shot acquire first Counts counts = _separated._counts; if (counts.SignalCount != 0) @@ -55,6 +51,8 @@ public bool Wait(int timeoutMs) } } + Thread.ThrowIfSingleThreaded(); + return WaitSlow(timeoutMs); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs index c2e970c27d955f..93fa0d54f44aee 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs @@ -153,6 +153,8 @@ private void WaitAndAcquire() { VerifyIsNotLocked(); + Thread.ThrowIfSingleThreaded(); + // Spin a bit to see if the lock becomes available, before forcing the thread into a wait state if (_spinWaiter.SpinWaitForCondition(s_spinWaitTryAcquireCallback, this, SpinCount, SpinSleep0Threshold)) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs index 54b6596d6a4c94..5d3327f343ca6a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs @@ -158,7 +158,7 @@ public ManualResetEventSlim(bool initialState) { // Specify the default spin count, and use default spin if we're // on a multi-processor machine. Otherwise, we won't. - Initialize(initialState, SpinWait.SpinCountforSpinBeforeWait); + Initialize(initialState, SpinWait.SpinCountForSpinBeforeWait); } /// @@ -352,9 +352,6 @@ public void Reset() #endif public void Wait() { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif Wait(Timeout.Infinite, CancellationToken.None); } @@ -491,12 +488,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeout, -1); -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif -#if FEATURE_WASM_MANAGED_THREADS - Thread.AssureBlockingPossible(); -#endif + Thread.ThrowIfSingleThreaded(); if (!IsSet) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Monitor.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Monitor.cs index d83a51f2a0f9cd..4aa19dda87e8a6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Monitor.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Monitor.cs @@ -97,6 +97,7 @@ private static Condition GetCondition(object obj) [UnsupportedOSPlatform("browser")] public static bool Wait(object obj, int millisecondsTimeout) { + Thread.ThrowIfSingleThreaded(); return GetCondition(obj).Wait(millisecondsTimeout, obj); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs index 574dbf5ee2ed28..1e57f434d6f834 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.Portable.cs @@ -55,15 +55,12 @@ public sealed partial class RegisteredWaitHandle : MarshalByRefObject internal RegisteredWaitHandle(WaitHandle waitHandle, _ThreadPoolWaitOrTimerCallback callbackHelper, int millisecondsTimeout, bool repeating) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif + Thread.ThrowIfSingleThreaded(); #if TARGET_WINDOWS Debug.Assert(!ThreadPool.UseWindowsThreadPool); #endif GC.SuppressFinalize(this); - Thread.ThrowIfNoThreadStart(); _waitHandle = waitHandle.SafeWaitHandle; _callbackHelper = callbackHelper; _signedMillisecondsTimeout = millisecondsTimeout; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs index 69fbef6d57dea8..a55ff012d9eecc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs @@ -174,9 +174,6 @@ public SemaphoreSlim(int initialCount, int maxCount) [UnsupportedOSPlatform("browser")] public void Wait() { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif // Call wait with infinite timeout WaitCore(Timeout.Infinite, CancellationToken.None); } @@ -194,9 +191,6 @@ public void Wait() [UnsupportedOSPlatform("browser")] public void Wait(CancellationToken cancellationToken) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif // Call wait with infinite timeout WaitCore(Timeout.Infinite, cancellationToken); } @@ -215,9 +209,6 @@ public void Wait(CancellationToken cancellationToken) [UnsupportedOSPlatform("browser")] public bool Wait(TimeSpan timeout) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif // Validate the timeout long totalMilliseconds = (long)timeout.TotalMilliseconds; if (totalMilliseconds < -1) @@ -248,9 +239,6 @@ public bool Wait(TimeSpan timeout) [UnsupportedOSPlatform("browser")] public bool Wait(TimeSpan timeout, CancellationToken cancellationToken) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif // Validate the timeout long totalMilliseconds = (long)timeout.TotalMilliseconds; if (totalMilliseconds < -1) @@ -276,9 +264,6 @@ public bool Wait(TimeSpan timeout, CancellationToken cancellationToken) [UnsupportedOSPlatform("browser")] public bool Wait(int millisecondsTimeout) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException( @@ -302,10 +287,6 @@ public bool Wait(int millisecondsTimeout) [UnsupportedOSPlatform("browser")] public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif - if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException( @@ -329,9 +310,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) private bool WaitCore(long millisecondsTimeout, CancellationToken cancellationToken) { CheckDispose(); -#if FEATURE_WASM_MANAGED_THREADS - Thread.AssureBlockingPossible(); -#endif + cancellationToken.ThrowIfCancellationRequested(); // Perf: Check the stack timeout parameter before checking the volatile count @@ -341,6 +320,8 @@ private bool WaitCore(long millisecondsTimeout, CancellationToken cancellationTo return false; } + Thread.ThrowIfSingleThreaded(); + long startTime = 0; if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout > 0) { @@ -365,7 +346,7 @@ private bool WaitCore(long millisecondsTimeout, CancellationToken cancellationTo // Monitor.Enter followed by Monitor.Wait is much more expensive than waiting on an event as it involves another // spin, contention, etc. The usual number of spin iterations that would otherwise be used here is increased to // lessen that extra expense of doing a proper wait. - int spinCount = SpinWait.SpinCountforSpinBeforeWait * 4; + int spinCount = SpinWait.SpinCountForSpinBeforeWait * 4; SpinWait spinner = default; while (spinner.Count < spinCount) @@ -470,9 +451,8 @@ private bool WaitCore(long millisecondsTimeout, CancellationToken cancellationTo [UnsupportedOSPlatform("browser")] private bool WaitUntilCountOrTimeout(long millisecondsTimeout, long startTime, CancellationToken cancellationToken) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif + Thread.ThrowIfSingleThreaded(); + int monitorWaitMilliseconds = Timeout.Infinite; // Wait on the monitor as long as the count is zero diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs index 4246a33191d10b..1862346abdb481 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs @@ -86,9 +86,9 @@ public struct SpinWait /// for here. /// #if FEATURE_SINGLE_THREADED - internal const int SpinCountforSpinBeforeWait = 1; + internal const int SpinCountForSpinBeforeWait = 1; #else - internal static readonly int SpinCountforSpinBeforeWait = Environment.IsSingleProcessor ? 1 : 35; + internal static readonly int SpinCountForSpinBeforeWait = Environment.IsSingleProcessor ? 1 : 35; #endif // The number of times we've spun already. diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs index 088f359877badb..487af7c324cf5b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs @@ -676,6 +676,8 @@ internal static Task FromAsyncImpl( } else { + Thread.ThrowIfSingleThreaded(); + #pragma warning disable CA1416 // Validate platform compatibility, issue: https://github.com/dotnet/runtime/issues/44544 ThreadPool.RegisterWaitForSingleObject( asyncResult.AsyncWaitHandle, diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index 1cd3b2e3b540dd..1e64b0c17a51e2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -2969,9 +2969,6 @@ internal bool InternalWait(int millisecondsTimeout, CancellationToken cancellati // to be able to see the method on the stack and inspect arguments). private bool InternalWaitCore(int millisecondsTimeout, CancellationToken cancellationToken) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif // If the task has already completed, there's nothing to wait for. bool returnValue = IsCompleted; if (returnValue) @@ -3007,6 +3004,8 @@ private bool InternalWaitCore(int millisecondsTimeout, CancellationToken cancell } else { + Thread.ThrowIfSingleThreaded(); + returnValue = SpinThenBlockingWait(millisecondsTimeout, cancellationToken); } @@ -3062,6 +3061,8 @@ private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken can bool returnValue = SpinWait(millisecondsTimeout); if (!returnValue) { + Thread.ThrowIfSingleThreaded(); + // We're about to block waiting for the task to complete, which is expensive, and if // the task being waited on depends on some other work to run, this thread could end up // waiting for some other thread to do work. If the two threads are part of the same scheduler, @@ -3140,13 +3141,18 @@ private bool SpinWait(int millisecondsTimeout) { if (IsCompleted) return true; + if (Thread.IsSingleThreaded) + { + return false; + } + if (millisecondsTimeout == 0) { // For 0-timeouts, we just return immediately. return false; } - int spinCount = Threading.SpinWait.SpinCountforSpinBeforeWait; + int spinCount = Threading.SpinWait.SpinCountForSpinBeforeWait; SpinWait spinner = default; while (spinner.Count < spinCount) { @@ -4706,9 +4712,6 @@ internal void RemoveContinuation(object continuationObject) // could be TaskCont [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger public static void WaitAll(params Task[] tasks) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif if (tasks is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); @@ -4734,9 +4737,6 @@ public static void WaitAll(params Task[] tasks) [UnsupportedOSPlatform("browser")] public static void WaitAll(params ReadOnlySpan tasks) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif bool waitResult = WaitAllCore(tasks, Timeout.Infinite, default); Debug.Assert(waitResult, "expected wait to succeed"); } @@ -4774,9 +4774,6 @@ public static void WaitAll(params ReadOnlySpan tasks) [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger public static bool WaitAll(Task[] tasks, TimeSpan timeout) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif long totalMilliseconds = (long)timeout.TotalMilliseconds; if (totalMilliseconds is < -1 or > int.MaxValue) { @@ -4821,9 +4818,6 @@ public static bool WaitAll(Task[] tasks, TimeSpan timeout) [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger public static bool WaitAll(Task[] tasks, int millisecondsTimeout) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif if (tasks is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); @@ -4858,9 +4852,6 @@ public static bool WaitAll(Task[] tasks, int millisecondsTimeout) [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger public static void WaitAll(Task[] tasks, CancellationToken cancellationToken) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif if (tasks is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); @@ -4907,9 +4898,6 @@ public static void WaitAll(Task[] tasks, CancellationToken cancellationToken) [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif if (tasks is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); @@ -4932,9 +4920,6 @@ public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationTo [UnsupportedOSPlatform("browser")] public static void WaitAll(IEnumerable tasks, CancellationToken cancellationToken = default) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif if (tasks is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); @@ -4953,9 +4938,6 @@ public static void WaitAll(IEnumerable tasks, CancellationToken cancellati [UnsupportedOSPlatform("browser")] private static bool WaitAllCore(ReadOnlySpan tasks, int millisecondsTimeout, CancellationToken cancellationToken) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif if (millisecondsTimeout < -1) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout); @@ -5016,6 +4998,8 @@ private static bool WaitAllCore(ReadOnlySpan tasks, int millisecondsTimeou if (waitedOnTaskList != null) { + Thread.ThrowIfSingleThreaded(); + // Block waiting for the tasks to complete. returnValue = WaitAllBlockingCore(waitedOnTaskList, millisecondsTimeout, cancellationToken); @@ -5086,12 +5070,11 @@ private static void AddToList(T item, ref List? list, int initSize) [UnsupportedOSPlatform("browser")] private static bool WaitAllBlockingCore(List tasks, int millisecondsTimeout, CancellationToken cancellationToken) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif Debug.Assert(tasks != null, "Expected a non-null list of tasks"); Debug.Assert(tasks.Count > 0, "Expected at least one task"); + Thread.ThrowIfSingleThreaded(); + bool waitCompleted = false; var mres = new SetOnCountdownMres(tasks.Count); try @@ -5357,6 +5340,8 @@ private static int WaitAnyCore(Task[] tasks, int millisecondsTimeout, Cancellati if (signaledTaskIndex == -1 && tasks.Length != 0) { + Thread.ThrowIfSingleThreaded(); + Task firstCompleted = TaskFactory.CommonCWAnyLogic(tasks, isSyncBlocking: true); bool waitCompleted = firstCompleted.Wait(millisecondsTimeout, cancellationToken); if (waitCompleted) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskAsyncEnumerableExtensions.ToBlockingEnumerable.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskAsyncEnumerableExtensions.ToBlockingEnumerable.cs index 2b66d88540b1b6..1c7ee620860a72 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskAsyncEnumerableExtensions.ToBlockingEnumerable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskAsyncEnumerableExtensions.ToBlockingEnumerable.cs @@ -26,9 +26,8 @@ public static partial class TaskAsyncEnumerableExtensions [UnsupportedOSPlatform("browser")] public static IEnumerable ToBlockingEnumerable(this IAsyncEnumerable source, CancellationToken cancellationToken = default) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif + Thread.ThrowIfSingleThreaded(); + IAsyncEnumerator enumerator = source.GetAsyncEnumerator(cancellationToken); // A ManualResetEventSlim variant that lets us reuse the same // awaiter callback allocation across the entire enumeration. @@ -82,9 +81,8 @@ public ManualResetEventWithAwaiterSupport() [UnsupportedOSPlatform("browser")] public void Wait(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif + Thread.ThrowIfSingleThreaded(); + awaiter.UnsafeOnCompleted(_onCompleted); Wait(); Reset(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs index 6de92eea0af7de..d4edf231413265 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs @@ -42,7 +42,7 @@ internal ThreadPoolTaskScheduler() protected internal override void QueueTask(Task task) { TaskCreationOptions options = task.Options; - if (Thread.IsThreadStartSupported && (options & TaskCreationOptions.LongRunning) != 0) + if (!Thread.IsSingleThreaded && (options & TaskCreationOptions.LongRunning) != 0) { // Run LongRunning tasks on their own dedicated thread. new Thread(s_longRunningThreadWork) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs index b9dd510b80c02a..161889ce1bd082 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @@ -169,23 +169,6 @@ public Thread(ParameterizedThreadStart start, int maxStackSize) Initialize(); } -#if (!TARGET_BROWSER && !TARGET_WASI) || FEATURE_WASM_MANAGED_THREADS - [UnsupportedOSPlatformGuard("browser")] - [UnsupportedOSPlatformGuard("wasi")] - internal static bool IsThreadStartSupported => true; -#else - [UnsupportedOSPlatformGuard("browser")] - [UnsupportedOSPlatformGuard("wasi")] - internal static bool IsThreadStartSupported => false; -#endif - - internal static void ThrowIfNoThreadStart() - { - if (IsThreadStartSupported) - return; - throw new PlatformNotSupportedException(); - } - /// Causes the operating system to change the state of the current instance to , and optionally supplies an object containing data to be used by the method the thread executes. /// An object that contains data to be used by the method the thread executes. /// The thread has already been started. @@ -212,10 +195,7 @@ internal static void ThrowIfNoThreadStart() private void Start(object? parameter, bool captureContext) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif - ThrowIfNoThreadStart(); + Thread.ThrowIfSingleThreaded(); StartHelper? startHelper = _startHelper; @@ -258,7 +238,7 @@ private void Start(object? parameter, bool captureContext) private void Start(bool captureContext) { - ThrowIfNoThreadStart(); + Thread.ThrowIfSingleThreaded(); StartHelper? startHelper = _startHelper; // In the case of a null startHelper (second call to start on same thread) @@ -449,7 +429,7 @@ internal void SetThreadPoolWorkerThreadName() internal void ResetThreadPoolThread() { Debug.Assert(this == CurrentThread); - Debug.Assert(!IsThreadStartSupported || IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads + Debug.Assert(IsSingleThreaded || IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads if (_mayNeedResetForThreadPool) { @@ -461,7 +441,7 @@ internal void ResetThreadPoolThread() private void ResetThreadPoolThreadSlow() { Debug.Assert(this == CurrentThread); - Debug.Assert(!IsThreadStartSupported || IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads + Debug.Assert(IsSingleThreaded || IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads Debug.Assert(_mayNeedResetForThreadPool); _mayNeedResetForThreadPool = false; @@ -757,6 +737,27 @@ public static int GetCurrentProcessorId() return ProcessorIdCache.GetCurrentProcessorId(); } + [SupportedOSPlatformGuard("browser")] + [SupportedOSPlatformGuard("wasi")] +#if FEATURE_SINGLE_THREADED + internal static bool IsSingleThreaded => true; + [DoesNotReturn] + internal static void ThrowIfSingleThreaded() + { + throw new PlatformNotSupportedException(); + } +#else + internal static bool IsSingleThreaded => false; +#if FEATURE_WASM_MANAGED_THREADS + internal static void ThrowIfSingleThreaded() + { + AssureBlockingPossible(); + } +#else + internal static void ThrowIfSingleThreaded() { } +#endif +#endif + #if FEATURE_WASM_MANAGED_THREADS [ThreadStatic] public static bool ThrowOnBlockingWaitOnJSInteropThread; @@ -800,6 +801,10 @@ public static void ForceBlockingWait(Action action, object? state = nul WarnOnBlockingWaitOnJSInteropThread = wflag; } } +#else + internal static unsafe void AssureBlockingPossible() + { + } #endif [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs index 236aa07bc32aae..f0cd642cbeab3b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs @@ -89,7 +89,7 @@ internal static RegisteredWaitHandle RegisterWaitForSingleObject( bool executeOnlyOnce, bool flowExecutionContext) { - Thread.ThrowIfNoThreadStart(); + Thread.ThrowIfSingleThreaded(); return PortableThreadPool.RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, flowExecutionContext); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs index 0480f93dfa35b1..c08cf4a4c715f5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs @@ -1468,11 +1468,11 @@ public static RegisteredWaitHandle RegisterWaitForSingleObject( bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC ) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif if (millisecondsTimeOutInterval > (uint)int.MaxValue && millisecondsTimeOutInterval != uint.MaxValue) throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal); + + Thread.ThrowIfSingleThreaded(); + return RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, true); } @@ -1488,11 +1488,11 @@ public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC ) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif if (millisecondsTimeOutInterval > (uint)int.MaxValue && millisecondsTimeOutInterval != uint.MaxValue) throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + + Thread.ThrowIfSingleThreaded(); + return RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, false); } @@ -1522,10 +1522,10 @@ public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC ) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeOutInterval, -1); + + Thread.ThrowIfSingleThreaded(); + return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, false); } @@ -1540,11 +1540,11 @@ public static RegisteredWaitHandle RegisterWaitForSingleObject( bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC ) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeOutInterval, -1); ArgumentOutOfRangeException.ThrowIfGreaterThan(millisecondsTimeOutInterval, int.MaxValue); + + Thread.ThrowIfSingleThreaded(); + return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, true); } @@ -1559,11 +1559,11 @@ public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC ) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeOutInterval, -1); ArgumentOutOfRangeException.ThrowIfGreaterThan(millisecondsTimeOutInterval, int.MaxValue); + + Thread.ThrowIfSingleThreaded(); + return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, false); } @@ -1578,14 +1578,13 @@ public static RegisteredWaitHandle RegisterWaitForSingleObject( bool executeOnlyOnce ) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif long tm = (long)timeout.TotalMilliseconds; ArgumentOutOfRangeException.ThrowIfLessThan(tm, -1, nameof(timeout)); ArgumentOutOfRangeException.ThrowIfGreaterThan(tm, int.MaxValue, nameof(timeout)); + Thread.ThrowIfSingleThreaded(); + return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)tm, executeOnlyOnce, true); } @@ -1600,14 +1599,13 @@ public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( bool executeOnlyOnce ) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif long tm = (long)timeout.TotalMilliseconds; ArgumentOutOfRangeException.ThrowIfLessThan(tm, -1, nameof(timeout)); ArgumentOutOfRangeException.ThrowIfGreaterThan(tm, int.MaxValue, nameof(timeout)); + Thread.ThrowIfSingleThreaded(); + return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)tm, executeOnlyOnce, false); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs index f5c5320e6409ed..07d11d18adb0d3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs @@ -117,10 +117,6 @@ internal bool WaitOneNoCheck( SafeWaitHandle? waitHandle = _waitHandle; ObjectDisposedException.ThrowIf(waitHandle is null, this); -#if FEATURE_WASM_MANAGED_THREADS - Thread.AssureBlockingPossible(); -#endif - bool success = false; try { diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskRtTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskRtTests.cs index 759502f6ba8567..c9657d3e5d5dfc 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskRtTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskRtTests.cs @@ -580,7 +580,7 @@ public static void TaskDelay_MaxSupported_Success() Assert.True(t.IsCanceled); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void RunDelayTests() { // diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskWaitAllAnyTest.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskWaitAllAnyTest.cs index 61d5e4fc01794a..6a3b1d5223bf54 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskWaitAllAnyTest.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskWaitAllAnyTest.cs @@ -188,6 +188,7 @@ public static void ThrowException() #endregion } + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public sealed class TaskWaitAllAnyTest { #region Private Fields @@ -447,9 +448,10 @@ private bool CheckResult(double result) #endregion + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public sealed class TaskWaitAllAny { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] [OuterLoop] public static void TaskWaitAllAny0() { @@ -460,7 +462,7 @@ public static void TaskWaitAllAny0() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] [OuterLoop] public static void TaskWaitAllAny1() { @@ -481,7 +483,7 @@ public static void TaskWaitAllAny2() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny3() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -491,7 +493,7 @@ public static void TaskWaitAllAny3() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny4() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -502,7 +504,7 @@ public static void TaskWaitAllAny4() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] [OuterLoop] public static void TaskWaitAllAny5() { @@ -519,7 +521,7 @@ public static void TaskWaitAllAny5() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny6() { TaskInfo node1 = new TaskInfo(WorkloadType.Light); @@ -529,7 +531,7 @@ public static void TaskWaitAllAny6() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] [OuterLoop] public static void TaskWaitAllAny7() { @@ -555,7 +557,7 @@ public static void TaskWaitAllAny8() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny9() { TaskInfo node1 = new TaskInfo(WorkloadType.Heavy); @@ -566,7 +568,7 @@ public static void TaskWaitAllAny9() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny10() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -576,7 +578,7 @@ public static void TaskWaitAllAny10() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny11() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -592,7 +594,7 @@ public static void TaskWaitAllAny11() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny12() { TaskInfo node1 = new TaskInfo(WorkloadType.VeryHeavy); @@ -603,7 +605,7 @@ public static void TaskWaitAllAny12() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny13() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -628,7 +630,7 @@ public static void TaskWaitAllAny14() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny15() { TaskInfo node1 = new TaskInfo(WorkloadType.Heavy); @@ -638,7 +640,7 @@ public static void TaskWaitAllAny15() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny16() { TaskInfo node1 = new TaskInfo(WorkloadType.Heavy); @@ -649,7 +651,7 @@ public static void TaskWaitAllAny16() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny17() { TaskInfo node1 = new TaskInfo(WorkloadType.VeryHeavy); @@ -668,7 +670,7 @@ public static void TaskWaitAllAny18() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny19() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -679,7 +681,7 @@ public static void TaskWaitAllAny19() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny20() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -713,7 +715,7 @@ public static void TaskWaitAllAny22() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny23() { TaskInfo node1 = new TaskInfo(WorkloadType.Heavy); @@ -723,7 +725,7 @@ public static void TaskWaitAllAny23() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny24() { TaskInfo node1 = new TaskInfo(WorkloadType.VeryLight); @@ -734,7 +736,7 @@ public static void TaskWaitAllAny24() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny25() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -759,7 +761,7 @@ public static void TaskWaitAllAny26() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny27() { TaskInfo node1 = new TaskInfo(WorkloadType.Light); @@ -769,7 +771,7 @@ public static void TaskWaitAllAny27() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny28() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -780,7 +782,7 @@ public static void TaskWaitAllAny28() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny29() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -805,7 +807,7 @@ public static void TaskWaitAllAny30() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] [OuterLoop] public static void TaskWaitAllAny31() { @@ -890,7 +892,7 @@ public static void TaskWaitAllAny32() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] [OuterLoop] public static void TaskWaitAllAny33() { @@ -901,7 +903,7 @@ public static void TaskWaitAllAny33() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] [OuterLoop] public static void TaskWaitAllAny34() { @@ -922,7 +924,7 @@ public static void TaskWaitAllAny35() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny36() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -938,7 +940,7 @@ public static void TaskWaitAllAny36() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] [OuterLoop] public static void TaskWaitAllAny37() { @@ -950,7 +952,7 @@ public static void TaskWaitAllAny37() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny38() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -960,7 +962,7 @@ public static void TaskWaitAllAny38() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny39() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -985,7 +987,7 @@ public static void TaskWaitAllAny40() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny41() { TaskInfo node1 = new TaskInfo(WorkloadType.VeryLight); @@ -1005,7 +1007,7 @@ public static void TaskWaitAllAny42() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny43() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -1015,7 +1017,7 @@ public static void TaskWaitAllAny43() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny44() { TaskInfo node1 = new TaskInfo(WorkloadType.Heavy); @@ -1026,7 +1028,7 @@ public static void TaskWaitAllAny44() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] [OuterLoop] public static void TaskWaitAllAny45() { @@ -1114,7 +1116,7 @@ public static void TaskWaitAllAny46() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny47() { TaskInfo node1 = new TaskInfo(WorkloadType.VeryHeavy); @@ -1125,7 +1127,7 @@ public static void TaskWaitAllAny47() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny48() { TaskInfo node1 = new TaskInfo(WorkloadType.Heavy); @@ -1141,7 +1143,7 @@ public static void TaskWaitAllAny48() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny49() { TaskInfo node1 = new TaskInfo(WorkloadType.Heavy); @@ -1151,7 +1153,7 @@ public static void TaskWaitAllAny49() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny50() { TaskInfo node1 = new TaskInfo(WorkloadType.Light); @@ -1161,7 +1163,7 @@ public static void TaskWaitAllAny50() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny51() { TaskInfo node1 = new TaskInfo(WorkloadType.Light); @@ -1172,7 +1174,7 @@ public static void TaskWaitAllAny51() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny52() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -1197,7 +1199,7 @@ public static void TaskWaitAllAny53() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny54() { TaskInfo node1 = new TaskInfo(WorkloadType.Heavy); @@ -1208,7 +1210,7 @@ public static void TaskWaitAllAny54() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny55() { TaskInfo node1 = new TaskInfo(WorkloadType.Light); @@ -1218,7 +1220,7 @@ public static void TaskWaitAllAny55() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny56() { TaskInfo node1 = new TaskInfo(WorkloadType.Heavy); @@ -1229,7 +1231,7 @@ public static void TaskWaitAllAny56() test.RealRun(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAllAny57() { TaskInfo node1 = new TaskInfo(WorkloadType.Medium); @@ -1263,7 +1265,7 @@ public static void TaskWaitAll_Enumerable_InvalidArguments() AssertExtensions.Throws("tasks", () => Task.WaitAll((IEnumerable)[null, Task.CompletedTask])); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAll_Enumerable_Canceled() { var tcs = new TaskCompletionSource(); @@ -1274,7 +1276,7 @@ public static void TaskWaitAll_Enumerable_Canceled() Assert.Throws(() => Task.WaitAll((IEnumerable)[Task.CompletedTask, tcs.Task], cts.Token)); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void TaskWaitAll_Enumerable_AllComplete() { Task.WaitAll((IEnumerable)[]); diff --git a/src/libraries/System.Threading.Tasks.Parallel/src/System.Threading.Tasks.Parallel.csproj b/src/libraries/System.Threading.Tasks.Parallel/src/System.Threading.Tasks.Parallel.csproj index ec479f8055b9d1..66eecbd8f95354 100644 --- a/src/libraries/System.Threading.Tasks.Parallel/src/System.Threading.Tasks.Parallel.csproj +++ b/src/libraries/System.Threading.Tasks.Parallel/src/System.Threading.Tasks.Parallel.csproj @@ -9,8 +9,8 @@ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) - true - $(DefineConstants);FEATURE_WASM_MANAGED_THREADS + true + $(DefineConstants);FEATURE_SINGLE_THREADED diff --git a/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.cs b/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.cs index db66e7d9ce8f7d..349b926a0286a2 100644 --- a/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.cs +++ b/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/Parallel.cs @@ -14,6 +14,7 @@ using System.Diagnostics; using System.Numerics; using System.Runtime.ExceptionServices; +using System.Runtime.Versioning; namespace System.Threading.Tasks { @@ -129,6 +130,15 @@ internal int EffectiveMaxConcurrencyLevel /// public static partial class Parallel { + + [SupportedOSPlatformGuard("browser")] + [SupportedOSPlatformGuard("wasi")] +#if FEATURE_SINGLE_THREADED + internal static bool IsSingleThreaded => true; +#else + internal static bool IsSingleThreaded => false; +#endif + // static counter for generating unique Fork/Join Context IDs to be used in ETW events internal static int s_forkJoinContextID; @@ -238,12 +248,7 @@ public static void Invoke(ParallelOptions parallelOptions, params Action[] actio { // If we've gotten this far, it's time to process the actions. -#if !FEATURE_WASM_MANAGED_THREADS - // Web browsers need special treatment that is implemented in TaskReplicator - if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi() || -#else - if ( -#endif + if (IsSingleThreaded || // This is more efficient for a large number of actions, or for enforcing MaxDegreeOfParallelism: (actionsCopy.Length > SMALL_ACTIONCOUNT_LIMIT) || (parallelOptions.MaxDegreeOfParallelism != -1 && parallelOptions.MaxDegreeOfParallelism < actionsCopy.Length) diff --git a/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/TaskReplicator.cs b/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/TaskReplicator.cs index 5090a6bd17dbc4..8ac39f0c832933 100644 --- a/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/TaskReplicator.cs +++ b/src/libraries/System.Threading.Tasks.Parallel/src/System/Threading/Tasks/TaskReplicator.cs @@ -131,8 +131,7 @@ public static void Run(ReplicatableUserAction action, ParallelOp { // Browser hosts do not support synchronous Wait so we want to run the // replicated task directly instead of going through Task infrastructure -#if !FEATURE_WASM_MANAGED_THREADS - if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi() ) + if (Parallel.IsSingleThreaded) { // Since we are running on a single thread, we don't want the action to time out long timeout = long.MaxValue - 1; @@ -143,7 +142,6 @@ public static void Run(ReplicatableUserAction action, ParallelOp throw new Exception("Replicated tasks cannot yield in this single-threaded browser environment"); } else -#endif { int maxConcurrencyLevel = (options.EffectiveMaxConcurrencyLevel > 0) ? options.EffectiveMaxConcurrencyLevel : int.MaxValue; diff --git a/src/libraries/System.Threading/tests/BarrierCancellationTests.cs b/src/libraries/System.Threading/tests/BarrierCancellationTests.cs index f13917b09a82bf..40ca8403faffbe 100644 --- a/src/libraries/System.Threading/tests/BarrierCancellationTests.cs +++ b/src/libraries/System.Threading/tests/BarrierCancellationTests.cs @@ -6,6 +6,7 @@ namespace System.Threading.Tests { + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static class BarrierCancellationTests { [Fact] @@ -30,7 +31,7 @@ public static void BarrierCancellationTestsCancelBeforeWait() barrier.Dispose(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void BarrierCancellationTestsCancelAfterWait_Negative() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); @@ -50,7 +51,7 @@ public static void BarrierCancellationTestsCancelAfterWait_Negative() // currently we don't expose this.. but it was verified manually } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void BarrierCancellationTestsCancelAfterWait() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); diff --git a/src/libraries/System.Threading/tests/CountdownEventCancellationTests.cs b/src/libraries/System.Threading/tests/CountdownEventCancellationTests.cs index 865508b6eb47ea..29d1a2d6966174 100644 --- a/src/libraries/System.Threading/tests/CountdownEventCancellationTests.cs +++ b/src/libraries/System.Threading/tests/CountdownEventCancellationTests.cs @@ -6,6 +6,7 @@ namespace System.Threading.Tests { + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static class CountdownEventCancellationTests { [Fact] @@ -25,7 +26,7 @@ public static void CancelBeforeWait() countdownEvent.Dispose(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void CancelAfterWait() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); diff --git a/src/libraries/System.Threading/tests/CountdownEventTests.cs b/src/libraries/System.Threading/tests/CountdownEventTests.cs index 7c9603f2af03ce..703b4cd7f918e7 100644 --- a/src/libraries/System.Threading/tests/CountdownEventTests.cs +++ b/src/libraries/System.Threading/tests/CountdownEventTests.cs @@ -59,7 +59,7 @@ public static void RunCountdownEventTest0_StateTrans(int initCount, int increms, Assert.Equal(ev.InitialCount, ev.CurrentCount); } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [InlineData(0)] [InlineData(100)] public static void RunCountdownEventTest1_SimpleTimeout(int ms) diff --git a/src/libraries/System.Threading/tests/ManualResetEventSlimCancellationTests.cs b/src/libraries/System.Threading/tests/ManualResetEventSlimCancellationTests.cs index a37208cd3376cd..f9ec49d70dbae8 100644 --- a/src/libraries/System.Threading/tests/ManualResetEventSlimCancellationTests.cs +++ b/src/libraries/System.Threading/tests/ManualResetEventSlimCancellationTests.cs @@ -6,6 +6,7 @@ namespace System.Threading.Tests { + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static class ManualResetEventCancellationTests { [Fact] @@ -25,7 +26,7 @@ public static void CancelBeforeWait() mres.Dispose(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void CancelAfterWait() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); diff --git a/src/libraries/System.Threading/tests/ManualResetEventSlimTests.cs b/src/libraries/System.Threading/tests/ManualResetEventSlimTests.cs index 11e84d146edb3a..cbffddb27c4fbe 100644 --- a/src/libraries/System.Threading/tests/ManualResetEventSlimTests.cs +++ b/src/libraries/System.Threading/tests/ManualResetEventSlimTests.cs @@ -50,7 +50,7 @@ public static void RunManualResetEventSlimTest1_SimpleWait() } // Tests timeout on an event that is never set. - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void RunManualResetEventSlimTest2_TimeoutWait() { for (int i = 0; i < 2; i++) diff --git a/src/libraries/System.Threading/tests/MonitorTests.cs b/src/libraries/System.Threading/tests/MonitorTests.cs index 1153330d0ec7e1..3afef4f0161104 100644 --- a/src/libraries/System.Threading/tests/MonitorTests.cs +++ b/src/libraries/System.Threading/tests/MonitorTests.cs @@ -397,7 +397,7 @@ public static void Enter_HasToWait() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void Wait_Invalid() { var obj = new object(); diff --git a/src/libraries/System.Threading/tests/SemaphoreSlimCancellationTests.cs b/src/libraries/System.Threading/tests/SemaphoreSlimCancellationTests.cs index 91f2a1ff079f45..96ac716c616fea 100644 --- a/src/libraries/System.Threading/tests/SemaphoreSlimCancellationTests.cs +++ b/src/libraries/System.Threading/tests/SemaphoreSlimCancellationTests.cs @@ -6,6 +6,7 @@ namespace System.Threading.Tests { + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static class SemaphoreSlimCancellationTests { [Fact] @@ -25,7 +26,7 @@ public static void CancelBeforeWait() semaphoreSlim.Dispose(); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Fact] public static void CancelAfterWait() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); @@ -49,7 +50,7 @@ public static void CancelAfterWait() // currently we don't expose this.. but it was verified manually } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [Theory] [InlineData(false)] [InlineData(true)] public static async Task Cancel_WaitAsync_ContinuationInvokedAsynchronously(bool withTimeout) diff --git a/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs b/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs index a432ee3d274083..227ccda9bb8ac7 100644 --- a/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs +++ b/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs @@ -44,7 +44,7 @@ public static void RunSemaphoreSlimTest0_Ctor_Negative() RunSemaphoreSlimTest0_Helper(-1, 10, typeof(ArgumentOutOfRangeException)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void RunSemaphoreSlimTest1_Wait() { // Infinite timeout @@ -63,7 +63,7 @@ public static void RunSemaphoreSlimTest1_Wait() RunSemaphoreSlimTest1_Wait_Helper(1, 10, TimeSpan.FromMilliseconds(uint.MaxValue), true, null); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void RunSemaphoreSlimTest1_Wait_NegativeCases() { // Invalid timeout @@ -121,7 +121,7 @@ public static void RunSemaphoreSlimTest2_Release_NegativeCases() RunSemaphoreSlimTest2_Release_Helper(int.MaxValue - 1, int.MaxValue, 10, typeof(SemaphoreFullException)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void RunSemaphoreSlimTest4_Dispose() { RunSemaphoreSlimTest4_Dispose_Helper(5, 10, null, null); @@ -136,7 +136,7 @@ public static void RunSemaphoreSlimTest4_Dispose() (5, 10, SemaphoreSlimActions.AvailableWaitHandle, typeof(ObjectDisposedException)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void RunSemaphoreSlimTest5_CurrentCount() { RunSemaphoreSlimTest5_CurrentCount_Helper(5, 10, null); @@ -145,7 +145,7 @@ public static void RunSemaphoreSlimTest5_CurrentCount() RunSemaphoreSlimTest5_CurrentCount_Helper(5, 10, SemaphoreSlimActions.Release); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void RunSemaphoreSlimTest7_AvailableWaitHandle() { RunSemaphoreSlimTest7_AvailableWaitHandle_Helper(5, 10, null, true); diff --git a/src/libraries/System.Threading/tests/SynchronizationContextTests.cs b/src/libraries/System.Threading/tests/SynchronizationContextTests.cs index 3872ae8254e4ab..3f2a83d3e6bae8 100644 --- a/src/libraries/System.Threading/tests/SynchronizationContextTests.cs +++ b/src/libraries/System.Threading/tests/SynchronizationContextTests.cs @@ -40,7 +40,7 @@ public static void WaitTest_ChangedInDotNetCore() Assert.Throws(() => TestSynchronizationContext.WaitHelper(null, false, 0)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [ActiveIssue("https://github.com/dotnet/runtime/issues/31977", TestRuntimes.Mono)] public static void WaitNotificationTest() { diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index c9c8de44a0a8d0..76b704621ac085 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -543,6 +543,8 @@ + + diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 69f06966ce1b4c..ba10df327a5f4b 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -139,7 +139,6 @@ - diff --git a/src/mono/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.wasm.singlethread.xml b/src/mono/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.wasm.singlethread.xml deleted file mode 100644 index 9e0b006e869e81..00000000000000 --- a/src/mono/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.wasm.singlethread.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs index 4633be109690a1..82345e3468a641 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs @@ -77,9 +77,8 @@ public static bool IsEntered(object obj) public static bool Wait(object obj, int millisecondsTimeout) { ArgumentNullException.ThrowIfNull(obj); -#if FEATURE_WASM_MANAGED_THREADS - Thread.AssureBlockingPossible(); -#endif + Thread.ThrowIfSingleThreaded(); + return ObjWait(millisecondsTimeout, obj); } diff --git a/src/mono/wasm/threads.md b/src/mono/wasm/threads.md index cb73898c2c9307..14afe4c1d0b5d2 100644 --- a/src/mono/wasm/threads.md +++ b/src/mono/wasm/threads.md @@ -32,8 +32,8 @@ assemblies. ### Implementation assemblies ### The implementation (in `System.Private.CoreLib`) we check -`System.Threading.Thread.IsThreadStartSupported` or call -`System.Threading.Thread.ThrowIfNoThreadStart()` to guard code paths that depends on +`System.Threading.Thread.IsSingleThreaded` or call +`System.Threading.Thread.ThrowIfSingleThreaded()` to guard code paths that depends on multi-threading. The property is a boolean constant that will allow the IL trimmer or the JIT/interpreter/AOT to drop the multi-threaded implementation in the single-threaded CoreLib. diff --git a/src/tests/issues.targets b/src/tests/issues.targets index da897d13d30750..7dc2c5faf6bb06 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -2814,7 +2814,7 @@ System.Threading.Thread.UnsafeStart not supported - System.Threading.Thread.ThrowIfNoThreadStart: PlatformNotSupportedException + System.Threading.Thread.ThrowIfSingleThreaded: PlatformNotSupportedException Could not load legacy Microsoft.Diagnostics.Tools.RuntimeClient