# frozen_string_literal: true# Released under the MIT License.# Copyright, 2017-2024, by Samuel Williams.# Copyright, 2017, by Kent Gruber.# Copyright, 2022, by Shannon Skipper.require"fiber/annotation"require_relative"list"moduleAsync# A list of children tasks.classChildren<List# Create an empty list of children tasks.definitializesuper@transient_count=0end# Some children may be marked as transient. Transient children do not prevent the parent from finishing.# @returns [Boolean] Whether the node has transient children.deftransients?@transient_count>0end# Whether all children are considered finished. Ignores transient children.deffinished?@size==@transient_countend# Whether the children is empty, preserved for compatibility.defnil?empty?end# Adjust the number of transient children, assuming it has changed.## Despite being public, this is not intended to be called directly. It is used internally by {Node#transient=}.## @parameter transient [Boolean] Whether to increment or decrement the transient count.defadjust_transient_count(transient)iftransient@transient_count+=1else@transient_count-=1endendprivatedefadded(node)ifnode.transient?@transient_count+=1endreturnsuperenddefremoved(node)ifnode.transient?@transient_count-=1endreturnsuperendend# A node in a tree, used for implementing the task hierarchy.classNode# Create a new node in the tree.# @parameter parent [Node | Nil] This node will attach to the given parent.definitialize(parent=nil,annotation: nil,transient: false)@parent=nil@children=nil@annotation=annotation@object_name=nil@transient=transient@head=nil@tail=nilifparentparent.add_child(self)endend# @returns [Node] The root node in the hierarchy.defroot@parent&.root||selfend# @privateattr_accessor:head# @privateattr_accessor:tail# @attribute [Node] The parent node.attr:parent# @attribute [Children | Nil] Optional list of children.attr:children# @attribute [String | Nil] A useful identifier for the current node.attr:annotation# Whether this node has any children.# @returns [Boolean]defchildren?@children&&!@children.empty?end# Represents whether a node is transient. Transient nodes are not considered# when determining if a node is finished. This is useful for tasks which are# internal to an object rather than explicit user concurrency. For example,# a child task which is pruning a connection pool is transient, because it# is not directly related to the parent task, and should not prevent the# parent task from finishing.deftransient?@transientend# Change the transient state of the node.## A transient node is not considered when determining if a node is finished, and propagates up if the parent is consumed.## @parameter value [Boolean] Whether the node is transient.deftransient=(value)if@transient!=value@transient=value@parent&.children&.adjust_transient_count(value)endend# Annotate the node with a description.## @parameter annotation [String] The description to annotate the node with.defannotate(annotation)ifblock_given?begincurrent_annotation=@annotation@annotation=annotationreturnyieldensure@annotation=current_annotationendelse@annotation=annotationendend# A description of the node, including the annotation and object name.## @returns [String] The description of the node.defdescription@object_name||="#{self.class}:#{format'%#018x',object_id}#{@transient?' transient':nil}"ifannotation=self.annotation"#{@object_name}#{annotation}"elsifline=self.backtrace(0,1)&.first"#{@object_name}#{line}"else@object_nameendend# Provides a backtrace for nodes that have an active execution context.## @returns [Array(Thread::Backtrace::Locations) | Nil] The backtrace of the node, if available.defbacktrace(*arguments)nilend# @returns [String] A description of the node.defto_s"\#<#{self.description}>"endaliasinspectto_s# Change the parent of this node.## @parameter parent [Node | Nil] The parent to attach to, or nil to detach.# @returns [Node] Itself.defparent=(parent)returnif@parent.equal?(parent)if@parent@parent.remove_child(self)@parent=nilendifparentparent.add_child(self)endreturnselfendprotecteddefset_parent(parent)@parent=parentendprotecteddefadd_child(child)@children||=Children.new@children.append(child)child.set_parent(self)endprotecteddefremove_child(child)@children.remove(child)child.set_parent(nil)end# Whether the node can be consumed (deleted) safely. By default, checks if the children set is empty.## @returns [Boolean]deffinished?@children.nil?||@children.finished?end# If the node has a parent, and is {finished?}, then remove this node from# the parent.defconsumeifparent=@parentandfinished?parent.remove_child(self)# If we have children, then we need to move them to our the parent if they are not finished:if@childrenwhilechild=@children.shiftifchild.finished?child.set_parent(nil)elseparent.add_child(child)endend@children=nilendparent.consumeendend# Traverse the task tree.## @returns [Enumerator] An enumerator which will traverse the tree if no block is given.# @yields {|node, level| ...} The node and the level relative to the given root.deftraverse(&block)returnenum_for(:traverse)unlessblock_given?self.traverse_recurse(&block)endprotecteddeftraverse_recurse(level=0,&block)yieldself,level@children&.eachdo|child|child.traverse_recurse(level+1,&block)endend# Immediately terminate all children tasks, including transient tasks. Internally invokes `stop(false)` on all children. This should be considered a last ditch effort and is used when closing the scheduler.defterminate# Attempt to stop the current task immediately, and all children:stop(false)# If that doesn't work, take more serious action:@children&.eachdo|child|child.terminateendreturn@children.nil?end# Attempt to stop the current node immediately, including all non-transient children. Invokes {#stop_children} to stop all children.## @parameter later [Boolean] Whether to defer stopping until some point in the future.defstop(later=false)# The implementation of this method may defer calling `stop_children`.stop_children(later)end# Attempt to stop all non-transient children.privatedefstop_children(later=false)@children&.eachdo|child|child.stop(later)unlesschild.transient?endend# Whether the node has been stopped.defstopped?@children.nil?end# Print the hierarchy of the task tree from the given node.## @parameter out [IO] The output stream to write to.# @parameter backtrace [Boolean] Whether to print the backtrace of each node.defprint_hierarchy(out=$stdout,backtrace: true)self.traversedo|node,level|indent="\t"*levelout.puts"#{indent}#{node}"print_backtrace(out,indent,node)ifbacktraceendendprivatedefprint_backtrace(out,indent,node)ifbacktrace=node.backtracebacktrace.each_with_indexdo|line,index|out.puts"#{indent}#{index.zero??"→ ":" "}#{line}"endendendendend