async icon indicating copy to clipboard operation
async copied to clipboard

Task hangs during stop if there are more than two sub tasks (?)

Open paddor opened this issue 3 years ago • 1 comments

I'm on Ruby 3.1.1 and Async 2.0.1, Ubuntu 20.04. The following script hangs after the first Ctrl+C, but only if both sleeping tasks are active:

#!/usr/bin/env ruby

require 'async'


class Server
  def initialize
    @running = false
  end

  def stopping?
    !@running
  end

  def stop(&after_shutdown)
    @after_shutdown = after_shutdown if after_shutdown
    @running = false
  end

  def start(task: Async::Task.current)
    task.async do |task|
      @running = true

      while @running
        run_once
        sleep 0.1
      end

      @after_shutdown.call if @after_shutdown
    end
  end

  def run_once
    print '.'
  end
end

Async do |task|
  server = Server.new

  Signal.trap(:INT) do
    puts "Got INT"
    abort 'Aborting' if server.stopping? # abort on second ^C

    server.stop do
      puts "Server shut down. Stopping task."
      task.stop
    end
  end

  server.start

  task.async do
    puts "sleeping #1"
    sleep
  end

  # NOTE: Hangs only if this second task is added
  task.async do
    puts "sleeping #2"
    sleep
  end
end

puts 'Done'

Maybe related to #137 but it also hangs with all puts commented out.

paddor avatar Mar 17 '22 15:03 paddor

Thanks, I'll check it.

ioquatix avatar Mar 17 '22 15:03 ioquatix

I was hoping this was fixed together with #137 but it's still happening with Ruby 3.2-preview2:

$ ruby -v
ruby 3.2.0preview2 (2022-09-09 master 35cfc9a3bb) [x86_64-linux]
$ bundle exec ./async_test.rb
Could not load native event selector: cannot load such file -- IO_Event
.sleeping #1
sleeping #2
.....................^CGot INT
Server shut down. Stopping task.
^CGot INT
Aborting

The process should terminate after the first ˆC, but hangs instead. The second one calls Kernel#abort.

My Gemfile (repos checked out at current master/main):

gem 'async', path: '../async'
gem 'io-event', path: '../io-event'
gem 'timers', path: '../timers'

paddor avatar Oct 18 '22 09:10 paddor

Thanks for the update, I'll investigate.

ioquatix avatar Oct 19 '22 04:10 ioquatix

I could reproduce the issue. I'll investigate more.

ioquatix avatar Oct 31 '22 11:10 ioquatix

Signal.trap behaves strangely in an asynchronous context. We might be able to make it more robust, but we've got a specific construct to handle it - Async::IO::Trap. Let me review this code a bit more and see if there is a better more straight forward solution.

ioquatix avatar Nov 01 '22 08:11 ioquatix

Any updates?

paddor avatar Feb 27 '23 22:02 paddor

For some odd reason, I could not reproduce the issue any more. Do you mind checking?

I'm sure it's something wrong on my end, I have a local branch with some work to deal with this, but have not pushed it yet.

ioquatix avatar Jun 06 '23 08:06 ioquatix

@ioquatix Yes, it's still happening with v2.5.1 on Ruby 3.2.2:

$ ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux]
$ ruby async_bug.rb
2.5.1
.sleeping #1
sleeping #2
...............^CGot INT
Server shut down. Stopping task.
^CGot INT
Aborting

paddor avatar Jun 06 '23 08:06 paddor

Okay great, thanks, I'll check what's going on.

ioquatix avatar Jun 06 '23 09:06 ioquatix

Here is a smaller repro:

#!/usr/bin/env ruby

require_relative 'lib/async'

Async(annotation: "Top") do |task|
  task.async(annotation: "Stopper") do
    sleep 0.1
    puts "Stopping parent..."
    task.stop
  end
  
  task.async(annotation: "Sleeper #1") do
    puts "sleeping #1"
    sleep
  end

  # NOTE: Hangs only if this second task is added
  task.async(annotation: "Sleeper #2") do
    puts "sleeping #2"
    sleep
  end
end

puts 'Done'

I understand the problem and am working on a fix.

ioquatix avatar Jun 06 '23 22:06 ioquatix

@ioquatix Thank you for the fix.

paddor avatar Jun 07 '23 08:06 paddor