mirror of
https://github.com/VSadov/Satori.git
synced 2025-06-09 09:34:49 +09:00
Implement ControlledExecution API (#71661)
This commit is contained in:
parent
93900881bc
commit
d365ff2e86
21 changed files with 516 additions and 13 deletions
|
@ -100,6 +100,7 @@ The PR that reveals the implementation of the `<IncludeInternalObsoleteAttribute
|
|||
| __`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
|
||||
|
||||
|
|
|
@ -208,6 +208,7 @@
|
|||
<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" />
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -190,6 +190,7 @@
|
|||
<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" />
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,6 +20,8 @@
|
|||
#ifdef FEATURE_READYTORUN
|
||||
IMPORT DynamicHelperWorker
|
||||
#endif
|
||||
IMPORT HijackHandler
|
||||
IMPORT ThrowControlForThread
|
||||
|
||||
#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
|
||||
IMPORT g_sw_ww_table
|
||||
|
@ -1028,6 +1030,31 @@ FaultingExceptionFrame_FrameOffset SETA SIZEOF__GSCookie
|
|||
MEND
|
||||
|
||||
|
||||
; ------------------------------------------------------------------
|
||||
;
|
||||
; 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
|
||||
;
|
||||
|
|
|
@ -154,6 +154,24 @@ $FuncName
|
|||
|
||||
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
|
||||
|
|
|
@ -918,12 +918,6 @@ PTR_CONTEXT GetCONTEXTFromRedirectedStubStackFrame(T_CONTEXT * pContext)
|
|||
return *ppContext;
|
||||
}
|
||||
|
||||
void RedirectForThreadAbort()
|
||||
{
|
||||
// ThreadAbort is not supported in .net core
|
||||
throw "NYI";
|
||||
}
|
||||
|
||||
#if !defined(DACCESS_COMPILE)
|
||||
FaultingExceptionFrame *GetFrameFromRedirectedStubStackFrame (DISPATCHER_CONTEXT *pDispatcherContext)
|
||||
{
|
||||
|
|
|
@ -9,4 +9,9 @@ extern "C"
|
|||
{
|
||||
PORTABILITY_ASSERT("Implement for PAL");
|
||||
}
|
||||
|
||||
void RedirectForThreadAbort()
|
||||
{
|
||||
PORTABILITY_ASSERT("Implement for PAL");
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1096,15 +1096,38 @@ extern "C" BOOL QCALLTYPE ThreadNative_YieldThread()
|
|||
|
||||
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;
|
||||
|
|
|
@ -103,6 +103,8 @@ extern "C" void QCALLTYPE ThreadNative_InformThreadNameChange(QCall::ThreadHandl
|
|||
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
|
||||
|
||||
|
|
|
@ -209,6 +209,8 @@ static const Entry s_QCall[] =
|
|||
DllImportEntry(ThreadNative_InformThreadNameChange)
|
||||
DllImportEntry(ThreadNative_YieldThread)
|
||||
DllImportEntry(ThreadNative_GetCurrentOSThreadId)
|
||||
DllImportEntry(ThreadNative_Abort)
|
||||
DllImportEntry(ThreadNative_ResetAbort)
|
||||
DllImportEntry(ThreadPool_GetCompletedWorkItemCount)
|
||||
DllImportEntry(ThreadPool_RequestWorkerThread)
|
||||
DllImportEntry(ThreadPool_PerformGateActivities)
|
||||
|
|
|
@ -2496,7 +2496,7 @@ private:
|
|||
|
||||
public:
|
||||
void MarkThreadForAbort(EEPolicy::ThreadAbortTypes abortType);
|
||||
void UnmarkThreadForAbort();
|
||||
void UnmarkThreadForAbort(EEPolicy::ThreadAbortTypes abortType = EEPolicy::TA_Rude);
|
||||
|
||||
static ULONGLONG GetNextSelfAbortEndTime()
|
||||
{
|
||||
|
|
|
@ -1785,7 +1785,7 @@ void Thread::RemoveAbortRequestBit()
|
|||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
|
@ -1794,11 +1794,14 @@ void Thread::UnmarkThreadForAbort()
|
|||
}
|
||||
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;
|
||||
|
|
|
@ -147,5 +147,8 @@ namespace System
|
|||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2500,6 +2500,10 @@
|
|||
<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>
|
||||
|
|
|
@ -12136,6 +12136,11 @@ namespace System.Runtime
|
|||
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;
|
||||
|
|
|
@ -226,6 +226,7 @@
|
|||
<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" />
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -254,6 +254,7 @@
|
|||
<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" />
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue