The Crystal Programming Language Forum

How to signal a fiber to quit

As fork is now deprecated, I am trying to find the correct way to signal a child fiber to exit.

The old fork-based version could look like this:

child = fork do
  Signal::INT.trap do
    puts "child killed"
    exit
  end

  loop do
    puts "child loop"
    sleep 3
  end
end

begin
  puts "running main"
  sleep 10
ensure
  puts "kill child"
  child.kill(Signal::INT)
  child.wait
end

When using spawn(), the naive approach would be using a global variable instead of sending a signal:

exit_channel = Channel(Nil).new
killit = false

spawn do
  loop do
    break if killit
    puts "child loop"
    sleep 3
  end

  puts "child killed"
  exit_channel.send(nil)
end

puts "running main"
sleep 10
puts "kill child"
killit = true
exit_channel.receive

But as far as I understand, in the (soon-to-be-default?) multi-thread mode, using a global variable is not considered safe, so we need to somehow use channels for this. The only way I could make this easily work without blocking the child fiber is by checking if a channel is closed, like this:

exit_channel = Channel(Nil).new
signal_channel = Channel(Nil).new

spawn do
  loop do
    break if signal_channel.closed?
    puts "child loop"
    sleep 3
  end

  puts "child killed"
  exit_channel.send(nil)
end

puts "running main"
sleep 10
puts "kill child"
signal_channel.close
exit_channel.receive

Is this the right way / is there a better way of signaling a fiber without blocking it?

Best

On MT using Atomic values or sharing values with other synchronization is safe.

I would recommend using the select statement that allows non-blocking channel operations.

Check https://lbarasti.com/post/select_statement/ there are some nice explanations there.

1 Like