lib/async/task.rb in async-2.5.1 vs lib/async/task.rb in async-2.6.0
- old
+ new
@@ -57,10 +57,16 @@
# Initialized --> Stopped : Stop
# ```
#
# @public Since `stable-v1`.
class Task < Node
+ class FinishedError < RuntimeError
+ def initialize(message = "Cannot create child task within a task that has finished execution!")
+ super
+ end
+ end
+
# @deprecated With no replacement.
def self.yield
Fiber.scheduler.transfer
end
@@ -88,10 +94,26 @@
def backtrace(*arguments)
@fiber&.backtrace(*arguments)
end
+ def annotate(annotation, &block)
+ if @fiber
+ @fiber.annotate(annotation, &block)
+ else
+ super
+ end
+ end
+
+ def annotation
+ if @fiber
+ @fiber.annotation
+ else
+ super
+ end
+ end
+
def to_s
"\#<#{self.description} (#{@status})>"
end
# @deprecated Prefer {Kernel#sleep} except when compatibility with `stable-v1` is required.
@@ -162,11 +184,11 @@
end
end
# Run an asynchronous task as a child of the current task.
def async(*arguments, **options, &block)
- raise "Cannot create child task within a task that has finished execution!" if self.finished?
+ raise FinishedError if self.finished?
task = Task.new(self, **options, &block)
task.run(*arguments)
@@ -197,29 +219,35 @@
# Access the result of the task without waiting. May be nil if the task is not completed. Does not raise exceptions.
attr :result
# Stop the task and all of its children.
+ #
+ # If `later` is false, it means that `stop` has been invoked directly. When `later` is true, it means that `stop` is invoked by `stop_children` or some other indirect mechanism. In that case, if we encounter the "current" fiber, we can't stop it right away, as it's currently performing `#stop`. Stopping it immediately would interrupt the current stop traversal, so we need to schedule the stop to occur later.
+ #
+ # @parameter later [Boolean] Whether to stop the task later, or immediately.
def stop(later = false)
if self.stopped?
# If we already stopped this task... don't try to stop it again:
return
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
# If the fiber is the current fiber and we want to stop it later, schedule it:
Fiber.scheduler.push(Stop::Later.new(self))
else
# Otherwise, raise the exception directly:
raise Stop, "Stopping current task!"
end
else
# If the fiber is not curent, we can raise the exception directly:
begin
+ # There is a chance that this will stop the fiber that originally called stop. If that happens, the exception handling in `#stopped` will rescue the exception and re-raise it later.
Fiber.scheduler.raise(@fiber, Stop)
rescue FiberError
# In some cases, this can cause a FiberError (it might be resumed already), so we schedule it to be stopped later:
Fiber.scheduler.push(Stop::Later.new(self))
end
@@ -242,11 +270,11 @@
def self.current?
Thread.current[:async_task]
end
def current?
- self.equal?(Thread.current[:async_task])
+ Fiber.current.equal?(@fiber)
end
private
# Finish the current task, moving any children to the parent.
@@ -287,24 +315,36 @@
end
end
end
def stopped!
- # Console.logger.info(self, self.annotation) {"Task was stopped with #{@children&.size.inspect} children!"}
+ # Console.logger.info(self, status:) {"Task #{self} was stopped with #{@children&.size.inspect} children!"}
@status = :stopped
- # We are not running, but children might be so we should stop them:
- stop_children(true)
+ stopped = false
+
+ begin
+ # We are bnot running, but children might be so we should stop them:
+ stop_children(true)
+ rescue Stop
+ stopped = true
+ # If we are stopping children, and one of them tries to stop the current task, we should ignore it. We will be stopped later.
+ retry
+ end
+
+ if stopped
+ raise Stop, "Stopping current task!"
+ end
end
def stop!
stopped!
finish!
end
def schedule(&block)
- @fiber = Fiber.new do
+ @fiber = Fiber.new(annotation: self.annotation) do
set!
begin
completed!(yield)
# Console.logger.debug(self) {"Task was completed with #{@children.size} children!"}