diff --git a/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs b/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs index 7f58fd5b2424e8..79f3a6210f297a 100644 --- a/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs @@ -200,7 +200,10 @@ await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeaders cts.Cancel(); // Verify that the task completed. - Assert.True(((IAsyncResult)task).AsyncWaitHandle.WaitOne(new TimeSpan(0, 5, 0))); + if (PlatformDetection.IsThreadingSupported) + { + Assert.True(((IAsyncResult)task).AsyncWaitHandle.WaitOne(new TimeSpan(0, 5, 0))); + } Assert.True(task.IsCompleted, "Task was not yet completed"); // Verify that the task completed successfully or is canceled. diff --git a/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionCancellationTests.cs b/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionCancellationTests.cs index 14fa6d8d487467..dd5c6d984f60cd 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.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj index 6cc3ad9efb0ef6..9605ba17573da9 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj @@ -16,6 +16,9 @@ System.Diagnostics.DiagnosticSource + $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) + true + $(DefineConstants);FEATURE_WASM_MANAGED_THREADS $(DefineConstants);ENABLE_HTTP_HANDLER $(DefineConstants);W3C_DEFAULT_ID_FORMAT;MEMORYMARSHAL_SUPPORT;OS_ISBROWSER_SUPPORT true diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs index 2dd9450c6eae50..2a44c197a75bd5 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs @@ -156,6 +156,10 @@ private void PublishedInstrument(Instrument instrument, MeterListener _) public void Start() { +#if OS_ISBROWSER_SUPPORT && !FEATURE_WASM_MANAGED_THREADS + if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); +#endif + // if already started or already stopped we can't be started again Debug.Assert(_collectThread == null && !_cts.IsCancellationRequested); Debug.Assert(CollectionPeriod.TotalSeconds >= MinCollectionTimeSecs); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs index 9640b68b8cf264..73f28593fe92cd 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs @@ -335,7 +335,7 @@ public void OnEventCommand(EventCommandEventArgs command) { try { -#if OS_ISBROWSER_SUPPORT +#if OS_ISBROWSER_SUPPORT && !FEATURE_WASM_MANAGED_THREADS if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()) { // AggregationManager uses a dedicated thread to avoid losing data for apps experiencing threadpool starvation diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs index 0a56bbd6772e84..f858d4ebb39bf6 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/libraries/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs b/src/libraries/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs index a5a61579d9787c..211ec7cc494f01 100644 --- a/src/libraries/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs +++ b/src/libraries/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -328,7 +329,7 @@ public async Task FlushAsyncThrowsIfPassedCanceledCancellationTokenAndPipeIsAble Assert.True(task.Result.IsCompleted); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public async Task ReadAsyncReturnsDataAfterItWasWrittenDuringCancelledRead() { ValueTask readTask = Pipe.Reader.ReadAsync(); diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Channels/AsynchronousChannel.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Channels/AsynchronousChannel.cs index 6bffc272ee8e2d..9c84c02bf1e3aa 100644 --- a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Channels/AsynchronousChannel.cs +++ b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Channels/AsynchronousChannel.cs @@ -324,6 +324,10 @@ private void EnqueueChunk(T[] chunk) private void WaitUntilNonFull() { +#if !FEATURE_WASM_MANAGED_THREADS + if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); +#endif + Debug.Assert(_producerEvent != null); // We must loop; sometimes the producer event will have been set diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Scheduling/QueryTaskGroupState.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Scheduling/QueryTaskGroupState.cs index 19631faf1603b4..041596730515ff 100644 --- a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Scheduling/QueryTaskGroupState.cs +++ b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Scheduling/QueryTaskGroupState.cs @@ -99,6 +99,8 @@ internal void QueryEnd(bool userInitiatedDispose) // See also "QueryOpeningEnumerator" which duplicates some of this logic. // See also "ExceptionAggregator" which duplicates some of this logic. + // TODO could we query the state of the tasks instead of waiting on them? + // or spin thread pool until solved? try { // Wait for all the tasks to complete diff --git a/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs b/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs index d0fa5bea4a4a5d..a61621e0e3c803 100644 --- a/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs +++ b/src/libraries/System.Net.WebSockets/tests/WebSocketDeflateTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics; @@ -67,7 +68,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 +165,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/Diagnostics/Tracing/CounterGroup.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs index d08c6949616c10..4ea17805af84c5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs @@ -148,6 +148,8 @@ internal static CounterGroup GetCounterGroup(EventSource eventSource) private void EnableTimer(float pollingIntervalInSeconds) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + Debug.Assert(pollingIntervalInSeconds > 0); Debug.Assert(Monitor.IsEntered(s_counterGroupLock)); if (_pollingIntervalInMilliseconds == 0 || pollingIntervalInSeconds * 1000 < _pollingIntervalInMilliseconds) diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.cs index ffa63f1c98db1c..525e0c1d8e043c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.cs @@ -131,10 +131,7 @@ private void CommitDispatchConfiguration() private void StartDispatchTask(ulong sessionID, DateTime syncTimeUtc, long syncTimeQPC, long timeQPCFrequency) { - if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()) - { - throw new PlatformNotSupportedException(); - } + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); Debug.Assert(Monitor.IsEntered(m_dispatchControlLock)); Debug.Assert(sessionID != 0); diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs index 654c1d1aaf8ba5..bc9ba69daf5622 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs @@ -40,7 +40,11 @@ public readonly struct ProcessCpuUsage /// /// Gets whether the current machine has only a single processor. /// +#if !FEATURE_SINGLE_THREADED internal static bool IsSingleProcessor => ProcessorCount == 1; +#else + internal const bool IsSingleProcessor = true; +#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..fdb97f9418ef3e 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,14 @@ internal Task BeginReadInternal( // thread if it does a second IO request until the first one completes. SemaphoreSlim semaphore = EnsureAsyncActiveSemaphoreInitialized(); Task? semaphoreTask = null; - if (serializeAsynchronously) + // TODO Pavel - review again + if (serializeAsynchronously || !Thread.IsMultiThreadedPlatform) { 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 +489,14 @@ internal Task BeginWriteInternal( // thread if it does a second IO request until the first one completes. SemaphoreSlim semaphore = EnsureAsyncActiveSemaphoreInitialized(); Task? semaphoreTask = null; - if (serializeAsynchronously) + // TODO Pavel - review again + if (serializeAsynchronously || !Thread.IsMultiThreadedPlatform) { 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/Threading/CancellationTokenSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs index 7ea36b67c970be..44239f5aeac281 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs @@ -91,6 +91,8 @@ internal WaitHandle WaitHandle { get { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + ThrowIfDisposed(); // Return the handle if it was already allocated. @@ -1167,6 +1169,7 @@ public async ValueTask WaitForCallbackToCompleteAsync(long id) /// Enters the lock for this instance. The current thread must not be holding the lock, but that is not validated. public void EnterLock() { +#if !FEATURE_SINGLE_THREADED ref bool value = ref _locked; if (Interlocked.Exchange(ref value, true)) { @@ -1179,6 +1182,9 @@ static void Contention(ref bool value) do { sw.SpinOnce(); } while (Interlocked.Exchange(ref value, true)); } } +#else + _locked = true; +#endif } /// Exits the lock for this instance. The current thread must be holding the lock, but that is not validated. 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 51e957eb184663..caec0e0047f46a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @@ -347,7 +347,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.IsMultiThreadedPlatform || 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 3c581f309e732d..5e4cd5fac2f29f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs @@ -392,6 +392,8 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId) spinCount = maxSpinCount; } + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); // this should not happen, but it helps to trim the code below on ST platforms + for (short spinIndex = 0; ;) { LowLevelSpinWaiter.Wait(spinIndex, SpinSleep0Threshold, isSingleProcessor: false); @@ -481,6 +483,8 @@ private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId) goto Locked; } + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); // this should not happen, but it helps to trim the code below on ST platforms + // 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 39233c87c15c96..dcaf1747edafc1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs @@ -39,6 +39,8 @@ public LowLevelLifoSemaphore(int initialSignalCount, int maximumSignalCount, int public bool Wait(int timeoutMs, bool spinWait) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + Debug.Assert(timeoutMs >= -1); #if FEATURE_WASM_MANAGED_THREADS 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..22ced68b75aae8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLock.cs @@ -13,13 +13,21 @@ namespace System.Threading /// internal sealed class LowLevelLock : IDisposable { + private const int LockedMask = 1; + +#if !FEATURE_SINGLE_THREADED private const int SpinCount = 8; private const int SpinSleep0Threshold = 4; + private static readonly Func s_spinWaitTryAcquireCallback = SpinWaitTryAcquireCallback; - private const int LockedMask = 1; private const int WaiterCountIncrement = 2; - private static readonly Func s_spinWaitTryAcquireCallback = SpinWaitTryAcquireCallback; + // Indicates whether a thread has been signaled, but has not yet been released from the wait. See SignalWaiter. Reads + // and writes must occur while _monitor is locked. + private bool _isAnyWaitingThreadSignaled; + + private LowLevelSpinWaiter _spinWaiter; +#endif // Layout: // - Bit 0: 1 if the lock is locked, 0 otherwise @@ -30,16 +38,13 @@ internal sealed class LowLevelLock : IDisposable private Thread? _ownerThread; #endif - // Indicates whether a thread has been signaled, but has not yet been released from the wait. See SignalWaiter. Reads - // and writes must occur while _monitor is locked. - private bool _isAnyWaitingThreadSignaled; - - private LowLevelSpinWaiter _spinWaiter; private LowLevelMonitor _monitor; public LowLevelLock() { +#if !FEATURE_SINGLE_THREADED _spinWaiter = default(LowLevelSpinWaiter); +#endif _monitor.Initialize(); } @@ -114,6 +119,7 @@ public bool TryAcquire() { VerifyIsNotLocked(); +#if !FEATURE_SINGLE_THREADED // A common case is that there are no waiters, so hope for that and try to acquire the lock int state = Interlocked.CompareExchange(ref _state, LockedMask, 0); if (state == 0 || TryAcquire_NoFastPath(state)) @@ -121,9 +127,18 @@ public bool TryAcquire() SetOwnerThreadToCurrent(); return true; } +#else + if (_state == 0) + { + _state = LockedMask; + SetOwnerThreadToCurrent(); + return true; + } +#endif return false; } +#if !FEATURE_SINGLE_THREADED private bool TryAcquire_NoFastPath(int state) { // The lock may be available, but there may be waiters. This thread could acquire the lock in that case. Acquiring @@ -140,6 +155,7 @@ private static bool SpinWaitTryAcquireCallback(object state) var thisRef = (LowLevelLock)state; return thisRef.TryAcquire_NoFastPath(thisRef._state); } +#endif public void Acquire() { @@ -151,6 +167,9 @@ public void Acquire() private void WaitAndAcquire() { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + +#if !FEATURE_SINGLE_THREADED VerifyIsNotLocked(); // Spin a bit to see if the lock becomes available, before forcing the thread into a wait state @@ -188,6 +207,7 @@ private void WaitAndAcquire() } _monitor.Release(); +#endif Debug.Assert((_state & LockedMask) != 0); SetOwnerThreadToCurrent(); @@ -198,12 +218,17 @@ public void Release() Debug.Assert((_state & LockedMask) != 0); ResetOwnerThread(); +#if !FEATURE_SINGLE_THREADED if (Interlocked.Decrement(ref _state) != 0) { SignalWaiter(); } +#else + _state--; +#endif } +#if !FEATURE_SINGLE_THREADED private void SignalWaiter() { // Since the lock was already released by the caller, there are no guarantees on the state at this point. For @@ -229,5 +254,6 @@ private void SignalWaiter() _monitor.Release(); } +#endif } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.Unix.cs index 4b3dfb3be1cc4b..b79e5c103d110b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.Unix.cs @@ -46,6 +46,8 @@ private void WaitCore() private bool WaitCore(int timeoutMilliseconds) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + Debug.Assert(timeoutMilliseconds >= -1); if (timeoutMilliseconds < 0) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.cs index 3553ab9e707dde..565f5d82f847fe 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelMonitor.cs @@ -87,6 +87,7 @@ public void Release() public void Wait() { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); ResetOwnerThread(); WaitCore(); SetOwnerThreadToCurrent(); @@ -94,6 +95,7 @@ public void Wait() public bool Wait(int timeoutMilliseconds) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); Debug.Assert(timeoutMilliseconds >= -1); ResetOwnerThread(); 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 41330649af1635..d3e9a4677bc448 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs @@ -186,7 +186,9 @@ public ManualResetEventSlim(bool initialState, int spinCount) /// /// Whether the event is set initially or not. /// The spin count that decides when the event will block. +#pragma warning disable IDE0060 // Remove unused parameter private void Initialize(bool initialState, int spinCount) +#pragma warning restore IDE0060 // Remove unused parameter { m_combinedState = initialState ? (1 << SignalledState_ShiftCount) : 0; // the spinCount argument has been validated by the ctors. @@ -350,9 +352,8 @@ 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + Wait(Timeout.Infinite, CancellationToken.None); } @@ -489,9 +490,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + #if FEATURE_WASM_MANAGED_THREADS Thread.AssureBlockingPossible(); #endif 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..8ef81cc724acec 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); #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 bab6bbd332b19a..ea7048361c6c55 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,8 @@ 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + // Call wait with infinite timeout Wait(Timeout.Infinite, CancellationToken.None); } @@ -194,9 +193,8 @@ 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + // Call wait with infinite timeout Wait(Timeout.Infinite, cancellationToken); } @@ -216,9 +214,8 @@ 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + // Validate the timeout long totalMilliseconds = (long)timeout.TotalMilliseconds; if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) @@ -250,9 +247,8 @@ 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + // Validate the timeout long totalMilliseconds = (long)timeout.TotalMilliseconds; if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) @@ -278,9 +274,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + return Wait(millisecondsTimeout, CancellationToken.None); } @@ -299,9 +294,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + CheckDispose(); #if FEATURE_WASM_MANAGED_THREADS Thread.AssureBlockingPossible(); @@ -451,9 +445,8 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) [UnsupportedOSPlatform("browser")] private bool WaitUntilCountOrTimeout(int millisecondsTimeout, uint startTime, CancellationToken cancellationToken) { -#if TARGET_WASI - if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 -#endif + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + int remainingWaitMilliseconds = 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 540dbf4c98f8b1..aab51be188fcd4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs @@ -85,7 +85,11 @@ public struct SpinWait /// depends on the likelihood of the spin being successful and how long the wait would be but those are not accounted /// for here. /// +#if !FEATURE_SINGLE_THREADED internal static readonly int SpinCountforSpinBeforeWait = Environment.IsSingleProcessor ? 1 : 35; +#else + internal const int SpinCountforSpinBeforeWait = 1; +#endif // The number of times we've spun already. private int _count; @@ -143,6 +147,7 @@ public void SpinOnce() /// public void SpinOnce(int sleep1Threshold) { +#if !FEATURE_SINGLE_THREADED ArgumentOutOfRangeException.ThrowIfLessThan(sleep1Threshold, -1); if (sleep1Threshold >= 0 && sleep1Threshold < YieldThreshold) @@ -151,6 +156,7 @@ public void SpinOnce(int sleep1Threshold) } SpinOnceCore(sleep1Threshold); +#endif } private void SpinOnceCore(int sleep1Threshold) 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..e24e644b120159 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 @@ -669,14 +669,14 @@ internal static Task FromAsyncImpl( if (Task.s_asyncDebuggingEnabled) Task.AddToActiveTasks(t); - if (asyncResult.IsCompleted) + // TODO Pavel - review again + if (asyncResult.IsCompleted || !Thread.IsMultiThreadedPlatform) { try { t.InternalRunSynchronously(scheduler, waitForCompletion: false); } catch (Exception e) { promise.TrySetException(e); } // catch and log any scheduler exceptions } else { -#pragma warning disable CA1416 // Validate platform compatibility, issue: https://github.com/dotnet/runtime/issues/44544 ThreadPool.RegisterWaitForSingleObject( asyncResult.AsyncWaitHandle, delegate @@ -687,7 +687,6 @@ internal static Task FromAsyncImpl( null, Timeout.Infinite, true); -#pragma warning restore CA1416 } return promise; 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 97b07d08bed620..683f81a567a4c6 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 @@ -2752,6 +2752,8 @@ public bool Wait(int millisecondsTimeout) /// public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (millisecondsTimeout < -1) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout); @@ -2967,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) @@ -2977,6 +2976,8 @@ private bool InternalWaitCore(int millisecondsTimeout, CancellationToken cancell return true; } + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + // ETW event for Task Wait Begin TplEventSource log = TplEventSource.Log; bool etwIsEnabled = log.IsEnabled(); @@ -3055,6 +3056,12 @@ internal SetOnInvokeMres() : base(false, 0) { } /// true if the task is completed; otherwise, false. private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken cancellationToken) { + // TODO Pavel - review again + if (!Thread.IsMultiThreadedPlatform) + { + return false; + } + bool infiniteWait = millisecondsTimeout == Timeout.Infinite; uint startTimeTicks = infiniteWait ? 0 : (uint)Environment.TickCount; bool returnValue = SpinWait(millisecondsTimeout); @@ -3085,7 +3092,6 @@ private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken can try { AddCompletionAction(mres, addBeforeOthers: true); -#pragma warning disable CA1416 // Validate platform compatibility, issue: https://github.com/dotnet/runtime/issues/44622 if (infiniteWait) { bool notifyWhenUnblocked = ThreadPool.NotifyThreadBlocked(); @@ -3120,7 +3126,6 @@ private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken can } } } -#pragma warning restore CA1416 } finally { @@ -3149,6 +3154,8 @@ private bool SpinWait(int millisecondsTimeout) return false; } + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + int spinCount = Threading.SpinWait.SpinCountforSpinBeforeWait; SpinWait spinner = default; while (spinner.Count < spinCount) @@ -4706,9 +4713,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (tasks is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); @@ -4734,9 +4740,8 @@ 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + bool waitResult = WaitAllCore(tasks, Timeout.Infinite, default); Debug.Assert(waitResult, "expected wait to succeed"); } @@ -4774,9 +4779,8 @@ 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + long totalMilliseconds = (long)timeout.TotalMilliseconds; if (totalMilliseconds is < -1 or > int.MaxValue) { @@ -4821,9 +4825,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (tasks is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); @@ -4858,9 +4861,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (tasks is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); @@ -4907,9 +4909,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (tasks is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); @@ -4932,9 +4933,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (tasks is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); @@ -4953,9 +4953,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (millisecondsTimeout < -1) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout); @@ -5086,9 +5085,8 @@ 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + Debug.Assert(tasks != null, "Expected a non-null list of tasks"); Debug.Assert(tasks.Count > 0, "Expected at least one task"); @@ -5322,6 +5320,8 @@ public static int WaitAny(Task[] tasks, int millisecondsTimeout, CancellationTok // to be able to inspect arguments). private static int WaitAnyCore(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (tasks == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); 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..b351c80caacf23 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + 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 45339d9bdd892b..752b7dfbfa468b 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.IsMultiThreadedPlatform && (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 7157468c7528fc..ce812db3d12fcc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @@ -156,23 +156,14 @@ public Thread(ParameterizedThreadStart start, int maxStackSize) Initialize(); } -#if (!TARGET_BROWSER && !TARGET_WASI) || FEATURE_WASM_MANAGED_THREADS +#if FEATURE_SINGLE_THREADED [UnsupportedOSPlatformGuard("browser")] [UnsupportedOSPlatformGuard("wasi")] - internal static bool IsThreadStartSupported => true; + internal static bool IsMultiThreadedPlatform => false; #else - [UnsupportedOSPlatformGuard("browser")] - [UnsupportedOSPlatformGuard("wasi")] - internal static bool IsThreadStartSupported => false; + internal static bool IsMultiThreadedPlatform => true; #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. @@ -199,10 +190,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(); + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); StartHelper? startHelper = _startHelper; @@ -245,7 +233,8 @@ private void Start(object? parameter, bool captureContext) private void Start(bool captureContext) { - ThrowIfNoThreadStart(); + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + StartHelper? startHelper = _startHelper; // In the case of a null startHelper (second call to start on same thread) @@ -409,7 +398,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(!IsMultiThreadedPlatform || IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads if (_mayNeedResetForThreadPool) { @@ -421,7 +410,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(!IsMultiThreadedPlatform || IsThreadPoolThread); // there are no dedicated threadpool threads on runtimes where we can't start threads Debug.Assert(_mayNeedResetForThreadPool); _mayNeedResetForThreadPool = false; 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 3766266fa671e7..b4168de29bc496 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 @@ -85,7 +85,8 @@ internal static RegisteredWaitHandle RegisterWaitForSingleObject( bool executeOnlyOnce, bool flowExecutionContext) { - Thread.ThrowIfNoThreadStart(); + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + 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 67ccbf4d7cdc06..10f88f41941823 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs @@ -1656,9 +1656,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (millisecondsTimeOutInterval > (uint)int.MaxValue && millisecondsTimeOutInterval != uint.MaxValue) throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal); return RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, true); @@ -1676,9 +1675,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (millisecondsTimeOutInterval > (uint)int.MaxValue && millisecondsTimeOutInterval != uint.MaxValue) throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); return RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, false); @@ -1710,9 +1708,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeOutInterval, -1); return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, false); } @@ -1728,9 +1725,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeOutInterval, -1); ArgumentOutOfRangeException.ThrowIfGreaterThan(millisecondsTimeOutInterval, int.MaxValue); return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, true); @@ -1747,9 +1743,8 @@ 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 (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeOutInterval, -1); ArgumentOutOfRangeException.ThrowIfGreaterThan(millisecondsTimeOutInterval, int.MaxValue); return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, false); @@ -1766,9 +1761,8 @@ 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + long tm = (long)timeout.TotalMilliseconds; ArgumentOutOfRangeException.ThrowIfLessThan(tm, -1, nameof(timeout)); @@ -1788,9 +1782,8 @@ 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 + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + long tm = (long)timeout.TotalMilliseconds; ArgumentOutOfRangeException.ThrowIfLessThan(tm, -1, nameof(timeout)); 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 70bbc3bd2e20e7..dd4705506ba28f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs @@ -99,6 +99,8 @@ public void Dispose() public virtual bool WaitOne(int millisecondsTimeout) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeout, -1); return WaitOneNoCheck(millisecondsTimeout); @@ -110,6 +112,8 @@ internal bool WaitOneNoCheck( object? associatedObject = null, NativeRuntimeEventSource.WaitHandleWaitSourceMap waitSource = NativeRuntimeEventSource.WaitHandleWaitSourceMap.Unknown) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + Debug.Assert(millisecondsTimeout >= -1); // The field value is modifiable via the public property, save it locally @@ -296,6 +300,8 @@ private static int WaitMultiple(WaitHandle[] waitHandles, bool waitAll, int mill private static int WaitMultiple(ReadOnlySpan waitHandles, bool waitAll, int millisecondsTimeout) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (waitHandles.Length == 0) { throw new ArgumentException(SR.Argument_EmptyWaithandleArray, nameof(waitHandles)); @@ -359,6 +365,8 @@ private static int WaitMultiple(ReadOnlySpan waitHandles, bool waitA private static int WaitAnyMultiple(ReadOnlySpan safeWaitHandles, int millisecondsTimeout) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + // - Callers are expected to manage the lifetimes of the safe wait handles such that they would not expire during // this wait // - If the safe wait handle that satisfies the wait is an abandoned mutex, the wait result would reflect that and diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs index a26587e5412a5f..aa6b388a8870f6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs @@ -122,6 +122,8 @@ private bool IsWaiting /// public WaitableObject?[] GetWaitedObjectArray(int requiredCapacity) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + Debug.Assert(_thread == Thread.CurrentThread); Debug.Assert(_waitedCount == 0); @@ -166,6 +168,8 @@ private WaitedListNode[] GetWaitedListNodeArray(int requiredCapacity) /// public void RegisterWait(int waitedCount, bool prioritize, bool isWaitForAll) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + s_lock.VerifyIsLocked(); Debug.Assert(_thread == Thread.CurrentThread); @@ -226,6 +230,8 @@ public void RegisterWait(int waitedCount, bool prioritize, bool isWaitForAll) public void UnregisterWait() { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + s_lock.VerifyIsLocked(); Debug.Assert(_waitedCount > (_isWaitForAll ? 1 : 0)); @@ -291,6 +297,22 @@ public int Wait(int timeoutMilliseconds, bool interruptible, bool isSleep, ref L Debug.Assert(timeoutMilliseconds >= -1); Debug.Assert(timeoutMilliseconds != 0); // caller should have taken care of it + if (!Thread.IsMultiThreadedPlatform) + { + // on single-threaded platforms, we don't support waiting except for Sleep() + if (!isSleep) throw new PlatformNotSupportedException(); + + // sleep is implemented using a busy loop + // it doesn't need to acquire the lock because nothing else could interrupt it or observe its state + int elapsedMilliseconds = 0; + int startTimeMilliseconds = Environment.TickCount; + while (elapsedMilliseconds < timeoutMilliseconds) + { + elapsedMilliseconds = Environment.TickCount - startTimeMilliseconds; + } + return WaitHandle.WaitTimeout; + } + _thread.SetWaitSleepJoinState(); // must be acquired before is released, to ensure that there is @@ -659,6 +681,8 @@ public WaitedListNode? NextThread public void RegisterWait(WaitableObject waitableObject) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + s_lock.VerifyIsLocked(); Debug.Assert(_waitInfo.Thread == Thread.CurrentThread); @@ -682,6 +706,8 @@ public void RegisterWait(WaitableObject waitableObject) public void RegisterPrioritizedWait(WaitableObject waitableObject) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + s_lock.VerifyIsLocked(); Debug.Assert(_waitInfo.Thread == Thread.CurrentThread); @@ -705,6 +731,8 @@ public void RegisterPrioritizedWait(WaitableObject waitableObject) public void UnregisterWait(WaitableObject waitableObject) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + s_lock.VerifyIsLocked(); Debug.Assert(waitableObject != null); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs index c8635142aab886..3df53da6ef880a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs @@ -333,6 +333,8 @@ public static int Wait( bool interruptible = true, bool prioritize = false) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + Debug.Assert(waitableObject != null); Debug.Assert(timeoutMilliseconds >= -1); @@ -344,6 +346,8 @@ public static int Wait( bool waitForAll, int timeoutMilliseconds) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + Debug.Assert(waitHandles.Length > 0); Debug.Assert(waitHandles.Length <= WaitHandle.MaxWaitHandles); Debug.Assert(timeoutMilliseconds >= -1); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs index fdf3ca54df23d5..0b80f4e48b05bd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs @@ -361,6 +361,8 @@ public int Wait_Locked(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool in return WaitHandle.WaitTimeout; } + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + WaitableObject?[] waitableObjects = waitInfo.GetWaitedObjectArray(1); waitableObjects[0] = this; waitInfo.RegisterWait(1, prioritize, isWaitForAll: false); @@ -382,6 +384,8 @@ public static int Wait( bool interruptible, bool prioritize) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + s_lock.VerifyIsNotLocked(); Debug.Assert(waitInfo != null); Debug.Assert(waitInfo.Thread == Thread.CurrentThread); diff --git a/src/libraries/System.Private.Xml/tests/XmlWriter/WriteWithEncodingWithFallback.cs b/src/libraries/System.Private.Xml/tests/XmlWriter/WriteWithEncodingWithFallback.cs index 30cca88b12e94d..4e409593ced303 100644 --- a/src/libraries/System.Private.Xml/tests/XmlWriter/WriteWithEncodingWithFallback.cs +++ b/src/libraries/System.Private.Xml/tests/XmlWriter/WriteWithEncodingWithFallback.cs @@ -4,6 +4,7 @@ using System.IO; using System.Text; using Xunit; +using System.Threading.Tasks; namespace System.Xml.XmlWriterTests { @@ -100,7 +101,7 @@ public static void XmlWriterConvertsSurrogatePairToEntity() } [Fact] - public static void AsyncXmlWriterConvertsInvalidCharacterToEntity() + public static async Task AsyncXmlWriterConvertsInvalidCharacterToEntity() { MemoryStream ms = new MemoryStream(); XmlWriterSettings settings = new XmlWriterSettings(); @@ -114,9 +115,9 @@ public static void AsyncXmlWriterConvertsInvalidCharacterToEntity() using (XmlWriter writer = XmlWriter.Create(ms, settings)) { - writer.WriteStartDocumentAsync().Wait(); - writer.WriteElementStringAsync(null, "test", null, problematicString).Wait(); - writer.FlushAsync().Wait(); + await writer.WriteStartDocumentAsync(); + await writer.WriteElementStringAsync(null, "test", null, problematicString); + await writer.FlushAsync(); } ms.Position = 0; @@ -128,7 +129,7 @@ public static void AsyncXmlWriterConvertsInvalidCharacterToEntity() } [Fact] - public static void AsyncEncodingFallbackFailsWhenInvalidCharacterInTagName() + public static async Task AsyncEncodingFallbackFailsWhenInvalidCharacterInTagName() { MemoryStream ms = new MemoryStream(); XmlWriterSettings settings = new XmlWriterSettings(); @@ -141,19 +142,17 @@ public static void AsyncEncodingFallbackFailsWhenInvalidCharacterInTagName() using (XmlWriter writer = XmlWriter.Create(ms, settings)) { - writer.WriteStartDocumentAsync().Wait(); - Exception exception = Assert.Throws(() => + await writer.WriteStartDocumentAsync(); + await Assert.ThrowsAsync(async () => { - writer.WriteElementStringAsync(null, problematicString, null, "test").Wait(); - writer.FlushAsync().Wait(); + await writer.WriteElementStringAsync(null, problematicString, null, "test"); + await writer.FlushAsync(); }); - - Assert.Equal(typeof(System.Text.EncoderFallbackException), exception.InnerException.GetType()); } } [Fact] - public static void AsyncXmlWriterConvertsSurrogatePairToEntity() + public static async Task AsyncXmlWriterConvertsSurrogatePairToEntity() { MemoryStream ms = new MemoryStream(); XmlWriterSettings settings = new XmlWriterSettings(); @@ -167,9 +166,9 @@ public static void AsyncXmlWriterConvertsSurrogatePairToEntity() using (XmlWriter writer = XmlWriter.Create(ms, settings)) { - writer.WriteStartDocumentAsync().Wait(); - writer.WriteElementStringAsync(null, "test", null, problematicString).Wait(); - writer.FlushAsync().Wait(); + await writer.WriteStartDocumentAsync(); + await writer.WriteElementStringAsync(null, "test", null, problematicString); + await writer.FlushAsync(); } ms.Position = 0; diff --git a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs index 59e6be759b4a09..855701eaea54fb 100644 --- a/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs +++ b/src/libraries/System.Runtime.Serialization.Xml/tests/XmlDictionaryWriterTest.cs @@ -115,7 +115,7 @@ public static void XmlBaseWriter_FlushAsync() } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void XmlBaseWriter_WriteStartEndElementAsync() { string actual; @@ -224,7 +224,7 @@ public static void CreateTextReaderWriterTest() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void StreamProvoiderTest() { List ReaderWriterType = new List diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FSAssert.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FSAssert.cs index af803b5bc5ea55..d445eafac969b5 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FSAssert.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FSAssert.cs @@ -36,9 +36,12 @@ public static void CompletesSynchronously(Task task) } else { - // first wait for the task to complete without throwing - using WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle; - wh.WaitOne(); + if (status != TaskStatus.RanToCompletion && status != TaskStatus.Faulted) + { + // first wait for the task to complete without throwing + using WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle; + wh.WaitOne(); + } // now assert, we ignore the result of the task intentionally, // As it previously did not complete synchronously. diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Diagnostics/Stopwatch.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Diagnostics/Stopwatch.cs index 64305d4deb6833..b760740006a190 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Diagnostics/Stopwatch.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Diagnostics/Stopwatch.cs @@ -163,7 +163,14 @@ public static void ElapsedMilliseconds_WithinExpectedWindow() private static void Sleep(int milliseconds) { - s_sleepEvent.WaitOne(milliseconds); + if (PlatformDetection.IsThreadingSupported) + { + s_sleepEvent.WaitOne(milliseconds); + } + else + { + Thread.Sleep(milliseconds); + } } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Threading/WaitHandleTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Threading/WaitHandleTests.cs index d0d66d160d8339..01fa160b10b8c1 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Threading/WaitHandleTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Threading/WaitHandleTests.cs @@ -1,12 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using Microsoft.Win32.SafeHandles; using Xunit; namespace System.Threading.Tests { + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static partial class WaitHandleTests { [Fact] diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/CancellationTokenTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/CancellationTokenTests.cs index 0d48e3840d96a9..1deb3c8cd72ec4 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/CancellationTokenTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/CancellationTokenTests.cs @@ -158,7 +158,7 @@ public static void CancellationToken_EqualityAndDispose() }); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TokenSourceDispose() { CancellationTokenSource tokenSource = new CancellationTokenSource(); @@ -185,7 +185,7 @@ public static void TokenSourceDispose() tokenSource.Dispose(); //Repeat calls to Dispose should be ok. } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TokenSourceDispose_Negative() { CancellationTokenSource tokenSource = new CancellationTokenSource(); @@ -354,7 +354,7 @@ public static void CancellationTokenWaitHandle_SignalAfterWait() /// The signal occurs on a separate thread, and should happen after the wait begins. /// /// - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void CancellationTokenWaitHandle_SignalBeforeWait() { CancellationTokenSource tokenSource = new CancellationTokenSource(); @@ -372,7 +372,7 @@ public static void CancellationTokenWaitHandle_SignalBeforeWait() /// Test that WaitAny can be used with a CancellationToken.WaitHandle /// /// - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void CancellationTokenWaitHandle_WaitAny() { CancellationTokenSource tokenSource = new CancellationTokenSource(); diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/MethodCoverage.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/MethodCoverage.cs index 579662f96dc1f5..b2cb448ab9f926 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/MethodCoverage.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/MethodCoverage.cs @@ -369,7 +369,7 @@ public static void FromAsync() mre2.WaitOne(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void Task_WaitAll_NullArgument_Throws() { AssertExtensions.Throws("tasks", () => { Task.WaitAll((Task[])null); }); @@ -379,7 +379,7 @@ public static void Task_WaitAll_NullArgument_Throws() AssertExtensions.Throws("tasks", () => { Task.WaitAll((Task[])null, 30_000, CancellationToken.None); }); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void Task_WaitAll_NullTaskElement_Throws() { Task[] nullElement = [null]; @@ -391,7 +391,7 @@ public static void Task_WaitAll_NullTaskElement_Throws() AssertExtensions.Throws("tasks", () => { Task.WaitAll(nullElement, 30_000, CancellationToken.None); }); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void Task_WaitAll_InvalidArgument_Throws() { Assert.Throws(() => Task.WaitAll([Task.Factory.StartNew(() => { })], -2)); diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncTaskMethodBuilderTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncTaskMethodBuilderTests.cs index 4364e917eeac7a..2e12149fc447d4 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncTaskMethodBuilderTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncTaskMethodBuilderTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.Tracing; @@ -407,7 +408,7 @@ private static void TaskMethodBuilderT_UsesCompletedCache(T result, bool shou } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void Tcs_ValidateFaultedTask() { var tcs = new TaskCompletionSource(); @@ -416,7 +417,7 @@ public static void Tcs_ValidateFaultedTask() ValidateFaultedTask(tcs.Task); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskMethodBuilder_ValidateFaultedTask() { var atmb = AsyncTaskMethodBuilder.Create(); @@ -425,7 +426,7 @@ public static void TaskMethodBuilder_ValidateFaultedTask() ValidateFaultedTask(atmb.Task); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskMethodBuilderT_ValidateFaultedTask() { var atmbtr = AsyncTaskMethodBuilder.Create(); diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/ExecutionContextFlowTest.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/ExecutionContextFlowTest.cs index bd151c68595a97..4e4020a619a086 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/ExecutionContextFlowTest.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/ExecutionContextFlowTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; @@ -10,7 +11,7 @@ namespace System.Threading.Tasks.Tests { public class ExecutionContextFlowTest { - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [InlineData(false)] [InlineData(true)] public void SuppressFlow_TaskCapturesContextAccordingly(bool suppressFlow) 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..5615780549bf6d 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 @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Xunit; +using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; @@ -580,7 +581,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/TaskRtTests_Core.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskRtTests_Core.cs index 1225ca6433a291..7757e63519d4ac 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskRtTests_Core.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/Task/TaskRtTests_Core.cs @@ -237,7 +237,7 @@ public static void RunTaskCompletionSourceTests_Negative() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void RunTaskCompletionSourceTests_SetException() { // Test that recorded exception is persistent @@ -964,7 +964,7 @@ public static void RunSynchronouslyTest() } // Verifications for the Task.RunSynchronously() API - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void CoreRunSynchronouslyTest() { //Executing RunSynchronously() validations on external thread 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..133f8420940b07 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 @@ -472,7 +472,7 @@ public static void TaskWaitAllAny1() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny2() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -546,7 +546,7 @@ public static void TaskWaitAllAny7() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny8() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -619,7 +619,7 @@ public static void TaskWaitAllAny13() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny14() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -659,7 +659,7 @@ public static void TaskWaitAllAny17() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny18() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -695,7 +695,7 @@ public static void TaskWaitAllAny20() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny21() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -704,7 +704,7 @@ public static void TaskWaitAllAny21() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny22() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -750,7 +750,7 @@ public static void TaskWaitAllAny25() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny26() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -796,7 +796,7 @@ public static void TaskWaitAllAny29() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny30() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -881,7 +881,7 @@ public static void TaskWaitAllAny31() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny32() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -913,7 +913,7 @@ public static void TaskWaitAllAny34() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny35() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -976,7 +976,7 @@ public static void TaskWaitAllAny39() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny40() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -1105,7 +1105,7 @@ public static void TaskWaitAllAny45() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny46() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -1188,7 +1188,7 @@ public static void TaskWaitAllAny52() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny53() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -1245,7 +1245,7 @@ public static void TaskWaitAllAny57() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAllAny58() { TaskInfo[] allTasks = new TaskInfo[0]; @@ -1254,7 +1254,7 @@ public static void TaskWaitAllAny58() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void TaskWaitAll_Enumerable_InvalidArguments() { AssertExtensions.Throws("tasks", () => Task.WaitAll((IEnumerable)null)); diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/TaskToAsyncResultTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/TaskToAsyncResultTests.cs index d0ee2a62034383..0b0d7502e34702 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/TaskToAsyncResultTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/TaskToAsyncResultTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Xunit; +using System; using System.Threading; using System.Threading.Tasks; @@ -27,7 +28,7 @@ public void InvalidArguments_ThrowExceptions() AssertExtensions.Throws("asyncResult", () => TaskToAsyncResult.Unwrap(Task.FromResult((long)42))); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public async Task BeginFromTask_UnwrapTask_EndFromTask_Roundtrips() { var tcs = new TaskCompletionSource(); @@ -82,7 +83,7 @@ public void BeginFromTask_CompletedSynchronously_CallbackInvokedSynchronously() Assert.Same(state, ar.AsyncState); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public async Task BeginFromTask_CompletedAsynchronously_CallbackInvokedAsynchronously() { var tcs = new TaskCompletionSource(); diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/UnwrapTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/UnwrapTests.cs index 4d28e59e39d995..b898d33a7ddf8e 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/UnwrapTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/UnwrapTests.cs @@ -1,12 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Reflection; using Xunit; namespace System.Threading.Tasks.Tests { + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public class UnwrapTests { /// Tests unwrap argument validation. diff --git a/src/libraries/System.Runtime/tests/System.Threading.Timer.Tests/TimerDisposeTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Timer.Tests/TimerDisposeTests.cs index 81cf68b4f2c6d8..025616bb32d532 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Timer.Tests/TimerDisposeTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Timer.Tests/TimerDisposeTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Threading.Tasks; using Xunit; @@ -8,7 +9,7 @@ namespace System.Threading.Tests { public class TimerDisposeTests { - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void Dispose_NotFired_WaitHandleSignaledImmediately() { var t = new Timer(_ => { }, null, int.MaxValue, int.MaxValue); diff --git a/src/libraries/System.Runtime/tests/System.Threading.Timer.Tests/TimerFiringTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Timer.Tests/TimerFiringTests.cs index 046de9ad6fb1d4..a9006646700bbf 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Timer.Tests/TimerFiringTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Timer.Tests/TimerFiringTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; @@ -87,7 +88,7 @@ public void Timer_Fires_After_DueTime_AndOn_Period(int dueTime, int period) } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void Timer_FiresOnlyOnce_OnDueTime_With_InfinitePeriod() { int count = 0; diff --git a/src/libraries/System.Threading.Channels/tests/ChannelTestBase.cs b/src/libraries/System.Threading.Channels/tests/ChannelTestBase.cs index cefa3fe6f29901..857a9c6e01793c 100644 --- a/src/libraries/System.Threading.Channels/tests/ChannelTestBase.cs +++ b/src/libraries/System.Threading.Channels/tests/ChannelTestBase.cs @@ -10,6 +10,7 @@ namespace System.Threading.Channels.Tests { + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public abstract partial class ChannelTestBase : TestBase { protected Channel CreateChannel() => CreateChannel(); 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..9cb05a4497b630 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 @@ -45,6 +45,10 @@ public void Start() public void Wait() { +#if !FEATURE_WASM_MANAGED_THREADS + if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); +#endif + // // We wait in a loop because each Task might queue another Task, and so on. // It's entirely possible for multiple Tasks to be queued without this loop seeing them, diff --git a/src/libraries/System.Threading/src/System.Threading.csproj b/src/libraries/System.Threading/src/System.Threading.csproj index e09ac58c61c410..cd283786b19816 100644 --- a/src/libraries/System.Threading/src/System.Threading.csproj +++ b/src/libraries/System.Threading/src/System.Threading.csproj @@ -1,11 +1,15 @@ - $(NetCoreAppCurrent) + $(NetCoreAppCurrent) + $(NetCoreAppCurrent);$(NetCoreAppCurrent)-browser true true true false + + + $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) true $(DefineConstants);FEATURE_WASM_MANAGED_THREADS diff --git a/src/libraries/System.Threading/src/System/Threading/Barrier.cs b/src/libraries/System.Threading/src/System/Threading/Barrier.cs index e5cd44e77fe9fc..eb196c1e732eff 100644 --- a/src/libraries/System.Threading/src/System/Threading/Barrier.cs +++ b/src/libraries/System.Threading/src/System/Threading/Barrier.cs @@ -320,6 +320,10 @@ public long AddParticipant() #endif public long AddParticipants(int participantCount) { +#if !FEATURE_WASM_MANAGED_THREADS + if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); +#endif + ObjectDisposedException.ThrowIf(_disposed, this); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(participantCount); diff --git a/src/libraries/System.Threading/src/System/Threading/CountdownEvent.cs b/src/libraries/System.Threading/src/System/Threading/CountdownEvent.cs index 837be45812ac31..e0242ab8712b6d 100644 --- a/src/libraries/System.Threading/src/System/Threading/CountdownEvent.cs +++ b/src/libraries/System.Threading/src/System/Threading/CountdownEvent.cs @@ -536,6 +536,10 @@ public bool Wait(int millisecondsTimeout) #endif public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { +#if !FEATURE_WASM_MANAGED_THREADS + if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); +#endif + ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeout, -1); ObjectDisposedException.ThrowIf(_disposed, this); diff --git a/src/libraries/System.Threading/src/System/Threading/ReaderWriterLock.cs b/src/libraries/System.Threading/src/System/Threading/ReaderWriterLock.cs index 3248f45eb505e7..44387d1a81cded 100644 --- a/src/libraries/System.Threading/src/System/Threading/ReaderWriterLock.cs +++ b/src/libraries/System.Threading/src/System/Threading/ReaderWriterLock.cs @@ -77,6 +77,10 @@ public bool AnyWritersSince(int seqNum) [UnsupportedOSPlatform("browser")] public void AcquireReaderLock(int millisecondsTimeout) { +#if !FEATURE_WASM_MANAGED_THREADS + if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); +#endif + ArgumentOutOfRangeException.ThrowIfLessThan(millisecondsTimeout, -1); ThreadLocalLockEntry threadLocalLockEntry = ThreadLocalLockEntry.GetOrCreateCurrent(_lockID); diff --git a/src/libraries/System.Threading/tests/CountdownEventTests.cs b/src/libraries/System.Threading/tests/CountdownEventTests.cs index 7c9603f2af03ce..19ca1e8717fe25 100644 --- a/src/libraries/System.Threading/tests/CountdownEventTests.cs +++ b/src/libraries/System.Threading/tests/CountdownEventTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Diagnostics; using Xunit; @@ -59,7 +60,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) @@ -70,7 +71,7 @@ public static void RunCountdownEventTest1_SimpleTimeout(int ms) Assert.False(ev.WaitHandle.WaitOne(ms)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void RunCountdownEventTest2_Exceptions() { CountdownEvent cde = null; diff --git a/src/libraries/System.Threading/tests/EventWaitHandleTests.cs b/src/libraries/System.Threading/tests/EventWaitHandleTests.cs index 0d75df65177ec2..8012783dd40365 100644 --- a/src/libraries/System.Threading/tests/EventWaitHandleTests.cs +++ b/src/libraries/System.Threading/tests/EventWaitHandleTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Diagnostics; using Microsoft.DotNet.RemoteExecutor; using Xunit; @@ -9,7 +10,7 @@ namespace System.Threading.Tests { public class EventWaitHandleTests { - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [InlineData(false, EventResetMode.AutoReset)] [InlineData(false, EventResetMode.ManualReset)] [InlineData(true, EventResetMode.AutoReset)] @@ -78,7 +79,7 @@ public void Ctor_NameUsedByOtherSynchronizationPrimitive_Windows(EventResetMode Assert.Throws(() => new EventWaitHandle(false, mode, name)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void SetReset() { using (EventWaitHandle are = new EventWaitHandle(false, EventResetMode.AutoReset)) @@ -111,7 +112,7 @@ public void SetReset() } [PlatformSpecific(TestPlatforms.Windows)] // OpenExisting not supported on Unix - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [MemberData(nameof(GetValidNames))] public void OpenExisting_Windows(string name) { diff --git a/src/libraries/System.Threading/tests/ManualResetEventTests.cs b/src/libraries/System.Threading/tests/ManualResetEventTests.cs index d75930ae5d3b35..d5db2dc94b8209 100644 --- a/src/libraries/System.Threading/tests/ManualResetEventTests.cs +++ b/src/libraries/System.Threading/tests/ManualResetEventTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Threading.Tasks; using Xunit; @@ -8,7 +9,7 @@ namespace System.Threading.Tests { public class ManualResetEventTests { - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void ConstructorAndDisposeTest() { var e = new ManualResetEvent(false); @@ -23,7 +24,7 @@ public void ConstructorAndDisposeTest() e.Dispose(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void SetAndResetTest() { var e = new ManualResetEvent(true); @@ -39,7 +40,7 @@ public void SetAndResetTest() Assert.True(e.WaitOne(0)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void WaitTest() { var e = new ManualResetEvent(true); @@ -50,7 +51,7 @@ public void WaitTest() Assert.False(e.WaitOne(ThreadTestHelpers.ExpectedTimeoutMilliseconds)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void MultiWaitWithAllIndexesSetTest() { var es = @@ -73,7 +74,7 @@ public void MultiWaitWithAllIndexesSetTest() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void MultiWaitWithInnerIndexesSetTest() { var es = @@ -94,7 +95,7 @@ public void MultiWaitWithInnerIndexesSetTest() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void MultiWaitWithAllIndexesResetTest() { var es = diff --git a/src/libraries/System.Threading/tests/MonitorTests.cs b/src/libraries/System.Threading/tests/MonitorTests.cs index 13cb675cef34ae..27424067cfc998 100644 --- a/src/libraries/System.Threading/tests/MonitorTests.cs +++ b/src/libraries/System.Threading/tests/MonitorTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; @@ -397,7 +398,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/MutexTests.cs b/src/libraries/System.Threading/tests/MutexTests.cs index 6d769c4297fae5..c0823148162259 100644 --- a/src/libraries/System.Threading/tests/MutexTests.cs +++ b/src/libraries/System.Threading/tests/MutexTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -13,7 +14,7 @@ namespace System.Threading.Tests { public class MutexTests : FileCleanupTestBase { - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void ConstructorAndDisposeTest() { var m = new Mutex(); @@ -41,7 +42,7 @@ public void ConstructorAndDisposeTest() Assert.Throws(() => m.WaitOne(0)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void AcquireAndReleaseTest() { var m = new Mutex(); @@ -58,7 +59,7 @@ public void AcquireAndReleaseTest() Assert.Throws(() => m.ReleaseMutex()); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void WaitTest() { var m = new Mutex(); @@ -72,7 +73,7 @@ public void WaitTest() m.ReleaseMutex(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void MultiWaitWithAllIndexesUnlockedTest() { var ms = @@ -118,7 +119,7 @@ public void MultiWaitWithAllIndexesUnlockedTest() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void MultiWaitWithOuterIndexesLockedTest() { var ms = @@ -162,7 +163,7 @@ public void MultiWaitWithOuterIndexesLockedTest() ms[3].ReleaseMutex(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void MultiWaitWithAllIndexesLockedTest() { var ms = diff --git a/src/libraries/System.Threading/tests/SemaphoreTests.cs b/src/libraries/System.Threading/tests/SemaphoreTests.cs index 200728cfb9406f..ee18eac02e1fbb 100644 --- a/src/libraries/System.Threading/tests/SemaphoreTests.cs +++ b/src/libraries/System.Threading/tests/SemaphoreTests.cs @@ -13,7 +13,7 @@ public class SemaphoreTests { private const int FailedWaitTimeout = 30000; - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void ConstructorAndDisposeTest() { var s = new Semaphore(0, 1); @@ -27,7 +27,7 @@ public void ConstructorAndDisposeTest() s.Dispose(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void SignalAndUnsignalTest() { var s = new Semaphore(0, 1); @@ -53,7 +53,7 @@ public void SignalAndUnsignalTest() Assert.Throws(() => s.Release()); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void WaitTest() { var s = new Semaphore(1, 2); @@ -71,7 +71,7 @@ public void WaitTest() Assert.False(s.WaitOne(ThreadTestHelpers.ExpectedTimeoutMilliseconds)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void MultiWaitWithAllIndexesSignaledTest() { var ss = @@ -119,7 +119,7 @@ public void MultiWaitWithAllIndexesSignaledTest() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void MultiWaitWithInnerIndexesSignaled() { var ss = @@ -152,7 +152,7 @@ public void MultiWaitWithInnerIndexesSignaled() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void MultiWaitWithAllIndexesUnsignaled() { var ss = @@ -236,7 +236,7 @@ public void Ctor_InvalidArguments() AssertExtensions.Throws(null, () => new Semaphore(2, 1, "CtorSemaphoreTest", out createdNew)); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void CanWaitWithoutBlockingUntilNoCount() { const int InitialCount = 5; @@ -248,7 +248,7 @@ public void CanWaitWithoutBlockingUntilNoCount() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void CanWaitWithoutBlockingForReleasedCount() { using (Semaphore s = new Semaphore(0, int.MaxValue)) 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..0010dc5c1869d9 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 @@ -76,6 +76,8 @@ public static bool IsEntered(object obj) #endif public static bool Wait(object obj, int millisecondsTimeout) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + ArgumentNullException.ThrowIfNull(obj); #if FEATURE_WASM_MANAGED_THREADS Thread.AssureBlockingPossible(); @@ -122,6 +124,8 @@ private static void ObjPulseAll(object obj) private static bool ObjWait(int millisecondsTimeout, object obj) { + if (!Thread.IsMultiThreadedPlatform) throw new PlatformNotSupportedException(); + if (millisecondsTimeout < 0 && millisecondsTimeout != (int)Timeout.Infinite) throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout)); if (!ObjectHeader.HasOwner(obj)) diff --git a/src/mono/wasm/threads.md b/src/mono/wasm/threads.md index 705cc89f8fa60a..03e3d697bc90b0 100644 --- a/src/mono/wasm/threads.md +++ b/src/mono/wasm/threads.md @@ -30,8 +30,7 @@ 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.IsMultiThreadedPlatform` 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.