@@ -89,6 +89,7 @@ public synchronized static ForkJoinPool addForkJoinPool(ForkJoinPool pool) {
8989 *
9090 * @param timeout The maximum time to wait <b><u>AFTER</u></b> a shutdown is initiated
9191 * @param unit the time unit of the timeout argument
92+ *
9293 * @return Whether all threads were joined before returning
9394 */
9495 public static boolean joinAll (long timeout , @ NotNull TimeUnit unit ) {
@@ -102,55 +103,35 @@ public static boolean joinAll(long timeout, @NotNull TimeUnit unit) {
102103
103104 while (true ) {
104105 try {
105- // must wait for the latch as hitting the for loop below first causes immediate evaluation of the
106- // for loop iterator, potentially missing new threads.
106+ // Must wait for the latch as hitting the for loop below first causes immediate evaluation of:
107+ // - The for loop iterator, potentially missing new threads.
108+ // - The end time, meaning waiting starts before shutdown begins.
107109 shutdownLatch .await ();
108110 break ;
109111 } catch (InterruptedException e ) {
110112 interrupted = true ;
111113 }
112114 }
113115
114- long startTime = System .nanoTime ();
115- long endTime = startTime + unit .toNanos (timeout );
116-
117- boolean anyAlive = false ;
118-
116+ long endTime = System .nanoTime () + unit .toNanos (timeout );
119117 try {
120- for (ExecutorService executorService : executorServices ) {
121- while (System .nanoTime () < endTime ) {
122- try {
123- long waitTime = endTime - startTime ;
124- if (waitTime > 0 ) {
125- executorService .awaitTermination (waitTime , TimeUnit .NANOSECONDS );
126- }
127- break ;
128- } catch (InterruptedException e ) {
129- interrupted = true ;
130- }
131- }
132- anyAlive |= !executorService .isTerminated ();
133- }
134- for (Thread thread : ChunkyThread .threads ) {
135- while (System .nanoTime () < endTime ) {
136- try {
137- long waitTimeMillis = TimeUnit .NANOSECONDS .toMillis (endTime - startTime );
138- if (waitTimeMillis > 0 ) {
139- thread .join (waitTimeMillis );
140- }
141- break ;
142- } catch (InterruptedException e ) {
143- interrupted = true ;
118+ while (System .nanoTime () < endTime ) {
119+ try {
120+ if (joinAllInterruptable (endTime )) {
121+ // All threads are joined, skip the rest of the wait time.
122+ return true ;
144123 }
124+ } catch (InterruptedException e ) {
125+ interrupted = true ;
145126 }
146- anyAlive |= thread .isAlive ();
147127 }
128+ // Got to the end of the wait time without joining everything, can give no guarantees
129+ return false ;
148130 } finally {
149131 if (interrupted ) {
150132 Thread .currentThread ().interrupt ();
151133 }
152134 }
153- return !anyAlive ;
154135 }
155136
156137 /**
@@ -178,6 +159,43 @@ public static boolean interruptAndJoinAll(long timeout, @NotNull TimeUnit unit)
178159 return joinAll (timeout , unit );
179160 }
180161
162+ /**
163+ * Await the joining of all threads managed by chunky.
164+ *
165+ * <p>This method is only safe to call if the {@link ChunkyThread#shutdownLatch} has been set.</p>
166+ *
167+ * <p><b><i>WARNING: calling this from any thread registered with {@link #addThread(Thread)} may <u>deadlock</u>.</i></b></p>
168+ *
169+ * @param endTimeNanos The time at which to stop waiting.
170+ *
171+ * @return Whether all threads were joined before returning
172+ *
173+ * @throws InterruptedException Propagates up when interrupted. The caller has no guarantee that shutdown has begun,
174+ * or that any of the inner threads have been joined.
175+ */
176+ private static boolean joinAllInterruptable (long endTimeNanos ) throws InterruptedException {
177+ // The intention here whether we return true or false, is to give the caller the most complete acquire load possible.
178+ // Even if we reach the timeout given by the caller, we still establish a happens-before with every dead thread.
179+
180+ boolean anyAlive = false ;
181+ for (ExecutorService executorService : executorServices ) {
182+ long waitTime = endTimeNanos - System .nanoTime ();
183+ anyAlive |= !executorService .awaitTermination (waitTime , TimeUnit .NANOSECONDS );
184+ }
185+ for (Thread thread : threads ) {
186+ long waitTime = endTimeNanos - System .nanoTime ();
187+ if (waitTime > 0 ) {
188+ thread .join (waitTime ); // joining with 0 is infinite wait time, very intuitive.
189+ }
190+ // Thread.isAlive() establishes a happens-before with the thread. As such the following are non-issues:
191+ // - Not joining the thread, if waitTime <= 0
192+ // - The thread stopping between Thread.join() and Thread.isAlive().
193+ anyAlive |= thread .isAlive ();
194+ }
195+
196+ return !anyAlive ;
197+ }
198+
181199 private void setDefaults () {
182200 this .setDaemon (true );
183201 addThread (this );
0 commit comments