lib/async/task.rb in async-1.31.0 vs lib/async/task.rb in async-1.32.0

- old
+ new

@@ -82,10 +82,12 @@ @finished = finished @logger = logger || @parent.logger @fiber = make_fiber(&block) + + @defer_stop = nil end attr :logger if Fiber.current.respond_to?(:backtrace) @@ -160,10 +162,17 @@ if self.stopped? # If we already stopped this task... don't try to stop it again: return end + # If we are deferring stop... + if @defer_stop == false + # Don't stop now... but update the state so we know we need to stop later. + @defer_stop = true + return false + end + if self.running? if self.current? if later @reactor << Stop::Later.new(self) else @@ -177,9 +186,44 @@ end end else # We are not running, but children might be, so transition directly into stopped state: stop! + end + end + + # Defer the handling of stop. During the execution of the given block, if a stop is requested, it will be deferred until the block exits. This is useful for ensuring graceful shutdown of servers and other long-running tasks. You should wrap the response handling code in a defer_stop block to ensure that the task is stopped when the response is complete but not before. + # + # You can nest calls to defer_stop, but the stop will only be deferred until the outermost block exits. + # + # If stop is invoked a second time, it will be immediately executed. + # + # @yields {} The block of code to execute. + def defer_stop + # Tri-state variable for controlling stop: + # - nil: defer_stop has not been called. + # - false: defer_stop has been called and we are not stopping. + # - true: defer_stop has been called and we will stop when exiting the block. + if @defer_stop.nil? + # If we are not deferring stop already, we can defer it now: + @defer_stop = false + + begin + yield + rescue Stop + # If we are exiting due to a stop, we shouldn't try to invoke stop again: + @defer_stop = nil + raise + ensure + # If we were asked to stop, we should do so now: + if @defer_stop + @defer_stop = nil + self.stop + end + end + else + # If we are deferring stop already, entering it again is a no-op. + yield end end # Lookup the {Task} for the current fiber. Raise `RuntimeError` if none is available. # @return [Async::Task]