Hi Ryan,

Here's how I reason about it:

>Returns a new worker thread operating in the given pool.

This is pretty straight-forward.

>Returning null or throwing an exception may result in tasks never being executed.

This indicates that implementations of this method should avoid returning null or throwing exceptions.

>If this method throws an exception, it is relayed to the caller of the method (for example execute) causing attempted thread creation.

This is pretty straight-forward.

>If this method returns null or throws an exception, it is not retried until the next attempted creation (for example another call to execute).

The key part here is "*it is not retried until the next attempted creation*" — there is no guarantee as to /when/ that will actually occur. "(for example another call to execute)" only states an example of something which /may/ trigger that to happen.

On 2026-03-13 13:40, Ryan Ernst wrote:
Hi Viktor,

Thanks for the Javadoc. Are you implying this is working within the contract of the thread factory? If so, I read it a little differently.

In this case the factory throws an exception, and it make sense one would expect the task may not be execute. But the following sentences are key. If an exception is thrown, it must be relayed to the caller of eg execute. And the sentence after that implies an exception will result in subsequent retries. But neither is happening in this case: the exception is swallowed and the task is silently lost forever.

Here’s the key bits of the test case to show what I mean:

var pool = new ForkJoinPool(1,
    ignored -> { throw new RuntimeException(“simulated exception”); },
    null, false);

pool.execute(() -> {});  // throws RuntimeException, ie exception is relayed to caller

pool.execute(() -> {}); // silently succeeds, task never runs, no exception relayed



On Mar 11, 2026, at 4:24 PM, Viktor Klang <[email protected]> wrote:

Hi Ryan,

Adding the contract for ForkJoinWorkerThreadFactory.newThread(ForkJoinPool) to the conversation (emphasis mine):

«Returns a new worker thread operating in the given pool. *Returning null or throwing an exception may result in tasks never being executed. *If this method throws an exception, it is relayed to the caller of the method (for example execute) causing attempted thread creation. If this method returns null or throws an exception, it is not retried until the next attempted creation (for example another call to execute).» - https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/ForkJoinPool.ForkJoinWorkerThreadFactory.html#newThread(java.util.concurrent.ForkJoinPool)



On 2026-03-12 00:07, Ryan Ernst wrote:
Hi,

I'd like to discuss a (semi-fixed) bug in ForkJoinPool where tasks are silently lost when the ForkJoinWorkerThreadFactory throws an exception.

The first call to ForkJoinPool.execute() correctly propagates a factory exception. However, subsequent execute() calls silently queue the task. No exception is thrown and the task never runs; it's effectively lost forever.

The root cause is in WorkQueue.push(), it only calls signalWork() when the previous queue slot is null (ie the queue appeared empty). After the first failed execute(), the unconsumed task remains in slot 0. The next push() sees a non-null slot, skips signaling, and never attempts to create a worker.

The bug was inadvertently fixed in JDK 23 by the large ForkJoinPool rewrite in JDK-8322732, and I've confirmed it does not reproduce on JDK 23+. As far as I can tell, the fix has not been backported to 17u or 21u (it reproduces on JDK 17.0.13 and 21.0.4).

The fix is a one-line change in WorkQueue.push(), checking one slot further back when deciding whether to signal. This would (roughly) match the logic in JDK 23+.

In JDK 21:
  -  if ((resize || (a[m & (s - 1)] == null && signalIfEmpty)) &&
  +  if ((resize || a[m & (s - 2)] == null && signalIfEmpty) &&
          pool != null)
          pool.signalWork();

I have a standalone reproduction case I'm happy to share, but I wanted to check if this is a change that would be worthwhile and/or accepted as a bugfix in those LTS versions.

Thanks
Ryan

--
Cheers,
√


Viktor Klang
Software Architect, Java Platform Group
Oracle

--
Cheers,
√


Viktor Klang
Software Architect, Java Platform Group
Oracle

Reply via email to