| /* | |
| * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package java.util.stream; | |
| import jdk.internal.invoke.MhUtil; | |
| import jdk.internal.vm.annotation.ForceInline; | |
| import java.lang.invoke.MethodHandles; | |
| import java.lang.invoke.VarHandle; | |
| import java.util.Spliterator; | |
| import java.util.concurrent.CountedCompleter; | |
| import java.util.concurrent.atomic.AtomicBoolean; | |
| import java.util.function.BiConsumer; | |
| import java.util.function.BinaryOperator; | |
| import java.util.function.Consumer; | |
| import java.util.function.Function; | |
| import java.util.function.IntFunction; | |
| import java.util.function.Supplier; | |
| import java.util.stream.Gatherer.Integrator; | |
| /** | |
| * Runtime machinery for evaluating Gatherers under different modes. | |
| * The performance-critical code below contains some more complicated encodings: | |
| * therefore, make sure to run benchmarks to verify changes to prevent regressions. | |
| * | |
| * @since 24 | |
| */ | |
| final class GathererOp<T, A, R> extends ReferencePipeline<T, R> { | |
| @SuppressWarnings("unchecked") | |
| static <P_IN, P_OUT extends T, T, A, R> Stream<R> of( | |
| ReferencePipeline<P_IN, P_OUT> upstream, | |
| Gatherer<T, A, R> gatherer) { | |
| // When attaching a gather-operation onto another gather-operation, | |
| // we can fuse them into one | |
| if (upstream.getClass() == GathererOp.class) { | |
| return new GathererOp<>( | |
| ((GathererOp<P_IN, Object, P_OUT>) upstream).gatherer.andThen(gatherer), | |
| (GathererOp<?, ?, P_IN>) upstream); | |
| } else { | |
| return new GathererOp<>( | |
| (ReferencePipeline<?, T>) upstream, | |
| gatherer); | |
| } | |
| } | |
| /* | |
| * GathererOp.NodeBuilder is a lazy accumulator of elements with O(1) | |
| * `append`, and O(8) `join` (concat). | |
| * | |
| * First `append` inflates a growable Builder, the O(8) for `join` is | |
| * because we prefer to delegate to `append` for small concatenations to | |
| * avoid excessive indirections (unbalanced Concat-trees) when joining many | |
| * NodeBuilders together. | |
| */ | |
| static final class NodeBuilder<X> implements Consumer<X> { | |
| private static final int LINEAR_APPEND_MAX = 8; // TODO revisit | |
| static final class Builder<X> extends SpinedBuffer<X> implements Node<X> { | |
| Builder() { | |
| } | |
| } | |
| NodeBuilder() { | |
| } | |
| private Builder<X> rightMost; | |
| private Node<X> leftMost; | |
| private boolean isEmpty() { | |
| return rightMost == null && leftMost == null; | |
| } | |
| @Override | |
| public void accept(X x) { | |
| final var b = rightMost; | |
| (b == null ? (rightMost = new NodeBuilder.Builder<>()) : b).accept(x); | |
| } | |
| public NodeBuilder<X> join(NodeBuilder<X> that) { | |
| if (isEmpty()) | |
| return that; | |
| if (!that.isEmpty()) { | |
| final var tb = that.build(); | |
| if (rightMost != null && tb instanceof NodeBuilder.Builder<X> | |
| && tb.count() < LINEAR_APPEND_MAX) | |
| tb.forEach(this); // Avoid conc for small nodes | |
| else | |
| leftMost = Nodes.conc(StreamShape.REFERENCE, this.build(), tb); | |
| } | |
| return this; | |
| } | |
| public Node<X> build() { | |
| if (isEmpty()) | |
| return Nodes.emptyNode(StreamShape.REFERENCE); | |
| final var rm = rightMost; | |
| if (rm != null) { | |
| rightMost = null; // Make sure builder isn't reused | |
| final var lm = leftMost; | |
| leftMost = (lm == null) ? rm : Nodes.conc(StreamShape.REFERENCE, lm, rm); | |
| } | |
| return leftMost; | |
| } | |
| } | |
| static final class GatherSink<T, A, R> implements Sink<T>, Gatherer.Downstream<R> { | |
| private final Sink<R> sink; | |
| private final Gatherer<T, A, R> gatherer; | |
| private final Integrator<A, T, R> integrator; // Optimization: reuse | |
| private A state; | |
| private boolean proceed = true; | |
| private boolean downstreamProceed = true; | |
| GatherSink(Gatherer<T, A, R> gatherer, Sink<R> sink) { | |
| this.gatherer = gatherer; | |
| this.sink = sink; | |
| this.integrator = gatherer.integrator(); | |
| } | |
| // java.util.stream.Sink contract below: | |
| @Override | |
| public void begin(long size) { | |
| final var initializer = gatherer.initializer(); | |
| if (initializer != Gatherer.defaultInitializer()) // Optimization | |
| state = initializer.get(); | |
| sink.begin(-1); // GathererOp does not know the size of the output | |
| } | |
| @Override | |
| public void accept(T t) { | |
| /* Benchmarks have indicated that doing an unconditional write to | |
| * `proceed` is more efficient than branching. | |
| * We use `&=` here to prevent flips from `false` -> `true`. | |
| * | |
| * As of writing this, taking `greedy` or `stateless` into | |
| * consideration at this point doesn't yield any performance gains. | |
| */ | |
| proceed &= integrator.integrate(state, t, this); | |
| } | |
| @Override | |
| public boolean cancellationRequested() { | |
| return cancellationRequested(proceed && downstreamProceed); | |
| } | |
| private boolean cancellationRequested(boolean knownProceed) { | |
| // Highly performance sensitive | |
| return !(knownProceed && (!sink.cancellationRequested() || (downstreamProceed = false))); | |
| } | |
| @Override | |
| public void end() { | |
| final var finisher = gatherer.finisher(); | |
| if (finisher != Gatherer.<A, R>defaultFinisher()) // Optimization | |
| finisher.accept(state, this); | |
| sink.end(); | |
| state = null; // GC assistance | |
| } | |
| // Gatherer.Sink contract below: | |
| @Override | |
| public boolean isRejecting() { | |
| return !downstreamProceed; | |
| } | |
| @Override | |
| public boolean push(R r) { | |
| var p = downstreamProceed; | |
| if (p) | |
| sink.accept(r); | |
| return !cancellationRequested(p); | |
| } | |
| } | |
| private static int opFlagsFor(Integrator<?, ?, ?> integrator) { | |
| return integrator instanceof Integrator.Greedy<?, ?, ?> | |
| ? GREEDY_FLAGS : SHORT_CIRCUIT_FLAGS; | |
| } | |
| private static final int DEFAULT_FLAGS = | |
| StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT | | |
| StreamOpFlag.NOT_SIZED; | |
| private static final int SHORT_CIRCUIT_FLAGS = | |
| DEFAULT_FLAGS | StreamOpFlag.IS_SHORT_CIRCUIT; | |
| private static final int GREEDY_FLAGS = | |
| DEFAULT_FLAGS; | |
| final Gatherer<T, A, R> gatherer; | |
| /* | |
| * This constructor is used for initial .gather() invocations | |
| */ | |
| private GathererOp(ReferencePipeline<?, T> upstream, Gatherer<T, A, R> gatherer) { | |
| /* TODO this is a prime spot for pre-super calls to make sure that | |
| * we only need to call `integrator()` once. | |
| */ | |
| super(upstream, opFlagsFor(gatherer.integrator())); | |
| this.gatherer = gatherer; | |
| } | |
| /* | |
| * This constructor is used when fusing subsequent .gather() invocations | |
| */ | |
| @SuppressWarnings("unchecked") | |
| private GathererOp(Gatherer<T, A, R> gatherer, GathererOp<?, ?, T> upstream) { | |
| super((AbstractPipeline<?, T, ?>) upstream.upstream(), | |
| upstream, | |
| opFlagsFor(gatherer.integrator())); | |
| this.gatherer = gatherer; | |
| } | |
| /* This allows internal access to the previous stage, | |
| * to be able to fuse `gather` followed by `collect`. | |
| */ | |
| @SuppressWarnings("unchecked") | |
| private AbstractPipeline<?, T, ?> upstream() { | |
| return (AbstractPipeline<?, T, ?>) super.previousStage; | |
| } | |
| @Override | |
| boolean opIsStateful() { | |
| // TODO | |
| /* Currently GathererOp is always stateful, but what could be tried is: | |
| * return gatherer.initializer() != Gatherer.defaultInitializer() | |
| * || gatherer.combiner() == Gatherer.defaultCombiner() | |
| * || gatherer.finisher() != Gatherer.defaultFinisher(); | |
| */ | |
| return true; | |
| } | |
| @Override | |
| Sink<T> opWrapSink(int flags, Sink<R> downstream) { | |
| return new GatherSink<>(gatherer, downstream); | |
| } | |
| /* | |
| * This is used when evaluating .gather() operations interspersed with | |
| * other Stream operations (in parallel) | |
| */ | |
| @Override | |
| <I> Node<R> opEvaluateParallel(PipelineHelper<R> unused1, | |
| Spliterator<I> spliterator, | |
| IntFunction<R[]> unused2) { | |
| return this.<NodeBuilder<R>, Node<R>>evaluate( | |
| upstream().wrapSpliterator(spliterator), | |
| true, | |
| gatherer, | |
| NodeBuilder::new, | |
| NodeBuilder::accept, | |
| NodeBuilder::join, | |
| NodeBuilder::build | |
| ); | |
| } | |
| @Override | |
| <P_IN> Spliterator<R> opEvaluateParallelLazy(PipelineHelper<R> helper, | |
| Spliterator<P_IN> spliterator) { | |
| /* | |
| * There's a very small subset of possible Gatherers which would be | |
| * expressible as Spliterators directly, | |
| * - the Gatherer's initializer is Gatherer.defaultInitializer(), | |
| * - the Gatherer's combiner is NOT Gatherer.defaultCombiner() | |
| * - the Gatherer's finisher is Gatherer.defaultFinisher() | |
| */ | |
| return opEvaluateParallel(null, spliterator, null).spliterator(); | |
| } | |
| /* gather-operations immediately followed by (terminal) collect-operations | |
| * are fused together to avoid having to first run the gathering to | |
| * completion and only after that be able to run the collection on top of | |
| * the output. This is highly beneficial in the parallel case as stateful | |
| * operations cannot be pipelined in the ReferencePipeline implementation. | |
| * Overriding collect-operations overcomes this limitation. | |
| */ | |
| @Override | |
| public <CR, CA> CR collect(Collector<? super R, CA, CR> c) { | |
| linkOrConsume(); // Important for structural integrity | |
| final var parallel = isParallel(); | |
| final var u = upstream(); | |
| return evaluate( | |
| u.wrapSpliterator(u.sourceSpliterator(0)), | |
| parallel, | |
| gatherer, | |
| c.supplier(), | |
| c.accumulator(), | |
| parallel ? c.combiner() : null, | |
| c.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH) | |
| ? null | |
| : c.finisher() | |
| ); | |
| } | |
| @Override | |
| public <RR> RR collect(Supplier<RR> supplier, | |
| BiConsumer<RR, ? super R> accumulator, | |
| BiConsumer<RR, RR> combiner) { | |
| linkOrConsume(); // Important for structural integrity | |
| final var parallel = isParallel(); | |
| final var u = upstream(); | |
| return evaluate( | |
| u.wrapSpliterator(u.sourceSpliterator(0)), | |
| parallel, | |
| gatherer, | |
| supplier, | |
| accumulator, | |
| parallel ? (l, r) -> { | |
| combiner.accept(l, r); | |
| return l; | |
| } : null, | |
| null | |
| ); | |
| } | |
| /* | |
| * evaluate(...) is the primary execution mechanism besides opWrapSink() | |
| * and implements both sequential, hybrid parallel-sequential, and | |
| * parallel evaluation | |
| */ | |
| private <CA, CR> CR evaluate(final Spliterator<T> spliterator, | |
| final boolean parallel, | |
| final Gatherer<T, A, R> gatherer, | |
| final Supplier<CA> collectorSupplier, | |
| final BiConsumer<CA, ? super R> collectorAccumulator, | |
| final BinaryOperator<CA> collectorCombiner, | |
| final Function<CA, CR> collectorFinisher) { | |
| // There are two main sections here: sequential and parallel | |
| final var initializer = gatherer.initializer(); | |
| final var integrator = gatherer.integrator(); | |
| // Optimization | |
| final boolean greedy = integrator instanceof Integrator.Greedy<A, T, R>; | |
| // Sequential evaluation section starts here. | |
| // Sequential is the fusion of a Gatherer and a Collector which can | |
| // be evaluated sequentially. | |
| final class Sequential implements Consumer<T>, Gatherer.Downstream<R> { | |
| A state; | |
| CA collectorState; | |
| boolean proceed; | |
| Sequential() { | |
| if (initializer != Gatherer.defaultInitializer()) | |
| state = initializer.get(); | |
| collectorState = collectorSupplier.get(); | |
| proceed = true; | |
| } | |
| @ForceInline | |
| Sequential evaluateUsing(Spliterator<T> spliterator) { | |
| if (greedy) | |
| spliterator.forEachRemaining(this); | |
| else | |
| do { | |
| } while (proceed && spliterator.tryAdvance(this)); | |
| return this; | |
| } | |
| /* | |
| * No need to override isKnownDone() as the default is `false` | |
| * and collectors can never short-circuit. | |
| */ | |
| @Override | |
| public boolean push(R r) { | |
| collectorAccumulator.accept(collectorState, r); | |
| return true; | |
| } | |
| @Override | |
| public void accept(T t) { | |
| /* | |
| * Benchmarking has shown that, in this case, conditional | |
| * writing of `proceed` is desirable and if that was not the | |
| * case, then the following line would've been clearer: | |
| * | |
| * proceed &= integrator.integrate(state, t, this); | |
| */ | |
| var ignore = integrator.integrate(state, t, this) | |
| || (!greedy && (proceed = false)); | |
| } | |
| @SuppressWarnings("unchecked") | |
| public CR get() { | |
| final var finisher = gatherer.finisher(); | |
| if (finisher != Gatherer.<A, R>defaultFinisher()) | |
| finisher.accept(state, this); | |
| // IF collectorFinisher == null -> IDENTITY_FINISH | |
| return (collectorFinisher == null) | |
| ? (CR) collectorState | |
| : collectorFinisher.apply(collectorState); | |
| } | |
| } | |
| /* | |
| * It could be considered to also go to sequential mode if the | |
| * operation is non-greedy AND the combiner is Gatherer.defaultCombiner() | |
| * as those operations will not benefit from upstream parallel | |
| * preprocessing which is the main advantage of the Hybrid evaluation | |
| * strategy. | |
| */ | |
| if (!parallel) | |
| return new Sequential().evaluateUsing(spliterator).get(); | |
| // Parallel section starts here: | |
| final var combiner = gatherer.combiner(); | |
| /* | |
| * The following implementation of hybrid parallel-sequential | |
| * Gatherer processing borrows heavily from ForeachOrderedTask, | |
| * and adds handling of short-circuiting. | |
| */ | |
| @SuppressWarnings("serial") | |
| final class Hybrid extends CountedCompleter<Sequential> { | |
| private final long targetSize; | |
| private final Hybrid leftPredecessor; | |
| private final AtomicBoolean cancelled; | |
| private final Sequential localResult; | |
| private Spliterator<T> spliterator; | |
| private Hybrid next; | |
| private static final VarHandle NEXT = MhUtil.findVarHandle( | |
| MethodHandles.lookup(), "next", Hybrid.class); | |
| protected Hybrid(Spliterator<T> spliterator) { | |
| super(null); | |
| this.spliterator = spliterator; | |
| this.targetSize = | |
| AbstractTask.suggestTargetSize(spliterator.estimateSize()); | |
| this.localResult = new Sequential(); | |
| this.cancelled = greedy ? null : new AtomicBoolean(false); | |
| this.leftPredecessor = null; | |
| } | |
| Hybrid(Hybrid parent, Spliterator<T> spliterator, Hybrid leftPredecessor) { | |
| super(parent); | |
| this.spliterator = spliterator; | |
| this.targetSize = parent.targetSize; | |
| this.localResult = parent.localResult; | |
| this.cancelled = parent.cancelled; | |
| this.leftPredecessor = leftPredecessor; | |
| } | |
| @Override | |
| public Sequential getRawResult() { | |
| return localResult; | |
| } | |
| @Override | |
| public void setRawResult(Sequential result) { | |
| if (result != null) throw new IllegalStateException(); | |
| } | |
| @Override | |
| public void compute() { | |
| var task = this; | |
| Spliterator<T> rightSplit = task.spliterator, leftSplit; | |
| long sizeThreshold = task.targetSize; | |
| boolean forkRight = false; | |
| while ((greedy || !cancelled.get()) | |
| && rightSplit.estimateSize() > sizeThreshold | |
| && (leftSplit = rightSplit.trySplit()) != null) { | |
| var leftChild = new Hybrid(task, leftSplit, task.leftPredecessor); | |
| var rightChild = new Hybrid(task, rightSplit, leftChild); | |
| /* leftChild and rightChild were just created and not | |
| * fork():ed yet so no need for a volatile write | |
| */ | |
| leftChild.next = rightChild; | |
| // Fork the parent task | |
| // Completion of the left and right children "happens-before" | |
| // completion of the parent | |
| task.addToPendingCount(1); | |
| // Completion of the left child "happens-before" completion of | |
| // the right child | |
| rightChild.addToPendingCount(1); | |
| // If task is not on the left spine | |
| if (task.leftPredecessor != null) { | |
| /* | |
| * Completion of left-predecessor, or left subtree, | |
| * "happens-before" completion of left-most leaf node of | |
| * right subtree. | |
| * The left child's pending count needs to be updated before | |
| * it is associated in the completion map, otherwise the | |
| * left child can complete prematurely and violate the | |
| * "happens-before" constraint. | |
| */ | |
| leftChild.addToPendingCount(1); | |
| // Update association of left-predecessor to left-most | |
| // leaf node of right subtree | |
| if (NEXT.compareAndSet(task.leftPredecessor, task, leftChild)) { | |
| // If replaced, adjust the pending count of the parent | |
| // to complete when its children complete | |
| task.addToPendingCount(-1); | |
| } else { | |
| // Left-predecessor has already completed, parent's | |
| // pending count is adjusted by left-predecessor; | |
| // left child is ready to complete | |
| leftChild.addToPendingCount(-1); | |
| } | |
| } | |
| if (forkRight) { | |
| rightSplit = leftSplit; | |
| task = leftChild; | |
| rightChild.fork(); | |
| } else { | |
| task = rightChild; | |
| leftChild.fork(); | |
| } | |
| forkRight = !forkRight; | |
| } | |
| /* | |
| * Task's pending count is either 0 or 1. If 1 then the completion | |
| * map will contain a value that is task, and two calls to | |
| * tryComplete are required for completion, one below and one | |
| * triggered by the completion of task's left-predecessor in | |
| * onCompletion. Therefore there is no data race within the if | |
| * block. | |
| * | |
| * IMPORTANT: Currently we only perform the processing of this | |
| * upstream data if we know the operation is greedy -- as we cannot | |
| * safely speculate on the cost/benefit ratio of parallelizing | |
| * the pre-processing of upstream data under short-circuiting. | |
| */ | |
| if (greedy && task.getPendingCount() > 0) { | |
| // Upstream elements are buffered | |
| NodeBuilder<T> nb = new NodeBuilder<>(); | |
| rightSplit.forEachRemaining(nb); // Run the upstream | |
| task.spliterator = nb.build().spliterator(); | |
| } | |
| task.tryComplete(); | |
| } | |
| @Override | |
| public void onCompletion(CountedCompleter<?> caller) { | |
| var s = spliterator; | |
| spliterator = null; // GC assistance | |
| /* Performance sensitive since each leaf-task could have a | |
| * spliterator of size 1 which means that all else is overhead | |
| * which needs minimization. | |
| */ | |
| if (s != null | |
| && (greedy || !cancelled.get()) | |
| && !localResult.evaluateUsing(s).proceed | |
| && !greedy) | |
| cancelled.set(true); | |
| // The completion of this task *and* the dumping of elements | |
| // "happens-before" completion of the associated left-most leaf task | |
| // of right subtree (if any, which can be this task's right sibling) | |
| @SuppressWarnings("unchecked") | |
| var leftDescendant = (Hybrid) NEXT.getAndSet(this, null); | |
| if (leftDescendant != null) { | |
| leftDescendant.tryComplete(); | |
| } | |
| } | |
| } | |
| /* | |
| * The following implementation of parallel Gatherer processing | |
| * borrows heavily from AbstractShortCircuitTask | |
| */ | |
| @SuppressWarnings("serial") | |
| final class Parallel extends CountedCompleter<Sequential> { | |
| private Spliterator<T> spliterator; | |
| private Parallel leftChild; // Only non-null if rightChild is | |
| private Parallel rightChild; // Only non-null if leftChild is | |
| private Sequential localResult; | |
| private volatile boolean canceled; | |
| private long targetSize; // lazily initialized | |
| private Parallel(Parallel parent, Spliterator<T> spliterator) { | |
| super(parent); | |
| this.targetSize = parent.targetSize; | |
| this.spliterator = spliterator; | |
| } | |
| Parallel(Spliterator<T> spliterator) { | |
| super(null); | |
| this.targetSize = 0L; | |
| this.spliterator = spliterator; | |
| } | |
| private long getTargetSize(long sizeEstimate) { | |
| long s; | |
| return ((s = targetSize) != 0 | |
| ? s | |
| : (targetSize = AbstractTask.suggestTargetSize(sizeEstimate))); | |
| } | |
| @Override | |
| public Sequential getRawResult() { | |
| return localResult; | |
| } | |
| @Override | |
| public void setRawResult(Sequential result) { | |
| if (result != null) throw new IllegalStateException(); | |
| } | |
| private void doProcess() { | |
| if (!(localResult = new Sequential()).evaluateUsing(spliterator).proceed | |
| && !greedy) | |
| cancelLaterTasks(); | |
| } | |
| @Override | |
| public void compute() { | |
| Spliterator<T> rs = spliterator, ls; | |
| long sizeEstimate = rs.estimateSize(); | |
| final long sizeThreshold = getTargetSize(sizeEstimate); | |
| Parallel task = this; | |
| boolean forkRight = false; | |
| boolean proceed; | |
| while ((proceed = (greedy || !task.isRequestedToCancel())) | |
| && sizeEstimate > sizeThreshold | |
| && (ls = rs.trySplit()) != null) { | |
| final var leftChild = task.leftChild = new Parallel(task, ls); | |
| final var rightChild = task.rightChild = new Parallel(task, rs); | |
| task.setPendingCount(1); | |
| if (forkRight) { | |
| rs = ls; | |
| task = leftChild; | |
| rightChild.fork(); | |
| } else { | |
| task = rightChild; | |
| leftChild.fork(); | |
| } | |
| forkRight = !forkRight; | |
| sizeEstimate = rs.estimateSize(); | |
| } | |
| if (proceed) | |
| task.doProcess(); | |
| task.tryComplete(); | |
| } | |
| Sequential merge(Sequential l, Sequential r) { | |
| /* | |
| * Only join the right if the left side didn't short-circuit, | |
| * or when greedy | |
| */ | |
| if (greedy || (l != null && r != null && l.proceed)) { | |
| l.state = combiner.apply(l.state, r.state); | |
| l.collectorState = | |
| collectorCombiner.apply(l.collectorState, r.collectorState); | |
| l.proceed = r.proceed; | |
| return l; | |
| } | |
| return (l != null) ? l : r; | |
| } | |
| @Override | |
| public void onCompletion(CountedCompleter<?> caller) { | |
| spliterator = null; // GC assistance | |
| if (leftChild != null) { | |
| /* Results can only be null in the case where there's | |
| * short-circuiting or when Gatherers are stateful but | |
| * uses `null` as their state value. | |
| */ | |
| localResult = merge(leftChild.localResult, rightChild.localResult); | |
| leftChild = rightChild = null; // GC assistance | |
| } | |
| } | |
| @SuppressWarnings("unchecked") | |
| private Parallel getParent() { | |
| return (Parallel) getCompleter(); | |
| } | |
| private boolean isRequestedToCancel() { | |
| boolean cancel = canceled; | |
| if (!cancel) { | |
| for (Parallel parent = getParent(); | |
| !cancel && parent != null; | |
| parent = parent.getParent()) | |
| cancel = parent.canceled; | |
| } | |
| return cancel; | |
| } | |
| private void cancelLaterTasks() { | |
| // Go up the tree, cancel right siblings of this node and all parents | |
| for (Parallel parent = getParent(), node = this; | |
| parent != null; | |
| node = parent, parent = parent.getParent()) { | |
| // If node is a left child of parent, then has a right sibling | |
| if (parent.leftChild == node) | |
| parent.rightChild.canceled = true; | |
| } | |
| } | |
| } | |
| if (combiner != Gatherer.defaultCombiner()) | |
| return new Parallel(spliterator).invoke().get(); | |
| else | |
| return new Hybrid(spliterator).invoke().get(); | |
| } | |
| } |