Ultimately speaking, my goal is to be able to kill (efficiently) a Task.
I imagine that the api would be something like:
var myTask = DoSomethingAsync();
myTask.Kill();
I feel that such a api would be very valuable to anyone creating platforms that will run code from third party developers, where you can't rely that the CancellationToken will be taken seriously.
So, please help me: Where should I begin?
I suggest reading about the problems with Thread.Abort. That will give you a good handle on why .NET Core has largely abandoned the abortive cancellation model. In summary, the overwhelming majority of code (including within .NET Core itself) is not written to be resilient to abortive cancellation, and attempting to do could corrupt the global process state. Developers are instead encouraged to use cooperative cancellation (such as via CancellationToken). Cooperative cancellation allows graceful unwinding of the work item.
Some resources to help start:
Adding to above, if you have misbehaving 3rd party libraries that you must run and you can control. Then I would suggest and have done before writing a method to launch them inside their own process and communicate the results via something like pipes. Then you can terminate the process safely without hurting your app if they freeze/don't return.
I did consider that option @Drawaes. I moved away from it for 2 reasons:
@GrabYourPitchforks please note that I don't want to kill a Thread, but kill a Task! It's an important distinction here: a task may span several threads during its lifetime. Killing a thread might serve no purpose if you intend to kill a task because it might have switched and continued somewhere else!
if anything on the assertion above is incorrect, please, feel free to educate me!
@Leonardo-Ferreira You're right in that a logical task might span multiple threads during its execution. As a strawman implementation I'm thinking that any proposed Task.Kill API would first figure out what threads are executing a particular Task - keeping in mind that it might be multiple threads in parallel if child Tasks were spawned from the parent Task - and to call Thread.Abort on each of those threads. That's why I think the Task.Kill API and the Thread.Abort API would be deeply interconnected.
@Leonardo-Ferreira - As another way of looking at it, you're trying to kill something that could be running:
for (int i = 0; i != 0; someOtherVariable++) {
PleaseDoSomeWork();
}
.... there isn't a good way to stop this except to kill the thread - that's what's actually executing the code. So you can't dodge Thread.Abort for this sort of thing.
- If the root platform itself crashes, the other process would live on out of control.
- The overall performance of the platform would increase due to the fact of the overhead of each separate process (thread pools, and others)...
Killing a thread might serve no purpose if you intend to kill a task because it might have switched and continued somewhere else!
And that would likely render the whole task killing story infeasible. Thread abort is already problematic, now you also have to find the right thread. "find" and "abort" operations will race and you need to figure out how to avoid aborting the wrong thread. For example:
If the root platform itself crashes, the other process would live on out of control.
There are ways to deal with this, eg., have a thread in the child process watch the parent. In Windows terms you would wait on the parent process handle. If the parent exits, the child would terminate itself.
@Leonardo-Ferreira I am going to close this since as discussed above it is not possible using an API and you should use a custom isolation mechanism.
@danmosemsft I respectfully would like to disagree. I know, deep down, that it is possible to effectively kill a Task, without harming the underlying thread and other infrastructure. We can gracefully spawn a thread, we can gracefully interrupt it.
No one has ever done it before, but the human didn't actually fly until it stopped to try to do it by flapping wings and imitating birds.
What I believe is happening here is that we are trying to use a previously conceived solution to this problem. I believe we need a new approach to it. We need a new tool on the toolbox.
Perhaps if we can come up with a new abstraction a solution might present itself. Perhaps if take a stab at it, someone might complete the job.
Respectfully, I didn't even ask for workarounds for the lack of the api. I didn't ask if it was possible or even feasible. I asked for directions. Respectfully, always.
I honestly didn't even imagined that someone your caliber would even read this, and I surely don't expect you to commit your time, or your teams time to this cause. What I am asking here is: please, make it less painful to gather the knowledge that will be helpful to work this issue. Help me to help everyone. Something like "first of all, read this book. Get familiar with that repo. Understand these concepts. Meanwhile try to talk to John, he might help you" would be already greatly appreciated.
Think of it this way: Satia Nadella walked into your office and said "Hello Dan, good morning. This crazy Brazilian guy here was lying in front of my car, saying that he would work for food on the Task.Kill api. You know I love a good deal, and he made such good points. So here he is. Have a nice day!"
馃槃 @Leonardo-Ferreira I am not sure what books to recommend.
No one has ever done it before, but the human didn't actually fly until it stopped to try to do it by flapping wings and imitating birds.
You're missing the point. This was done before (Thread.Abort), was used before (classic ASP.NET used it), and has failed before (myself I've seen quite a few reports of issues involving corrupted state in ASP.NET apps and MS itself surely has seen a lot more).
Think of it this way: Satia Nadella walked into your office and said "Hello Dan, good morning. This crazy Brazilian guy here was lying in front of my car, saying that he would work for food on the Task.Kill api. You know I love a good deal, and he made such good points. So here he is. Have a nice day!"
And because this was done before and failed the problem now isn't really that someone just needs to do some work and get it done. The problem is that you need to convince the team that such a feature is truly needed and that the risks it comes with are acceptable.
You can read how many books you want, you can write a million of lines of code, you can create a PR to implement Task.Kill(). And that PR will quite likely be instantly rejected.
Not to say that it's completely impossible to have a "kill this task" operation that doesn't harm reliablity - build a new runtime and language from scratch, make it have "tasks" that cannot have shared state, perhaps use message passing for task - task communication and you may have something. But it's not going to be dotnet. It's like using C++, complaining about issues with memory fragmentation and someone telling you - well, just use a garbage collected language instead.
This was done before (Thread.Abort), was used before [...], and has failed before [...]
The same way everyone who tried to fly flapping wings. One more time: it's time to think outside the box. What was done in the past clearly was not ideal, but that is no reason not to try again.
you can write a million of lines of code, you can create a PR to implement Task.Kill(). And that PR will quite likely be instantly rejected.
Good! perhaps we can define what a MVP would be. Perhaps we could tackle it on a TDD fashion and define tests the ideal implementation would comply. Then we define that a MVP would have to pass at least 60% of the scenarios, or 80% or 50%...
build a new runtime and language from scratch, make it have "tasks" that cannot have shared state, perhaps use message passing for task
Well well well look at that, we have already 3 ideas on how to approach the problem! I do believe that we gonna have to re-evaluate the Task platform and this will take a lot of time. The start from scratch ideia is good, but a little too radical in my opinion... Understanding the state sharing between Tasks and try to untagle it seems promissor, regardless of the outcome, because this kind of thing will show itself useful on several other areas/scenarios. Message passing also seems promising!
Definitely there's market for Thread.Kill/Task.Kill... over my 11 year career working C#+.Net solutions (not that long, but im proud of it) I've worked on several solutions that use Thread.Kill. These are market solutions, generating business and revenue, one is actually a Hospital focused drug manipulation and administration (so, very mission critical). These that I have in mind will probably not be migrated to any platform that doesn't offer Thread/Task.Kill because of their extensibility functionalities.
Perhaps you could help me a bit further by pointing out a starting point for my endeavour? Something like: I would start by understating everything that is going on "on this class here"! would be greatly appreciated!
Well well well look at that, we have already 3 ideas on how to approach the problem! I do believe that we gonna have to re-evaluate the Task platform and this will take a lot of time. The start from scratch ideia is good, but a little too radical in my opinion... Understanding the state sharing between Tasks and try to untagle it seems promissor, regardless of the outcome, because this kind of thing will show itself useful on several other areas/scenarios. Message passing also seems promising!
@Leonardo-Ferreira - He didn't give three ideas on how to approach the problem. It's one idea with three parts (one part being a potential route to take). Note that doing this is likely going to have roughly the safe effect as spinning up a new process currently.
I think you're underestimating exactly how much work this is. For one thing, because C# (unlike, say, Rust) doesn't have an exclusive ownership system, there's no way to enforce "no shared state". This is far more troublesome than just consumer code, it effects large sections of the underlying runtime code itself. You can make things _better_ by using no shared mutable state, but that includes things like interior mutability. And many foundational classes/types aren't hardened against abort abuse in any case, because the abort can take effect between any two lines of code. You would, in essence, be reinventing the modified runtime Midori had.
one is actually a Hospital focused drug manipulation and administration (so, very mission critical)
Please tell me which ones, so I can avoid them.
Thread.Abort brings some other thread down (or attempts to - there's no actual guarantee) ... immediately. You're almost certainly not getting the full cleanup you think you are. If the other processes are "trustworthy"/well behaved, you should be switching to cooperative cancellation. If they're not, you _really want_ full process isolation to prevent the "main" process from getting corrupted. And to make sure the child process state is correctly cleaned up, too.
Perhaps you could help me a bit further by pointing out a starting point for my endeavour? Something like: I would start by understating everything that is going on "on this class here"! would be greatly appreciated!
I'd recommend looking around the web, or on StackOverflow - there's been a number of posts about the issues with Thread.Abort.
@Leonardo-Ferreira if you are interested in the design of the runtime in general, you might be interested in https://github.com/dotnet/runtime/tree/master/docs/design/coreclr/botr and maybe Shared Source CLI Essentials which is pretty dated at this point but still relevant.
@danmosemsft Yeah thank you for your direction, you caught me reading the Threading section of the BOTR, right after the introduction.
ps: I'll post a PR correcting some typos and missing parenthesis soon
ps2: The threading part apparently was written for the classical .Net Framework. There are several references to appDomain and thread abortion... good read though, kinda loving it
Most helpful comment
Adding to above, if you have misbehaving 3rd party libraries that you must run and you can control. Then I would suggest and have done before writing a method to launch them inside their own process and communicate the results via something like pipes. Then you can terminate the process safely without hurting your app if they freeze/don't return.