Proposal: add spawn! to exit(1) on any unhandled exception in spawned Fiber

In my opinion, it’s too easy to have an Unhandled exception in spawn while the rest of the program keeps chugging along. (I ran into this with a long-lived process, with a cleanup Fiber that died due to an unanticipated IO::Error, resulting in consequences later due to that cleanup not running…) I’d generally like to have my program die if there’s an unanticipated and uncaught exception in any Fiber, not just the main one.

I’m proposing spawn! which looks like this:

def spawn!(*args, &block)
  spawn do
    begin
      block.call
    rescue ex
      STDERR.print "Unhandled exception in spawn!: "
      ex.inspect_with_backtrace(STDERR)
      STDERR.print "spawn!: Fatal. Dying..."
      STDERR.flush
      exit(1)
    end
  end
end

Alternatively, the docs might need to more loudly declare that every use of spawn should probably have a catch-all rescue block.

1 Like

The use case certainly demands attention. But I’m not conviced about the proposed solution. spawn! is not very clear about what it’s doing and this behaviour does not seem very intuitive.
Termination of the entire process is only one possible reaction to an exception in a fiber that’s fatal and unrecoverable. There may be other, less grave conclusions in scenarios where the failure only affects part of the program or may be recovered.
I’d see a possible solution for this problem with structured concurrency (cf. [RFC] Structured Concurrency · Issue #6468 · crystal-lang/crystal · GitHub) which would allow to configure error handling for a concurrency scope.

3 Likes

The spawn! name and usage is clearly and useful, why introduce more complexity for such a simple proposal?