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]