github.com/searKing/golang/go@v1.2.74/container/stream/op/terminal/task.go (about)

     1  // Copyright 2020 The searKing Author. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package terminal
     6  
     7  import (
     8  	"context"
     9  	"runtime"
    10  	"sync"
    11  
    12  	"github.com/searKing/golang/go/error/exception"
    13  	"github.com/searKing/golang/go/util/class"
    14  	"github.com/searKing/golang/go/util/spliterator"
    15  )
    16  
    17  type Task interface {
    18  	GetSpliterator() spliterator.Spliterator
    19  	/**
    20  	 * Returns the parent of this task, or null if this task is the root
    21  	 *
    22  	 * @return the parent of this task, or null if this task is the root
    23  	 */
    24  	GetParent() Task
    25  
    26  	/**
    27  	 * The left child.
    28  	 * null if no children
    29  	 * if non-null rightChild is non-null
    30  	 *
    31  	 * @return the right child
    32  	 */
    33  	LeftChild() Task
    34  
    35  	SetLeftChild(task Task)
    36  
    37  	/**
    38  	 * The right child.
    39  	 * null if no children
    40  	 * if non-null rightChild is non-null
    41  	 *
    42  	 * @return the right child
    43  	 */
    44  	RightChild() Task
    45  
    46  	SetRightChild(task Task)
    47  
    48  	/**
    49  	 * Indicates whether this task is a leaf node.  (Only valid after
    50  	 * {@link #compute} has been called on this node).  If the node is not a
    51  	 * leaf node, then children will be non-null and numChildren will be
    52  	 * positive.
    53  	 *
    54  	 * @return {@code true} if this task is a leaf node
    55  	 */
    56  	IsLeaf() bool
    57  
    58  	/**
    59  	 * Indicates whether this task is the root node
    60  	 *
    61  	 * @return {@code true} if this task is the root node.
    62  	 */
    63  	IsRoot() bool
    64  
    65  	/**
    66  	 * Target leaf size, common to all tasks in a computation
    67  	 *
    68  	 * @return target leaf size.
    69  	 */
    70  	TargetSize() int
    71  
    72  	/**
    73  	 * Default target of leaf tasks for parallel decomposition.
    74  	 * To allow load balancing, we over-partition, currently to approximately
    75  	 * four tasks per processor, which enables others to help out
    76  	 * if leaf tasks are uneven or some processors are otherwise busy.
    77  	 *
    78  	 * @return the default target size of leaf tasks
    79  	 */
    80  	GetLeafTarget() int
    81  
    82  	/**
    83  	 * Constructs a new node of type T whose parent is the receiver; must call
    84  	 * the TODOTask(T, Spliterator) constructor with the receiver and the
    85  	 * provided Spliterator.
    86  	 *
    87  	 * @param spliterator {@code Spliterator} describing the subtree rooted at
    88  	 *        this node, obtained by splitting the parent {@code Spliterator}
    89  	 * @return newly constructed child node
    90  	 */
    91  	MakeChild(spliterator spliterator.Spliterator) Task
    92  
    93  	/**
    94  	 * Computes the result associated with a leaf node.  Will be called by
    95  	 * {@code compute()} and the result passed to @{code setLocalResult()}
    96  	 *
    97  	 * @return the computed result of a leaf node
    98  	 */
    99  	DoLeaf(ctx context.Context) Sink
   100  
   101  	/**
   102  	 * Retrieves a result previously stored with {@link #setLocalResult}
   103  	 *
   104  	 * @return local result for this node previously stored with
   105  	 * {@link #setLocalResult}
   106  	 */
   107  	GetLocalResult() Sink
   108  
   109  	/**
   110  	 * Associates the result with the task, can be retrieved with
   111  	 * {@link #GetLocalResult}
   112  	 *
   113  	 * @param localResult local result for this node
   114  	 */
   115  	SetLocalResult(localResult Sink)
   116  
   117  	/**
   118  	 * Decides whether or not to split a task further or compute it
   119  	 * directly. If computing directly, calls {@code doLeaf} and pass
   120  	 * the result to {@code setRawResult}. Otherwise splits off
   121  	 * subtasks, forking one and continuing as the other.
   122  	 *
   123  	 * <p> The method is structured to conserve resources across a
   124  	 * range of uses.  The loop continues with one of the child tasks
   125  	 * when split, to avoid deep recursion. To cope with spliterators
   126  	 * that may be systematically biased toward left-heavy or
   127  	 * right-heavy splits, we alternate which child is forked versus
   128  	 * continued in the loop.
   129  	 */
   130  	Compute(ctx context.Context)
   131  
   132  	/**
   133  	 * {@inheritDoc}
   134  	 *
   135  	 * @implNote
   136  	 * Clears spliterator and children fields.  Overriders MUST call
   137  	 * {@code super.onCompletion} as the last thing they do if they want these
   138  	 * cleared.
   139  	 */
   140  	OnCompletion(caller Task)
   141  
   142  	/**
   143  	 * Returns whether this node is a "leftmost" node -- whether the path from
   144  	 * the root to this node involves only traversing leftmost child links.  For
   145  	 * a leaf node, this means it is the first leaf node in the encounter order.
   146  	 *
   147  	 * @return {@code true} if this node is a "leftmost" node
   148  	 */
   149  	IsLeftmostNode() bool
   150  
   151  	/**
   152  	 * Arranges to asynchronously execute this task in the pool the
   153  	 * current task is running in, if applicable, or using the {@link
   154  	 * ForkJoinPool#commonPool()} if not {@link #inForkJoinPool}.  While
   155  	 * it is not necessarily enforced, it is a usage error to fork a
   156  	 * task more than once unless it has completed and been
   157  	 * reinitialized.  Subsequent modifications to the state of this
   158  	 * task or any data it operates on are not necessarily
   159  	 * consistently observable by any thread other than the one
   160  	 * executing it unless preceded by a call to {@link #join} or
   161  	 * related methods, or a call to {@link #isDone} returning {@code
   162  	 * true}.
   163  	 *
   164  	 * @return {@code this}, to simplify usage
   165  	 */
   166  	Fork(ctx context.Context)
   167  
   168  	/**
   169  	 * Returns the result of the computation when it
   170  	 * {@linkplain #isDone is done}.
   171  	 * This method differs from {@link #get()} in that abnormal
   172  	 * completion results in {@code RuntimeException} or {@code Error},
   173  	 * not {@code ExecutionException}, and that interrupts of the
   174  	 * calling thread do <em>not</em> cause the method to abruptly
   175  	 * return by throwing {@code InterruptedException}.
   176  	 *
   177  	 * @return the computed result
   178  	 */
   179  	Join() Sink
   180  
   181  	/**
   182  	 * Commences performing this task, awaits its completion if
   183  	 * necessary, and returns its result, or throws an (unchecked)
   184  	 * {@code RuntimeException} or {@code Error} if the underlying
   185  	 * computation did so.
   186  	 *
   187  	 * @return the computed result
   188  	 */
   189  	Invoke(ctx context.Context) Sink
   190  }
   191  
   192  type TODOTask struct {
   193  	class.Class
   194  
   195  	parent Task
   196  
   197  	/**
   198  	 * The spliterator for the portion of the input associated with the subtree
   199  	 * rooted at this task
   200  	 */
   201  	spliterator spliterator.Spliterator
   202  
   203  	/** Target leaf size, common to all tasks in a computation */
   204  	targetSize int // may be lazily initialized
   205  	/**
   206  	 * The left child.
   207  	 * null if no children
   208  	 * if non-null rightChild is non-null
   209  	 */
   210  	leftChild Task
   211  
   212  	/**
   213  	 * The right child.
   214  	 * null if no children
   215  	 * if non-null leftChild is non-null
   216  	 */
   217  	rightChild Task
   218  
   219  	/** The result of this node, if completed */
   220  	localResult Sink
   221  
   222  	computer func(ctx context.Context)
   223  
   224  	wg sync.WaitGroup
   225  }
   226  
   227  /**
   228   * Constructor for root nodes.
   229   *
   230   * @param helper The {@code PipelineHelper} describing the stream pipeline
   231   *               up to this operation
   232   * @param spliterator The {@code Spliterator} describing the source for this
   233   *                    pipeline
   234   */
   235  func (task *TODOTask) WithSpliterator(spliterator spliterator.Spliterator) *TODOTask {
   236  	task.spliterator = spliterator
   237  	return task
   238  }
   239  
   240  /**
   241   * Constructor for non-root nodes.
   242   *
   243   * @param parent this node's parent task
   244   * @param spliterator {@code Spliterator} describing the subtree rooted at
   245   *        this node, obtained by splitting the parent {@code Spliterator}
   246   */
   247  func (task *TODOTask) WithParent(parent Task, spliterator spliterator.Spliterator) *TODOTask {
   248  	task.parent = parent
   249  	task.spliterator = spliterator
   250  	task.targetSize = parent.TargetSize()
   251  	return task
   252  }
   253  
   254  func (task *TODOTask) GetSpliterator() spliterator.Spliterator {
   255  	return task.spliterator
   256  }
   257  
   258  func (task *TODOTask) GetLeafTarget() int {
   259  	return runtime.GOMAXPROCS(-1) << 2
   260  }
   261  
   262  func (task *TODOTask) LeftChild() Task {
   263  	return task.leftChild
   264  }
   265  
   266  func (task *TODOTask) SetLeftChild(task_ Task) {
   267  	task.leftChild = task_
   268  }
   269  
   270  func (task *TODOTask) RightChild() Task {
   271  	return task.rightChild
   272  }
   273  
   274  func (task *TODOTask) SetRightChild(task_ Task) {
   275  	task.rightChild = task_
   276  }
   277  
   278  func (task *TODOTask) TargetSize() int {
   279  	return task.targetSize
   280  }
   281  
   282  func (task *TODOTask) MakeChild(spliterator spliterator.Spliterator) Task {
   283  	panic(exception.NewUnsupportedOperationException())
   284  	return nil
   285  }
   286  
   287  func (task *TODOTask) DoLeaf(ctx context.Context) Sink {
   288  	panic(exception.NewUnsupportedOperationException())
   289  	return nil
   290  }
   291  
   292  /**
   293   * Returns a suggested target leaf size based on the initial size estimate.
   294   *
   295   * @return suggested target leaf size
   296   */
   297  func (task *TODOTask) suggestTargetSize(sizeEstimate int) int {
   298  	this := task.GetDerivedElse(task).(Task)
   299  	est := sizeEstimate / this.GetLeafTarget()
   300  	if est > 0 {
   301  		return est
   302  	}
   303  	return 1
   304  }
   305  
   306  /**
   307   * Returns the targetSize, initializing it via the supplied
   308   * size estimate if not already initialized.
   309   */
   310  func (task *TODOTask) getTargetSize(sizeEstimate int) int {
   311  	if task.targetSize != 0 {
   312  		return task.targetSize
   313  	}
   314  
   315  	task.targetSize = task.suggestTargetSize(sizeEstimate)
   316  	return task.targetSize
   317  }
   318  
   319  /**
   320   * Returns the local result, if any. Subclasses should use
   321   * {@link #setLocalResult(Object)} and {@link #GetLocalResult()} to manage
   322   * results.  This returns the local result so that calls from within the
   323   * fork-join framework will return the correct result.
   324   *
   325   * @return local result for this node previously stored with
   326   * {@link #setLocalResult}
   327   */
   328  func (task *TODOTask) getRawResult() Sink {
   329  	return task.localResult
   330  }
   331  
   332  /**
   333   * Does nothing; instead, subclasses should use
   334   * {@link #setLocalResult(Object)}} to manage results.
   335   *
   336   * @param result must be null, or an exception is thrown (this is a safety
   337   *        tripwire to detect when {@code setRawResult()} is being used
   338   *        instead of {@code setLocalResult()}
   339   */
   340  func (task *TODOTask) SetRawResult(result Sink) {
   341  	if result != nil {
   342  		panic(exception.NewIllegalStateException())
   343  	}
   344  }
   345  
   346  /**
   347   * Retrieves a result previously stored with {@link #setLocalResult}
   348   *
   349   * @return local result for this node previously stored with
   350   * {@link #setLocalResult}
   351   */
   352  func (task *TODOTask) GetLocalResult() Sink {
   353  	return task.localResult
   354  }
   355  
   356  /**
   357   * Associates the result with the task, can be retrieved with
   358   * {@link #GetLocalResult}
   359   *
   360   * @param localResult local result for this node
   361   */
   362  func (task *TODOTask) SetLocalResult(localResult Sink) {
   363  	task.localResult = localResult
   364  }
   365  
   366  func (task *TODOTask) IsLeaf() bool {
   367  	return task.leftChild == nil
   368  }
   369  
   370  func (task *TODOTask) IsRoot() bool {
   371  	this := task.GetDerivedElse(task).(Task)
   372  	return this.GetParent() == nil
   373  }
   374  
   375  func (task *TODOTask) GetParent() Task {
   376  	return task.parent
   377  }
   378  
   379  func (task *TODOTask) Compute(ctx context.Context) {
   380  	rs := task.spliterator
   381  	var ls spliterator.Spliterator
   382  	sizeEstimate := rs.EstimateSize()
   383  	sizeThreshold := task.getTargetSize(sizeEstimate)
   384  
   385  	var this = task.GetDerivedElse(task).(Task)
   386  	var forkRight bool
   387  	for {
   388  		select {
   389  		case <-ctx.Done():
   390  			return
   391  		default:
   392  		}
   393  
   394  		if sizeEstimate <= sizeThreshold {
   395  			break
   396  		}
   397  
   398  		ls = rs.TrySplit()
   399  		if ls == nil {
   400  			break
   401  		}
   402  
   403  		var leftChild, rightChild, taskToFork Task
   404  		leftChild = this.MakeChild(ls)
   405  		rightChild = this.MakeChild(rs)
   406  		this.SetLeftChild(leftChild)
   407  		this.SetRightChild(rightChild)
   408  
   409  		if forkRight {
   410  			forkRight = false
   411  			rs = ls
   412  			this = leftChild
   413  			taskToFork = rightChild
   414  		} else {
   415  			forkRight = true
   416  			this = rightChild
   417  			taskToFork = leftChild
   418  		}
   419  
   420  		// fork
   421  		taskToFork.Fork(ctx)
   422  
   423  		sizeEstimate = rs.EstimateSize()
   424  	}
   425  	this.SetLocalResult(this.DoLeaf(ctx))
   426  }
   427  
   428  func (task *TODOTask) Fork(ctx context.Context) {
   429  	go func() {
   430  		task.wg.Add(1)
   431  		defer task.wg.Done()
   432  		this := task.GetDerivedElse(task).(Task)
   433  		this.Compute(ctx)
   434  		this.OnCompletion(task.GetDerivedElse(task).(Task))
   435  	}()
   436  }
   437  
   438  func (task *TODOTask) Join() Sink {
   439  	task.wg.Wait()
   440  	return task.getRawResult()
   441  }
   442  
   443  func (task *TODOTask) Invoke(ctx context.Context) Sink {
   444  	this := task.GetDerivedElse(task).(Task)
   445  
   446  	this.Fork(ctx)
   447  	return this.Join()
   448  }
   449  
   450  func (task *TODOTask) OnCompletion(caller Task) {
   451  	task.spliterator = nil
   452  	task.leftChild = nil
   453  	task.rightChild = nil
   454  }
   455  
   456  func (task *TODOTask) IsLeftmostNode() bool {
   457  	var node = task.GetDerivedElse(task).(Task)
   458  	for node != nil {
   459  		parent := node.GetParent()
   460  		if parent != nil && parent.LeftChild() != node {
   461  			return false
   462  		}
   463  		node = parent
   464  	}
   465  	return true
   466  }