ADDITIONAL SYSTEM INFORMATION :
Is independent of system architecture or OS, is JDK 19 specific:
openjdk version "19-ea" 2022-09-20
OpenJDK Runtime Environment (build 19-ea+30-2169)
OpenJDK 64-Bit Server VM (build 19-ea+30-2169, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
In JDKs prior to v19, a subclass of ForkJoinWorkerThread would call the start() method when being started by a ForkJoinPool. In JDK 19, that is no longer happening. Application code which is dependent on the start() method being invoked is broken.
REGRESSION : Last worked in version 18.0.2
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Execute the demo application (class jdkbug.JDKBugDemo) provided. No command line arguments are required.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
On JDK 16, the output is listed below. You can see that for each thread spawned, the start() method is called, as we see (for example) "TestForkJoinWorkerThread (4) 18 is starting." messages.
*****
TestForkJoinWorkerThread (0) 14 is starting.
TestForkJoinWorkerThread (0) 14 is running.
WorkThread (0) 14: Initializing task counter.
TestForkJoinWorkerThread (1) 15 is starting.
TestForkJoinWorkerThread (1) 15 is running.
WorkThread (1) 15: Initializing task counter.
TestForkJoinWorkerThread (2) 16 is starting.
TestForkJoinWorkerThread (3) 17 is starting.
TestForkJoinWorkerThread (2) 16 is running.
WorkThread (2) 16: Initializing task counter.
TestForkJoinWorkerThread (4) 18 is starting.
TestForkJoinWorkerThread (3) 17 is running.
TestForkJoinWorkerThread (4) 18 is running.
WorkThread (4) 18: Initializing task counter.
WorkThread (3) 17: Initializing task counter.
WorkerThread (14) 0: 0 terminating
WorkerThread (17) 3: 0 terminating
*****
Result:
lorem ipsum dolor sit amet, consectetur adipiscing elit. curabitur non pellentesque odio. proin eget tortor a ex viverra molestie eget a diam. sed accumsan mauris ut ipsum tempor, nec iaculis sem accumsan. etiam id lectus consectetur, suscipit dui in, commodo odio. mauris ullamcorper pulvinar ultrices. nam eleifend eros id nunc rutrum pretium. etiam imperdiet elit urna, ac ullamcorper ante auctor eu. proin condimentum nunc hendrerit odio ultrices sagittis. praesent et tristique enim. nam volutpat accumsan elit sed posuere. lorem ipsum dolor sit amet, consectetur adipiscing elit. duis id tortor id dolor semper finibus eu ut ante. nulla facilisi. phasellus sapien tellus, pulvinar in efficitur nec, accumsan nec tortor. vivamus aliquam, dolor at vestibulum placerat, dui odio semper neque, id aliquam neque justo eu orci.
WorkerThread (15) 1: 0 terminating
WorkerThread (16)
ACTUAL -
In JDK 19, we get the following output (Note that the starting messages are no longer being generated, meaning start() isn't called.):
*****
TestForkJoinWorkerThread (0) 21 is running.
WorkThread (0) 21: Initializing task counter.
TestForkJoinWorkerThread (1) 22 is running.
WorkThread (1) 22: Initializing task counter.
TestForkJoinWorkerThread (2) 23 is running.
WorkThread (2) 23: Initializing task counter.
TestForkJoinWorkerThread (3) 24 is running.
WorkThread (3) 24: Initializing task counter.
WorkerThread (24) 3: 0 terminating
*****
Result:
lorem ipsum dolor sit amet, consectetur adipiscing elit. curabitur non pellentesque odio. proin eget tortor a ex viverra molestie eget a diam. sed accumsan mauris ut ipsum tempor, nec iaculis sem accumsan. etiam id lectus consectetur, suscipit dui in, commodo odio. mauris ullamcorper pulvinar ultrices. nam eleifend eros id nunc rutrum pretium. etiam imperdiet elit urna, ac ullamcorper ante auctor eu. proin condimentum nunc hendrerit odio ultrices sagittis. praesent et tristique enim. nam volutpat accumsan elit sed posuere. lorem ipsum dolor sit amet, consectetur adipiscing elit. duis id tortor id dolor semper finibus eu ut ante. nulla facilisi. phasellus sapien tellus, pulvinar in efficitur nec, accumsan nec tortor. vivamus aliquam, dolor at vestibulum placerat, dui odio semper neque, id aliquam neque justo eu orci.
WorkerThread (23) 2: 0 terminating
WorkerThread (21) 0:
---------- BEGIN SOURCE ----------
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;
public class JDKBugDemo {
static int counter = 0;
static String orig_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur non pellentesque odio. Proin eget tortor a ex viverra molestie eget a diam. Sed accumsan mauris ut ipsum tempor, nec iaculis sem accumsan. Etiam id lectus consectetur, suscipit dui in, commodo odio. Mauris ullamcorper pulvinar ultrices. Nam eleifend eros id nunc rutrum pretium. Etiam imperdiet elit urna, ac ullamcorper ante auctor eu. Proin condimentum nunc hendrerit odio ultrices sagittis. Praesent et tristique enim. Nam volutpat accumsan elit sed posuere. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis id tortor id dolor semper finibus eu ut ante. Nulla facilisi. Phasellus sapien tellus, pulvinar in efficitur nec, accumsan nec tortor. Vivamus aliquam, dolor at vestibulum placerat, dui odio semper neque, id aliquam neque justo eu orci.";
static ForkJoinPool pool = new ForkJoinPool(5, new WorkerThreadFactory(), new UEH(), false);
public static void main(String[] args) throws Exception {
new JDKBugDemo().doDemo();
}
private void doDemo() throws Exception {
System.out.println("*****\n");
char[] output = new char[orig_text.length()];
RecursiveToLower t = new RecursiveToLower(orig_text.toCharArray(), output);
pool.invoke(t);
do {
TimeUnit.SECONDS.sleep(1);
} while (!t.isDone());
pool.shutdown();
System.out.println("\n*****\nResult:\n" + new String(output));
}
static class WorkerThreadFactory implements ForkJoinWorkerThreadFactory {
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
return new TestForkJoinWorkerThread(counter++, pool);
}
}
static class UEH implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("UEH reporting from " + t + ": " + t);
}
}
class RecursiveToLower extends RecursiveAction {
private char[] text;
private char[] result;
private int start;
private int length;
private final int maxLength = 200;
public RecursiveToLower(char[] text, char[] result) {
this.text = text;
this.result = result;
start = 0;
length = text.length;
}
public RecursiveToLower(char[] text, char[] result, int start, int length) {
this.text = text;
this.result = result;
this.start = start;
this.length = length;
}
private void computeDirectly() {
for (int index = start; index < start + length; index++) {
result[index] = Character.toLowerCase(text[index]);
}
}
protected void compute() {
if (length > maxLength) {
int halfLength = length / 2;
RecursiveToLower subtask1 = new RecursiveToLower(text, result, start, halfLength);
RecursiveToLower subtask2 = new RecursiveToLower(text, result, start + halfLength, length - halfLength);
invokeAll(subtask1, subtask2);
} else {
computeDirectly();
}
}
}
public static class TestForkJoinWorkerThread extends ForkJoinWorkerThread {
private static ThreadLocal<Integer> taskCounter = new ThreadLocal();
private int id;
protected TestForkJoinWorkerThread(int id, ForkJoinPool pool) {
super(pool);
this.id = id;
}
@Override
protected void onStart() {
super.onStart();
System.out.printf("WorkThread (%d) %d: Initializing task counter.\n", id, this.getId());
taskCounter.set(0);
}
@Override
protected void onTermination(Throwable exception) {
System.out.printf("WorkerThread (%d) %d: %d terminating\n", getId(), id, taskCounter.get());
super.onTermination(exception);
}
public void addTask() {
int counter = taskCounter.get().intValue();
counter++;
taskCounter.set(counter);
}
public void run() {
System.out.printf("TestForkJoinWorkerThread (%d) %d is running.\n", id, this.getId());
super.run();
}
public void start() {
System.out.printf("TestForkJoinWorkerThread (%d) %d is starting.\n", id, this.getId());
super.start();
}
}
}
---------- END SOURCE ----------
FREQUENCY : always