1 /*
  2  * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package java.util.concurrent;
 26 
 27 import java.lang.invoke.MethodHandles;
 28 import java.lang.invoke.VarHandle;
 29 import java.util.ArrayList;
 30 import java.util.Comparator;
 31 import java.util.List;
 32 import java.util.NoSuchElementException;
 33 import java.util.Objects;
 34 import java.util.concurrent.StructuredTaskScope.Joiner;
 35 import java.util.concurrent.StructuredTaskScope.Subtask;
 36 import java.util.function.Predicate;
 37 import jdk.internal.invoke.MhUtil;
 38 
 39 /**
 40  * Built-in StructuredTaskScope.Joiner implementations.
 41  */
 42 class Joiners {
 43     private Joiners() { }
 44 
 45     /**
 46      * Throws IllegalArgumentException if the subtask is not in the UNAVAILABLE state.
 47      */
 48     private static void ensureUnavailable(Subtask<?> subtask) {
 49         if (subtask.state() != Subtask.State.UNAVAILABLE) {
 50             throw new IllegalArgumentException("Subtask not in UNAVAILABLE state");
 51         }
 52     }
 53 
 54     /**
 55      * Throws IllegalArgumentException if the subtask has not completed.
 56      */
 57     private static Subtask.State ensureCompleted(Subtask<?> subtask) {
 58         Subtask.State state = subtask.state();
 59         if (state == Subtask.State.UNAVAILABLE) {
 60             throw new IllegalArgumentException("Subtask has not completed");
 61         }
 62         return state;
 63     }
 64 
 65     /**
 66      * A joiner that returns a list of all results when all subtasks complete
 67      * successfully. Cancels the scope if any subtask fails.
 68      */
 69     static final class AllSuccessful<T> implements Joiner<T, List<T>> {
 70         private static final VarHandle FIRST_EXCEPTION =
 71                 MhUtil.findVarHandle(MethodHandles.lookup(), "firstException", Throwable.class);
 72 
 73         // list of forked subtasks, created lazily, only accessed by owner thread
 74         private List<Subtask<T>> subtasks;
 75 
 76         private volatile Throwable firstException;
 77 
 78         @Override
 79         public boolean onFork(Subtask<T> subtask) {
 80             ensureUnavailable(subtask);
 81             if (subtasks == null) {
 82                 subtasks = new ArrayList<>();
 83             }
 84             subtasks.add(subtask);
 85             return false;
 86         }
 87 
 88         @Override
 89         public boolean onComplete(Subtask<T> subtask) {
 90             Subtask.State state = ensureCompleted(subtask);
 91             return (state == Subtask.State.FAILED)
 92                     && (firstException == null)
 93                     && FIRST_EXCEPTION.compareAndSet(this, null, subtask.exception());
 94         }
 95 
 96         @Override
 97         public List<T> result() throws Throwable {
 98             Throwable ex = firstException;
 99             try {
100                 if (ex != null) {
101                     throw ex;
102                 }
103                 return (subtasks != null)
104                         ? subtasks.stream().map(Subtask::get).toList()
105                         : List.of();
106             } finally {
107                 subtasks = null;  // allow subtasks to be GC'ed
108             }
109         }
110     }
111 
112     /**
113      * A joiner that returns the result of the first subtask to complete successfully.
114      * Cancels the scope if any subtasks succeeds.
115      */
116     static final class AnySuccessful<T> implements Joiner<T, T> {
117         private static final VarHandle SUBTASK =
118                 MhUtil.findVarHandle(MethodHandles.lookup(), "subtask", Subtask.class);
119 
120         // UNAVAILABLE < FAILED < SUCCESS
121         private static final Comparator<Subtask.State> SUBTASK_STATE_COMPARATOR =
122                 Comparator.comparingInt(AnySuccessful::stateToInt);
123 
124         private volatile Subtask<T> subtask;
125 
126         /**
127          * Maps a Subtask.State to an int that can be compared.
128          */
129         private static int stateToInt(Subtask.State s) {
130             return switch (s) {
131                 case UNAVAILABLE -> 0;
132                 case FAILED      -> 1;
133                 case SUCCESS     -> 2;
134             };
135         }
136 
137         @Override
138         public boolean onComplete(Subtask<T> subtask) {
139             Subtask.State state = ensureCompleted(subtask);
140             Subtask<T> s;
141             while (((s = this.subtask) == null)
142                     || SUBTASK_STATE_COMPARATOR.compare(s.state(), state) < 0) {
143                 if (SUBTASK.compareAndSet(this, s, subtask)) {
144                     return (state == Subtask.State.SUCCESS);
145                 }
146             }
147             return false;
148         }
149 
150         @Override
151         public T result() throws Throwable {
152             Subtask<T> subtask = this.subtask;
153             if (subtask == null) {
154                 throw new NoSuchElementException("No subtasks completed");
155             }
156             return switch (subtask.state()) {
157                 case SUCCESS -> subtask.get();
158                 case FAILED  -> throw subtask.exception();
159                 default      -> throw new InternalError();
160             };
161         }
162     }
163 
164     /**
165      * A joiner that that waits for all successful subtasks. Cancels the scope if any
166      * subtask fails.
167      */
168     static final class AwaitSuccessful<T> implements Joiner<T, Void> {
169         private static final VarHandle FIRST_EXCEPTION =
170                 MhUtil.findVarHandle(MethodHandles.lookup(), "firstException", Throwable.class);
171         private volatile Throwable firstException;
172 
173         @Override
174         public boolean onComplete(Subtask<T> subtask) {
175             Subtask.State state = ensureCompleted(subtask);
176             return (state == Subtask.State.FAILED)
177                     && (firstException == null)
178                     && FIRST_EXCEPTION.compareAndSet(this, null, subtask.exception());
179         }
180 
181         @Override
182         public Void result() throws Throwable {
183             Throwable ex = firstException;
184             if (ex != null) {
185                 throw ex;
186             } else {
187                 return null;
188             }
189         }
190     }
191 
192     /**
193      * A joiner that returns a list of all subtasks.
194      */
195     static final class AllSubtasks<T> implements Joiner<T, List<Subtask<T>>> {
196         private final Predicate<Subtask<T>> isDone;
197 
198         // list of forked subtasks, created lazily, only accessed by owner thread
199         private List<Subtask<T>> subtasks;
200 
201         AllSubtasks(Predicate<Subtask<T>> isDone) {
202             this.isDone = Objects.requireNonNull(isDone);
203         }
204 
205         @Override
206         public boolean onFork(Subtask<T> subtask) {
207             ensureUnavailable(subtask);
208             if (subtasks == null) {
209                 subtasks = new ArrayList<>();
210             }
211             subtasks.add(subtask);
212             return false;
213         }
214 
215         @Override
216         public boolean onComplete(Subtask<T> subtask) {
217             ensureCompleted(subtask);
218             return isDone.test(subtask);
219         }
220 
221         @Override
222         public void onTimeout() {
223             // do nothing, this joiner does not throw TimeoutException
224         }
225 
226         @Override
227         public List<Subtask<T>> result() {
228             if (subtasks != null) {
229                 List<Subtask<T>> result = List.copyOf(subtasks);
230                 subtasks = null;  // allow subtasks to be GC'ed
231                 return result;
232             } else {
233                 return List.of();
234             }
235         }
236     }
237 }