Skip to content

Commit

Permalink
Implement ControlledExecution API
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonLapounov committed Jul 17, 2022
1 parent cc75427 commit f93ded4
Show file tree
Hide file tree
Showing 16 changed files with 428 additions and 0 deletions.
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,173 @@
// 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.InteropServices;
using System.Threading;

namespace System.Runtime
{
/// <summary>
/// Allows to run code and abort it asynchronously.
/// </summary>
public static partial class ControlledExecution
{
/// <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>
[Obsolete(Obsoletions.ControlledExecutionRunMessage, DiagnosticId = Obsoletions.ControlledExecutionRunDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
public static void Run(Action action, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(action);
var execution = new Execution(action, cancellationToken);
cancellationToken.Register(execution.Abort, useSynchronizationContext: false);
execution.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 (S means the Started flag and so on):
// N ⟶ S ⟶ SF
// ↓ ↓
// AR SAR ⟶ SFAR
// ↓ ↓ ↓
// A SA ⟶ SFA
private enum State : int
{
None = 0,
Started = 1,
Finished = 2,
AbortRequested = 4,
RunningAbort = 8
}

[ThreadStatic]
private static Execution? t_execution;

// 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.Started) == 0 && _thread == null);

// Nested ControlledExecution.Run methods are not supported
if (t_execution != null)
throw new InvalidOperationException(SR.InvalidOperation_NestedControlledExecutionRun);

_thread = Thread.CurrentThread;

try
{
try
{
// As soon as the Started flag is set, this thread may be aborted asynchronously
if (Interlocked.CompareExchange(ref _state, (int)State.Started, (int)State.None) == (int)State.None)
{
t_execution = this;
_action();
}
}
finally
{
if ((_state & (int)State.Started) != 0)
{
// 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 SFAR or SFA 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, so make this FCall as
// soon as possible.
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);
}
}

t_execution = null;
}
}
}
catch (ThreadAbortException) when (_cancellationToken.IsCancellationRequested)
{
throw new OperationCanceledException(_cancellationToken);
}
}

public void Abort()
{
// Prevent potential refetching of _state from shared memory
State curState = (State)Volatile.Read(ref _state);
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
{
// If the execution has not started yet, we are done
if ((curState & State.Started) == 0)
return;

// Must be in SAR or SFAR 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,15 @@
// 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
{
public static void Run(Action action, CancellationToken cancellationToken)
{
throw new PlatformNotSupportedException();
}
}
}
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)
{
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
1 change: 1 addition & 0 deletions src/coreclr/vm/qcallentrypoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ static const Entry s_QCall[] =
DllImportEntry(ThreadNative_InformThreadNameChange)
DllImportEntry(ThreadNative_YieldThread)
DllImportEntry(ThreadNative_GetCurrentOSThreadId)
DllImportEntry(ThreadNative_Abort)
DllImportEntry(ThreadPool_GetCompletedWorkItemCount)
DllImportEntry(ThreadPool_RequestWorkerThread)
DllImportEntry(ThreadPool_PerformGateActivities)
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/Common/src/System/Obsoletions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,8 @@ internal static class Obsoletions

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";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
5 changes: 5 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12131,6 +12131,11 @@ public sealed partial class AssemblyTargetedPatchBandAttribute : System.Attribut
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
Loading

0 comments on commit f93ded4

Please sign in to comment.