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 2 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,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>
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
[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();
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
}

[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;
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved

// 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);

// Recursive ControlledExecution.Run calls 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)
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
{
t_execution = this;
_action();
}
}
finally
{
if ((_state & (int)State.Started) != 0)
{
// Set the Finished flag to prevent a potential subsequent AbortThread call
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
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;
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
catch (ThreadAbortException) when (_cancellationToken.IsCancellationRequested)
{
throw new OperationCanceledException(_cancellationToken);
}
AntonLapounov marked this conversation as resolved.
Show resolved Hide resolved
}

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
{
// 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,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();
}
}
}
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
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