diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 41f6f584..c18f6f0c 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -20,6 +20,7 @@ jobs: - ubuntu ruby: + - "3.1" - "3.2" - "ruby-head" diff --git a/lib/async/scheduler.rb b/lib/async/scheduler.rb index ec38fb26..accf41e9 100644 --- a/lib/async/scheduler.rb +++ b/lib/async/scheduler.rb @@ -306,17 +306,22 @@ def run(...) initial_task = self.async(...) if block_given? - # In theory, we could use Exception here to be a little bit safer, but we've only shown the case for SignalException to be a problem, so let's not over-engineer this. - Thread.handle_interrupt(SignalException => :never) do - while true - # If we are interrupted, we need to exit: - break if self.interrupted? - - # If we are finished, we need to exit: - break unless self.run_once + begin + # In theory, we could use Exception here to be a little bit safer, but we've only shown the case for SignalException to be a problem, so let's not over-engineer this. + Thread.handle_interrupt(SignalException => :never) do + while true + # If we are interrupted, we need to exit: + break if self.interrupted? + + # If we are finished, we need to exit: + break unless self.run_once + end end + rescue Interrupt + self.stop + retry end - + return initial_task ensure Console.logger.debug(self) {"Exiting run-loop because #{$! ? $! : 'finished'}."} diff --git a/test/async/scheduler.rb b/test/async/scheduler.rb index 1a34e910..e1285de1 100644 --- a/test/async/scheduler.rb +++ b/test/async/scheduler.rb @@ -87,6 +87,43 @@ scheduler.close scheduler.interrupt end + + it "can interrupt a scheduler from a different thread" do + finished = false + sleeping = Thread::Queue.new + + thread = Thread.new do + scheduler = Async::Scheduler.new + Fiber.set_scheduler(scheduler) + + scheduler.run do |task| + sleeping.push(true) + sleep + ensure + begin + sleeping.push(true) + sleep + ensure + finished = true + end + end + # rescue Interrupt + # # Ignore. + end + + expect(sleeping.pop).to be == true + expect(finished).to be == false + + thread.raise(Interrupt) + + expect(sleeping.pop).to be == true + expect(finished).to be == false + + thread.raise(Interrupt) + thread.join + + expect(finished).to be == true + end end with '#block' do