class Async::Task
block.
A task represents the state associated with the execution of an asynchronous
def self.current
-
(RuntimeError)- if task was not {set!} for the current fiber.
Returns:
-
(Async::Task)-
def self.current Thread.current[:async_task] or raise RuntimeError, "No async task available!" end
def self.current?
-
(Async::Task, nil)-
def self.current? Thread.current[:async_task] end
def self.yield
- Yield: - result of the task if a block if given.
Raises:
-
(Exception)- if the result is an exception
Returns:
-
(Object)- result of the task
def self.yield if block_given? result = yield else result = Fiber.yield end if result.is_a? Exception raise result else return result end end
def alive?
def alive? @fiber&.alive? end
def async(*arguments, **options, &block)
def async(*arguments, **options, &block) task = Task.new(@reactor, self, **options, &block) task.run(*arguments) return task end
def backtrace(*arguments)
def backtrace(*arguments) @fiber&.backtrace(*arguments) end
def complete?
def complete? @status == :complete end
def current?
def current? self.equal?(Thread.current[:async_task]) end
def defer_stop
If stop is invoked a second time, it will be immediately executed.
You can nest calls to defer_stop, but the stop will only be deferred until the outermost block exits.
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.
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
def fail!(exception = false, propagate = true)
def fail!(exception = false, propagate = true) @status = :failed @result = exception if exception if propagate raise exception elsif @finished.nil? # If no one has called wait, we log this as a warning: Console.logger.warn(self, "Task may have ended with unhandled exception.", exception) else Console.logger.debug(self, exception) end end end
def failed?
def failed? @status == :failed end
def finish!
def finish! # Allow the fiber to be recycled. @fiber = nil # Attempt to remove this node from the task tree. consume # If this task was being used as a future, signal completion here: if @finished @finished.signal(@result) end end
def finished?
-
(Boolean)-
def finished? super && @status != :running end
def initialize(reactor, parent = Task.current?, logger: nil, finished: nil, **options, &block)
-
parent(Async::Task) -- the parent task. -
reactor(Async::Reactor) -- the reactor this task will run within.
def initialize(reactor, parent = Task.current?, logger: nil, finished: nil, **options, &block) super(parent || reactor, **options) @reactor = reactor @status = :initialized @result = nil @finished = finished @logger = logger || @parent.logger @fiber = make_fiber(&block) @defer_stop = nil end
def make_fiber(&block)
def make_fiber(&block) Fiber.new do |*arguments| set! begin @result = yield(self, *arguments) @status = :complete # Console.logger.debug(self) {"Task was completed with #{@children.size} children!"} rescue Stop stop! rescue StandardError => error fail!(error, false) rescue Exception => exception fail!(exception, true) ensure # Console.logger.debug(self) {"Task ensure $!=#{$!} with #{@children.size} children!"} finish! end end end
def run(*arguments)
def run(*arguments) if @status == :initialized @status = :running @fiber.resume(*arguments) else raise RuntimeError, "Task already running!" end end
def running?
-
(Boolean)-
def running? @status == :running end
def set!
def set! # This is actually fiber-local: Thread.current[:async_task] = self Console.logger = @logger if @logger end
def stop(later = false)
def stop(later = false) 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 raise Stop, "Stopping current task!" end elsif @fiber&.alive? begin @fiber.resume(Stop.new) rescue FiberError @reactor << Stop::Later.new(self) end end else # We are not running, but children might be, so transition directly into stopped state: stop! end end
def stop!
def stop! # logger.debug(self) {"Task was stopped with #{@children&.size.inspect} children!"} @status = :stopped stop_children(true) end
def stopped?
def stopped? @status == :stopped end
def stopping?
def stopping? @status == :stopping end
def to_s
def to_s "\#<#{self.description} (#{@status})>" end
def wait
-
(Object)- the final expression/result of the task's block.
Raises:
-
(RuntimeError)- if the task's fiber is the current fiber.
def wait raise RuntimeError, "Cannot wait on own fiber" if Fiber.current.equal?(@fiber) if running? @finished ||= Condition.new @finished.wait else Task.yield{@result} end end
def yield
def yield Task.yield{reactor.yield} end