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!"}