github.com/searKing/golang/go@v1.2.74/container/stream/op/terminal/short_circuit_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 "sync/atomic" 10 11 "github.com/searKing/golang/go/error/exception" 12 "github.com/searKing/golang/go/util/spliterator" 13 ) 14 15 //go:generate go-atomicvalue -type "sharedResult<Sink>" 16 type sharedResult atomic.Value 17 18 /** 19 * Abstract class for fork-join tasks used to implement short-circuiting 20 * stream ops, which can produce a result without processing all elements of the 21 * stream. 22 * 23 * @param <P_IN> type of input elements to the pipeline 24 * @param <P_OUT> type of output elements from the pipeline 25 * @param <R> type of intermediate result, may be different from operation 26 * result type 27 * @param <K> type of child and sibling tasks 28 * @since 1.8 29 */ 30 type ShortCircuitTask interface { 31 Task 32 33 SharedResult() *sharedResult 34 /** 35 * The result for this computation; this is shared among all tasks and set 36 * exactly once 37 */ 38 GetSharedResult() Sink 39 40 /** 41 * Declares that a globally valid result has been found. If another task has 42 * not already found the answer, the result is installed in 43 * {@code sharedResult}. The {@code compute()} method will check 44 * {@code sharedResult} before proceeding with computation, so this causes 45 * the computation to terminate early. 46 * 47 * @param result the result found 48 */ 49 ShortCircuit(result Sink) 50 51 /** 52 * Mark this task as canceled 53 */ 54 Cancel() 55 56 /** 57 * Queries whether this task is canceled. A task is considered canceled if 58 * it or any of its parents have been canceled. 59 * 60 * @return {@code true} if this task or any parent is canceled. 61 */ 62 TaskCanceled() bool 63 64 /** 65 * Cancels all tasks which succeed this one in the encounter order. This 66 * includes canceling all the current task's right sibling, as well as the 67 * later right siblings of all its parents. 68 */ 69 CancelLaterNodes() 70 } 71 72 type TODOShortCircuitTask struct { 73 TODOTask 74 75 /** 76 * The result for this computation; this is shared among all tasks and set 77 * exactly once 78 */ 79 sharedResult *sharedResult 80 81 /** 82 * Indicates whether this task has been canceled. Tasks may cancel other 83 * tasks in the computation under various conditions, such as in a 84 * find-first operation, a task that finds a value will cancel all tasks 85 * that are later in the encounter order. 86 */ 87 canceled bool 88 } 89 90 /** 91 * Constructor for root tasks. 92 * 93 * @param helper the {@code PipelineHelper} describing the stream pipeline 94 * up to this operation 95 * @param spliterator the {@code Spliterator} describing the source for this 96 * pipeline 97 */ 98 func (task *TODOShortCircuitTask) WithSpliterator(spliterator spliterator.Spliterator) *TODOShortCircuitTask { 99 task.TODOTask.WithSpliterator(spliterator) 100 task.sharedResult = &sharedResult{} 101 return task 102 } 103 104 func (task *TODOShortCircuitTask) SharedResult() *sharedResult { 105 return task.sharedResult 106 } 107 108 /** 109 * Constructor for non-root nodes. 110 * 111 * @param parent parent task in the computation tree 112 * @param spliterator the {@code Spliterator} for the portion of the 113 * computation tree described by this task 114 */ 115 func (task *TODOShortCircuitTask) WithParent(parent ShortCircuitTask, spliterator spliterator.Spliterator) *TODOShortCircuitTask { 116 task.TODOTask.WithParent(parent, spliterator) 117 task.sharedResult = parent.SharedResult() 118 task.SetDerived(task) 119 return task 120 } 121 122 // Helper 123 124 /** 125 * Overrides TODOTask version to include checks for early 126 * exits while splitting or computing. 127 */ 128 func (task *TODOShortCircuitTask) Compute(ctx context.Context) { 129 rs := task.spliterator 130 var ls spliterator.Spliterator 131 sizeEstimate := rs.EstimateSize() 132 sizeThreshold := task.getTargetSize(sizeEstimate) 133 134 var this = task.GetDerivedElse(task).(Task) 135 var forkRight bool 136 var sr = task.sharedResult 137 for sr.Load() == nil { 138 select { 139 case <-ctx.Done(): 140 return 141 default: 142 } 143 144 if sizeEstimate <= sizeThreshold { 145 break 146 } 147 148 ls = rs.TrySplit() 149 if ls == nil { 150 break 151 } 152 153 var leftChild, rightChild, taskToFork Task 154 leftChild = this.MakeChild(ls) 155 rightChild = this.MakeChild(rs) 156 this.SetLeftChild(leftChild) 157 this.SetRightChild(rightChild) 158 159 if forkRight { 160 forkRight = false 161 rs = ls 162 this = leftChild 163 taskToFork = rightChild 164 } else { 165 forkRight = true 166 this = rightChild 167 taskToFork = leftChild 168 } 169 170 // fork 171 taskToFork.Fork(ctx) 172 173 sizeEstimate = rs.EstimateSize() 174 } 175 this.SetLocalResult(this.DoLeaf(ctx)) 176 } 177 178 func (task *TODOShortCircuitTask) ShortCircuit(result Sink) { 179 if result != nil { 180 task.sharedResult.Store(result) 181 } 182 } 183 184 func (task *TODOShortCircuitTask) GetSharedResult() Sink { 185 return task.sharedResult.Load() 186 } 187 188 /** 189 * Does nothing; instead, subclasses should use 190 * {@link #setLocalResult(Object)}} to manage results. 191 * 192 * @param result must be null, or an exception is thrown (this is a safety 193 * tripwire to detect when {@code setRawResult()} is being used 194 * instead of {@code setLocalResult()} 195 */ 196 func (task TODOShortCircuitTask) SetRawResult(result Sink) { 197 if result != nil { 198 panic(exception.NewIllegalStateException()) 199 } 200 } 201 202 /** 203 * Sets a local result for this task. If this task is the root, set the 204 * shared result instead (if not already set). 205 * 206 * @param localResult The result to set for this task 207 */ 208 func (task *TODOShortCircuitTask) SetLocalResult(localResult Sink) { 209 var this = task.GetDerivedElse(task).(Task) 210 if this.IsRoot() { 211 if localResult != nil { 212 task.sharedResult.Store(localResult) 213 } 214 } else { 215 task.TODOTask.SetLocalResult(localResult) 216 } 217 } 218 219 /** 220 * Retrieves the local result for this task 221 */ 222 func (task *TODOShortCircuitTask) getRawResult() Sink { 223 var this = task.GetDerivedElse(task).(Task) 224 return this.GetLocalResult() 225 } 226 227 /** 228 * Retrieves the local result for this task. If this task is the root, 229 * retrieves the shared result instead. 230 */ 231 func (task *TODOShortCircuitTask) GetLocalResult() Sink { 232 var this = task.GetDerivedElse(task).(Task) 233 if this.IsRoot() { 234 answer := task.sharedResult.Load() 235 return answer 236 } 237 return task.TODOTask.GetLocalResult() 238 } 239 240 func (task *TODOShortCircuitTask) Cancel() { 241 task.canceled = true 242 } 243 244 func (task *TODOShortCircuitTask) TaskCanceled() bool { 245 if task.canceled { 246 return true 247 } 248 var this = task.GetDerivedElse(task).(Task) 249 parent := this.GetParent() 250 if parent != nil { 251 return parent.(ShortCircuitTask).TaskCanceled() 252 } 253 return false 254 } 255 256 func (task *TODOShortCircuitTask) CancelLaterNodes() { 257 // Go up the tree, cancel right siblings of this node and all parents 258 parent := task.GetParent() 259 var node = task.GetDerivedElse(task).(Task) 260 261 for parent != nil { 262 // If node is a left child of parent, then has a right sibling 263 if parent.LeftChild() == node { 264 rightSibling := parent.RightChild() 265 if !rightSibling.(*TODOShortCircuitTask).canceled { 266 rightSibling.(ShortCircuitTask).Cancel() 267 } 268 } 269 270 node = parent 271 parent = parent.GetParent() 272 } 273 }