Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ControlledExecution API #71661

Merged
merged 10 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/project/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
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.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>
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
/// <remarks>
/// <see cref="ControlledExecution"/> enables aborting arbitrary code in a non-cooperative manner.
/// Doing so may corrupt the process. This method is not recommended for use in production code
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
/// in which reliability is important.
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
/// </remarks>
[Obsolete(Obsoletions.ControlledExecutionRunMessage, DiagnosticId = Obsoletions.ControlledExecutionRunDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
public static void Run(Action action, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(action);

// Recursive ControlledExecution.Run calls are not supported
if (t_executing)
throw new InvalidOperationException(SR.InvalidOperation_NestedControlledExecutionRun);

new Execution(action, cancellationToken).Run();
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_Abort")]
private static partial void AbortThread(ThreadHandle thread);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool ResetAbortThread();

private sealed partial class Execution
{
// The state transition diagram (F means the Finished flag and so on):
// N ⟶ F
// ↓
// AR ⟶ FAR
// ↓ ↓
// A ⟶ FA
private enum State : int
{
None = 0,
Finished = 1,
AbortRequested = 2,
RunningAbort = 4
}

// Interpreted as a value of the State enumeration type
private int _state;
private readonly Action _action;
private readonly CancellationToken _cancellationToken;
private Thread? _thread;

public Execution(Action action, CancellationToken cancellationToken)
{
_action = action;
_cancellationToken = cancellationToken;
}

public void Run()
{
Debug.Assert(_state == (int)State.None && _thread == null);
_thread = Thread.CurrentThread;

try
{
try
{
t_executing = true;
// Cannot Dispose this registration in a finally or a catch block as that may deadlock with AbortThread
_cancellationToken.UnsafeRegister(e => ((Execution)e!).Abort(), this);
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
_action();
}
finally
{
t_executing = false;

// Set the Finished flag to prevent a potential subsequent AbortThread call
State oldState = (State)Interlocked.Or(ref _state, (int)State.Finished);

if ((oldState & State.AbortRequested) != 0)
{
// Either in FAR or FA state
while (true)
{
// The enclosing finally may be cloned by the JIT for the non-exceptional code flow.
// In that case this code is not guarded against a thread abort. In particular, any
// QCall may be aborted. That is OK as we will catch the ThreadAbortException and call
// ResetAbortThread again below. The only downside is that a successfully executed
// action may be reported as canceled.
bool resetAbortRequest = ResetAbortThread();

// If there is an Abort in progress, we need to wait until it sets the TS_AbortRequested
// flag on this thread, then we can reset the flag and safely exit this frame.
if (((oldState & State.RunningAbort) == 0) || resetAbortRequest)
{
break;
}

// It should take very short time for AbortThread to set the TS_AbortRequested flag
Thread.Sleep(0);
oldState = (State)Volatile.Read(ref _state);
}
}
}
}
catch (ThreadAbortException tae) when (_cancellationToken.IsCancellationRequested)
{
t_executing = false;
ResetAbortThread();

var e = new OperationCanceledException(_cancellationToken);
if (tae.StackTrace is string stackTrace)
{
ExceptionDispatchInfo.SetRemoteStackTrace(e, stackTrace);
}
throw e;
}
}

public void Abort()
{
// Prevent potential refetching of _state from shared memory
State curState = (State)Volatile.Read(ref _state);
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
State oldState;

do
{
Debug.Assert((curState & (State.AbortRequested | State.RunningAbort)) == 0);

// If the execution has finished, there is nothing to do
if ((curState & State.Finished) != 0)
return;

// Try to set the AbortRequested and RunningAbort flags
oldState = curState;
curState = (State)Interlocked.CompareExchange(ref _state,
(int)(oldState | State.AbortRequested | State.RunningAbort), (int)oldState);
}
while (curState != oldState);

try
{
// Must be in AR or FAR state now
Debug.Assert(_thread != null);
AbortThread(_thread.GetNativeHandle());
}
finally
{
// Reset the RunningAbort flag to signal the executing thread it is safe to exit
Interlocked.And(ref _state, (int)~State.RunningAbort);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,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" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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("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(Action action, CancellationToken cancellationToken)
{
throw new PlatformNotSupportedException();
}
}
}
27 changes: 27 additions & 0 deletions src/coreclr/vm/arm64/asmhelpers.asm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
;
Expand Down
18 changes: 18 additions & 0 deletions src/coreclr/vm/arm64/asmmacros.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,24 @@ __EndLabelName SETS "$FuncName":CC:"_End"

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
Expand Down
6 changes: 0 additions & 6 deletions src/coreclr/vm/arm64/stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/vm/arm64/unixstubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ extern "C"
{
PORTABILITY_ASSERT("Implement for PAL");
}

void RedirectForThreadAbort()
{
PORTABILITY_ASSERT("Implement for PAL");
}
};
30 changes: 30 additions & 0 deletions src/coreclr/vm/comsynchronizable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,36 @@ extern "C" BOOL QCALLTYPE ThreadNative_YieldThread()
return ret;
}

extern "C" void QCALLTYPE ThreadNative_Abort(QCall::ThreadHandle thread)
{
QCALL_CONTRACT;

BEGIN_QCALL

thread->UserAbort(EEPolicy::TA_Safe, INFINITE);

END_QCALL
}

// Unmarks the current thread for abort.
// Returns true if the thread had the TS_AbortRequested flag set.
FCIMPL0(FC_BOOL_RET, ThreadNative::ResetAbort)
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
{
FCALL_CONTRACT;

BOOL ret = FALSE;

Thread *pThread = GetThreadNULLOk();
if (pThread != NULL && pThread->IsAbortRequested())
{
pThread->UnmarkThreadForAbort();
ret = TRUE;
}

FC_RETURN_BOOL(ret);
}
FCIMPLEND

FCIMPL0(INT32, ThreadNative::GetCurrentProcessorNumber)
{
FCALL_CONTRACT;
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/vm/comsynchronizable.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ friend class ThreadBaseObject;
static FCDECL1(INT32, GetPriority, ThreadBaseObject* pThisUNSAFE);
static FCDECL2(void, SetPriority, ThreadBaseObject* pThisUNSAFE, INT32 iPriority);
static FCDECL1(void, Interrupt, ThreadBaseObject* pThisUNSAFE);
static FCDECL0(FC_BOOL_RET, ResetAbort);
static FCDECL1(FC_BOOL_RET, IsAlive, ThreadBaseObject* pThisUNSAFE);
static FCDECL2(FC_BOOL_RET, Join, ThreadBaseObject* pThisUNSAFE, INT32 Timeout);
static FCDECL1(void, Sleep, INT32 iTime);
Expand Down Expand Up @@ -110,6 +111,7 @@ 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);

#endif // _COMSYNCHRONIZABLE_H

5 changes: 5 additions & 0 deletions src/coreclr/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,10 @@ FCFuncStart(gWeakReferenceOfTFuncs)
FCFuncElement("IsTrackResurrection", WeakReferenceOfTNative::IsTrackResurrection)
FCFuncEnd()

FCFuncStart(gControlledExecutionFuncs)
FCFuncElement("ResetAbortThread", ThreadNative::ResetAbort)
FCFuncEnd()

#ifdef FEATURE_COMINTEROP

//
Expand Down Expand Up @@ -753,6 +757,7 @@ FCClassElement("AssemblyLoadContext", "System.Runtime.Loader", gAssemblyLoadCont
FCClassElement("Buffer", "System", gBufferFuncs)
FCClassElement("CastHelpers", "System.Runtime.CompilerServices", gCastHelpers)
FCClassElement("CompatibilitySwitch", "System.Runtime.Versioning", gCompatibilitySwitchFuncs)
FCClassElement("ControlledExecution", "System.Runtime", gControlledExecutionFuncs)
FCClassElement("CustomAttribute", "System.Reflection", gCOMCustomAttributeFuncs)
FCClassElement("CustomAttributeEncodedArgument", "System.Reflection", gCustomAttributeEncodedArgument)
FCClassElement("Debugger", "System.Diagnostics", gDiagnosticsDebugger)
Expand Down
Loading