class Async::Task

block.
A task represents the state associated with the execution of an asynchronous

def self.current

Raises:
  • (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?

Returns:
  • (Async::Task, nil) -
def self.current?
	Thread.current[:async_task]
end

def self.yield

Other tags:
    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

@yields {} The block of code to execute.

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)

This is a very tricky aspect of tasks to get right. I've modelled it after `Thread` but it's slightly different in that the exception can propagate back up through the reactor. If the user writes code which raises an exception, that exception should always be visible, i.e. cause a failure. If it's not visible, such code fails silently and can be very difficult to debug.
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!

Finish the current task, and all bound bound IO objects.
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?

Returns:
  • (Boolean) -
def finished?
	super && @status != :running
end

def initialize(reactor, parent = Task.current?, logger: nil, finished: nil, **options, &block)

Parameters:
  • 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)

Begin the execution of the task.
def run(*arguments)
	if @status == :initialized
		@status = :running
		
		@fiber.resume(*arguments)
	else
		raise RuntimeError, "Task already running!"
	end
end

def running?

Returns:
  • (Boolean) -
def running?
	@status == :running
end

def set!

Set the current fiber's `:async_task` to this task.
def set!
	# This is actually fiber-local:
	Thread.current[:async_task] = self
	Console.logger = @logger if @logger
end

def stop(later = false)

Stop the task and all of its children.
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

Returns:
  • (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

Yield back to the reactor and allow other fibers to execute.
def yield
	Task.yield{reactor.yield}
end