Proposal for non-cooperative abortion of code execution #66480
Replies: 12 comments 51 replies
-
We've deleted a whole bunch of code in .NET Core that was there to help try to improve reliability in the face of thread aborts, e.g. we might now have: _spinLock.Enter();
_i++;
_j++;
_spinLock.Exit(); instead of: bool entered = false;
try
{
_spinLock.Enter(ref entered);
_i++;
_j++;
}
finally
{
if (entered)
{
_spinLock.Exit();
}
} New code has also been written assuming no thread aborts. Is the proposal that all such code needs to come back? Or is the idea that if a ThreadAbortException is thrown out of Run, the caller should promptly tear down the process? |
Beta Was this translation helpful? Give feedback.
-
What constitutes a "safe" spot? Also would it raise Thread abort or another specific exception for this case? |
Beta Was this translation helpful? Give feedback.
-
The proposal looks good. Although I was wondering why there is Abort and TryAbort, especially since abort is not guaranteed. I like the idea of a ControlledExecutionException, obviously ResetAbort should not work for the new exception. But it should be automatically rethrown if caught and handled. |
Beta Was this translation helpful? Give feedback.
-
The underlying Do we want to support both types of aborts? For instance, the debugger first tries to perform a safe thread abort with a half second timeout before initiating a rude thread abort with another half second timeout. |
Beta Was this translation helpful? Give feedback.
-
I assume that we would allow |
Beta Was this translation helpful? Give feedback.
-
I would really like to see a core framework / library for handling out of process function invocation. I have a (rather specific) library I've built that I use to do the following:
I have found this really helpful when invoking wonky COM objects that might hang. I also like it because it keeps unsanitary threads/instances out of my core process and contains them in a disposable process. |
Beta Was this translation helpful? Give feedback.
-
It seems we have somewhat conflicting design requirements here:
Does that make sense? If yes, we may start with a rude thread abort on a separate thread and see how that goes. @KevinRansom |
Beta Was this translation helpful? Give feedback.
-
Who marked the discussion as |
Beta Was this translation helpful? Give feedback.
-
I have an amendment I'd like to propose: Can there be a way to disable this? Possibly with a My reason for proposing/requesting this is that I do not want plugins in my app to be able to use something this dangerous, as it could have cascading effects on the stability of the app, ultimately resulting in data loss for the user. In my case, the app is Paint.NET. There are many plugins (some users have 1000+ installed!). The experience level of plugin developers is wide-ranging. Some are expert software engineers, some are students, while many are just hackers having fun with a copy/paste/modify/run/"neat!" loop. Many of the latter do not know C#, or barely know it, and just push things around it until they get their desired visual result (for an effect plugin). I absolutely do NOT trust plugin developers to write "good" code. Not just fast code. I mean code that doesn't do bad things, intentionally or not. I say this because I've seen a lot of plugins do very seriously weird or even downright evil stuff -- using reflection to grab things inside of API objects or to modify the Paint.NET UI, hooking major event handlers (AppDomain stuff) to achieve local results but with negative global consequences, etc. While porting Paint.NET to .NET 5 last year I ran into a whole lot of this stuff and had to engineer a lot of creative ways to shim the plugins to workaround and avoid some of the worst behaviors (I go as far as literally rewriting the plugin's IL to detour them around bad methods). I'm seriously worried that some plugin will use this, and then other plugins will copy-by-example. Or it will get inside of some library / nuget package that becomes popular, with the same result. I want to be able to just block any of that silliness from ever entering my process. I do believe I'd have a workaround if this does not make it passed API review: I have a highly customized system for loading plugins, using |
Beta Was this translation helpful? Give feedback.
-
Also, the environment variable is just an idea. Neither that nor the DLL blocking in the ALC are perfect solutions, I can already think of a few ways for a plugin to get around either. If it's possible for this setting to be in |
Beta Was this translation helpful? Give feedback.
-
After working on a prototype and test cases, I started leaning towards the original proposal with |
Beta Was this translation helpful? Give feedback.
-
I have shared my prototype with @KevinRansom the last week. If anyone is interested to try or review, please take a look: AntonLapounov@f6025e9. As I mentioned earlier, reusing existing I am wondering if we should consider making public the switch to disable |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
There is a number of execution environments for .NET code that may benefit from having an option to perform non-cooperative abortion of user code, e.g., C# and F# interactive compilers (
csi
andfsi
) or .NET Interactive Notebooks (see #41291). While the legacy Framework provided theThread.Abort
method, it has never been supported by .NET Core, because aborting code execution at a random point is inherently unsafe and may leave the process in an inconsistent or a corrupted state. Thus, the only available option to interrupt stuck code execution is to terminate the process and lose its state, which is undesirable for interactive scenarios.Below is the proposed
System.Runtime.CompilerServices.ControlledExecution
API to allow controlled execution of given code. TheRun
method starts execution of the delegate andTryAbort
method may be used to attempt aborting its execution by throwing an asynchronous exception on that thread. Aborting may be successful only when the code is executing managed code, e.g., running a CPU-intensive loop, on the original thread. This API would not help cases where the thread is stuck in unmanaged code or waiting for some other thread to finish its work.The implementation will generally use the same mechanism as the one utilized by the debugger to abort threads doing function evaluation (
Thread::UserAbort
). That includes marking the thread for abortion, suspending it, checking whether it is running managed code and safe to redirect, redirecting the thread to theThrowControlForThread
runtime helper, walking the stack to verify the tread is in a safe spot, and raising the exception for thread abort.A possible option to make the implementation simpler is to rely only on GC polls / polls for thread abort instead of redirecting the thread on Windows, similarly to what is done on Unixes.
Beta Was this translation helpful? Give feedback.
All reactions