| __`SYSLIB0043`__ | ECDiffieHellmanPublicKey.ToByteArray() and the associated constructor do not have a consistent and interoperable implementation on all platforms. Use ECDiffieHellmanPublicKey.ExportSubjectPublicKeyInfo() instead. |
| __`SYSLIB0044`__ | AssemblyName.CodeBase and AssemblyName.EscapedCodeBase are obsolete. Using them for loading an assembly is not supported. |
| __`SYSLIB0045`__ | Cryptographic factory methods accepting an algorithm name are obsolete. Use the parameterless Create factory method on the algorithm type instead. |
+| __`SYSLIB0046`__ | ControlledExecution.Run method may corrupt the process and should not be used in production code. |
## Analyzer Warnings
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeFeature.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeHelpers.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\TypeDependencyAttribute.cs" />
+ <Compile Include="$(BclSourcesRoot)\System\Runtime\ControlledExecution.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\DependentHandle.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\GCSettings.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\JitInfo.CoreCLR.cs" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.ExceptionServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace System.Runtime
+{
+ /// <summary>
+ /// Allows to run code and abort it asynchronously.
+ /// </summary>
+ public static partial class ControlledExecution
+ {
+ [ThreadStatic]
+ private static bool t_executing;
+
+ /// <summary>
+ /// Runs code that may be aborted asynchronously.
+ /// </summary>
+ /// <param name="action">The delegate that represents the code to execute.</param>
+ /// <param name="cancellationToken">The cancellation token that may be used to abort execution.</param>
+ /// <exception cref="System.PlatformNotSupportedException">The method is not supported on this platform.</exception>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="action"/> argument is null.</exception>
+ /// <exception cref="System.InvalidOperationException">
+ /// The current thread is already running the <see cref="ControlledExecution.Run"/> method.
+ /// </exception>
+ /// <exception cref="System.OperationCanceledException">The execution was aborted.</exception>
+ /// <remarks>
+ /// <para>This method enables aborting arbitrary managed code in a non-cooperative manner by throwing an exception
+ /// in the thread executing that code. While the exception may be caught by the code, it is re-thrown at the end
+ /// of `catch` blocks until the execution flow returns to the `ControlledExecution.Run` method.</para>
+ /// <para>Execution of the code is not guaranteed to abort immediately, or at all. This situation can occur, for
+ /// example, if a thread is stuck executing unmanaged code or the `catch` and `finally` blocks that are called as
+ /// part of the abort procedure, thereby indefinitely delaying the abort. Furthermore, execution may not be
+ /// aborted immediately if the thread is currently executing a `catch` or `finally` block.</para>
+ /// <para>Aborting code at an unexpected location may corrupt the state of data structures in the process and lead
+ /// to unpredictable results. For that reason, this method should not be used in production code and calling it
+ /// produces a compile-time warning.</para>
+ /// </remarks>
+ [Obsolete(Obsoletions.ControlledExecutionRunMessage, DiagnosticId = Obsoletions.ControlledExecutionRunDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
+ public static void Run(Action action, CancellationToken cancellationToken)
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ ArgumentNullException.ThrowIfNull(action);
+
+ // ControlledExecution.Run does not support nested invocations. If there's one already in flight
+ // on this thread, fail.
+ if (t_executing)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_NestedControlledExecutionRun);
+ }
+
+ // Store the current thread so that it may be referenced by the Canceler.Cancel callback if one occurs.
+ Canceler canceler = new(Thread.CurrentThread);
+
+ try
+ {
+ // Mark this thread as now running a ControlledExecution.Run to prevent recursive usage.
+ t_executing = true;
+
+ // Register for aborting. From this moment until ctr.Unregister is called, this thread is subject to being
+ // interrupted at any moment. This could happen during the call to UnsafeRegister if cancellation has
+ // already been requested at the time of the registration.
+ CancellationTokenRegistration ctr = cancellationToken.UnsafeRegister(e => ((Canceler)e!).Cancel(), canceler);
+ try
+ {
+ // Invoke the caller's code.
+ action();
+ }
+ finally
+ {
+ // This finally block may be cloned by JIT for the non-exceptional code flow. In that case the code
+ // below is not guarded against aborting. That is OK as the outer try block will catch the
+ // ThreadAbortException and call ResetAbortThread.
+
+ // Unregister the callback. Unlike Dispose, Unregister will not block waiting for an callback in flight
+ // to complete, and will instead return false if the callback has already been invoked or is currently
+ // in flight.
+ if (!ctr.Unregister())
+ {
+ // Wait until the callback has completed. Either the callback is already invoked and completed
+ // (in which case IsCancelCompleted will be true), or it may still be in flight. If it's in flight,
+ // the AbortThread call may be waiting for this thread to exit this finally block to exit, so while
+ // spinning waiting for the callback to complete, we also need to call ResetAbortThread in order to
+ // reset the flag the AbortThread call is polling in its waiting loop.
+ SpinWait sw = default;
+ while (!canceler.IsCancelCompleted)
+ {
+ ResetAbortThread();
+ sw.SpinOnce();
+ }
+ }
+ }
+ }
+ catch (ThreadAbortException tae)
+ {
+ // We don't want to leak ThreadAbortExceptions to user code. Instead, translate the exception into
+ // an OperationCanceledException, preserving stack trace details from the ThreadAbortException in
+ // order to aid in diagnostics and debugging.
+ OperationCanceledException e = cancellationToken.IsCancellationRequested ? new(cancellationToken) : new();
+ if (tae.StackTrace is string stackTrace)
+ {
+ ExceptionDispatchInfo.SetRemoteStackTrace(e, stackTrace);
+ }
+ throw e;
+ }
+ finally
+ {
+ // Unmark this thread for recursion detection.
+ t_executing = false;
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ // Reset an abort request that may still be pending on this thread.
+ ResetAbortThread();
+ }
+ }
+ }
+
+ [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_Abort")]
+ private static partial void AbortThread(ThreadHandle thread);
+
+ [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_ResetAbort")]
+ [SuppressGCTransition]
+ private static partial void ResetAbortThread();
+
+ private sealed class Canceler
+ {
+ private readonly Thread _thread;
+ private volatile bool _cancelCompleted;
+
+ public Canceler(Thread thread)
+ {
+ _thread = thread;
+ }
+
+ public bool IsCancelCompleted => _cancelCompleted;
+
+ public void Cancel()
+ {
+ try
+ {
+ // Abort the thread executing the action (which may be the current thread).
+ AbortThread(_thread.GetNativeHandle());
+ }
+ finally
+ {
+ _cancelCompleted = true;
+ }
+ }
+ }
+ }
+}
<Compile Include="System\Resources\ManifestBasedResourceGroveler.NativeAot.cs" />
<Compile Include="System\RuntimeArgumentHandle.cs" />
<Compile Include="System\RuntimeType.cs" />
+ <Compile Include="System\Runtime\ControlledExecution.NativeAot.cs" />
<Compile Include="System\Runtime\DependentHandle.cs" />
<Compile Include="System\Runtime\CompilerServices\ForceLazyDictionaryAttribute.cs" />
<Compile Include="System\Runtime\CompilerServices\EagerStaticClassConstructionAttribute.cs" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Threading;
+
+namespace System.Runtime
+{
+ public static class ControlledExecution
+ {
+ [Obsolete(Obsoletions.ControlledExecutionRunMessage, DiagnosticId = Obsoletions.ControlledExecutionRunDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
+ public static void Run(Action action, CancellationToken cancellationToken)
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+}
#ifdef FEATURE_READYTORUN
IMPORT DynamicHelperWorker
#endif
+ IMPORT HijackHandler
+ IMPORT ThrowControlForThread
#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
IMPORT g_sw_ww_table
; ------------------------------------------------------------------
+;
+; Helpers for ThreadAbort exceptions
+;
+
+ NESTED_ENTRY RedirectForThreadAbort2,,HijackHandler
+ PROLOG_SAVE_REG_PAIR fp,lr, #-16!
+
+ ; stack must be 16 byte aligned
+ CHECK_STACK_ALIGNMENT
+
+ ; On entry:
+ ;
+ ; x0 = address of FaultingExceptionFrame
+ ;
+ ; Invoke the helper to setup the FaultingExceptionFrame and raise the exception
+ bl ThrowControlForThread
+
+ ; ThrowControlForThread doesn't return.
+ EMIT_BREAKPOINT
+
+ NESTED_END RedirectForThreadAbort2
+
+ GenerateRedirectedStubWithFrame RedirectForThreadAbort, RedirectForThreadAbort2
+
+; ------------------------------------------------------------------
; ResolveWorkerChainLookupAsmStub
;
; This method will perform a quick chained lookup of the entry if the
MEND
;-----------------------------------------------------------------------------
+; Macro used to check (in debug builds only) whether the stack is 16-bytes aligned (a requirement before calling
+; out into C++/OS code). Invoke this directly after your prolog (if the stack frame size is fixed) or directly
+; before a call (if you have a frame pointer and a dynamic stack). A breakpoint will be invoked if the stack
+; is misaligned.
+;
+ MACRO
+ CHECK_STACK_ALIGNMENT
+
+#ifdef _DEBUG
+ add x9, sp, xzr
+ tst x9, #15
+ beq %F0
+ EMIT_BREAKPOINT
+0
+#endif
+ MEND
+
+;-----------------------------------------------------------------------------
; The Following sets of SAVE_*_REGISTERS expect the memory to be reserved and
; base address to be passed in $reg
;
return *ppContext;
}
-void RedirectForThreadAbort()
-{
- // ThreadAbort is not supported in .net core
- throw "NYI";
-}
-
#if !defined(DACCESS_COMPILE)
FaultingExceptionFrame *GetFrameFromRedirectedStubStackFrame (DISPATCHER_CONTEXT *pDispatcherContext)
{
{
PORTABILITY_ASSERT("Implement for PAL");
}
+
+ void RedirectForThreadAbort()
+ {
+ PORTABILITY_ASSERT("Implement for PAL");
+ }
};
BOOL ret = FALSE;
- BEGIN_QCALL
+ BEGIN_QCALL;
ret = __SwitchToThread(0, CALLER_LIMITS_SPINNING);
- END_QCALL
+ END_QCALL;
return ret;
}
+extern "C" void QCALLTYPE ThreadNative_Abort(QCall::ThreadHandle thread)
+{
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ thread->UserAbort(EEPolicy::TA_Safe, INFINITE);
+
+ END_QCALL;
+}
+
+// Unmark the current thread for a safe abort.
+extern "C" void QCALLTYPE ThreadNative_ResetAbort()
+{
+ QCALL_CONTRACT_NO_GC_TRANSITION;
+
+ Thread *pThread = GetThread();
+ if (pThread->IsAbortRequested())
+ {
+ pThread->UnmarkThreadForAbort(EEPolicy::TA_Safe);
+ }
+}
+
FCIMPL0(INT32, ThreadNative::GetCurrentProcessorNumber)
{
FCALL_CONTRACT;
extern "C" UINT64 QCALLTYPE ThreadNative_GetProcessDefaultStackSize();
extern "C" BOOL QCALLTYPE ThreadNative_YieldThread();
extern "C" UINT64 QCALLTYPE ThreadNative_GetCurrentOSThreadId();
+extern "C" void QCALLTYPE ThreadNative_Abort(QCall::ThreadHandle thread);
+extern "C" void QCALLTYPE ThreadNative_ResetAbort();
#endif // _COMSYNCHRONIZABLE_H
DllImportEntry(ThreadNative_InformThreadNameChange)
DllImportEntry(ThreadNative_YieldThread)
DllImportEntry(ThreadNative_GetCurrentOSThreadId)
+ DllImportEntry(ThreadNative_Abort)
+ DllImportEntry(ThreadNative_ResetAbort)
DllImportEntry(ThreadPool_GetCompletedWorkItemCount)
DllImportEntry(ThreadPool_RequestWorkerThread)
DllImportEntry(ThreadPool_PerformGateActivities)
public:
void MarkThreadForAbort(EEPolicy::ThreadAbortTypes abortType);
- void UnmarkThreadForAbort();
+ void UnmarkThreadForAbort(EEPolicy::ThreadAbortTypes abortType = EEPolicy::TA_Rude);
static ULONGLONG GetNextSelfAbortEndTime()
{
}
// Make sure that when AbortRequest bit is cleared, we also dec TrapReturningThreads count.
-void Thread::UnmarkThreadForAbort()
+void Thread::UnmarkThreadForAbort(EEPolicy::ThreadAbortTypes abortType /* = EEPolicy::TA_Rude */)
{
CONTRACTL
{
}
CONTRACTL_END;
- // Switch to COOP (for ClearAbortReason) before acquiring AbortRequestLock
- GCX_COOP();
-
AbortRequestLockHolder lh(this);
+ if (m_AbortType > (DWORD)abortType)
+ {
+ // Aborting at a higher level
+ return;
+ }
+
m_AbortType = EEPolicy::TA_None;
m_AbortEndTime = MAXULONGLONG;
m_RudeAbortEndTime = MAXULONGLONG;
internal const string CryptoStringFactoryMessage = "Cryptographic factory methods accepting an algorithm name are obsolete. Use the parameterless Create factory method on the algorithm type instead.";
internal const string CryptoStringFactoryDiagId = "SYSLIB0045";
+
+ internal const string ControlledExecutionRunMessage = "ControlledExecution.Run method may corrupt the process and should not be used in production code.";
+ internal const string ControlledExecutionRunDiagId = "SYSLIB0046";
}
}
<data name="InvalidOperation_NativeOverlappedReused" xml:space="preserve">
<value>NativeOverlapped cannot be reused for multiple operations.</value>
</data>
+ <data name="InvalidOperation_NestedControlledExecutionRun" xml:space="preserve">
+ <value>The thread is already executing the ControlledExecution.Run method.</value>
+ <comment>{Locked="ControlledExecution.Run"}</comment>
+ </data>
<data name="InvalidOperation_NoMultiModuleAssembly" xml:space="preserve">
<value>You cannot have more than one dynamic module in each dynamic assembly in this version of the runtime.</value>
</data>
public AssemblyTargetedPatchBandAttribute(string targetedPatchBand) { }
public string TargetedPatchBand { get { throw null; } }
}
+ public static partial class ControlledExecution
+ {
+ [System.ObsoleteAttribute("ControlledExecution.Run method may corrupt the process and should not be used in production code.", DiagnosticId = "SYSLIB0046", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
+ public static void Run(System.Action action, System.Threading.CancellationToken cancellationToken) { throw null; }
+ }
public partial struct DependentHandle : System.IDisposable
{
private object _dummy;
<Compile Include="System\Reflection\TypeDelegatorTests.cs" />
<Compile Include="System\Reflection\TypeTests.Get.CornerCases.cs" />
<Compile Include="System\Reflection\TypeTests.GetMember.cs" />
+ <Compile Include="System\Runtime\ControlledExecutionTests.cs" />
<Compile Include="System\Runtime\DependentHandleTests.cs" />
<Compile Include="System\Runtime\JitInfoTests.cs" />
<Compile Include="System\Runtime\MemoryFailPointTests.cs" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Threading;
+using Xunit;
+
+// Disable warnings for ControlledExecution.Run
+#pragma warning disable SYSLIB0046
+
+namespace System.Runtime.Tests
+{
+ public class ControlledExecutionTests
+ {
+ private bool _startedExecution, _caughtException, _finishedExecution;
+ private Exception _exception;
+ private CancellationTokenSource _cts;
+ private volatile int _counter;
+
+ // Tests cancellation on timeout. The ThreadAbortException must be mapped to OperationCanceledException.
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/72703", TestPlatforms.AnyUnix)]
+ public void CancelOnTimeout()
+ {
+ var cts = new CancellationTokenSource();
+ cts.CancelAfter(200);
+ RunTest(LengthyAction, cts.Token);
+
+ Assert.True(_startedExecution);
+ Assert.True(_caughtException);
+ Assert.False(_finishedExecution);
+ Assert.IsType<OperationCanceledException>(_exception);
+ }
+
+ // Tests that catch blocks are not aborted. The action catches the ThreadAbortException and throws an exception of a different type.
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/72703", TestPlatforms.AnyUnix)]
+ public void CancelOnTimeout_ThrowFromCatch()
+ {
+ var cts = new CancellationTokenSource();
+ cts.CancelAfter(200);
+ RunTest(LengthyAction_ThrowFromCatch, cts.Token);
+
+ Assert.True(_startedExecution);
+ Assert.True(_caughtException);
+ Assert.False(_finishedExecution);
+ Assert.IsType<TimeoutException>(_exception);
+ }
+
+ // Tests that finally blocks are not aborted. The action throws an exception from a finally block.
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/72703", TestPlatforms.AnyUnix)]
+ public void CancelOnTimeout_ThrowFromFinally()
+ {
+ var cts = new CancellationTokenSource();
+ cts.CancelAfter(200);
+ RunTest(LengthyAction_ThrowFromFinally, cts.Token);
+
+ Assert.True(_startedExecution);
+ Assert.IsType<TimeoutException>(_exception);
+ }
+
+ // Tests that finally blocks are not aborted. The action throws an exception from a try block.
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/72703", TestPlatforms.AnyUnix)]
+ public void CancelOnTimeout_Finally()
+ {
+ var cts = new CancellationTokenSource();
+ cts.CancelAfter(200);
+ RunTest(LengthyAction_Finally, cts.Token);
+
+ Assert.True(_startedExecution);
+ Assert.True(_finishedExecution);
+ Assert.IsType<TimeoutException>(_exception);
+ }
+
+ // Tests cancellation before calling the Run method
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/72703", TestPlatforms.AnyUnix)]
+ public void CancelBeforeRun()
+ {
+ var cts = new CancellationTokenSource();
+ cts.Cancel();
+ Thread.Sleep(100);
+ RunTest(LengthyAction, cts.Token);
+
+ Assert.IsType<OperationCanceledException>(_exception);
+ }
+
+ // Tests cancellation by the action itself
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime), nameof(PlatformDetection.IsNotNativeAot))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/72703", TestPlatforms.AnyUnix)]
+ public void CancelItself()
+ {
+ _cts = new CancellationTokenSource();
+ RunTest(Action_CancelItself, _cts.Token);
+
+ Assert.True(_startedExecution);
+ Assert.False(_finishedExecution);
+ Assert.IsType<AggregateException>(_exception);
+ Assert.IsType<ThreadAbortException>(_exception.InnerException);
+ }
+
+ private void RunTest(Action action, CancellationToken cancellationToken)
+ {
+ _startedExecution = _caughtException = _finishedExecution = false;
+ _exception = null;
+
+ try
+ {
+ ControlledExecution.Run(action, cancellationToken);
+ }
+ catch (Exception e)
+ {
+ _exception = e;
+ }
+ }
+
+ private void LengthyAction()
+ {
+ _startedExecution = true;
+ // Redirection via thread suspension is supported on Windows only.
+ // Make a call in the loop to allow redirection on other platforms.
+ bool sleep = !PlatformDetection.IsWindows;
+
+ try
+ {
+ for (_counter = 0; _counter < int.MaxValue; _counter++)
+ {
+ if ((_counter & 0xfffff) == 0 && sleep)
+ {
+ Thread.Sleep(0);
+ }
+ }
+ }
+ catch
+ {
+ // Swallow all exceptions to verify that the exception is automatically rethrown
+ _caughtException = true;
+ }
+
+ _finishedExecution = true;
+ }
+
+ private void LengthyAction_ThrowFromCatch()
+ {
+ _startedExecution = true;
+ bool sleep = !PlatformDetection.IsWindows;
+
+ try
+ {
+ for (_counter = 0; _counter < int.MaxValue; _counter++)
+ {
+ if ((_counter & 0xfffff) == 0 && sleep)
+ {
+ Thread.Sleep(0);
+ }
+ }
+ }
+ catch
+ {
+ _caughtException = true;
+ // The catch block must not be aborted
+ Thread.Sleep(100);
+ throw new TimeoutException();
+ }
+
+ _finishedExecution = true;
+ }
+
+ private void LengthyAction_ThrowFromFinally()
+ {
+ _startedExecution = true;
+
+ try
+ {
+ // Make sure to run the non-inlined finally
+ throw new Exception();
+ }
+ finally
+ {
+ // The finally block must not be aborted
+ Thread.Sleep(400);
+ throw new TimeoutException();
+ }
+ }
+
+ private void LengthyAction_Finally()
+ {
+ _startedExecution = true;
+
+ try
+ {
+ // Make sure to run the non-inlined finally
+ throw new TimeoutException();
+ }
+ finally
+ {
+ // The finally block must not be aborted
+ Thread.Sleep(400);
+ _finishedExecution = true;
+ }
+ }
+
+ private void Action_CancelItself()
+ {
+ _startedExecution = true;
+
+ try
+ {
+ // Make sure to run the non-inlined finally
+ throw new TimeoutException();
+ }
+ finally
+ {
+ // The finally block must be aborted
+ _cts.Cancel();
+ _finishedExecution = true;
+ }
+ }
+ }
+}
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\AssemblyExtensions.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\MetadataUpdater.cs" />
<Compile Include="$(BclSourcesRoot)\System\Resources\ManifestBasedResourceGroveler.Mono.cs" />
+ <Compile Include="$(BclSourcesRoot)\System\Runtime\ControlledExecution.Mono.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\DependentHandle.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\GCSettings.Mono.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\JitInfo.Mono.cs" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Threading;
+
+namespace System.Runtime
+{
+ public static class ControlledExecution
+ {
+ [Obsolete(Obsoletions.ControlledExecutionRunMessage, DiagnosticId = Obsoletions.ControlledExecutionRunDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
+ public static void Run(Action action, CancellationToken cancellationToken)
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+}