lib/async/task.rb in async-2.9.0 vs lib/async/task.rb in async-2.10.0

- old
+ new

@@ -65,10 +65,12 @@ @fiber = nil @status = :initialized @result = nil @finished = finished + + @defer_stop = nil end def reactor self.root end @@ -210,10 +212,17 @@ if self.stopped? # If the task is already stopped, a `stop` state transition re-enters the same state which is a no-op. However, we will also attempt to stop any running children too. This can happen if the children did not stop correctly the first time around. Doing this should probably be considered a bug, but it's better to be safe than sorry. return stopped! 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 the fiber is alive, we need to stop it: if @fiber&.alive? if self.current? # If the fiber is current, and later is `true`, we need to schedule the fiber to be stopped later, as it's currently invoking `stop`: if later @@ -234,9 +243,45 @@ 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. + # @public Since `stable-v1`. + 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. # @returns [Task]