How to kill a thread, stop a promise execution in Raku

Tinmarino :

I am looking for a wait to stop (send an exception) to a running promise on SIGINT. The examples given in the doc exit the whole process and not just one worker.

Does someone know how to "kill", "unschedule", "stop" a running thread ?

This is for a p6-jupyter-kernel issue or this REPL issue.

Current solution is restarting the repl but not killing the blocked thread

await Promise.anyof(
  start {
      ENTER $running = True;
      LEAVE $running = False;
      CATCH {
          say $_;
          reset;
      }
      $output :=
        self.repl-eval($code,:outer_ctx($!save_ctx),|%adverbs);
  },
  $ctrl-c
);
Jonathan Worthington :

Short version: don't use threads for this, use processes. Killing the running process probably is the best thing that can be achieved in this situation in general.

Long answer: first, it's helpful to clear up a little confusion in the question.

First of all, there's no such thing as a "running Promise"; a Promise is a data structure for conveying a result of an asynchronous operation. A start block is really doing three things:

  1. Creating a Promise (which it evaluates to)
  2. Scheduling some code to run
  3. Arranging that the outcome of running that code is reflected by keeping or breaking the Promise

That may sound a little academic, but really matters: a Promise has no awareness of what will ultimately end up keeping or breaking it.

Second, a start block is not - at least with the built-in scheduler - backed by a thread, but rather runs on the thread pool. Even if you could figure out a way to "take out" the thread, the thread pool scheduler is not going to be happy with having one of the threads it expects to eat from the work queue on disappear. You could write your own scheduler that really does back work with a fresh thread each time, but that still isn't a complete solution: what if the piece of code the user has requested execution of schedules work of its own, and then awaits that? Then there is no one thread to kill to really bring things to a halt.

Let's assume, however, that we did manage to solve all of this, and we get ourselves a list of one or more threads that we really want to kill without their cooperation (cooperative situations are fairly easy; we use a Promise and have code poll that every so often and die if that cancellation Promise is ever kept/broken).

Any such mechanism that wants to be able to stop a thread blocked on anything (not just compute, but also I/O, locking, etc.) would need deep integration and cooperation from the underlying runtime (such as MoarVM). For example, trying to cancel a thread that is currently performing garbage collection will be a disaster (most likely deadlocking the VM as a whole). Other unfortunate cancellation times could lead to memory corruption if it was half way through an operation that is not safe to interrupt, deadlocks elsewhere if the killed thread was holding locks, and so forth. Thus one would need some kind of safe-pointing mechanism. (We already have things along those lines in MoarVM to know when it's safe to GC, however cancellation implies different demands. It probably cross-cuts numerous parts of the VM codebase.)

And that's not all: the same situation repeats at the Raku language level too. Lock::Async, for example, is not a kind of lock that the underlying runtime is aware of. Probably the best one can do is try to tear down the callstack and run all of the LEAVE phasers; that way there's some hope (if folks used the .protect method; if they just called lock and unlock explicitly, we're done for). But even if we manage not to leak resources (already a big ask), we still don't know - in general - if the code we killed has left the world in any kind of consistent state. In a REPL context this could lead to dubious outcomes in follow-up executions that access the same global state. That's probably annoying, but what really frightens me is folks using such a cancellation mechanism in a production system - which they will if we implement it.

So, effectively, implementing such a feature would entail doing a significant amount of difficult work on the runtime and Rakudo itself, and the result would be a huge footgun (I've not even enumerated all the things that could go wrong, just the first few that came to mind). By contrast, killing a process clears up all resources, and a process has its own memory space, so there's no consistency worries either.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=390594&siteId=1