From 82444515f46c65e4c2694f352746a498dcb9b454 Mon Sep 17 00:00:00 2001 From: Jie Fu Date: Sat, 4 Jun 2022 02:49:52 +0000 Subject: [PATCH 01/22] 8287826: javax/accessibility/4702233/AccessiblePropertiesTest.java fails to compile Reviewed-by: dcubed --- .../javax/accessibility/4702233/AccessibleRoleConstants.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/jdk/javax/accessibility/4702233/AccessibleRoleConstants.java b/test/jdk/javax/accessibility/4702233/AccessibleRoleConstants.java index 2cd767cec30..7f480dfab3b 100644 --- a/test/jdk/javax/accessibility/4702233/AccessibleRoleConstants.java +++ b/test/jdk/javax/accessibility/4702233/AccessibleRoleConstants.java @@ -1,5 +1,3 @@ -package bug4702233; - /* * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. From 0e198fcb3063f846dfa9ff106593f781000f71d5 Mon Sep 17 00:00:00 2001 From: Manukumar V S Date: Sat, 4 Jun 2022 03:00:44 +0000 Subject: [PATCH 02/22] 8286772: java/awt/dnd/DropTargetInInternalFrameTest/DropTargetInInternalFrameTest.html times out and fails in Windows Reviewed-by: prr --- .../dnd/DropTargetInInternalFrameTest.java | 380 ++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 test/jdk/java/awt/dnd/DropTargetInInternalFrameTest.java diff --git a/test/jdk/java/awt/dnd/DropTargetInInternalFrameTest.java b/test/jdk/java/awt/dnd/DropTargetInInternalFrameTest.java new file mode 100644 index 00000000000..6ee0f425724 --- /dev/null +++ b/test/jdk/java/awt/dnd/DropTargetInInternalFrameTest.java @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2000, 2022, 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. + * + * 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. + */ + +import java.awt.Button; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Robot; +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DragGestureEvent; +import java.awt.dnd.DragGestureListener; +import java.awt.dnd.DragSource; +import java.awt.dnd.DragSourceDragEvent; +import java.awt.dnd.DragSourceDropEvent; +import java.awt.dnd.DragSourceEvent; +import java.awt.dnd.DragSourceListener; +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetContext; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.dnd.DropTargetEvent; +import java.awt.dnd.DropTargetListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.InputEvent; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import javax.imageio.ImageIO; +import javax.swing.JButton; +import javax.swing.JDesktopPane; +import javax.swing.JFrame; +import javax.swing.JInternalFrame; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +/* + @test + @bug 4395279 + @summary Tests that a drop target in InternalFrame functions properly + @key headful + @run main DropTargetInInternalFrameTest +*/ +public class DropTargetInInternalFrameTest implements Serializable { + private static final CountDownLatch dropLatch = new CountDownLatch(1); + private static final CountDownLatch focusLatch = new CountDownLatch(1); + private static JFrame frame; + private static JInternalFrame sourceFrame; + private static JInternalFrame targetFrame; + private static DragSourcePanel dragSourcePanel; + private static DropTargetPanel dropTargetPanel; + private static Robot robot; + + private static void createUI() { + frame = new JFrame("Test frame"); + sourceFrame = new JInternalFrame("Source"); + targetFrame = new JInternalFrame("Destination"); + dragSourcePanel = new DragSourcePanel(); + dropTargetPanel = new DropTargetPanel(dropLatch); + JDesktopPane desktopPane = new JDesktopPane(); + + sourceFrame.getContentPane().setLayout(new GridLayout(3, 1)); + + // add panels to content panes + sourceFrame.getContentPane().add(dragSourcePanel); + targetFrame.getContentPane().add(dropTargetPanel); + + sourceFrame.setSize(200, 200); + targetFrame.setSize(200, 200); + targetFrame + .setLocation(sourceFrame.getX() + sourceFrame.getWidth() + 10, + sourceFrame.getY()); + + desktopPane.add(sourceFrame); + desktopPane.add(targetFrame); + + frame.setTitle("Test frame"); + frame.setBounds(200, 200, 450, 250); + frame.getContentPane().add(desktopPane); + frame.setAlwaysOnTop(true); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + sourceFrame.setVisible(true); + targetFrame.setVisible(true); + frame.setVisible(true); + dragSourcePanel.dragSourceButton.requestFocusInWindow(); + } + + public static void main(String[] argv) throws Exception { + SwingUtilities.invokeAndWait(DropTargetInInternalFrameTest::createUI); + + robot = new Robot(); + robot.setAutoDelay(50); + robot.setAutoWaitForIdle(true); + robot.waitForIdle(); + if (!focusLatch.await(5, TimeUnit.SECONDS)) { + captureScreen(); + SwingUtilities + .invokeAndWait(DropTargetInInternalFrameTest::disposeFrame); + System.out.println( + "Test failed, waited too long for the drag button to gain focus"); + } + final AtomicReference p1Ref = new AtomicReference<>(); + final AtomicReference p2Ref = new AtomicReference<>(); + SwingUtilities.invokeAndWait(() -> { + final Point dragLocation = + dragSourcePanel.dragSourceButton.getLocationOnScreen(); + Dimension d1 = dragSourcePanel.dragSourceButton.getSize(); + dragLocation.translate(d1.width / 2, d1.height / 2); + p1Ref.set(dragLocation); + final Point dropLocation = dropTargetPanel.getLocationOnScreen(); + dropLocation.translate(d1.width / 2, d1.height / 2); + p2Ref.set(dropLocation); + }); + Point p1 = p1Ref.get(); + Point p2 = p2Ref.get(); + + dragAndDrop(p1, p2); + + if (!dropLatch.await(5, TimeUnit.SECONDS)) { + captureScreen(); + System.out.println("Test Failed, Waited too long for the Drop to complete"); + } + int calledMethods = dropTargetPanel.getCalledMethods(); + SwingUtilities + .invokeAndWait(DropTargetInInternalFrameTest::disposeFrame); + System.out.println("CalledMethods = " + calledMethods); + if ((calledMethods & DropTargetPanel.ENTER_CALLED) == 0) { + throw new RuntimeException( + "Test Failed, DropTargetListener.dragEnter() not called"); + } + if ((calledMethods & DropTargetPanel.OVER_CALLED) == 0) { + throw new RuntimeException( + "Test Failed, DropTargetListener.dragOver() not called"); + } + if ((calledMethods & DropTargetPanel.DROP_CALLED) == 0) { + throw new RuntimeException( + "Test Failed, DropTargetListener.drop() not called."); + } + + System.out.println("Test Passed"); + } + + private static void dragAndDrop(final Point p1, final Point p2) { + robot.mouseMove(p1.x, p1.y); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + int dx = 1; + while (p1.x < p2.x) { + p1.translate(dx, 0); + robot.mouseMove(p1.x, p1.y); + dx++; + } + robot.mouseMove(p2.x, p2.y); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + } + + private static void captureScreen() { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + try { + ImageIO.write(robot.createScreenCapture( + new Rectangle(0, 0, screenSize.width, screenSize.height)), + "png", new File("screenImage.png")); + } catch (IOException ignore) { + } + } + + private static void disposeFrame() { + sourceFrame.dispose(); + targetFrame.dispose(); + frame.dispose(); + } + + private static class DragSourcePanel extends JPanel { + + final Dimension preferredDimension = new Dimension(200, 100); + final DragSourceButton dragSourceButton = new DragSourceButton(); + + public DragSourcePanel() { + setLayout(new GridLayout(1, 1)); + dragSourceButton.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(final FocusEvent e) { + super.focusGained(e); + focusLatch.countDown(); + } + }); + add(dragSourceButton); + } + + public Dimension getPreferredSize() { + return preferredDimension; + } + + } + + private static class DropTargetPanel extends JPanel + implements DropTargetListener { + + public static final int ENTER_CALLED = 0x1; + public static final int OVER_CALLED = 0x2; + public static final int DROP_CALLED = 0x4; + private final Dimension preferredDimension = new Dimension(200, 100); + private final CountDownLatch dropLatch; + private volatile int calledMethods = 0; + + public DropTargetPanel(final CountDownLatch dropLatch) { + this.dropLatch = dropLatch; + setDropTarget(new DropTarget(this, this)); + } + + public Dimension getPreferredSize() { + return preferredDimension; + } + + public void dragEnter(DropTargetDragEvent dtde) { + calledMethods |= ENTER_CALLED; + } + + public void dragOver(DropTargetDragEvent dtde) { + calledMethods |= OVER_CALLED; + } + + public void dropActionChanged(DropTargetDragEvent dtde) { + } + + public void dragExit(DropTargetEvent dte) { + } + + public void drop(DropTargetDropEvent dtde) { + System.out.println("Drop!!!!!!!!!!!! "); + calledMethods |= DROP_CALLED; + DropTargetContext dtc = dtde.getDropTargetContext(); + + if ((dtde.getSourceActions() & DnDConstants.ACTION_COPY) != 0) { + dtde.acceptDrop(DnDConstants.ACTION_COPY); + } else { + dtde.rejectDrop(); + } + + DataFlavor[] dfs = dtde.getCurrentDataFlavors(); + Component comp = null; + + if (dfs != null && dfs.length >= 1) { + Transferable transfer = dtde.getTransferable(); + + try { + comp = (Component) transfer.getTransferData(dfs[0]); + } catch (Throwable e) { + e.printStackTrace(); + dtc.dropComplete(false); + } + } + dtc.dropComplete(true); + add(comp); + dropLatch.countDown(); + } + + public int getCalledMethods() { + return calledMethods; + } + + } + + private static class DragSourceButton extends JButton + implements Serializable, Transferable, DragGestureListener, + DragSourceListener { + private final DataFlavor dataflavor = + new DataFlavor(Button.class, "DragSourceButton"); + + public DragSourceButton() { + this("DragSourceButton"); + } + + public DragSourceButton(String str) { + super(str); + DragSource ds = DragSource.getDefaultDragSource(); + ds.createDefaultDragGestureRecognizer(this, + DnDConstants.ACTION_COPY, + this); + } + + public void dragGestureRecognized(DragGestureEvent dge) { + dge.startDrag(new Cursor(Cursor.HAND_CURSOR), this, this); + } + + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[]{dataflavor}; + } + + public boolean isDataFlavorSupported(DataFlavor dflavor) { + return dataflavor.equals(dflavor); + } + + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, IOException { + + if (!isDataFlavorSupported(flavor)) { + throw new UnsupportedFlavorException(flavor); + } + Object retObj; + ByteArrayOutputStream baoStream = new ByteArrayOutputStream(); + ObjectOutputStream ooStream = new ObjectOutputStream(baoStream); + ooStream.writeObject(this); + + ByteArrayInputStream baiStream = + new ByteArrayInputStream(baoStream.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(baiStream); + try { + retObj = ois.readObject(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + throw new RuntimeException(e.toString()); + } + return retObj; + } + + @Override + public void dragEnter(final DragSourceDragEvent dsde) { + + } + + @Override + public void dragOver(final DragSourceDragEvent dsde) { + + } + + @Override + public void dropActionChanged(final DragSourceDragEvent dsde) { + + } + + @Override + public void dragExit(final DragSourceEvent dse) { + + } + + @Override + public void dragDropEnd(final DragSourceDropEvent dsde) { + + } + + } + +} From 2929abf7287053519d37c7b5a6ccebd1f1eb8ffa Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Sat, 4 Jun 2022 06:15:49 +0000 Subject: [PATCH 03/22] 8284199: Implementation of Structured Concurrency (Incubator) Co-authored-by: Ron Pressler Co-authored-by: Alan Bateman Co-authored-by: Brian Goetz Co-authored-by: Paul Sandoz Reviewed-by: psandoz, mcimadamore, darcy --- make/conf/docs-modules.conf | 1 + make/conf/module-loader-map.conf | 3 +- src/java.base/share/classes/module-info.java | 1 + .../StructureViolationException.java | 55 + .../concurrent/StructuredTaskScope.java | 1189 ++++++++++++++++ .../incubator/concurrent/package-info.java | 30 + .../share/classes/module-info.java | 35 + test/jdk/TEST.groups | 2 + .../PreviewFeaturesNotEnabled.java | 53 + .../StructuredTaskScopeTest.java | 1248 +++++++++++++++++ .../StructuredThreadDumpTest.java | 200 +++ 11 files changed, 2816 insertions(+), 1 deletion(-) create mode 100644 src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructureViolationException.java create mode 100644 src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructuredTaskScope.java create mode 100644 src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/package-info.java create mode 100644 src/jdk.incubator.concurrent/share/classes/module-info.java create mode 100644 test/jdk/jdk/incubator/concurrent/StructuredTaskScope/PreviewFeaturesNotEnabled.java create mode 100644 test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java create mode 100644 test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java diff --git a/make/conf/docs-modules.conf b/make/conf/docs-modules.conf index 09b26eae9a1..e009deb85b3 100644 --- a/make/conf/docs-modules.conf +++ b/make/conf/docs-modules.conf @@ -42,6 +42,7 @@ DOCS_MODULES= \ jdk.hotspot.agent \ jdk.httpserver \ jdk.jpackage \ + jdk.incubator.concurrent \ jdk.incubator.vector \ jdk.jartool \ jdk.javadoc \ diff --git a/make/conf/module-loader-map.conf b/make/conf/module-loader-map.conf index f3e38bf9c28..186fd2b906e 100644 --- a/make/conf/module-loader-map.conf +++ b/make/conf/module-loader-map.conf @@ -1,5 +1,5 @@ # -# Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 2022, 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 @@ -43,6 +43,7 @@ BOOT_MODULES= \ java.rmi \ java.security.sasl \ java.xml \ + jdk.incubator.concurrent \ jdk.incubator.vector \ jdk.internal.vm.ci \ jdk.jfr \ diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 0dc3666ae4e..e280defabe0 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -202,6 +202,7 @@ jdk.charsets, jdk.compiler, jdk.crypto.cryptoki, + jdk.incubator.concurrent, jdk.incubator.vector, jdk.jfr, jdk.jshell, diff --git a/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructureViolationException.java b/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructureViolationException.java new file mode 100644 index 00000000000..01bef436176 --- /dev/null +++ b/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructureViolationException.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021, 2022, 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 jdk.incubator.concurrent; + +/** + * Thrown when a structure violation is detected. + * + * @see StructuredTaskScope#fork(Callable) + * @see StructuredTaskScope#close() + * + * @since 19 + */ +public final class StructureViolationException extends RuntimeException { + @java.io.Serial + private static final long serialVersionUID = -7705327650798235468L; + + /** + * Constructs a {@code StructureViolationException} with no detail message. + */ + public StructureViolationException() { + super(); + } + + /** + * Constructs a {@code StructureViolationException} with the specified + * detail message. + * + * @param message the detail message, can be null + */ + public StructureViolationException(String message) { + super(message); + } +} diff --git a/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructuredTaskScope.java b/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructuredTaskScope.java new file mode 100644 index 00000000000..a6041ab73b7 --- /dev/null +++ b/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructuredTaskScope.java @@ -0,0 +1,1189 @@ +/* + * Copyright (c) 2021, 2022, 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 jdk.incubator.concurrent; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.time.Duration; +import java.time.Instant; +import java.util.Comparator; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import jdk.internal.misc.PreviewFeatures; +import jdk.internal.misc.ThreadFlock; + +/** + * A basic API for structured concurrency. {@code StructuredTaskScope} supports + * cases where a task splits into several concurrent subtasks, to be executed in their + * own threads, and where the subtasks must complete before the main task continues. A + * {@code StructuredTaskScope} can be used to ensure that the lifetime of a concurrent + * operation is confined by a syntax block, just like that of a sequential + * operation in structured programming. + * + *

Basic usage

+ * + * A {@code StructuredTaskScope} is created with one of its public constructors. It defines + * the {@link #fork(Callable) fork} method to start a thread to execute a task, the {@link + * #join() join} method to wait for all threads to finish, and the {@link #close() close} + * method to close the task scope. The API is intended to be used with the {@code + * try-with-resources} construct. The intention is that code in the block uses + * the {@code fork} method to fork threads to execute the subtasks, wait for the threads + * to finish with the {@code join} method, and then process the results. + * Processing of results may include handling or re-throwing of exceptions. + * {@snippet lang=java : + * try (var scope = new StructuredTaskScope()) { + * + * Future future1 = scope.fork(task1); // @highlight substring="fork" + * Future future2 = scope.fork(task2); // @highlight substring="fork" + * + * scope.join(); // @highlight substring="join" + * + * ... process results/exceptions ... + * + * } // close // @highlight substring="close" + * } + * To ensure correct usage, the {@code join} and {@code close} methods may only be invoked + * by the owner (the thread that opened/created the task scope}, and the + * {@code close} method throws an exception after closing if the owner did not invoke the + * {@code join} method after forking. + * + *

{@code StructuredTaskScope} defines the {@link #shutdown() shutdown} method to shut + * down a task scope without closing it. Shutdown is useful for cases where a subtask + * completes with a result (or exception) and the results of other unfinished subtasks are + * no longer needed. If a subtask invokes {@code shutdown} while the owner is waiting in + * the {@code join} method then it will cause {@code join} to wakeup, all unfinished + * threads to be {@linkplain Thread#interrupt() interrupted} and prevents new threads + * from starting in the task scope. + * + *

Subclasses with policies for common cases

+ * + * Two subclasses of {@code StructuredTaskScope} are defined to implement policy for + * common cases: + *
    + *
  1. {@link ShutdownOnSuccess ShutdownOnSuccess} captures the first result and + * shuts down the task scope to interrupt unfinished threads and wakeup the owner. This + * class is intended for cases where the result of any subtask will do ("invoke any") + * and where there is no need to wait for results of other unfinished tasks. It defines + * methods to get the first result or throw an exception if all subtasks fail. + *
  2. {@link ShutdownOnFailure ShutdownOnFailure} captures the first exception and + * shuts down the task scope. This class is intended for cases where the results of all + * subtasks are required ("invoke all"); if any subtask fails then the results of other + * unfinished subtasks are no longer needed. If defines methods to throw an exception if + * any of the subtasks fail. + *
+ * + *

The following are two examples that use the two classes. In both cases, a pair of + * subtasks are forked to fetch resources from two URL locations "left" and "right". The + * first example creates a ShutdownOnSuccess object to capture the result of the first + * subtask to complete normally, cancelling the other by way of shutting down the task + * scope. The main task waits in {@code join} until either subtask completes with a result + * or both subtasks fail. It invokes {@link ShutdownOnSuccess#result(Function) + * result(Function)} method to get the captured result. If both subtasks fail then this + * method throws a {@code WebApplicationException} with the exception from one of the + * subtasks as the cause. + * {@snippet lang=java : + * try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) { + * + * scope.fork(() -> fetch(left)); + * scope.fork(() -> fetch(right)); + * + * scope.join(); + * + * // @link regex="result(?=\()" target="ShutdownOnSuccess#result" : + * String result = scope.result(e -> new WebApplicationException(e)); + * + * ... + * } + * } + * The second example creates a ShutdownOnFailure object to capture the exception of the + * first subtask to fail, cancelling the other by way of shutting down the task scope. The + * main task waits in {@link #joinUntil(Instant)} until both subtasks complete with a + * result, either fails, or a deadline is reached. It invokes {@link + * ShutdownOnFailure#throwIfFailed(Function) throwIfFailed(Function)} to throw an exception + * when either subtask fails. This method is a no-op if no subtasks fail. The main task + * uses {@code Future}'s {@link Future#resultNow() resultNow()} method to retrieve the + * results. + * + * {@snippet lang=java : + * Instant deadline = ... + * + * try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { + * + * Future future1 = scope.fork(() -> query(left)); + * Future future2 = scope.fork(() -> query(right)); + * + * scope.joinUntil(deadline); + * + * // @link substring="throwIfFailed" target="ShutdownOnFailure#throwIfFailed" : + * scope.throwIfFailed(e -> new WebApplicationException(e)); + * + * // both subtasks completed successfully + * String result = Stream.of(future1, future2) + * // @link substring="Future::resultNow" target="Future#resultNow" : + * .map(Future::resultNow) + * .collect(Collectors.joining(", ", "{ ", " }")); + * + * ... + * } + * } + * + *

Extending StructuredTaskScope

+ * + * {@code StructuredTaskScope} can be extended, and the {@link #handleComplete(Future) + * handleComplete} overridden, to implement policies other than those implemented by + * {@code ShutdownOnSuccess} and {@code ShutdownOnFailure}. The method may be overridden + * to, for example, collect the results of subtasks that complete with a result and ignore + * subtasks that fail. It may collect exceptions when subtasks fail. It may invoke the + * {@link #shutdown() shutdown} method to shut down and cause {@link #join() join} to + * wakeup when some condition arises. + * + *

A subclass will typically define methods to make available results, state, or other + * outcome to code that executes after the {@code join} method. A subclass that collects + * results and ignores subtasks that fail may define a method that returns a collection of + * results. A subclass that implements a policy to shut down when a subtask fails may + * define a method to retrieve the exception of the first subtask to fail. + * + *

The following is an example of a {@code StructuredTaskScope} implementation that + * collects the results of subtasks that complete successfully. It defines the method + * {@code results()} to be used by the main task to retrieve the results. + * + * {@snippet lang=java : + * class MyScope extends StructuredTaskScope { + * private final Queue results = new ConcurrentLinkedQueue<>(); + * + * MyScope() { + * super(null, Thread.ofVirtual().factory()); + * } + * + * @Override + * // @link substring="handleComplete" target="handleComplete" : + * protected void handleComplete(Future future) { + * if (future.state() == Future.State.SUCCESS) { + * T result = future.resultNow(); + * results.add(result); + * } + * } + * + * // Returns a stream of results from the subtasks that completed successfully + * public Stream results() { // @highlight substring="results" + * return results.stream(); + * } + * } + * } + * + *

Tree structure

+ * + * StructuredTaskScopes form a tree where parent-child relations are established + * implicitly when opening a new task scope: + *
    + *
  • A parent-child relation is established when a thread started in a task scope + * opens its own task scope. A thread started in task scope "A" that opens task scope + * "B" establishes a parent-child relation where task scope "A" is the parent of task + * scope "B". + *
  • A parent-child relation is established with nesting. If a thread opens task + * scope "B", then opens task scope "C" (before it closes "B"), then the enclosing task + * scope "B" is the parent of the nested task scope "C". + *
+ * + *

The tree structure supports confinement checks. The phrase "threads contained in + * the task scope" in method descriptions means threads started in the task scope or + * descendant scopes. {@code StructuredTaskScope} does not define APIs that exposes the + * tree structure at this time. + * + *

Unless otherwise specified, passing a {@code null} argument to a constructor + * or method in this class will cause a {@link NullPointerException} to be thrown. + * + *

Memory consistency effects

+ * + *

Actions in the owner thread of, or a thread contained in, the task scope prior to + * {@linkplain #fork forking} of a {@code Callable} task + * + * happen-before any actions taken by that task, which in turn happen-before + * the task result is retrieved via its {@code Future}, or happen-before any actions + * taken in a thread after {@linkplain #join() joining} of the task scope. + * + * @jls 17.4.5 Happens-before Order + * + * @param the result type of tasks executed in the scope + * @since 19 + */ +public class StructuredTaskScope implements AutoCloseable { + private static final VarHandle FUTURES; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + FUTURES = l.findVarHandle(StructuredTaskScope.class, "futures", Set.class); + } catch (Exception e) { + throw new InternalError(e); + } + } + + private final ThreadFactory factory; + private final ThreadFlock flock; + private final ReentrantLock shutdownLock = new ReentrantLock(); + + // lazily created set of Future objects with threads waiting in Future::get + private volatile Set> futures; + + // set by owner when it forks, reset by owner when it joins + private boolean needJoin; + + // states: OPEN -> SHUTDOWN -> CLOSED + private static final int OPEN = 0; // initial state + private static final int SHUTDOWN = 1; + private static final int CLOSED = 2; + + // scope state, set by owner, read by any thread + private volatile int state; + + /** + * Creates a structured task scope with the given name and thread factory. The task + * scope is optionally named for the purposes of monitoring and management. The thread + * factory is used to {@link ThreadFactory#newThread(Runnable) create} threads when + * tasks are {@linkplain #fork(Callable) forked}. The task scope is owned by the + * current thread. + * + * @param name the name of the task scope, can be null + * @param factory the thread factory + */ + public StructuredTaskScope(String name, ThreadFactory factory) { + this.factory = Objects.requireNonNull(factory, "'factory' is null"); + this.flock = ThreadFlock.open(name); + } + + /** + * Creates an unnamed structured task scope that creates virtual threads. The task + * scope is owned by the current thread. + * + *

This constructor is equivalent to invoking the 2-arg constructor with a name + * of {@code null} and a thread factory that creates virtual threads. + * + * @throws UnsupportedOperationException if preview features are not enabled + */ + public StructuredTaskScope() { + PreviewFeatures.ensureEnabled(); + this.factory = FactoryHolder.VIRTUAL_THREAD_FACTORY; + this.flock = ThreadFlock.open(null); + } + + /** + * Throws WrongThreadException if the current thread is not the owner. + */ + private void ensureOwner() { + if (Thread.currentThread() != flock.owner()) + throw new WrongThreadException("Current thread not owner"); + } + + /** + * Throws WrongThreadException if the current thread is not the owner + * or a thread contained in the tree. + */ + private void ensureOwnerOrContainsThread() { + Thread currentThread = Thread.currentThread(); + if (currentThread != flock.owner() && !flock.containsThread(currentThread)) + throw new WrongThreadException("Current thread not owner or thread in the tree"); + } + + /** + * Tests if the task scope is shutdown. + */ + private boolean isShutdown() { + return state >= SHUTDOWN; + } + + /** + * Track the given Future. + */ + private void track(Future future) { + // create the set of Futures if not already created + Set> futures = this.futures; + if (futures == null) { + futures = ConcurrentHashMap.newKeySet(); + if (!FUTURES.compareAndSet(this, null, futures)) { + // lost the race + futures = this.futures; + } + } + futures.add(future); + } + + /** + * Stop tracking the Future. + */ + private void untrack(Future future) { + assert futures != null; + futures.remove(future); + } + + /** + * Invoked when a task completes before the scope is shut down. + * + *

The {@code handleComplete} method should be thread safe. It may be invoked by + * several threads concurrently. + * + * @implSpec The default implementation does nothing. + * + * @param future the completed task + */ + protected void handleComplete(Future future) { } + + /** + * Starts a new thread to run the given task. + * + *

The new thread is created with the task scope's {@link ThreadFactory}. + * + *

If the task completes before the task scope is {@link #shutdown() shutdown} + * then the {@link #handleComplete(Future) handle} method is invoked to consume the + * completed task. The {@code handleComplete} method is run when the task completes + * with a result or exception. If the {@code Future} {@link Future#cancel(boolean) + * cancel} method is used the cancel a task before the task scope is shut down, then + * the {@code handleComplete} method is run by the thread that invokes {@code cancel}. + * If the task scope shuts down at or around the same time that the task completes or + * is cancelled then the {@code handleComplete} method may or may not be invoked. + * + *

If this task scope is {@linkplain #shutdown() shutdown} (or in the process + * of shutting down) then {@code fork} returns a {@code Future} representing a {@link + * Future.State#CANCELLED cancelled} task that was not run. + * + *

This method may only be invoked by the task scope owner or threads contained + * in the task scope. The {@link Future#cancel(boolean) cancel} method of the returned + * {@code Future} object is also restricted to the task scope owner or threads contained + * in the task scope. The {@code cancel} method throws {@link WrongThreadException} + * if invoked from another thread. All other methods on the returned {@code Future} + * object, such as {@link Future#get() get}, are not restricted. + * + * @param task the task to run + * @param the result type + * @return a future + * @throws IllegalStateException if this task scope is closed + * @throws WrongThreadException if the current thread is not the owner or a thread + * contained in the task scope + * @throws RejectedExecutionException if the thread factory rejected creating a + * thread to run the task + */ + public Future fork(Callable task) { + Objects.requireNonNull(task, "'task' is null"); + + // create future + var future = new FutureImpl(this, task); + + boolean shutdown = (state >= SHUTDOWN); + + if (!shutdown) { + // create thread + Thread thread = factory.newThread(future); + if (thread == null) { + throw new RejectedExecutionException("Rejected by thread factory"); + } + + // attempt to start the thread + try { + flock.start(thread); + } catch (IllegalStateException e) { + // shutdown or in the process of shutting down + shutdown = true; + } + } + + if (shutdown) { + if (state == CLOSED) { + throw new IllegalStateException("Task scope is closed"); + } else { + future.cancel(false); + } + } + + // if owner forks then it will need to join + if (Thread.currentThread() == flock.owner() && !needJoin) { + needJoin = true; + } + + return future; + } + + /** + * Wait for all threads to finish or the task scope to shut down. + */ + private void implJoin(Duration timeout) + throws InterruptedException, TimeoutException + { + ensureOwner(); + needJoin = false; + int s = state; + if (s >= SHUTDOWN) { + if (s == CLOSED) + throw new IllegalStateException("Task scope is closed"); + return; + } + + // wait for all threads, wakeup, interrupt, or timeout + if (timeout != null) { + flock.awaitAll(timeout); + } else { + flock.awaitAll(); + } + } + + /** + * Wait for all threads to finish or the task scope to shut down. This method waits + * until all threads started in the task scope finish execution (of both task and + * {@link #handleComplete(Future) handleComplete} method), the {@link #shutdown() + * shutdown} method is invoked to shut down the task scope, or the current thread + * is {@linkplain Thread#interrupt() interrupted}. + * + *

This method may only be invoked by the task scope owner. + * + * @return this task scope + * @throws IllegalStateException if this task scope is closed + * @throws WrongThreadException if the current thread is not the owner + * @throws InterruptedException if interrupted while waiting + */ + public StructuredTaskScope join() throws InterruptedException { + try { + implJoin(null); + } catch (TimeoutException e) { + throw new InternalError(); + } + return this; + } + + /** + * Wait for all threads to finish or the task scope to shut down, up to the given + * deadline. This method waits until all threads started in the task scope finish + * execution (of both task and {@link #handleComplete(Future) handleComplete} method), + * the {@link #shutdown() shutdown} method is invoked to shut down the task scope, + * the current thread is {@linkplain Thread#interrupt() interrupted}, or the deadline + * is reached. + * + *

This method may only be invoked by the task scope owner. + * + * @param deadline the deadline + * @return this task scope + * @throws IllegalStateException if this task scope is closed + * @throws WrongThreadException if the current thread is not the owner + * @throws InterruptedException if interrupted while waiting + * @throws TimeoutException if the deadline is reached while waiting + */ + public StructuredTaskScope joinUntil(Instant deadline) + throws InterruptedException, TimeoutException + { + Duration timeout = Duration.between(Instant.now(), deadline); + implJoin(timeout); + return this; + } + + /** + * Cancel all tracked Future objects. + */ + private void cancelTrackedFutures() { + Set> futures = this.futures; + if (futures != null) { + futures.forEach(f -> f.cancel(false)); + } + } + + /** + * Interrupt all unfinished threads. + */ + private void implInterruptAll() { + flock.threads().forEach(t -> { + if (t != Thread.currentThread()) { + t.interrupt(); + } + }); + } + + @SuppressWarnings("removal") + private void interruptAll() { + if (System.getSecurityManager() == null) { + implInterruptAll(); + } else { + PrivilegedAction pa = () -> { + implInterruptAll(); + return null; + }; + AccessController.doPrivileged(pa); + } + } + + /** + * Shutdown the task scope if not already shutdown. Return true if this method + * shutdowns the task scope, false if already shutdown. + */ + private boolean implShutdown() { + if (state < SHUTDOWN) { + shutdownLock.lock(); + try { + if (state < SHUTDOWN) { + + // prevent new threads from starting + flock.shutdown(); + + // wakeup any threads waiting in Future::get + cancelTrackedFutures(); + + // interrupt all unfinished threads + interruptAll(); + + state = SHUTDOWN; + return true; + } + } finally { + shutdownLock.unlock(); + } + } + assert state >= SHUTDOWN; + return false; + } + + /** + * Shut down the task scope without closing it. Shutting down a task scope prevents + * new threads from starting, interrupts all unfinished threads, and causes the + * {@link #join() join} method to wakeup. Shutdown is useful for cases where the + * results of unfinished subtasks are no longer needed. + * + *

More specifically, this method: + *

    + *
  • {@linkplain Future#cancel(boolean) Cancels} the tasks that have threads + * {@linkplain Future#get() waiting} on a result so that the waiting threads wakeup. + *
  • {@linkplain Thread#interrupt() Interrupts} all unfinished threads in the + * task scope (except the current thread). + *
  • Wakes up the owner if it is waiting in {@link #join()} or {@link + * #joinUntil(Instant)}. If the owner is not waiting then its next call to {@code + * join} or {@code joinUntil} will return immediately. + *
+ * + *

When this method completes then the {@code Future} objects for all tasks will + * be {@linkplain Future#isDone() done}, normally or abnormally. There may still + * be threads that have not finished because they are executing code that did not + * respond (or respond promptly) to thread interrupt. This method does not wait + * for these threads. When the owner invokes the {@link #close() close} method + * to close the task scope then it will wait for the remaining threads to finish. + * + *

This method may only be invoked by the task scope owner or threads contained + * in the task scope. + * + * @throws IllegalStateException if this task scope is closed + * @throws WrongThreadException if the current thread is not the owner or + * a thread contained in the task scope + */ + public void shutdown() { + ensureOwnerOrContainsThread(); + if (state == CLOSED) + throw new IllegalStateException("Task scope is closed"); + if (implShutdown()) + flock.wakeup(); + } + + /** + * Closes this task scope. + * + *

This method first shuts down the task scope (as if by invoking the {@link + * #shutdown() shutdown} method). It then waits for the threads executing any + * unfinished tasks to finish. If interrupted then this method will continue to + * wait for the threads to finish before completing with the interrupt status set. + * + *

This method may only be invoked by the task scope owner. If the task scope + * is already closed then the owner invoking this method has no effect. + * + *

A {@code StructuredTaskScope} is intended to be used in a structured + * manner. If this method is called to close a task scope before nested task + * scopes are closed then it closes the underlying construct of each nested task scope + * (in the reverse order that they were created in), closes this task scope, and then + * throws {@link StructureViolationException}. + * If a thread terminates without first closing task scopes that it owns then + * termination will cause the underlying construct of each of its open tasks scopes to + * be closed. Closing is performed in the reverse order that the task scopes were + * created in. Thread termination may therefore be delayed when the owner has to wait + * for threads forked in these task scopes to finish. + * + * @throws IllegalStateException thrown after closing the task scope if the owner + * did not invoke join after forking + * @throws WrongThreadException if the current thread is not the owner + * @throws StructureViolationException if a structure violation was detected + */ + @Override + public void close() { + ensureOwner(); + if (state == CLOSED) + return; + + try { + implShutdown(); + flock.close(); + } finally { + state = CLOSED; + } + + if (needJoin) { + throw new IllegalStateException("Owner did not invoke join or joinUntil after fork"); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + String name = flock.name(); + if (name != null) { + sb.append(name); + sb.append('/'); + } + sb.append(Objects.toIdentityString(this)); + int s = state; + if (s == CLOSED) + sb.append("/closed"); + else if (s == SHUTDOWN) + sb.append("/shutdown"); + return sb.toString(); + } + + /** + * The Future implementation returned by the fork methods. Most methods are + * overridden to support cancellation when the task scope is shutdown. + * The blocking get methods register the Future with the task scope so that they + * are cancelled when the task scope shuts down. + */ + private static final class FutureImpl extends FutureTask { + private final StructuredTaskScope scope; + + @SuppressWarnings("unchecked") + FutureImpl(StructuredTaskScope scope, Callable task) { + super((Callable) task); + this.scope = (StructuredTaskScope) scope; + } + + @Override + protected void done() { + if (!scope.isShutdown()) { + scope.handleComplete(this); + } + } + + private void cancelIfShutdown() { + if (scope.isShutdown() && !super.isDone()) { + super.cancel(false); + } + } + + @Override + public boolean isDone() { + cancelIfShutdown(); + return super.isDone(); + } + + @Override + public boolean isCancelled() { + cancelIfShutdown(); + return super.isCancelled(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + scope.ensureOwnerOrContainsThread(); + cancelIfShutdown(); + return super.cancel(mayInterruptIfRunning); + } + + @Override + public V get() throws InterruptedException, ExecutionException { + if (super.isDone()) + return super.get(); + scope.track(this); + try { + cancelIfShutdown(); + return super.get(); + } finally { + scope.untrack(this); + } + } + + @Override + public V get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + Objects.requireNonNull(unit); + if (super.isDone()) + return super.get(); + scope.track(this); + try { + cancelIfShutdown(); + return super.get(timeout, unit); + } finally { + scope.untrack(this); + } + } + + @Override + public V resultNow() { + cancelIfShutdown(); + return super.resultNow(); + } + + @Override + public Throwable exceptionNow() { + cancelIfShutdown(); + return super.exceptionNow(); + } + + @Override + public State state() { + cancelIfShutdown(); + return super.state(); + } + + @Override + public String toString() { + cancelIfShutdown(); + return super.toString(); + } + } + + /** + * Maps a Future.State to an int that can be compared. + * RUNNING < CANCELLED < FAILED < SUCCESS. + */ + private static int futureStateToInt(Future.State s) { + return switch (s) { + case RUNNING -> 0; + case CANCELLED -> 1; + case FAILED -> 2; + case SUCCESS -> 3; + }; + } + + // RUNNING < CANCELLED < FAILED < SUCCESS + private static final Comparator FUTURE_STATE_COMPARATOR = + Comparator.comparingInt(StructuredTaskScope::futureStateToInt); + + /** + * A {@code StructuredTaskScope} that captures the result of the first subtask to + * complete successfully. Once captured, it invokes the {@linkplain #shutdown() shutdown} + * method to interrupt unfinished threads and wakeup the owner. The policy + * implemented by this class is intended for cases where the result of any subtask + * will do ("invoke any") and where the results of other unfinished subtask are no + * longer needed. + * + *

Unless otherwise specified, passing a {@code null} argument to a method + * in this class will cause a {@link NullPointerException} to be thrown. + * + * @param the result type + * @since 19 + */ + public static final class ShutdownOnSuccess extends StructuredTaskScope { + private static final VarHandle FUTURE; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + FUTURE = l.findVarHandle(ShutdownOnSuccess.class, "future", Future.class); + } catch (Exception e) { + throw new InternalError(e); + } + } + private volatile Future future; + + /** + * Constructs a new {@code ShutdownOnSuccess} with the given name and thread factory. + * The task scope is optionally named for the purposes of monitoring and management. + * The thread factory is used to {@link ThreadFactory#newThread(Runnable) create} + * threads when tasks are {@linkplain #fork(Callable) forked}. The task scope is + * owned by the current thread. + * + * @param name the name of the task scope, can be null + * @param factory the thread factory + */ + public ShutdownOnSuccess(String name, ThreadFactory factory) { + super(name, factory); + } + + /** + * Constructs a new unnamed {@code ShutdownOnSuccess} that creates virtual threads. + * + *

This constructor is equivalent to invoking the 2-arg constructor with a + * name of {@code null} and a thread factory that creates virtual threads. + */ + public ShutdownOnSuccess() { + super(null, FactoryHolder.VIRTUAL_THREAD_FACTORY); + } + + /** + * Shut down the given task scope when invoked for the first time with a {@code + * Future} for a task that completed with a result. + * + * @param future the completed task + * @see #shutdown() + * @see Future.State#SUCCESS + */ + @Override + protected void handleComplete(Future future) { + Future.State state = future.state(); + if (state == Future.State.RUNNING) { + throw new IllegalArgumentException("Task is not completed"); + } + + Future f; + while (((f = this.future) == null) + || FUTURE_STATE_COMPARATOR.compare(f.state(), state) < 0) { + if (FUTURE.compareAndSet(this, f, future)) { + if (state == Future.State.SUCCESS) + shutdown(); + break; + } + } + } + + /** + * {@inheritDoc} + * @return this task scope + * @throws IllegalStateException {@inheritDoc} + * @throws WrongThreadException {@inheritDoc} + */ + @Override + public ShutdownOnSuccess join() throws InterruptedException { + super.join(); + return this; + } + + /** + * {@inheritDoc} + * @return this task scope + * @throws IllegalStateException {@inheritDoc} + * @throws WrongThreadException {@inheritDoc} + */ + @Override + public ShutdownOnSuccess joinUntil(Instant deadline) + throws InterruptedException, TimeoutException + { + super.joinUntil(deadline); + return this; + } + + /** + * {@return the result of the first subtask that completed with a result} + * + *

When no subtask completed with a result but a task completed with an + * exception then {@code ExecutionException} is thrown with the exception as the + * {@linkplain Throwable#getCause() cause}. If only cancelled subtasks were + * notified to the {@code handleComplete} method then {@code CancellationException} + * is thrown. + * + * @apiNote This method is intended to be invoked by the task scope owner after it + * has invoked {@link #join() join} (or {@link #joinUntil(Instant) joinUntil}). + * A future release may add enforcement to prevent the method being called by + * other threads or before joining. + * + * @throws ExecutionException if no subtasks completed with a result but a subtask + * completed with an exception + * @throws CancellationException if all subtasks were cancelled + * @throws IllegalStateException if the handle method was not invoked with a + * completed subtask + */ + public T result() throws ExecutionException { + Future f = future; + if (f == null) { + throw new IllegalStateException("No completed subtasks"); + } + return switch (f.state()) { + case SUCCESS -> f.resultNow(); + case FAILED -> throw new ExecutionException(f.exceptionNow()); + case CANCELLED -> throw new CancellationException(); + default -> throw new InternalError("Unexpected state: " + f); + }; + + } + + /** + * Returns the result of the first subtask that completed with a result, otherwise + * throws an exception produced by the given exception supplying function. + * + *

When no subtask completed with a result but a subtask completed with an + * exception then the exception supplying function is invoked with the exception. + * If only cancelled subtasks were notified to the {@code handleComplete} method + * then the exception supplying function is invoked with a {@code CancellationException}. + * + * @apiNote This method is intended to be invoked by the task scope owner after it + * has invoked {@link #join() join} (or {@link #joinUntil(Instant) joinUntil}). + * A future release may add enforcement to prevent the method being called by + * other threads or before joining. + * + * @param esf the exception supplying function + * @param type of the exception to be thrown + * @return the result of the first subtask that completed with a result + * @throws X if no subtask completed with a result + * @throws IllegalStateException if the handle method was not invoked with a + * completed subtask + */ + public T result(Function esf) throws X { + Objects.requireNonNull(esf); + Future f = future; + if (f == null) { + throw new IllegalStateException("No completed subtasks"); + } + Future.State state = f.state(); + if (state == Future.State.SUCCESS) { + return f.resultNow(); + } else { + Throwable throwable = (state == Future.State.FAILED) + ? f.exceptionNow() + : new CancellationException(); + X ex = esf.apply(throwable); + Objects.requireNonNull(ex, "esf returned null"); + throw ex; + } + } + } + + /** + * A {@code StructuredTaskScope} that captures the exception of the first subtask to + * complete abnormally. Once captured, it invokes the {@linkplain #shutdown() shutdown} + * method to interrupt unfinished threads and wakeup the owner. The policy implemented + * by this class is intended for cases where the results for all subtasks are required + * ("invoke all"); if any subtask fails then the results of other unfinished subtasks + * are no longer needed. + * + *

Unless otherwise specified, passing a {@code null} argument to a method + * in this class will cause a {@link NullPointerException} to be thrown. + * + * @since 19 + */ + public static final class ShutdownOnFailure extends StructuredTaskScope { + private static final VarHandle FUTURE; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + FUTURE = l.findVarHandle(ShutdownOnFailure.class, "future", Future.class); + } catch (Exception e) { + throw new InternalError(e); + } + } + private volatile Future future; + + /** + * Constructs a new {@code ShutdownOnFailure} with the given name and thread factory. + * The task scope is optionally named for the purposes of monitoring and management. + * The thread factory is used to {@link ThreadFactory#newThread(Runnable) create} + * threads when tasks are {@linkplain #fork(Callable) forked}. The task scope + * is owned by the current thread. + * + * @param name the name of the task scope, can be null + * @param factory the thread factory + */ + public ShutdownOnFailure(String name, ThreadFactory factory) { + super(name, factory); + } + + /** + * Constructs a new unnamed {@code ShutdownOnFailure} that creates virtual threads. + * + *

This constructor is equivalent to invoking the 2-arg constructor with a + * name of {@code null} and a thread factory that creates virtual threads. + */ + public ShutdownOnFailure() { + super(null, FactoryHolder.VIRTUAL_THREAD_FACTORY); + } + + /** + * Shut down the given task scope when invoked for the first time with a {@code + * Future} for a task that completed abnormally (exception or cancelled). + * + * @param future the completed task + * @see #shutdown() + * @see Future.State#FAILED + * @see Future.State#CANCELLED + */ + @Override + protected void handleComplete(Future future) { + Future.State state = future.state(); + if (state == Future.State.RUNNING) { + throw new IllegalArgumentException("Task is not completed"); + } else if (state == Future.State.SUCCESS) { + return; + } + + // A failed task overrides a cancelled task. + // The first failed or cancelled task causes the scope to shutdown. + Future f; + while (((f = this.future) == null) + || FUTURE_STATE_COMPARATOR.compare(f.state(), state) < 0) { + if (FUTURE.compareAndSet(this, f, future)) { + shutdown(); + break; + } + } + } + + /** + * {@inheritDoc} + * @return this task scope + * @throws IllegalStateException {@inheritDoc} + * @throws WrongThreadException {@inheritDoc} + */ + @Override + public ShutdownOnFailure join() throws InterruptedException { + super.join(); + return this; + } + + /** + * {@inheritDoc} + * @return this task scope + * @throws IllegalStateException {@inheritDoc} + * @throws WrongThreadException {@inheritDoc} + */ + @Override + public ShutdownOnFailure joinUntil(Instant deadline) + throws InterruptedException, TimeoutException + { + super.joinUntil(deadline); + return this; + } + + /** + * Returns the exception for the first subtask that completed with an exception. + * If no subtask completed with an exception but cancelled subtasks were notified + * to the {@code handleComplete} method then a {@code CancellationException} + * is returned. If no subtasks completed abnormally then an empty {@code Optional} + * is returned. + * + * @apiNote This method is intended to be invoked by the task scope owner after it + * has invoked {@link #join() join} (or {@link #joinUntil(Instant) joinUntil}). + * A future release may add enforcement to prevent the method being called by + * other threads or before joining. + * + * @return the exception for a subtask that completed abnormally or an empty + * optional if no subtasks completed abnormally + */ + public Optional exception() { + Future f = future; + if (f != null) { + Throwable throwable = (f.state() == Future.State.FAILED) + ? f.exceptionNow() + : new CancellationException(); + return Optional.of(throwable); + } else { + return Optional.empty(); + } + } + + /** + * Throws if a subtask completed abnormally. If any subtask completed with an + * exception then {@code ExecutionException} is thrown with the exception of the + * first subtask to fail as the {@linkplain Throwable#getCause() cause}. If no + * subtask completed with an exception but cancelled subtasks were notified to the + * {@code handleComplete} method then {@code CancellationException} is thrown. + * This method does nothing if no subtasks completed abnormally. + * + * @apiNote This method is intended to be invoked by the task scope owner after it + * has invoked {@link #join() join} (or {@link #joinUntil(Instant) joinUntil}). + * A future release may add enforcement to prevent the method being called by + * other threads or before joining. + * + * @throws ExecutionException if a subtask completed with an exception + * @throws CancellationException if no subtasks completed with an exception but + * subtasks were cancelled + */ + public void throwIfFailed() throws ExecutionException { + Future f = future; + if (f != null) { + if (f.state() == Future.State.FAILED) { + throw new ExecutionException(f.exceptionNow()); + } else { + throw new CancellationException(); + } + } + } + + /** + * Throws the exception produced by the given exception supplying function if + * a subtask completed abnormally. If any subtask completed with an exception then + * the function is invoked with the exception of the first subtask to fail. + * If no subtask completed with an exception but cancelled subtasks were notified + * to the {@code handleComplete} method then the function is called with a {@code + * CancellationException}. The exception returned by the function is thrown. + * This method does nothing if no subtasks completed abnormally. + * + * @apiNote This method is intended to be invoked by the task scope owner after it + * has invoked {@link #join() join} (or {@link #joinUntil(Instant) joinUntil}). + * A future release may add enforcement to prevent the method being called by + * other threads or before joining. + * + * @param esf the exception supplying function + * @param type of the exception to be thrown + * @throws X produced by the exception supplying function + */ + public + void throwIfFailed(Function esf) throws X { + Objects.requireNonNull(esf); + Future f = future; + if (f != null) { + Throwable throwable = (f.state() == Future.State.FAILED) + ? f.exceptionNow() + : new CancellationException(); + X ex = esf.apply(throwable); + Objects.requireNonNull(ex, "esf returned null"); + throw ex; + } + } + } + + /** + * Holder class for the virtual thread factory. It uses reflection to allow + * this class be compiled in an incubator module without also enabling preview + * features. + */ + private static class FactoryHolder { + static final ThreadFactory VIRTUAL_THREAD_FACTORY = virtualThreadFactory(); + + @SuppressWarnings("removal") + private static ThreadFactory virtualThreadFactory() { + PrivilegedAction pa = () -> { + try { + Method ofVirtualMethod = Thread.class.getDeclaredMethod("ofVirtual"); + Object virtualThreadBuilder = ofVirtualMethod.invoke(null); + Class ofVirtualClass = Class.forName("java.lang.Thread$Builder$OfVirtual"); + Method factoryMethod = ofVirtualClass.getMethod("factory"); + return (ThreadFactory) factoryMethod.invoke(virtualThreadBuilder); + } catch (Exception e) { + throw new InternalError(e); + } + }; + return AccessController.doPrivileged(pa); + } + } +} diff --git a/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/package-info.java b/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/package-info.java new file mode 100644 index 00000000000..b51a8c12a3b --- /dev/null +++ b/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022, 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. + */ + +/** + * Defines non-final APIs for concurrent programming. + * {@Incubating} + */ +package jdk.incubator.concurrent; diff --git a/src/jdk.incubator.concurrent/share/classes/module-info.java b/src/jdk.incubator.concurrent/share/classes/module-info.java new file mode 100644 index 00000000000..9245c50d29c --- /dev/null +++ b/src/jdk.incubator.concurrent/share/classes/module-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022, 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. + */ + +/** + * Defines non-final APIs for concurrent programming. + * {@Incubating} + * + * @moduleGraph + */ +module jdk.incubator.concurrent { + exports jdk.incubator.concurrent; +} + diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups index 7eea47c8405..4c921a34ce9 100644 --- a/test/jdk/TEST.groups +++ b/test/jdk/TEST.groups @@ -274,6 +274,7 @@ jdk_loom = \ java/util/concurrent \ java/net/vthread \ java/nio/channels/vthread \ + jdk/incubator/concurrent \ jdk/internal/misc/ThreadFlock \ jdk/internal/vm/Continuation \ jdk/jfr/threading @@ -313,6 +314,7 @@ jdk_other = \ javax/xml \ -javax/xml/crypto \ jdk/dynalink \ + jdk/incubator/concurrent \ jdk/internal/jline \ com/sun/jndi \ lib/testlibrary diff --git a/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/PreviewFeaturesNotEnabled.java b/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/PreviewFeaturesNotEnabled.java new file mode 100644 index 00000000000..c02d74d1ab9 --- /dev/null +++ b/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/PreviewFeaturesNotEnabled.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022, 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. + * + * 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. + */ + +/** + * @test + * @bug 8284199 + * @summary Test StructuredTaskScope without --enable-preview + * @modules jdk.incubator.concurrent + * @run testng/othervm PreviewFeaturesNotEnabled + */ + +import jdk.incubator.concurrent.StructuredTaskScope; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class PreviewFeaturesNotEnabled { + /** + * One-arg constructor needs --enable-preview. + */ + @Test + public void testUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, StructuredTaskScope::new); + } + + /** + * Two-arg constructor does not need --enable-preview. + */ + @Test + public void testNoUnsupportedOperationException() { + try (var scope = new StructuredTaskScope(null, Thread::new)) { + } + } +} diff --git a/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java b/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java new file mode 100644 index 00000000000..91094b5e470 --- /dev/null +++ b/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java @@ -0,0 +1,1248 @@ +/* + * Copyright (c) 2021, 2022, 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. + * + * 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. + */ + +/* + * @test + * @bug 8284199 + * @summary Basic tests for StructuredTaskScope + * @enablePreview + * @modules jdk.incubator.concurrent + * @run testng/othervm StructuredTaskScopeTest + */ + +import jdk.incubator.concurrent.StructuredTaskScope; +import jdk.incubator.concurrent.StructuredTaskScope.ShutdownOnSuccess; +import jdk.incubator.concurrent.StructuredTaskScope.ShutdownOnFailure; +import jdk.incubator.concurrent.StructureViolationException; +import java.time.Duration; +import java.io.IOException; +import java.time.Instant; +import java.util.NoSuchElementException; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class StructuredTaskScopeTest { + private ScheduledExecutorService scheduler; + + @BeforeClass + public void setUp() throws Exception { + ThreadFactory factory = (task) -> { + Thread thread = new Thread(task); + thread.setDaemon(true); + return thread; + }; + scheduler = Executors.newSingleThreadScheduledExecutor(factory); + } + + @AfterClass + public void tearDown() { + scheduler.shutdown(); + } + + /** + * A provider of ThreadFactory objects for tests. + */ + @DataProvider + public Object[][] factories() { + var defaultThreadFactory = Executors.defaultThreadFactory(); + var virtualThreadFactory = Thread.ofVirtual().factory(); + return new Object[][] { + { defaultThreadFactory, }, + { virtualThreadFactory, }, + }; + } + + /** + * Test that each fork creates a thread. + */ + @Test(dataProvider = "factories") + public void testFork1(ThreadFactory factory) throws Exception { + AtomicInteger count = new AtomicInteger(); + try (var scope = new StructuredTaskScope(null, factory)) { + for (int i = 0; i < 100; i++) { + scope.fork(() -> count.incrementAndGet()); + } + scope.join(); + } + assertTrue(count.get() == 100); + } + + /** + * Test that fork uses the specified thread factory. + */ + @Test(dataProvider = "factories") + public void testFork2(ThreadFactory factory) throws Exception { + AtomicInteger count = new AtomicInteger(); + ThreadFactory countingFactory = task -> { + count.incrementAndGet(); + return factory.newThread(task); + }; + try (var scope = new StructuredTaskScope(null, countingFactory)) { + for (int i = 0; i < 100; i++) { + scope.fork(() -> null); + } + scope.join(); + } + assertTrue(count.get() == 100); + } + + /** + * Test fork is confined to threads in the scope "tree". + */ + @Test(dataProvider = "factories") + public void testForkConfined(ThreadFactory factory) throws Exception { + try (var scope1 = new StructuredTaskScope(); + var scope2 = new StructuredTaskScope()) { + + // thread in scope1 cannot fork thread in scope2 + Future future1 = scope1.fork(() -> { + scope2.fork(() -> null).get(); + return null; + }); + Throwable ex = expectThrows(ExecutionException.class, future1::get); + assertTrue(ex.getCause() instanceof WrongThreadException); + + // thread in scope2 can fork thread in scope1 + Future future2 = scope2.fork(() -> { + scope1.fork(() -> null).get(); + return null; + }); + future2.get(); + assertTrue(future2.resultNow() == null); + + // random thread cannot fork + try (var pool = Executors.newCachedThreadPool(factory)) { + Future future = pool.submit(() -> { + scope1.fork(() -> null); + return null; + }); + ex = expectThrows(ExecutionException.class, future::get); + assertTrue(ex.getCause() instanceof WrongThreadException); + } + + scope2.join(); + scope1.join(); + } + } + + /** + * Test fork when scope is shutdown. + */ + @Test(dataProvider = "factories") + public void testForkAfterShutdown(ThreadFactory factory) throws Exception { + AtomicInteger count = new AtomicInteger(); + try (var scope = new StructuredTaskScope(null, factory)) { + scope.shutdown(); + Future future = scope.fork(() -> { + count.incrementAndGet(); + return "foo"; + }); + assertTrue(future.isCancelled()); + scope.join(); + } + assertTrue(count.get() == 0); // check that task did not run. + } + + /** + * Test fork when scope is closed. + */ + @Test(dataProvider = "factories") + public void testForkAfterClose(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + scope.join(); + scope.close(); + assertThrows(IllegalStateException.class, () -> scope.fork(() -> null)); + } + } + + /** + * Test fork when the thread factory rejects creating a thread. + */ + @Test + public void testForkReject() throws Exception { + ThreadFactory factory = task -> null; + try (var scope = new StructuredTaskScope(null, factory)) { + assertThrows(RejectedExecutionException.class, () -> scope.fork(() -> null)); + scope.join(); + } + } + + /** + * A StructuredTaskScope that collects all Future objects notified to the + * handleComplete method. + */ + private static class CollectAll extends StructuredTaskScope { + private final List> futures = new CopyOnWriteArrayList<>(); + + CollectAll(ThreadFactory factory) { + super(null, factory); + } + + @Override + protected void handleComplete(Future future) { + assertTrue(future.isDone()); + futures.add(future); + } + + Stream> futures() { + return futures.stream(); + } + + Set> futuresAsSet() { + return futures.stream().collect(Collectors.toSet()); + } + } + + /** + * Test that handleComplete method is invoked for tasks that complete normally + * and abnormally. + */ + @Test(dataProvider = "factories") + public void testHandleComplete1(ThreadFactory factory) throws Exception { + try (var scope = new CollectAll(factory)) { + + // completes normally + Future future1 = scope.fork(() -> "foo"); + + // completes with exception + Future future2 = scope.fork(() -> { throw new FooException(); }); + + // cancelled + Future future3 = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }); + future3.cancel(true); + + scope.join(); + + Set> futures = scope.futuresAsSet(); + assertEquals(futures, Set.of(future1, future2, future3)); + } + } + + /** + * Test that the handeComplete method is not invoked after the scope has been shutdown. + */ + @Test(dataProvider = "factories") + public void testHandleComplete2(ThreadFactory factory) throws Exception { + try (var scope = new CollectAll(factory)) { + + var latch = new CountDownLatch(1); + + // start task that does not respond to interrupt + Future future1 = scope.fork(() -> { + boolean done = false; + while (!done) { + try { + latch.await(); + done = true; + } catch (InterruptedException e) { } + } + return null; + }); + + // start a second task to shutdown the scope after 500ms + Future future2 = scope.fork(() -> { + Thread.sleep(Duration.ofMillis(500)); + scope.shutdown(); + return null; + }); + + scope.join(); + + // let task finish + latch.countDown(); + + // handleComplete should not have been called + assertTrue(future1.isDone()); + assertTrue(scope.futures().count() == 0L); + } + } + + /** + * Test join with no threads. + */ + @Test + public void testJoinWithNoThreads() throws Exception { + try (var scope = new StructuredTaskScope()) { + scope.join(); + } + } + + /** + * Test join with threads running. + */ + @Test(dataProvider = "factories") + public void testJoinWithThreads(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofMillis(500)); + return "foo"; + }); + scope.join(); + assertEquals(future.resultNow(), "foo"); + } + } + + /** + * Test join is owner confined. + */ + @Test(dataProvider = "factories") + public void testJoinConfined(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope()) { + // attempt to join on thread in scope + Future future1 = scope.fork(() -> { + scope.join(); + return null; + }); + Throwable ex = expectThrows(ExecutionException.class, future1::get); + assertTrue(ex.getCause() instanceof WrongThreadException); + + // random thread cannot join + try (var pool = Executors.newCachedThreadPool(factory)) { + Future future2 = pool.submit(() -> { + scope.join(); + return null; + }); + ex = expectThrows(ExecutionException.class, future2::get); + assertTrue(ex.getCause() instanceof WrongThreadException); + } + + scope.join(); + } + } + + /** + * Test join with interrupt status set. + */ + @Test(dataProvider = "factories") + public void testInterruptJoin1(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofSeconds(3)); + return "foo"; + }); + + // join should throw + Thread.currentThread().interrupt(); + try { + scope.join(); + fail(); + } catch (InterruptedException expected) { + assertFalse(Thread.interrupted()); // interrupt status should be clear + } + + // join should complete + scope.join(); + assertEquals(future.resultNow(), "foo"); + } + } + + /** + * Test interrupt of thread blocked in join. + */ + @Test(dataProvider = "factories") + public void testInterruptJoin2(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofSeconds(3)); + return "foo"; + }); + + // join should throw + scheduleInterrupt(Thread.currentThread(), Duration.ofMillis(500)); + try { + scope.join(); + fail(); + } catch (InterruptedException expected) { + assertFalse(Thread.interrupted()); // interrupt status should be clear + } + + // join should complete + scope.join(); + assertEquals(future.resultNow(), "foo"); + } + } + + /** + * Test join when scope is already shutdown. + */ + @Test(dataProvider = "factories") + public void testJoinWithShutdown1(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return "foo"; + }); + scope.shutdown(); // interrupts task + scope.join(); + + // task should have completed abnormally + assertTrue(future.isDone() && future.state() != Future.State.SUCCESS); + } + } + + /** + * Test shutdown when owner is blocked in join. + */ + @Test(dataProvider = "factories") + public void testJoinWithShutdown2(ThreadFactory factory) throws Exception { + class MyScope extends StructuredTaskScope { + MyScope(ThreadFactory factory) { + super(null, factory); + } + @Override + protected void handleComplete(Future future) { + shutdown(); + } + } + + try (var scope = new MyScope(factory)) { + Future future1 = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return "foo"; + }); + Future future2 = scope.fork(() -> { + Thread.sleep(Duration.ofMillis(500)); + return null; + }); + scope.join(); + + // task1 should have completed abnormally + assertTrue(future1.isDone() && future1.state() != Future.State.SUCCESS); + + // task2 should have completed normally + assertTrue(future2.isDone() && future2.state() == Future.State.SUCCESS); + } + } + + /** + * Test join after scope is shutdown. + */ + @Test + public void testJoinAfterShutdown() throws Exception { + try (var scope = new StructuredTaskScope()) { + scope.shutdown(); + scope.join(); + } + } + + /** + * Test join after scope is closed. + */ + @Test + public void testJoinAfterClose() throws Exception { + try (var scope = new StructuredTaskScope()) { + scope.join(); + scope.close(); + assertThrows(IllegalStateException.class, () -> scope.join()); + assertThrows(IllegalStateException.class, () -> scope.joinUntil(Instant.now())); + } + } + + /** + * Test joinUntil, threads finish before deadline expires. + */ + @Test(dataProvider = "factories") + public void testJoinUntil1(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Future future = scope.fork(() -> { + try { + Thread.sleep(Duration.ofSeconds(2)); + } catch (InterruptedException e) { } + return null; + }); + + long startMillis = millisTime(); + scope.joinUntil(Instant.now().plusSeconds(30)); + assertTrue(future.isDone() && future.resultNow() == null); + expectDuration(startMillis, /*min*/1900, /*max*/20_000); + } + } + + /** + * Test joinUntil, deadline expires before threads finish. + */ + @Test(dataProvider = "factories") + public void testJoinUntil2(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Future future = scope.fork(() -> { + try { + Thread.sleep(Duration.ofSeconds(30)); + } catch (InterruptedException e) { } + return null; + }); + + long startMillis = millisTime(); + try { + scope.joinUntil(Instant.now().plusSeconds(2)); + } catch (TimeoutException e) { + expectDuration(startMillis, /*min*/1900, /*max*/20_000); + } + assertFalse(future.isDone()); + } + } + + /** + * Test joinUntil many times. + */ + @Test(dataProvider = "factories") + public void testJoinUntil3(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Future future = scope.fork(() -> { + try { + Thread.sleep(Duration.ofSeconds(30)); + } catch (InterruptedException e) { } + return null; + }); + + try { + for (int i = 0; i < 3; i++) { + try { + scope.joinUntil(Instant.now().plusSeconds(1)); + fail(); + } catch (TimeoutException expected) { + assertFalse(future.isDone()); + } + } + } finally { + future.cancel(true); + } + } + } + + /** + * Test joinUntil with a deadline that has already expired. + */ + @Test(dataProvider = "factories") + public void testJoinUntil4(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Future future = scope.fork(() -> { + try { + Thread.sleep(Duration.ofSeconds(30)); + } catch (InterruptedException e) { } + return null; + }); + + try { + + // now + try { + scope.joinUntil(Instant.now()); + fail(); + } catch (TimeoutException expected) { + assertFalse(future.isDone()); + } + + // in the past + try { + scope.joinUntil(Instant.now().minusSeconds(1)); + fail(); + } catch (TimeoutException expected) { + assertFalse(future.isDone()); + } + + } finally { + future.cancel(true); + } + } + } + + /** + * Test joinUntil with interrupt status set. + */ + @Test(dataProvider = "factories") + public void testInterruptJoinUntil1(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofSeconds(3)); + return "foo"; + }); + + // join should throw + Thread.currentThread().interrupt(); + try { + scope.joinUntil(Instant.now().plusSeconds(10)); + fail(); + } catch (InterruptedException expected) { + assertFalse(Thread.interrupted()); // interrupt status should be clear + } + + // join should complete + scope.join(); + assertEquals(future.resultNow(), "foo"); + } + } + + /** + * Test interrupt of thread blocked in joinUntil + */ + @Test(dataProvider = "factories") + public void testInterruptJoinUntil2(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofSeconds(3)); + return "foo"; + }); + + // join should throw + scheduleInterrupt(Thread.currentThread(), Duration.ofMillis(500)); + try { + scope.joinUntil(Instant.now().plusSeconds(10)); + fail(); + } catch (InterruptedException expected) { + assertFalse(Thread.interrupted()); // interrupt status should be clear + } + + // join should complete + scope.join(); + assertEquals(future.resultNow(), "foo"); + } + } + + /** + * Test shutdown after scope is closed. + */ + public void testShutdownAfterClose() throws Exception { + try (var scope = new StructuredTaskScope()) { + scope.join(); + scope.close(); + assertThrows(IllegalStateException.class, () -> scope.shutdown()); + } + } + + /** + * Test shutdown is confined to threads in the scope "tree". + */ + @Test(dataProvider = "factories") + public void testShutdownConfined(ThreadFactory factory) throws Exception { + try (var scope1 = new StructuredTaskScope(); + var scope2 = new StructuredTaskScope()) { + + // random thread cannot shutdown + try (var pool = Executors.newCachedThreadPool(factory)) { + Future future = pool.submit(() -> { + scope1.shutdown(); + return null; + }); + Throwable ex = expectThrows(ExecutionException.class, future::get); + assertTrue(ex.getCause() instanceof WrongThreadException); + } + + // thread in scope1 cannot shutdown scope2 + Future future1 = scope1.fork(() -> { + scope2.shutdown(); + return null; + }); + Throwable ex = expectThrows(ExecutionException.class, future1::get); + assertTrue(ex.getCause() instanceof WrongThreadException); + + // thread in scope2 can shutdown scope1 + Future future2 = scope2.fork(() -> { + scope1.shutdown(); + return null; + }); + future2.get(); + assertTrue(future2.resultNow() == null); + + scope2.join(); + scope1.join(); + } + } + + /** + * Test close without join, no threads forked. + */ + public void testCloseWithoutJoin1() { + try (var scope = new StructuredTaskScope()) { + // do nothing + } + } + + /** + * Test close without join, threads forked. + */ + @Test(dataProvider = "factories") + public void testCloseWithoutJoin2(ThreadFactory factory) { + try (var scope = new StructuredTaskScope(null, factory)) { + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }); + assertThrows(IllegalStateException.class, scope::close); + assertTrue(future.isDone() && future.exceptionNow() != null); + } + } + + /** + * Test close with threads forked after join. + */ + @Test(dataProvider = "factories") + public void testCloseWithoutJoin3(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + scope.fork(() -> "foo"); + scope.join(); + + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }); + assertThrows(IllegalStateException.class, scope::close); + assertTrue(future.isDone() && future.exceptionNow() != null); + } + } + + /** + * Test close is owner confined. + */ + @Test(dataProvider = "factories") + public void testCloseConfined(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope()) { + // attempt to close on thread in scope + Future future1 = scope.fork(() -> { + scope.close(); + return null; + }); + Throwable ex = expectThrows(ExecutionException.class, future1::get); + assertTrue(ex.getCause() instanceof WrongThreadException); + + // random thread cannot close scope + try (var pool = Executors.newCachedThreadPool(factory)) { + Future future2 = pool.submit(() -> { + scope.close(); + return null; + }); + ex = expectThrows(ExecutionException.class, future2::get); + assertTrue(ex.getCause() instanceof WrongThreadException); + } + + scope.join(); + } + } + + /** + * Test close with interrupt status set. + */ + @Test(dataProvider = "factories") + public void testInterruptClose1(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + var latch = new CountDownLatch(1); + + // start task that does not respond to interrupt + scope.fork(() -> { + boolean done = false; + while (!done) { + try { + latch.await(); + done = true; + } catch (InterruptedException e) { } + } + return null; + }); + + scope.shutdown(); + scope.join(); + + // release task after a delay + scheduler.schedule(latch::countDown, 1, TimeUnit.SECONDS); + + // invoke close with interrupt status set + Thread.currentThread().interrupt(); + try { + scope.close(); + } finally { + assertTrue(Thread.interrupted()); // clear interrupt status + } + } + } + + /** + * Test interrupting thread waiting in close. + */ + @Test(dataProvider = "factories") + public void testInterruptClose2(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + var latch = new CountDownLatch(1); + + // start task that does not respond to interrupt + scope.fork(() -> { + boolean done = false; + while (!done) { + try { + latch.await(); + done = true; + } catch (InterruptedException e) { } + } + return null; + }); + + scope.shutdown(); + scope.join(); + + // release task after a delay + scheduleInterrupt(Thread.currentThread(), Duration.ofMillis(500)); + scheduler.schedule(latch::countDown, 3, TimeUnit.SECONDS); + try { + scope.close(); + } finally { + assertTrue(Thread.interrupted()); // clear interrupt status + } + } + } + + /** + * Test that closing an enclosing scope closes the thread flock of a + * nested scope. + */ + @Test + public void testStructureViolation1() throws Exception { + try (var scope1 = new StructuredTaskScope()) { + try (var scope2 = new StructuredTaskScope()) { + + // join + close enclosing scope + scope1.join(); + try { + scope1.close(); + fail(); + } catch (StructureViolationException expected) { } + + // underlying flock should be closed, fork should return a cancelled task + AtomicBoolean ran = new AtomicBoolean(); + Future future = scope2.fork(() -> { + ran.set(true); + return null; + }); + assertTrue(future.isCancelled()); + scope2.join(); + assertFalse(ran.get()); + } + } + } + + /** + * Test Future::get, task completes normally. + */ + @Test(dataProvider = "factories") + public void testFuture1(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofMillis(100)); + return "foo"; + }); + + assertEquals(future.get(), "foo"); + assertTrue(future.state() == Future.State.SUCCESS); + assertEquals(future.resultNow(), "foo"); + + scope.join(); + } + } + + /** + * Test Future::get, task completes with exception. + */ + @Test(dataProvider = "factories") + public void testFuture2(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofMillis(100)); + throw new FooException(); + }); + + Throwable ex = expectThrows(ExecutionException.class, future::get); + assertTrue(ex.getCause() instanceof FooException); + assertTrue(future.state() == Future.State.FAILED); + assertTrue(future.exceptionNow() instanceof FooException); + + scope.join(); + } + } + + /** + * Test Future::get, task is cancelled. + */ + @Test(dataProvider = "factories") + public void testFuture3(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }); + + // timed-get, should timeout + try { + future.get(100, TimeUnit.MICROSECONDS); + fail(); + } catch (TimeoutException expected) { } + + future.cancel(true); + assertThrows(CancellationException.class, future::get); + assertTrue(future.state() == Future.State.CANCELLED); + + scope.join(); + } + } + + /** + * Test scope shutdown with a thread blocked in Future::get. + */ + @Test(dataProvider = "factories") + public void testFutureWithShutdown(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + + // long running task + Future future = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }); + + // start a thread to wait in Future::get + AtomicBoolean waitDone = new AtomicBoolean(); + Thread waiter = Thread.startVirtualThread(() -> { + try { + future.get(); + } catch (ExecutionException | CancellationException e) { + waitDone.set(true); + } catch (InterruptedException e) { + System.out.println("waiter thread interrupted!"); + } + }); + + // shutdown scope + scope.shutdown(); + + // Future should be done and thread should be awakened + assertTrue(future.isDone()); + waiter.join(); + assertTrue(waitDone.get()); + + scope.join(); + } + } + + /** + * Test Future::cancel throws if invoked by a thread that is not in the tree. + */ + @Test(dataProvider = "factories") + public void testFutureCancelConfined(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope()) { + Future future1 = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return "foo"; + }); + + // random thread cannot cancel + try (var pool = Executors.newCachedThreadPool(factory)) { + Future future2 = pool.submit(() -> { + future1.cancel(true); + return null; + }); + Throwable ex = expectThrows(ExecutionException.class, future2::get); + assertTrue(ex.getCause() instanceof WrongThreadException); + } finally { + future1.cancel(true); + } + scope.join(); + } + } + + /** + * Test StructuredTaskScope::toString includes the scope name. + */ + @Test + public void testToString() throws Exception { + ThreadFactory factory = Thread.ofVirtual().factory(); + try (var scope = new StructuredTaskScope("xxx", factory)) { + // open + assertTrue(scope.toString().contains("xxx")); + + // shutdown + scope.shutdown(); + assertTrue(scope.toString().contains("xxx")); + + // closed + scope.join(); + scope.close(); + assertTrue(scope.toString().contains("xxx")); + } + } + + /** + * Test for NullPointerException. + */ + @Test + public void testNulls() throws Exception { + assertThrows(NullPointerException.class, () -> new StructuredTaskScope("", null)); + try (var scope = new StructuredTaskScope()) { + assertThrows(NullPointerException.class, () -> scope.fork(null)); + assertThrows(NullPointerException.class, () -> scope.joinUntil(null)); + } + + assertThrows(NullPointerException.class, () -> new ShutdownOnSuccess("", null)); + try (var scope = new ShutdownOnSuccess()) { + assertThrows(NullPointerException.class, () -> scope.fork(null)); + assertThrows(NullPointerException.class, () -> scope.joinUntil(null)); + assertThrows(NullPointerException.class, () -> scope.result(null)); + } + + assertThrows(NullPointerException.class, () -> new ShutdownOnFailure("", null)); + try (var scope = new ShutdownOnFailure()) { + assertThrows(NullPointerException.class, () -> scope.fork(null)); + assertThrows(NullPointerException.class, () -> scope.joinUntil(null)); + assertThrows(NullPointerException.class, () -> scope.throwIfFailed(null)); + } + } + + /** + * Test ShutdownOnSuccess with no completed tasks. + */ + @Test + public void testShutdownOnSuccess1() throws Exception { + try (var scope = new ShutdownOnSuccess()) { + assertThrows(IllegalStateException.class, () -> scope.result()); + assertThrows(IllegalStateException.class, () -> scope.result(e -> null)); + } + } + + /** + * Test ShutdownOnSuccess with tasks that completed normally. + */ + @Test + public void testShutdownOnSuccess2() throws Exception { + try (var scope = new ShutdownOnSuccess()) { + + // two tasks complete normally + scope.fork(() -> "foo"); + scope.join(); // ensures foo completes first + scope.fork(() -> "bar"); + scope.join(); + + assertEquals(scope.result(), "foo"); + assertEquals(scope.result(e -> null), "foo"); + } + } + + /** + * Test ShutdownOnSuccess with tasks that completed normally and abnormally. + */ + @Test + public void testShutdownOnSuccess3() throws Exception { + try (var scope = new ShutdownOnSuccess()) { + + // one task completes normally, the other with an exception + scope.fork(() -> "foo"); + scope.fork(() -> { throw new ArithmeticException(); }); + scope.join(); + + assertEquals(scope.result(), "foo"); + assertEquals(scope.result(e -> null), "foo"); + } + } + + /** + * Test ShutdownOnSuccess with a task that completed with an exception. + */ + @Test + public void testShutdownOnSuccess4() throws Exception { + try (var scope = new ShutdownOnSuccess()) { + + // tasks completes with exception + scope.fork(() -> { throw new ArithmeticException(); }); + scope.join(); + + Throwable ex = expectThrows(ExecutionException.class, () -> scope.result()); + assertTrue(ex.getCause() instanceof ArithmeticException); + + ex = expectThrows(FooException.class, () -> scope.result(e -> new FooException(e))); + assertTrue(ex.getCause() instanceof ArithmeticException); + } + } + + /** + * Test ShutdownOnSuccess with a cancelled task. + */ + @Test + public void testShutdownOnSuccess5() throws Exception { + try (var scope = new ShutdownOnSuccess()) { + + // cancelled task + var future = scope.fork(() -> { + Thread.sleep(60_000); + return null; + }); + future.cancel(false); + + scope.join(); + + assertThrows(CancellationException.class, () -> scope.result()); + Throwable ex = expectThrows(FooException.class, + () -> scope.result(e -> new FooException(e))); + assertTrue(ex.getCause() instanceof CancellationException); + } + } + + /** + * Test ShutdownOnFailure with no completed tasks. + */ + @Test + public void testShutdownOnFailure1() throws Throwable { + try (var scope = new ShutdownOnFailure()) { + assertTrue(scope.exception().isEmpty()); + scope.throwIfFailed(); + scope.throwIfFailed(e -> new FooException(e)); + } + } + + /** + * Test ShutdownOnFailure with tasks that completed normally. + */ + @Test + public void testShutdownOnFailure2() throws Throwable { + try (var scope = new ShutdownOnFailure()) { + scope.fork(() -> "foo"); + scope.fork(() -> "bar"); + scope.join(); + + // no exception + assertTrue(scope.exception().isEmpty()); + scope.throwIfFailed(); + scope.throwIfFailed(e -> new FooException(e)); + } + } + + /** + * Test ShutdownOnFailure with tasks that completed normally and abnormally. + */ + @Test + public void testShutdownOnFailure3() throws Throwable { + try (var scope = new ShutdownOnFailure()) { + + // one task completes normally, the other with an exception + scope.fork(() -> "foo"); + scope.fork(() -> { throw new ArithmeticException(); }); + scope.join(); + + Throwable ex = scope.exception().orElse(null); + assertTrue(ex instanceof ArithmeticException); + + ex = expectThrows(ExecutionException.class, () -> scope.throwIfFailed()); + assertTrue(ex.getCause() instanceof ArithmeticException); + + ex = expectThrows(FooException.class, + () -> scope.throwIfFailed(e -> new FooException(e))); + assertTrue(ex.getCause() instanceof ArithmeticException); + } + } + + /** + * Test ShutdownOnFailure with a cancelled task. + */ + @Test + public void testShutdownOnFailure4() throws Throwable { + try (var scope = new ShutdownOnFailure()) { + + var future = scope.fork(() -> { + Thread.sleep(60_000); + return null; + }); + future.cancel(false); + + scope.join(); + + Throwable ex = scope.exception().orElse(null); + assertTrue(ex instanceof CancellationException); + + assertThrows(CancellationException.class, () -> scope.throwIfFailed()); + + ex = expectThrows(FooException.class, + () -> scope.throwIfFailed(e -> new FooException(e))); + assertTrue(ex.getCause() instanceof CancellationException); + } + } + + /** + * A runtime exception for tests. + */ + private static class FooException extends RuntimeException { + FooException() { } + FooException(Throwable cause) { super(cause); } + } + + /** + * Schedules a thread to be interrupted after the given delay. + */ + private void scheduleInterrupt(Thread thread, Duration delay) { + long millis = delay.toMillis(); + scheduler.schedule(thread::interrupt, millis, TimeUnit.MILLISECONDS); + } + + /** + * Returns the current time in milliseconds. + */ + private static long millisTime() { + long now = System.nanoTime(); + return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS); + } + + /** + * Check the duration of a task + * @param start start time, in milliseconds + * @param min minimum expected duration, in milliseconds + * @param max maximum expected duration, in milliseconds + * @return the duration (now - start), in milliseconds + */ + private static long expectDuration(long start, long min, long max) { + long duration = millisTime() - start; + assertTrue(duration >= min, + "Duration " + duration + "ms, expected >= " + min + "ms"); + assertTrue(duration <= max, + "Duration " + duration + "ms, expected <= " + max + "ms"); + return duration; + } +} diff --git a/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java b/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java new file mode 100644 index 00000000000..be737170e83 --- /dev/null +++ b/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2022, 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. + * + * 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. + */ + +/* + * @test + * @bug 8284199 + * @summary Test thread dumps with StructuredTaskScope + * @enablePreview + * @modules jdk.incubator.concurrent + * @library /test/lib + * @run testng/othervm StructuredThreadDumpTest + */ + +import jdk.incubator.concurrent.StructuredTaskScope; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; +import java.util.stream.Stream; +import com.sun.management.HotSpotDiagnosticMXBean; +import com.sun.management.HotSpotDiagnosticMXBean.ThreadDumpFormat; +import jdk.test.lib.threaddump.ThreadDump; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class StructuredThreadDumpTest { + + /** + * Test that a thread dump with a tree of task scopes contains a thread grouping for + * each task scope. + */ + @Test + public void testTree() throws Exception { + ThreadFactory factory = Thread.ofVirtual().factory(); + try (var scope = new StructuredTaskScope<>("scope", factory)) { + Thread thread1 = fork(scope, "child-scope-A"); + Thread thread2 = fork(scope, "child-scope-B"); + try { + ThreadDump threadDump = threadDump(); + + // thread dump should have a thread container for each scope + var rootContainer = threadDump.rootThreadContainer(); + var container1 = threadDump.findThreadContainer("scope").orElseThrow(); + var container2 = threadDump.findThreadContainer("child-scope-A").orElseThrow(); + var container3 = threadDump.findThreadContainer("child-scope-B").orElseThrow(); + + // check parents + assertFalse(rootContainer.parent().isPresent()); + assertTrue(container1.parent().get() == rootContainer); + assertTrue(container2.parent().get() == container1); + assertTrue(container3.parent().get() == container1); + + // check owners + assertFalse(rootContainer.owner().isPresent()); + assertTrue(container1.owner().getAsLong() == Thread.currentThread().threadId()); + assertTrue(container2.owner().getAsLong() == thread1.threadId()); + assertTrue(container3.owner().getAsLong() == thread2.threadId()); + + // thread1 and threads2 should be in threads array of "scope" + container1.findThread(thread1.threadId()).orElseThrow(); + container1.findThread(thread2.threadId()).orElseThrow(); + + } finally { + scope.shutdown(); + scope.join(); + } + } + } + + /** + * Test that a thread dump with nested tasks scopes contains a thread grouping for + * each task scope. + */ + @Test + public void testNested() throws Exception { + ThreadFactory factory = Thread.ofVirtual().factory(); + try (var scope1 = new StructuredTaskScope<>("scope-A", factory)) { + Thread thread1 = fork(scope1); + + try (var scope2 = new StructuredTaskScope<>("scope-B", factory)) { + Thread thread2 = fork(scope2); + try { + ThreadDump threadDump = threadDump(); + + // thread dump should have a thread container for both scopes + var rootContainer = threadDump.rootThreadContainer(); + var container1 = threadDump.findThreadContainer("scope-A").orElseThrow(); + var container2 = threadDump.findThreadContainer("scope-B").orElseThrow(); + + // check parents + assertFalse(rootContainer.parent().isPresent()); + assertTrue(container1.parent().get() == rootContainer); + assertTrue(container2.parent().get() == container1); + + // check owners + long tid = Thread.currentThread().threadId(); + assertFalse(rootContainer.owner().isPresent()); + assertTrue(container1.owner().getAsLong() == tid); + assertTrue(container2.owner().getAsLong() == tid); + + // thread1 should be in threads array of "scope-A" + container1.findThread(thread1.threadId()).orElseThrow(); + + // thread2 should be in threads array of "scope-B" + container2.findThread(thread2.threadId()).orElseThrow(); + + } finally { + scope2.shutdown(); + scope2.join(); + } + } finally { + scope1.shutdown(); + scope1.join(); + } + } + } + + /** + * Generates a JSON formatted thread dump to a temporary file, prints it to standard + * output, parses the JSON text and returns a ThreadDump object for the thread dump. + */ + private static ThreadDump threadDump() throws IOException { + Path dir = Path.of(".").toAbsolutePath(); + Path file = Files.createTempFile(dir, "threadump", "json"); + Files.delete(file); + ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class) + .dumpThreads(file.toString(), ThreadDumpFormat.JSON); + + try (Stream stream = Files.lines(file)) { + stream.forEach(System.out::println); + } + + String jsonText = Files.readString(file); + return ThreadDump.parse(jsonText); + } + + /** + * Forks a subtask in the given scope that parks, returning the Thread that executes + * the subtask. + */ + private static Thread fork(StructuredTaskScope scope) throws Exception { + var ref = new AtomicReference(); + scope.fork(() -> { + ref.set(Thread.currentThread()); + LockSupport.park(); + return null; + }); + Thread thread; + while ((thread = ref.get()) == null) { + Thread.sleep(10); + } + return thread; + } + + /** + * Forks a subtask in the given scope. The subtask creates a new child scope with + * the given name, then parks. This method returns Thread that executes the subtask. + */ + private static Thread fork(StructuredTaskScope scope, + String childScopeName) throws Exception { + var ref = new AtomicReference(); + scope.fork(() -> { + ThreadFactory factory = Thread.ofVirtual().factory(); + try (var childScope = new StructuredTaskScope(childScopeName, factory)) { + ref.set(Thread.currentThread()); + LockSupport.park(); + } + return null; + }); + Thread thread; + while ((thread = ref.get()) == null) { + Thread.sleep(10); + } + return thread; + } + +} From 4839e2e046fb46b6b83cc9a18ae81cd5c2536936 Mon Sep 17 00:00:00 2001 From: Jie Fu Date: Sat, 4 Jun 2022 06:47:29 +0000 Subject: [PATCH 04/22] 8287830: gtest fails to compile after JDK-8287661 Reviewed-by: shade --- test/hotspot/gtest/utilities/test_bitMap.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/hotspot/gtest/utilities/test_bitMap.cpp b/test/hotspot/gtest/utilities/test_bitMap.cpp index 80404907e90..4c33f27c312 100644 --- a/test/hotspot/gtest/utilities/test_bitMap.cpp +++ b/test/hotspot/gtest/utilities/test_bitMap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022, 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 @@ -96,7 +96,7 @@ class BitMapTest { EXPECT_TRUE(map.is_same(map2)) << "With init_size " << init_size; } -#ifndef PRODUCT +#ifdef ASSERT static void testPrintOn(BitMap::idx_t size) { ResourceMark rm; @@ -165,7 +165,7 @@ TEST_VM(BitMap, reinitialize) { BitMapTest::testReinitialize(BitMapTest::BITMAP_SIZE); } -#ifndef PRODUCT +#ifdef ASSERT TEST_VM(BitMap, print_on) { BitMapTest::testPrintOn(0); From 7ab9b70f174fa2607f69e4c6c98e877ef7886d38 Mon Sep 17 00:00:00 2001 From: Pavel Rappo Date: Sat, 4 Jun 2022 15:55:43 +0000 Subject: [PATCH 05/22] 8287753: [spelling] close well-established compounds Reviewed-by: jjg --- .../share/classes/com/sun/source/tree/Tree.java | 4 ++-- .../com/sun/tools/javac/code/AnnoConstruct.java | 4 ++-- .../classes/com/sun/tools/javac/code/Flags.java | 4 ++-- .../com/sun/tools/javac/code/TypeAnnotations.java | 4 ++-- .../classes/com/sun/tools/javac/code/Types.java | 12 ++++++------ .../share/classes/com/sun/tools/javac/comp/Attr.java | 2 +- .../classes/com/sun/tools/javac/comp/Check.java | 2 +- .../classes/com/sun/tools/javac/comp/Resolve.java | 2 +- .../comp/dependencies/NewDependencyCollector.java | 6 +++--- .../internal/doclets/formats/html/HtmlLinkInfo.java | 2 +- .../doclets/formats/html/PackageWriterImpl.java | 4 ++-- .../internal/doclets/toolkit/ClassWriter.java | 2 +- .../internal/doclets/toolkit/DocletElement.java | 4 ++-- .../doclets/toolkit/builders/ClassBuilder.java | 2 +- .../toolkit/builders/PackageSummaryBuilder.java | 2 +- .../javadoc/internal/doclets/toolkit/util/Utils.java | 8 ++++---- .../doclets/toolkit/util/VisibleMemberTable.java | 12 ++++++------ .../jdk/javadoc/internal/tool/ElementsTable.java | 4 ++-- 18 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java index 6dd7bb64579..c4b71dc015c 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java @@ -613,13 +613,13 @@ public enum Kind { /** * Used for instances of {@link WildcardTree} representing - * an extends bounded wildcard type argument. + * an upper-bounded wildcard type argument. */ EXTENDS_WILDCARD(WildcardTree.class), /** * Used for instances of {@link WildcardTree} representing - * a super bounded wildcard type argument. + * a lower-bounded wildcard type argument. */ SUPER_WILDCARD(WildcardTree.class), diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/AnnoConstruct.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/AnnoConstruct.java index 2208594b280..3557af4ac7a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/AnnoConstruct.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/AnnoConstruct.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2022, 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 @@ -40,7 +40,7 @@ import com.sun.tools.javac.util.ListBuffer; /** - * Common super type for annotated constructs such as Types and Symbols. + * Common supertype for annotated constructs such as Types and Symbols. * * This class should *not* contain any fields since it would have a significant * impact on the javac memory footprint. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java index a8621853618..386f9eb0239 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2022, 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 @@ -179,7 +179,7 @@ public static EnumSet asFlagSet(long flags) { public static final int ANONCONSTR = 1<<29; //non-class members /** - * Flag to indicate the super classes of this ClassSymbol has been attributed. + * Flag to indicate the superclasses of this ClassSymbol has been attributed. */ public static final int SUPER_OWNER_ATTRIBUTED = 1<<29; //ClassSymbols diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java index ae80d86625e..f9e297fa8e1 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2022, 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 @@ -1362,7 +1362,7 @@ public void visitNewClass(JCNewClass tree) { scan(tree.typeargs); if (tree.def == null) { scan(tree.clazz); - } // else super type will already have been scanned in the context of the anonymous class. + } // else supertype will already have been scanned in the context of the anonymous class. scan(tree.args); // The class body will already be scanned. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java index f0b85ec2631..a6f7ca9778d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java @@ -2524,7 +2524,7 @@ public Type supertype(Type t) { public Type visitType(Type t, Void ignored) { // A note on wildcards: there is no good way to - // determine a supertype for a super bounded wildcard. + // determine a supertype for a lower-bounded wildcard. return Type.noType; } @@ -2786,17 +2786,17 @@ public Type visitErrorType(ErrorType t, Void ignored) { }; // - // + // /** - * Returns true iff the first signature is a sub - * signature of the other. This is not an equivalence + * Returns true iff the first signature is a subsignature + * of the other. This is not an equivalence * relation. * * @jls 8.4.2 Method Signature * @see #overrideEquivalent(Type t, Type s) * @param t first signature (possibly raw). * @param s second signature (could be subjected to erasure). - * @return true if t is a sub signature of s. + * @return true if t is a subsignature of s. */ public boolean isSubSignature(Type t, Type s) { return isSubSignature(t, s, true); @@ -2817,7 +2817,7 @@ public boolean isSubSignature(Type t, Type s, boolean strict) { * erasure). * @param s a signature (possible raw, could be subjected to * erasure). - * @return true if either argument is a sub signature of the other. + * @return true if either argument is a subsignature of the other. */ public boolean overrideEquivalent(Type t, Type s) { return hasSameArgs(t, s) || diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 81fa717da85..262ad9433c2 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -4555,7 +4555,7 @@ private Symbol selectSym(JCFieldAccess tree, case TYPEVAR: // Normally, site.getUpperBound() shouldn't be null. // It should only happen during memberEnter/attribBase - // when determining the super type which *must* be + // when determining the supertype which *must* be // done before attributing the type variables. In // other words, we are seeing this illegal program: // class B extends A {} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index 16386fe804d..9ac8da265df 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -4606,7 +4606,7 @@ private void checkCtorAccess(JCClassDecl tree, ClassSymbol c) { return ; // Don't try to recover } } - // Non-Serializable super class + // Non-Serializable superclass try { ClassSymbol supertype = ((ClassSymbol)(((DeclaredType)superClass).asElement())); for(var sym : supertype.getEnclosedElements()) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java index 28a5b0b14a5..21963ae8752 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java @@ -3778,7 +3778,7 @@ Symbol resolveSelf(DiagnosticPosition pos, types.asSuper(env.enclClass.type, c), env.enclClass.sym); } } - //find a direct super type that is a subtype of 'c' + //find a direct supertype that is a subtype of 'c' for (Type i : types.directSupertypes(env.enclClass.type)) { if (i.tsym.isSubClass(c, types) && i.tsym != c) { log.error(pos, diff --git a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/NewDependencyCollector.java b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/NewDependencyCollector.java index ccb13e3f265..916047f195a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/NewDependencyCollector.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/NewDependencyCollector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022, 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 @@ -158,8 +158,8 @@ private Map>> getDependencies(Context context, } // The completion dependency graph is not transitively closed for inheritance relations. - // For sjavac's purposes however, a class depends on it's super super type, so below we - // make sure that we include super types. + // For sjavac's purposes however, a class depends on its super-supertype, so below we + // make sure that we include supertypes. for (ClassSymbol cs : allSupertypes(cnode.getClassSymbol())) { if (isSymbolRelevant(cp, cs)) { fqDeps.add(cs.outermostClass().flatname.toString()); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkInfo.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkInfo.java index 2d4508c2290..bb9367b85cc 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkInfo.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlLinkInfo.java @@ -131,7 +131,7 @@ public enum Kind { EXECUTABLE_MEMBER_PARAM, /** - * Super interface links. + * Superinterface links. */ SUPER_INTERFACES, diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageWriterImpl.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageWriterImpl.java index d8fcb72bae2..53582e5bf4e 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageWriterImpl.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageWriterImpl.java @@ -153,7 +153,7 @@ private SortedSet filterClasses(SortedSet types) { private List findRelatedPackages() { String pkgName = packageElement.getQualifiedName().toString(); - // always add super package + // always add superpackage int lastdot = pkgName.lastIndexOf('.'); String pkgPrefix = lastdot > 0 ? pkgName.substring(0, lastdot) : null; List packages = new ArrayList<>( @@ -168,7 +168,7 @@ private List findRelatedPackages() { packages.addAll(subpackages); } - // only add sibling packages if there is a non-empty super package, we are beneath threshold, + // only add sibling packages if there is a non-empty superpackage, we are beneath threshold, // and number of siblings is beneath threshold as well if (hasSuperPackage && pkgPrefix != null && packages.size() <= MAX_SIBLING_PACKAGES) { Pattern siblingPattern = Pattern.compile(pkgPrefix.replace(".", "\\.") + "\\.\\w+"); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/ClassWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/ClassWriter.java index 3ac1f05bd26..a887e1f6572 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/ClassWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/ClassWriter.java @@ -64,7 +64,7 @@ public interface ClassWriter { void addParamInfo(Content target); /** - * Add all super interfaces if this is an interface. + * Add all superinterfaces if this is an interface. * * @param target the content to which the documentation will be added */ diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/DocletElement.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/DocletElement.java index 32da21cf6de..5b81f5bc88d 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/DocletElement.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/DocletElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022, 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 @@ -112,7 +112,7 @@ default A[] getAnnotationsByType(Class annotationType) Kind getSubKind(); /** - * Sub kind enums that this element supports. + * Subkind enums that this element supports. */ enum Kind { OVERVIEW, DOCFILE; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/ClassBuilder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/ClassBuilder.java index a1f1ba186a3..eee09cff359 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/ClassBuilder.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/ClassBuilder.java @@ -170,7 +170,7 @@ protected void buildParamInfo(Content target) { } /** - * If this is an interface, list all super interfaces. + * If this is an interface, list all superinterfaces. * * @param target the content to which the documentation will be added */ diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/PackageSummaryBuilder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/PackageSummaryBuilder.java index 4c65de4a32a..a56a4ec268d 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/PackageSummaryBuilder.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/PackageSummaryBuilder.java @@ -144,7 +144,7 @@ protected void buildSummary(Content packageContent) throws DocletException { } /** - * Builds a list of "nearby" packages (subpackages, super and sibling packages). + * Builds a list of "nearby" packages (subpackages, superpackages, and sibling packages). * * @param summariesList the list of summaries to which the summary will be added */ diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java index 864343ad93a..af3c5dc26b1 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java @@ -1102,9 +1102,9 @@ public TypeElement getFirstVisibleSuperClassAsTypeElement(TypeElement te) { } /** - * Given a class, return the closest visible super class. + * Given a class, return the closest visible superclass. * @param type the TypeMirror to be interrogated - * @return the closest visible super class. Return null if it cannot + * @return the closest visible superclass. Return null if it cannot * be found. */ public TypeMirror getFirstVisibleSuperClass(TypeMirror type) { @@ -1113,10 +1113,10 @@ public TypeMirror getFirstVisibleSuperClass(TypeMirror type) { /** - * Given a class, return the closest visible super class. + * Given a class, return the closest visible superclass. * * @param te the TypeElement to be interrogated - * @return the closest visible super class. Return null if it cannot + * @return the closest visible superclass. Return null if it cannot * be found. */ public TypeMirror getFirstVisibleSuperClass(TypeElement te) { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/VisibleMemberTable.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/VisibleMemberTable.java index f7b13dfc51e..8386806d903 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/VisibleMemberTable.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/VisibleMemberTable.java @@ -75,7 +75,7 @@ * Extra Members: these are members enclosed in an undocumented * package-private type element, and may not be linkable (or documented), * however, the members of such a type element may be documented, as if - * declared in the sub type, only if the enclosing type is not being + * declared in the subtype, only if the enclosing type is not being * documented by a filter such as -public, -protected, etc. *

* Visible Members: these are the members that are "visible" @@ -205,8 +205,8 @@ List getAllSuperinterfaces() { * sole {@code {@inheritDoc}} or devoid of any API comments. *

* b.The list may contain (extra) members, inherited by inaccessible - * super types, primarily package private types. These members are - * required to be documented in the subtype when the super type is + * supertypes, primarily package private types. These members are + * required to be documented in the subtype when the supertype is * not documented. * * @param kind the member kind @@ -307,12 +307,12 @@ public Set getVisibleTypeElements() { // Add this type element first. result.add(te); - // Add the super classes. + // Add the superclasses. allSuperclasses.stream() .map(vmt -> vmt.te) .forEach(result::add); - // ... and finally the sorted super interfaces. + // ... and finally the sorted superinterfaces. allSuperinterfaces.stream() .map(vmt -> vmt.te) .sorted(utils.comparators.makeGeneralPurposeComparator()) @@ -419,7 +419,7 @@ private void computeParents() { VisibleMemberTable vmt = mcache.getVisibleMemberTable(parent); allSuperclasses.add(vmt); allSuperclasses.addAll(vmt.getAllSuperclasses()); - // Add direct super interfaces of a super class, if any. + // Add direct superinterfaces of a superclass, if any. allSuperinterfaces.addAll(vmt.getAllSuperinterfaces()); parents.add(vmt); } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ElementsTable.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ElementsTable.java index 9d5fbad5bb9..4318b2ea404 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ElementsTable.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ElementsTable.java @@ -483,10 +483,10 @@ ElementsTable packages(Collection packageNames) { /** * Returns the aggregate set of included packages and specified - * sub packages. + * subpackages. * * @return the aggregate set of included packages and specified - * sub packages + * subpackages */ Iterable getPackagesToParse() throws IOException { List result = new ArrayList<>(); From 40118cb198535b758f2f4473b5be204795252b5f Mon Sep 17 00:00:00 2001 From: "Daniel D. Daugherty" Date: Sun, 5 Jun 2022 14:08:55 +0000 Subject: [PATCH 06/22] 8287837: ProblemList java/lang/ref/OOMEInReferenceHandler.java in -Xcomp Reviewed-by: rriggs --- test/jdk/ProblemList-Xcomp.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jdk/ProblemList-Xcomp.txt b/test/jdk/ProblemList-Xcomp.txt index df1b9da12b0..94283fd855b 100644 --- a/test/jdk/ProblemList-Xcomp.txt +++ b/test/jdk/ProblemList-Xcomp.txt @@ -29,3 +29,4 @@ java/lang/invoke/MethodHandles/CatchExceptionTest.java 8146623 generic-all java/lang/ref/ReferenceEnqueue.java 8284236 generic-all +java/lang/ref/OOMEInReferenceHandler.java 8066859 generic-all From 6b10b4cfc6cb042f33984e021d8985943c6c44a7 Mon Sep 17 00:00:00 2001 From: Nikita Gubarkov Date: Sun, 5 Jun 2022 15:34:47 +0000 Subject: [PATCH 07/22] 8287609: macOS: SIGSEGV at [CoreFoundation] CFArrayGetCount / sun.font.CFont.getTableBytesNative Reviewed-by: prr --- src/java.desktop/macosx/native/libawt_lwawt/font/AWTFont.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/java.desktop/macosx/native/libawt_lwawt/font/AWTFont.m b/src/java.desktop/macosx/native/libawt_lwawt/font/AWTFont.m index 360baab2b9a..5e1d6ba0cb1 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/font/AWTFont.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/font/AWTFont.m @@ -379,6 +379,9 @@ static OSStatus CreateFSRef(FSRef *myFSRefPtr, NSString *inPath) CTFontRef ctfont = (CTFontRef)nsFont; CFArrayRef tagsArray = CTFontCopyAvailableTables(ctfont, kCTFontTableOptionNoOptions); + if (tagsArray == NULL) { + return NULL; + } CFIndex numTags = CFArrayGetCount(tagsArray); for (i=0; i Date: Mon, 6 Jun 2022 00:37:54 +0000 Subject: [PATCH 08/22] 8283894: Intrinsify compress and expand bits on x86 Reviewed-by: psandoz, sviswanathan, jrose, kvn --- .../share/classes/java/lang/Integer.java | 4 +- .../share/classes/java/lang/Long.java | 4 +- .../gtest/opto/test_compress_expand_bits.cpp | 43 ++ .../intrinsics/TestBitShuffleOpers.java | 504 ++++++++++++++++++ test/jdk/ProblemList.txt | 1 - .../java/lang/CompressExpandSanityTest.java | 4 +- test/jdk/java/lang/CompressExpandTest.java | 1 + 7 files changed, 555 insertions(+), 6 deletions(-) create mode 100644 test/hotspot/gtest/opto/test_compress_expand_bits.cpp create mode 100644 test/hotspot/jtreg/compiler/intrinsics/TestBitShuffleOpers.java diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 13e69da6bb4..2d20b5a79d3 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -1839,7 +1839,7 @@ public static int reverse(int i) { * @see #expand * @since 19 */ - // @IntrinsicCandidate + @IntrinsicCandidate public static int compress(int i, int mask) { // See Hacker's Delight (2nd ed) section 7.4 Compress, or Generalized Extract @@ -1927,7 +1927,7 @@ public static int compress(int i, int mask) { * @see #compress * @since 19 */ - // @IntrinsicCandidate + @IntrinsicCandidate public static int expand(int i, int mask) { // Save original mask int originalMask = mask; diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 5e44cf5c112..477b414ec94 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -1978,7 +1978,7 @@ public static long reverse(long i) { * @see #expand * @since 19 */ - // @IntrinsicCandidate + @IntrinsicCandidate public static long compress(long i, long mask) { // See Hacker's Delight (2nd ed) section 7.4 Compress, or Generalized Extract @@ -2066,7 +2066,7 @@ public static long compress(long i, long mask) { * @see #compress * @since 19 */ - // @IntrinsicCandidate + @IntrinsicCandidate public static long expand(long i, long mask) { // Save original mask long originalMask = mask; diff --git a/test/hotspot/gtest/opto/test_compress_expand_bits.cpp b/test/hotspot/gtest/opto/test_compress_expand_bits.cpp new file mode 100644 index 00000000000..f5017418303 --- /dev/null +++ b/test/hotspot/gtest/opto/test_compress_expand_bits.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022, 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. + * + * 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. + * + */ + +#include "precompiled.hpp" +#include "opto/intrinsicnode.hpp" +#include "unittest.hpp" + +TEST_VM(opto, compress_expand_bits) { + ASSERT_EQ(CompressBitsNode::compress_bits(-4LL, -1LL, 64), -4LL); + ASSERT_EQ(CompressBitsNode::compress_bits(-4LL, -1LL, 32), (-4LL & 0xFFFFFFFFLL)); + ASSERT_EQ(CompressBitsNode::compress_bits(2147483647LL, -65535LL, 64), 65535LL); + ASSERT_EQ(CompressBitsNode::compress_bits(2147483647LL, -65535LL, 32), 65535LL); + ASSERT_EQ(CompressBitsNode::compress_bits(-2147483648LL, -65535LL, 64), 562949953355776LL); + ASSERT_EQ(CompressBitsNode::compress_bits(-2147483648LL, -65535LL, 32), 65536LL); + ASSERT_EQ(ExpandBitsNode::expand_bits(-4LL, -1LL, 64), -4LL); + ASSERT_EQ(ExpandBitsNode::expand_bits(-4LL, -1LL, 32), (-4LL & 0xFFFFFFFFLL)); + ASSERT_EQ(ExpandBitsNode::expand_bits(2147483647LL, -65535LL, 64), 70368744112129LL); + ASSERT_EQ(ExpandBitsNode::expand_bits(2147483647LL, -65535LL, 32), (-65535LL & 0xFFFFFFFFLL)); + ASSERT_EQ(ExpandBitsNode::expand_bits(-2147483648LL, -65535LL, 64), -70368744177664LL); + ASSERT_EQ(ExpandBitsNode::expand_bits(-2147483648LL, -65535LL, 32), 0LL); +} + diff --git a/test/hotspot/jtreg/compiler/intrinsics/TestBitShuffleOpers.java b/test/hotspot/jtreg/compiler/intrinsics/TestBitShuffleOpers.java new file mode 100644 index 00000000000..1e3ca995ba9 --- /dev/null +++ b/test/hotspot/jtreg/compiler/intrinsics/TestBitShuffleOpers.java @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2022, 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. + * + * 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. + */ + +/** + * @test + * @bug 8283894 + * @key randomness + * @summary To test various transforms added for bit COMPRESS_BITS and EXPAND_BITS operations + * @requires vm.compiler2.enabled + * @requires vm.cpu.features ~= ".*bmi2.*" + * @requires vm.cpu.features ~= ".*bmi1.*" + * @requires vm.cpu.features ~= ".*sse2.*" + * @library /test/lib / + * @run driver compiler.intrinsics.TestBitShuffleOpers + */ + +package compiler.intrinsics; + +import java.util.concurrent.Callable; +import compiler.lib.ir_framework.*; +import jdk.test.lib.Utils; +import java.util.Random; + +public class TestBitShuffleOpers { + int [] ri; + int [] ai; + int [] bi; + + long [] rl; + long [] al; + long [] bl; + + //===================== Compress Bits Transforms ================ + @Test + @IR(counts = {"RShiftI", " > 0 ", "AndI", " > 0"}) + public void test1(int[] ri, int[] ai, int[] bi) { + for (int i = 0; i < ri.length; i++) { + ri[i] = Integer.compress(ai[i], 1 << bi[i]); + } + } + + @Run(test = {"test1"}, mode = RunMode.STANDALONE) + public void kernel_test1() { + for (int i = 0; i < 5000; i++) { + test1(ri, ai, bi); + } + } + + @Test + @IR(counts = {"URShiftI", " > 0 "}) + public void test2(int[] ri, int[] ai, int[] bi) { + for (int i = 0; i < ri.length; i++) { + ri[i] = Integer.compress(ai[i], -1 << bi[i]); + } + } + + @Run(test = {"test2"}, mode = RunMode.STANDALONE) + public void kernel_test2() { + for (int i = 0; i < 5000; i++) { + test2(ri, ai, bi); + } + } + + @Test + @IR(counts = {"CompressBits", " > 0 ", "AndI" , " > 0 "}) + public void test3(int[] ri, int[] ai, int[] bi) { + for (int i = 0; i < ri.length; i++) { + ri[i] = Integer.compress(Integer.expand(ai[i], bi[i]), bi[i]); + } + } + + @Run(test = {"test3"}, mode = RunMode.STANDALONE) + public void kernel_test3() { + for (int i = 0; i < 5000; i++) { + test3(ri, ai, bi); + } + } + + @Test + @IR(counts = {"RShiftL", " > 0 ", "AndL", " > 0"}) + public void test4(long[] rl, long[] al, long[] bl) { + for (int i = 0; i < rl.length; i++) { + rl[i] = Long.compress(al[i], 1L << bl[i]); + } + } + + @Run(test = {"test4"}, mode = RunMode.STANDALONE) + public void kernel_test4() { + for (int i = 0; i < 5000; i++) { + test4(rl, al, bl); + } + } + + @Test + @IR(counts = {"URShiftL", " > 0 "}) + public void test5(long[] rl, long[] al, long[] bl) { + for (int i = 0; i < rl.length; i++) { + rl[i] = Long.compress(al[i], -1L << bl[i]); + } + } + + @Run(test = {"test5"}, mode = RunMode.STANDALONE) + public void kernel_test5() { + for (int i = 0; i < 5000; i++) { + test5(rl, al, bl); + } + } + + @Test + @IR(counts = {"CompressBits", " > 0 ", "AndL" , " > 0 "}) + public void test6(long[] rl, long[] al, long[] bl) { + for (int i = 0; i < rl.length; i++) { + rl[i] = Long.compress(Long.expand(al[i], bl[i]), bl[i]); + } + } + + @Run(test = {"test6"}, mode = RunMode.STANDALONE) + public void kernel_test6() { + for (int i = 0; i < 5000; i++) { + test6(rl, al, bl); + } + } + //===================== Expand Bits Transforms ================ + @Test + @IR(counts = {"LShiftI", " > 0 ", "AndI", " > 0"}) + public void test7(int[] ri, int[] ai, int[] bi) { + for (int i = 0; i < ri.length; i++) { + ri[i] = Integer.expand(ai[i], 1 << bi[i]); + } + } + + @Run(test = {"test7"}, mode = RunMode.STANDALONE) + public void kernel_test7() { + for (int i = 0; i < 5000; i++) { + test7(ri, ai, bi); + } + } + + @Test + @IR(counts = {"LShiftI", " > 0 "}) + public void test8(int[] ri, int[] ai, int[] bi) { + for (int i = 0; i < ri.length; i++) { + ri[i] = Integer.expand(ai[i], -1 << bi[i]); + } + } + + @Run(test = {"test8"}, mode = RunMode.STANDALONE) + public void kernel_test8() { + for (int i = 0; i < 5000; i++) { + test8(ri, ai, bi); + } + } + + @Test + @IR(counts = {"AndI" , " > 0 "}) + public void test9(int[] ri, int[] ai, int[] bi) { + for (int i = 0; i < ri.length; i++) { + ri[i] = Integer.expand(Integer.compress(ai[i], bi[i]), bi[i]); + } + } + + @Run(test = {"test9"}, mode = RunMode.STANDALONE) + public void kernel_test9() { + for (int i = 0; i < 5000; i++) { + test9(ri, ai, bi); + } + } + + @Test + @IR(counts = {"LShiftL", " > 0 ", "AndL", " > 0"}) + public void test10(long[] rl, long[] al, long[] bl) { + for (int i = 0; i < rl.length; i++) { + rl[i] = Long.expand(al[i], 1L << bl[i]); + } + } + + @Run(test = {"test10"}, mode = RunMode.STANDALONE) + public void kernel_test10() { + for (int i = 0; i < 5000; i++) { + test10(rl, al, bl); + } + } + + @Test + @IR(counts = {"LShiftL", " > 0 "}) + public void test11(long[] rl, long[] al, long[] bl) { + for (int i = 0; i < rl.length; i++) { + rl[i] = Long.expand(al[i], -1L << bl[i]); + } + } + + @Run(test = {"test11"}, mode = RunMode.STANDALONE) + public void kernel_test11() { + for (int i = 0; i < 5000; i++) { + test11(rl, al, bl); + } + } + + @Test + @IR(counts = {"AndL" , " > 0 "}) + public void test12(long[] rl, long[] al, long[] bl) { + for (int i = 0; i < rl.length; i++) { + rl[i] = Long.expand(Long.compress(al[i], bl[i]), bl[i]); + } + } + + @Run(test = {"test12"}, mode = RunMode.STANDALONE) + public void kernel_test12() { + for (int i = 0; i < 5000; i++) { + test12(rl, al, bl); + } + } + + // ================ Compress/ExpandBits Vanilla ================= // + + @Test + @IR(counts = {"CompressBits", " > 0 "}) + public void test13(int[] ri, int[] ai, int[] bi) { + for (int i = 0; i < ri.length; i++) { + ri[i] = Integer.compress(ai[i], bi[i]); + } + } + + @Run(test = {"test13"}, mode = RunMode.STANDALONE) + public void kernel_test13() { + for (int i = 0; i < 5000; i++) { + test13(ri, ai, bi); + } + verifyCompressInts(ri, ai, bi); + } + + @Test + @IR(counts = {"CompressBits", " > 0 "}) + public void test14(long[] rl, long[] al, long[] bl) { + for (int i = 0; i < rl.length; i++) { + rl[i] = Long.compress(al[i], bl[i]); + } + } + + @Run(test = {"test14"}, mode = RunMode.STANDALONE) + public void kernel_test14() { + for (int i = 0; i < 5000; i++) { + test14(rl, al, bl); + } + verifyCompressLongs(rl, al, bl); + } + + @Test + @IR(counts = {"ExpandBits", " > 0 "}) + public void test15(int[] ri, int[] ai, int[] bi) { + for (int i = 0; i < ri.length; i++) { + ri[i] = Integer.expand(ai[i], bi[i]); + } + } + + @Run(test = {"test15"}, mode = RunMode.STANDALONE) + public void kernel_test15() { + for (int i = 0; i < 5000; i++) { + test15(ri, ai, bi); + } + verifyExpandInts(ri, ai, bi); + } + + @Test + @IR(counts = {"ExpandBits", " > 0 "}) + public void test16(long[] rl, long[] al, long[] bl) { + for (int i = 0; i < rl.length; i++) { + rl[i] = Long.expand(al[i], bl[i]); + } + } + + @Run(test = {"test16"}, mode = RunMode.STANDALONE) + public void kernel_test16() { + for (int i = 0; i < 5000; i++) { + test16(rl, al, bl); + } + verifyExpandLongs(rl, al, bl); + } + + @Test + public void test17() { + int resI = 0; + long resL = 0L; + for (int i = 0; i < 5000; i++) { + resI = Integer.expand(-1, -1); + verifyExpandInt(resI, -1, -1); + resI = Integer.compress(-1, -1); + verifyCompressInt(resI, -1, -1); + + resI = Integer.expand(ai[i&(SIZE-1)], -1); + verifyExpandInt(resI, ai[i&(SIZE-1)], -1); + resI = Integer.expand(ai[i&(SIZE-1)], -2); + verifyExpandInt(resI, ai[i&(SIZE-1)], -2); + resI = Integer.expand(ai[i&(SIZE-1)], 5); + verifyExpandInt(resI, ai[i&(SIZE-1)], 5); + resI = Integer.compress(ai[i&(SIZE-1)], -1); + verifyCompressInt(resI, ai[i&(SIZE-1)], -1); + resI = Integer.compress(ai[i&(SIZE-1)], -2); + verifyCompressInt(resI, ai[i&(SIZE-1)], -2); + resI = Integer.compress(ai[i&(SIZE-1)], 5); + verifyCompressInt(resI, ai[i&(SIZE-1)], 5); + + resI = Integer.expand(ai[i&(SIZE-1)], bi[i&(SIZE-1)] & ~(0x10000000)); + verifyExpandInt(resI, ai[i&(SIZE-1)], bi[i&(SIZE-1)] & ~(0x10000000)); + resI = Integer.expand(ai[i&(SIZE-1)], bi[i&(SIZE-1)] | (0x10000000)); + verifyExpandInt(resI, ai[i&(SIZE-1)], bi[i&(SIZE-1)] | (0x10000000)); + resI = Integer.compress(ai[i&(SIZE-1)], bi[i&(SIZE-1)] & ~(0x10000000)); + verifyCompressInt(resI, ai[i&(SIZE-1)], bi[i&(SIZE-1)] & ~(0x10000000)); + resI = Integer.compress(ai[i&(SIZE-1)], bi[i&(SIZE-1)] | (0x10000000)); + verifyCompressInt(resI, ai[i&(SIZE-1)], bi[i&(SIZE-1)] | (0x10000000)); + + resI = Integer.compress(0x12123434, 0xFF00FF00); + verifyCompressInt(resI, 0x12123434, 0xFF00FF00); + resI = Integer.expand(0x12123434, 0xFF00FF00); + verifyExpandInt(resI, 0x12123434, 0xFF00FF00); + + resL = Long.expand(-1L, -1L); + verifyExpandLong(resL, -1L, -1L); + resL = Long.compress(-1L, -1L); + verifyCompressLong(resL, -1L, -1L); + + resL = Long.compress(0x1212343412123434L, 0xFF00FF00FF00FF00L); + verifyCompressLong(resL, 0x1212343412123434L, 0xFF00FF00FF00FF00L); + resL = Long.expand(0x1212343412123434L, 0xFF00FF00FF00FF00L); + verifyExpandLong(resL, 0x1212343412123434L, 0xFF00FF00FF00FF00L); + + resL = Long.expand(al[i&(SIZE-1)], -1); + verifyExpandLong(resL, al[i&(SIZE-1)], -1); + resL = Long.expand(al[i&(SIZE-1)], -2); + verifyExpandLong(resL, al[i&(SIZE-1)], -2); + resL = Long.expand(al[i&(SIZE-1)], 5); + verifyExpandLong(resL, al[i&(SIZE-1)], 5); + resL = Long.compress(al[i&(SIZE-1)], -1); + verifyCompressLong(resL, al[i&(SIZE-1)], -1); + resL = Long.compress(al[i&(SIZE-1)], -2); + verifyCompressLong(resL, al[i&(SIZE-1)], -2); + resL = Long.compress(al[i&(SIZE-1)], 5); + verifyCompressLong(resL, al[i&(SIZE-1)], 5); + + resL = Long.expand(al[i&(SIZE-1)], bl[i&(SIZE-1)] & ~(0x10000000)); + verifyExpandLong(resL, al[i&(SIZE-1)], bl[i&(SIZE-1)] & ~(0x10000000)); + resL = Long.expand(al[i&(SIZE-1)], bl[i&(SIZE-1)] | (0x10000000)); + verifyExpandLong(resL, al[i&(SIZE-1)], bl[i&(SIZE-1)] | (0x10000000)); + resL = Long.compress(al[i&(SIZE-1)], bl[i&(SIZE-1)] & ~(0x10000000)); + verifyCompressLong(resL, al[i&(SIZE-1)], bl[i&(SIZE-1)] & ~(0x10000000)); + resL = Long.compress(al[i&(SIZE-1)], bl[i&(SIZE-1)] | (0x10000000)); + verifyCompressLong(resL, al[i&(SIZE-1)], bl[i&(SIZE-1)] | (0x10000000)); + } + } + + private static final Random R = Utils.getRandomInstance(); + + static int[] fillIntRandom(Callable factory) { + try { + int[] arr = factory.call(); + for (int i = 0; i < arr.length; i++) { + arr[i] = R.nextInt(); + } + return arr; + } catch (Exception e) { + throw new InternalError(e); + } + } + static long[] fillLongRandom(Callable factory) { + try { + long[] arr = factory.call(); + for (int i = 0; i < arr.length; i++) { + arr[i] = R.nextLong(); + } + return arr; + } catch (Exception e) { + throw new InternalError(e); + } + } + + static void verifyExpandInt(int actual, int src, int mask) { + int exp = 0; + for(int j = 0, k = 0; j < Integer.SIZE; j++) { + if ((mask & 0x1) == 1) { + exp |= (src & 0x1) << j; + src >>= 1; + } + mask >>= 1; + } + if (actual != exp) { + throw new Error("expand_int: src = " + src + " mask = " + mask + + " acutal = " + actual + " expected = " + exp); + } + } + + static void verifyExpandInts(int [] actual_res, int [] inp_arr, int [] mask_arr) { + assert inp_arr.length == mask_arr.length && inp_arr.length == actual_res.length; + for(int i = 0; i < actual_res.length; i++) { + verifyExpandInt(actual_res[i], inp_arr[i], mask_arr[i]); + } + } + + static void verifyExpandLong(long actual, long src, long mask) { + long exp = 0; + for(int j = 0, k = 0; j < Long.SIZE; j++) { + if ((mask & 0x1) == 1) { + exp |= (src & 0x1) << j; + src >>= 1; + } + mask >>= 1; + } + if (actual != exp) { + throw new Error("expand_long: src = " + src + " mask = " + mask + + " acutal = " + actual + " expected = " + exp); + } + } + + static void verifyExpandLongs(long [] actual_res, long [] inp_arr, long [] mask_arr) { + assert inp_arr.length == mask_arr.length && inp_arr.length == actual_res.length; + for(int i = 0; i < actual_res.length; i++) { + verifyExpandLong(actual_res[i], inp_arr[i], mask_arr[i]); + } + } + + static void verifyCompressInt(int actual, int src, int mask) { + int exp = 0; + for(int j = 0, k = 0; j < Integer.SIZE; j++) { + if ((mask & 0x1) == 1) { + exp |= (src & 0x1) << k++; + } + mask >>= 1; + src >>= 1; + } + if (actual != exp) { + throw new Error("compress_int: src = " + src + " mask = " + mask + + " acutal = " + actual + " expected = " + exp); + } + } + + static void verifyCompressInts(int [] actual_res, int [] inp_arr, int [] mask_arr) { + assert inp_arr.length == mask_arr.length && inp_arr.length == actual_res.length; + for(int i = 0; i < actual_res.length; i++) { + verifyCompressInt(actual_res[i], inp_arr[i], mask_arr[i]); + } + } + + static void verifyCompressLong(long actual, long src, long mask) { + long exp = 0; + for(int j = 0, k = 0; j < Long.SIZE; j++) { + if ((mask & 0x1) == 1) { + exp |= (src & 0x1) << k++; + } + mask >>= 1; + src >>= 1; + } + if (actual != exp) { + throw new Error("compress_long: src = " + src + " mask = " + mask + + " acutal = " + actual + " expected = " + exp); + } + } + + static void verifyCompressLongs(long [] actual_res, long [] inp_arr, long [] mask_arr) { + assert inp_arr.length == mask_arr.length && inp_arr.length == actual_res.length; + for(int i = 0; i < actual_res.length; i++) { + verifyCompressLong(actual_res[i], inp_arr[i], mask_arr[i]); + } + } + + // ===================================================== // + + static final int SIZE = 512; + + + public TestBitShuffleOpers() { + ri = new int[SIZE]; + ai = fillIntRandom(()-> new int[SIZE]); + bi = fillIntRandom(()-> new int[SIZE]); + + rl = new long[SIZE]; + al = fillLongRandom(() -> new long[SIZE]); + bl = fillLongRandom(() -> new long[SIZE]); + + } + + public static void main(String[] args) { + TestFramework.runWithFlags("-XX:-TieredCompilation", + "-XX:CompileThresholdScaling=0.3"); + } +} diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 6a683bf4ae3..e11910b5423 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -766,7 +766,6 @@ jdk/jfr/jvm/TestWaste.java 8282427 generic- # jdk_jpackage ############################################################################ - # Client manual tests java/awt/event/MouseEvent/SpuriousExitEnter/SpuriousExitEnter_1.java 7131438,8022539 generic-all diff --git a/test/jdk/java/lang/CompressExpandSanityTest.java b/test/jdk/java/lang/CompressExpandSanityTest.java index 8e3c37ae087..3fa78b7a1ea 100644 --- a/test/jdk/java/lang/CompressExpandSanityTest.java +++ b/test/jdk/java/lang/CompressExpandSanityTest.java @@ -21,10 +21,12 @@ * questions. */ +// Disabled by default +// @test /* - * @test * @summary Test compress expand as if the test methods are the implementation methods * @key randomness + * @run testng/othervm -XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_expand_i,_expand_l,_compress_i,_compress_l CompressExpandSanityTest * @run testng CompressExpandSanityTest */ diff --git a/test/jdk/java/lang/CompressExpandTest.java b/test/jdk/java/lang/CompressExpandTest.java index 90e9ff61705..6971d518979 100644 --- a/test/jdk/java/lang/CompressExpandTest.java +++ b/test/jdk/java/lang/CompressExpandTest.java @@ -25,6 +25,7 @@ * @test * @summary Test compress expand methods * @key randomness + * @run testng/othervm -XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_expand_i,_expand_l,_compress_i,_compress_l CompressExpandTest * @run testng CompressExpandTest */ From e0677ec53b8480d8edc56440335b75cfbbf7686b Mon Sep 17 00:00:00 2001 From: Fei Gao Date: Mon, 6 Jun 2022 02:02:10 +0000 Subject: [PATCH 09/22] 8283307: Vectorize unsigned shift right on signed subword types Reviewed-by: jiefu, pli, sviswanathan, kvn --- .../irTests/TestVectorizeURShiftSubword.java | 162 ++++++++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 2 + .../runner/ArrayShiftOpTest.java | 2 - .../vectorization/runner/BasicByteOpTest.java | 2 - .../runner/BasicShortOpTest.java | 2 - .../bench/vm/compiler/VectorShiftRight.java | 14 ++ 6 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/irTests/TestVectorizeURShiftSubword.java diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizeURShiftSubword.java b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizeURShiftSubword.java new file mode 100644 index 00000000000..3dc4db05187 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizeURShiftSubword.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2022, Arm Limited. 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. + * + * 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 compiler.c2.irTests; + +import compiler.lib.ir_framework.*; +import java.util.Random; +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +/* + * @test + * @bug 8283307 + * @key randomness + * @summary Auto-vectorization enhancement for unsigned shift right on signed subword types + * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" + * @library /test/lib / + * @run driver compiler.c2.irTests.TestVectorizeURShiftSubword + */ + +public class TestVectorizeURShiftSubword { + + private static final Random RANDOM = Utils.getRandomInstance(); + + final private static int NUM = 3000; + + private short[] shorta = new short[NUM]; + private short[] shortb = new short[NUM]; + private byte[] bytea = new byte[NUM]; + private byte[] byteb = new byte[NUM]; + + private static final int[] SPECIALS = { + 0, 0x1, 0x8, 0xF, 0x3F, 0x7C, 0x7F, 0x80, 0x81, 0x8F, 0xF3, 0xF8, 0xFF, + 0x38FF, 0x3FFF, 0x8F8F, 0x8FFF, 0x7FF3, 0x7FFF, 0xFF33, 0xFFF8, 0xFFFF, 0xFFFFFF, + Integer.MAX_VALUE, Integer.MIN_VALUE + }; + + public byte urshift(byte input, int amount) { + return (byte) (input >>> amount); + } + + public short urshift(short input, int amount) { + return (short) (input >>> amount); + } + + public static void main(String[] args) { + TestFramework.runWithFlags("-XX:CompileCommand=exclude,*.urshift"); + } + + @Test + @IR(counts = {IRNode.LOAD_VECTOR, ">0", IRNode.RSHIFT_VB, ">0", IRNode.STORE_VECTOR, ">0"}) + public void testByte0() { + for(int i = 0; i < NUM; i++) { + byteb[i] = (byte) (bytea[i] >>> 3); + } + } + + @Test + @IR(counts = {IRNode.LOAD_VECTOR, ">0", IRNode.RSHIFT_VB, ">0", IRNode.STORE_VECTOR, ">0"}) + public void testByte1() { + for(int i = 0; i < NUM; i++) { + byteb[i] = (byte) (bytea[i] >>> 24); + } + } + + @Test + @IR(failOn = {IRNode.LOAD_VECTOR, IRNode.RSHIFT_VB, IRNode.STORE_VECTOR}) + public void testByte2() { + for(int i = 0; i < NUM; i++) { + byteb[i] = (byte) (bytea[i] >>> 25); + } + } + + @Test + @IR(counts = {IRNode.LOAD_VECTOR, ">0", IRNode.RSHIFT_VS, ">0", IRNode.STORE_VECTOR, ">0"}) + public void testShort0() { + for(int i = 0; i < NUM; i++) { + shortb[i] = (short) (shorta[i] >>> 10); + } + } + + @Test + @IR(counts = {IRNode.LOAD_VECTOR, ">0", IRNode.RSHIFT_VS, ">0", IRNode.STORE_VECTOR, ">0"}) + public void testShort1() { + for(int i = 0; i < NUM; i++) { + shortb[i] = (short) (shorta[i] >>> 16); + } + } + + @Test + @IR(failOn = {IRNode.LOAD_VECTOR, IRNode.RSHIFT_VS, IRNode.STORE_VECTOR}) + public void testShort2() { + for(int i = 0; i < NUM; i++) { + shortb[i] = (short) (shorta[i] >>> 17); + } + } + + @Test + public void checkTest() { + testByte0(); + for (int i = 0; i < bytea.length; i++) { + Asserts.assertEquals(byteb[i], urshift(bytea[i], 3)); + } + testByte1(); + for (int i = 0; i < bytea.length; i++) { + Asserts.assertEquals(byteb[i], urshift(bytea[i], 24)); + } + testByte2(); + for (int i = 0; i < bytea.length; i++) { + Asserts.assertEquals(byteb[i], urshift(bytea[i], 25)); + } + testShort0(); + for (int i = 0; i < shorta.length; i++) { + Asserts.assertEquals(shortb[i], urshift(shorta[i], 10)); + } + testShort1(); + for (int i = 0; i < shorta.length; i++) { + Asserts.assertEquals(shortb[i], urshift(shorta[i], 16)); + } + testShort2(); + for (int i = 0; i < shorta.length; i++) { + Asserts.assertEquals(shortb[i], urshift(shorta[i], 17)); + } + + } + + @Run(test = "checkTest") + public void checkTest_runner() { + for (int i = 0; i < SPECIALS.length; i++) { + for (int j = 0; j < shorta.length; j++) { + shorta[j] = (short) SPECIALS[i]; + bytea[j] = (byte) SPECIALS[i]; + } + checkTest(); + } + for (int j = 0; j < shorta.length; j++) { + shorta[j] = (short) RANDOM.nextInt();; + bytea[j] = (byte) RANDOM.nextInt(); + } + checkTest(); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 8aaf5d4466c..9b13545163d 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -157,6 +157,8 @@ public class IRNode { public static final String RSHIFT = START + "RShift(I|L)" + MID + END; public static final String RSHIFT_I = START + "RShiftI" + MID + END; public static final String RSHIFT_L = START + "RShiftL" + MID + END; + public static final String RSHIFT_VB = START + "RShiftVB" + MID + END; + public static final String RSHIFT_VS = START + "RShiftVS" + MID + END; public static final String URSHIFT = START + "URShift(B|S|I|L)" + MID + END; public static final String URSHIFT_I = START + "URShiftI" + MID + END; public static final String URSHIFT_L = START + "URShiftL" + MID + END; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayShiftOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayShiftOpTest.java index 21d84683d31..9d77f0dee59 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayShiftOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayShiftOpTest.java @@ -133,8 +133,6 @@ public long[] variantShiftDistance() { } @Test - // Note that unsigned shift right on subword signed integer types can't - // be vectorized since the sign extension bits would be lost. public short[] vectorUnsignedShiftRight() { short[] res = new short[SIZE]; for (int i = 0; i < SIZE; i++) { diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/BasicByteOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/BasicByteOpTest.java index 1d787ac0a5c..df92231eb11 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/BasicByteOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/BasicByteOpTest.java @@ -180,8 +180,6 @@ public byte[] vectorSignedShiftRight() { } @Test - // Note that unsigned shift right on subword signed integer types can - // not be vectorized since the sign extension bit would be lost. public byte[] vectorUnsignedShiftRight() { byte[] res = new byte[SIZE]; for (int i = 0; i < SIZE; i++) { diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/BasicShortOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/BasicShortOpTest.java index 772dc9546a4..9861ff10c86 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/BasicShortOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/BasicShortOpTest.java @@ -180,8 +180,6 @@ public short[] vectorSignedShiftRight() { } @Test - // Note that unsigned shift right on subword signed integer types can - // not be vectorized since the sign extension bits would be lost. public short[] vectorUnsignedShiftRight() { short[] res = new short[SIZE]; for (int i = 0; i < SIZE; i++) { diff --git a/test/micro/org/openjdk/bench/vm/compiler/VectorShiftRight.java b/test/micro/org/openjdk/bench/vm/compiler/VectorShiftRight.java index 068d1bd704d..1edfb50bd5b 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/VectorShiftRight.java +++ b/test/micro/org/openjdk/bench/vm/compiler/VectorShiftRight.java @@ -85,6 +85,13 @@ public void urShiftByte() { } } + @Benchmark + public void urShiftImmByte() { + for (int i = 0; i < SIZE; i++) { + bytesB[i] = (byte) (bytesA[i] >>> 3); + } + } + @Benchmark public void rShiftShort() { for (int i = 0; i < SIZE; i++) { @@ -92,6 +99,13 @@ public void rShiftShort() { } } + @Benchmark + public void urShiftImmShort() { + for (int i = 0; i < SIZE; i++) { + shortsB[i] = (short) (shortsA[i] >>> 3); + } + } + @Benchmark public void urShiftChar() { for (int i = 0; i < SIZE; i++) { From 19132efcc8e78842abd48941e9699c892af27345 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Mon, 6 Jun 2022 05:29:30 +0000 Subject: [PATCH 10/22] 8287732: jdk/jshell/ToolEnablePreviewTest.java fails on x86_32 after JDK-8287496 Reviewed-by: alanb, kvn --- test/langtools/TEST.ROOT | 14 ++++++++++++++ .../jdk/jshell/ToolEnablePreviewTest.java | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/test/langtools/TEST.ROOT b/test/langtools/TEST.ROOT index 1986a949ed2..44030ce3810 100644 --- a/test/langtools/TEST.ROOT +++ b/test/langtools/TEST.ROOT @@ -26,3 +26,17 @@ useNewPatchModule=true # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../ notation to reach them external.lib.roots = ../../ + +# Allow querying of various System properties in @requires clauses +# +# Source files for classes that will be used at the beginning of each test suite run, +# to determine additional characteristics of the system for use with the @requires tag. +# Note: compiled bootlibs classes will be added to BCP. +requires.extraPropDefns = ../jtreg-ext/requires/VMProps.java +requires.extraPropDefns.bootlibs = ../lib/jdk/test/whitebox +requires.extraPropDefns.libs = \ + ../lib/jdk/test/lib/Platform.java \ + ../lib/jdk/test/lib/Container.java +requires.extraPropDefns.vmOpts = -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI +requires.properties= \ + vm.continuations diff --git a/test/langtools/jdk/jshell/ToolEnablePreviewTest.java b/test/langtools/jdk/jshell/ToolEnablePreviewTest.java index e61eb0b537e..ff83516c871 100644 --- a/test/langtools/jdk/jshell/ToolEnablePreviewTest.java +++ b/test/langtools/jdk/jshell/ToolEnablePreviewTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2022, 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 @@ -25,6 +25,7 @@ * @test * @bug 8199193 * @summary Tests for the --enable-preview option + * @requires vm.continuations * @run testng ToolEnablePreviewTest */ From f603a943323fe726a470609837a89ef902533537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Mon, 6 Jun 2022 11:11:43 +0000 Subject: [PATCH 11/22] 8287524: Improve checkboxes to select releases on deprecated API page Reviewed-by: jjg --- .../formats/html/DeprecatedListWriter.java | 37 ++++-- .../formats/html/NewAPIListWriter.java | 8 +- .../formats/html/SummaryListWriter.java | 20 ++- .../html/resources/standard.properties | 1 + .../doclets/toolkit/resources/script.js | 26 ++-- .../util/DeprecatedAPIListBuilder.java | 22 +++- .../doclet/testNewApiList/TestNewApiList.java | 118 ++++++++---------- .../mdl/pkg/TestAnnotation.java | 6 +- .../testNewApiList/mdl/pkg/TestClass.java | 4 +- 9 files changed, 138 insertions(+), 104 deletions(-) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/DeprecatedListWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/DeprecatedListWriter.java index 574dd89a07b..1cd75e9593e 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/DeprecatedListWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/DeprecatedListWriter.java @@ -27,7 +27,6 @@ import com.sun.source.doctree.DeprecatedTree; import java.util.List; -import java.util.ListIterator; import javax.lang.model.element.Element; @@ -81,19 +80,12 @@ public static void generate(HtmlConfiguration configuration) throws DocFileIOExc @Override protected void addExtraSection(DeprecatedAPIListBuilder list, Content content) { List releases = configuration.deprecatedAPIListBuilder.releases; - if (!releases.isEmpty()) { + if (releases.size() > 1) { Content tabs = HtmlTree.DIV(HtmlStyle.checkboxes, contents.getContent( "doclet.Deprecated_API_Checkbox_Label")); for (int i = 0; i < releases.size(); i++) { - int releaseIndex = i + 1; - String release = releases.get(i); - HtmlId htmlId = HtmlId.of("release-" + releaseIndex); - tabs.add(HtmlTree.LABEL(htmlId.name(), - HtmlTree.INPUT("checkbox", htmlId) - .put(HtmlAttr.CHECKED, "") - .put(HtmlAttr.ONCLICK, - "toggleGlobal(this, '" + releaseIndex + "', 3)")) - .add(HtmlTree.SPAN(Text.of(release)))); + // Table column ids are 1-based + tabs.add(getReleaseCheckbox(releases.get(i), i + 1)); } content.add(tabs); } @@ -101,6 +93,23 @@ protected void addExtraSection(DeprecatedAPIListBuilder list, Content content) { TERMINALLY_DEPRECATED_KEY, "doclet.Element", content); } + private Content getReleaseCheckbox(String name, int index) { + // Empty string represents other/uncategorized releases. Since we can't make any assumptions + // about release names this is arguably the safest way to avoid naming collisions. + boolean isOtherReleases = name.isEmpty(); + Content releaseLabel = isOtherReleases + ? contents.getContent("doclet.Deprecated_API_Checkbox_Other_Releases") + : Text.of(name); + HtmlId htmlId = HtmlId.of("release-" + index); + String releaseId = isOtherReleases ? "" : Integer.toString(index); + return HtmlTree.LABEL(htmlId.name(), + HtmlTree.INPUT("checkbox", htmlId) + .put(HtmlAttr.CHECKED, "") + .put(HtmlAttr.ONCLICK, + "toggleGlobal(this, '" + releaseId + "', 3)")) + .add(HtmlTree.SPAN(releaseLabel)); + } + @Override protected void addExtraIndexLink(DeprecatedAPIListBuilder list, Content target) { if (!list.getForRemoval().isEmpty()) { @@ -122,10 +131,12 @@ protected void addComments(Element e, Content desc) { protected void addTableTabs(Table table, String headingKey) { List releases = configuration.deprecatedAPIListBuilder.releases; if (!releases.isEmpty()) { + table.setGridStyle(HtmlStyle.threeColumnReleaseSummary); + } + if (releases.size() > 1) { table.setDefaultTab(getTableCaption(headingKey)) .setAlwaysShowDefaultTab(true) - .setRenderTabs(false) - .setGridStyle(HtmlStyle.threeColumnReleaseSummary); + .setRenderTabs(false); for (String release : releases) { Content tab = TERMINALLY_DEPRECATED_KEY.equals(headingKey) ? contents.getContent("doclet.Terminally_Deprecated_In_Release", release) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NewAPIListWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NewAPIListWriter.java index 3944c49d32c..8c2851388b7 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NewAPIListWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NewAPIListWriter.java @@ -79,7 +79,7 @@ public static void generate(HtmlConfiguration configuration) throws DocFileIOExc @Override protected void addExtraSection(NewAPIBuilder list, Content content) { List releases = configuration.newAPIPageBuilder.releases; - if (!releases.isEmpty()) { + if (releases.size() > 1) { Content tabs = HtmlTree.DIV(HtmlStyle.checkboxes, contents.getContent("doclet.New_API_Checkbox_Label")); for (int i = 0; i < releases.size(); i++) { @@ -98,12 +98,12 @@ protected void addExtraSection(NewAPIBuilder list, Content content) { @Override protected void addTableTabs(Table table, String headingKey) { + table.setGridStyle(HtmlStyle.threeColumnReleaseSummary); List releases = configuration.newAPIPageBuilder.releases; - if (!releases.isEmpty()) { + if (releases.size() > 1) { table.setDefaultTab(getTableCaption(headingKey)) .setAlwaysShowDefaultTab(true) - .setRenderTabs(false) - .setGridStyle(HtmlStyle.threeColumnReleaseSummary); + .setRenderTabs(false); for (String release : releases) { table.addTab( releases.size() == 1 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SummaryListWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SummaryListWriter.java index a4c49f2b1e9..927a2d13733 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SummaryListWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SummaryListWriter.java @@ -34,6 +34,7 @@ import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; import jdk.javadoc.internal.doclets.formats.html.markup.HtmlId; import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.Script; import jdk.javadoc.internal.doclets.formats.html.markup.TagName; import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; import jdk.javadoc.internal.doclets.formats.html.Navigation.PageMode; @@ -131,6 +132,23 @@ protected void generateSummaryListFile(L summaryapi) } } bodyContents.addMainContent(content); + // The script below enables checkboxes in the page and invokes their click handler + // to restore any previous state when the page is loaded via back/forward button. + bodyContents.addMainContent(new Script(""" + document.addEventListener("DOMContentLoaded", function(e) { + document.querySelectorAll('input[type="checkbox"]').forEach( + function(c) { + c.disabled = false; + c.onclick(); + }); + }); + window.addEventListener("load", function(e) { + document.querySelectorAll('input[type="checkbox"]').forEach( + function(c) { + c.onclick(); + }); + }); + """).asContent()); bodyContents.setFooter(getFooter()); body.add(bodyContents); printHtmlDocument(null, description, body); @@ -140,7 +158,7 @@ protected void generateSummaryListFile(L summaryapi) * Add the index link. * * @param id the id for the link - * @param headingKey + * @param headingKey the key for the heading content * @param content the content to which the index link will be added */ protected void addIndexLink(HtmlId id, String headingKey, Content content) { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties index d88ecf5d715..e63a7013e06 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties @@ -110,6 +110,7 @@ doclet.tag.invalid_input=invalid input: ''{0}'' doclet.tag.invalid=invalid @{0} doclet.Deprecated_API=Deprecated API doclet.Deprecated_API_Checkbox_Label=Show API deprecated in: +doclet.Deprecated_API_Checkbox_Other_Releases=other doclet.Deprecated_Elements=Deprecated {0} doclet.Deprecated_Elements_Release_Column_Header=Deprecated in doclet.Deprecated_In_Release=Deprecated in {0} diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/script.js b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/script.js index e5d8f71b960..62953632e9c 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/script.js +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/script.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022, 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 @@ -91,18 +91,15 @@ function sortTable(header, columnIndex, columns) { var ka = makeComparable(a[columnIndex].textContent); var kb = makeComparable(b[columnIndex].textContent); if (ka < kb) - return -1; + return descending ? 1 : -1; if (ka > kb) - return 1; + return descending ? -1 : 1; return 0; }; var sorted = rows.sort(comparator); - if (descending) { - sorted = sorted.reverse(); - } var visible = 0; sorted.forEach(function(row) { - if (row[0].style.display === '') { + if (row[0].style.display !== 'none') { var isEvenRow = visible++ % 2 === 0; } row.forEach(function(cell) { @@ -118,11 +115,17 @@ function toggleGlobal(checkbox, selected, columns) { var display = checkbox.checked ? '' : 'none'; document.querySelectorAll("div.table-tabs").forEach(function(t) { var id = t.parentElement.getAttribute("id"); - selectedClass = id + "-tab" + selected; + var selectedClass = id + "-tab" + selected; + // if selected is empty string it selects all uncategorized entries + var selectUncategorized = !Boolean(selected); var visible = 0; document.querySelectorAll('div.' + id) .forEach(function(elem) { - if (elem.classList.contains(selectedClass)) { + if (selectUncategorized) { + if (elem.className.indexOf(selectedClass) === -1) { + elem.style.display = display; + } + } else if (elem.classList.contains(selectedClass)) { elem.style.display = display; } if (elem.style.display === '') { @@ -260,9 +263,4 @@ document.addEventListener("DOMContentLoaded", function(e) { if (!location.hash) { history.replaceState(contentDiv.scrollTop, document.title); } - document.querySelectorAll('input[type="checkbox"]').forEach( - function(c, i) { - c.disabled = false; - toggleGlobal(c, String(i + 1), 3) - }); }); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DeprecatedAPIListBuilder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DeprecatedAPIListBuilder.java index e6d7d158580..d6e8d127abf 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DeprecatedAPIListBuilder.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DeprecatedAPIListBuilder.java @@ -38,17 +38,32 @@ public class DeprecatedAPIListBuilder extends SummaryAPIListBuilder { private SortedSet forRemoval; public final List releases; + private final Set foundReleases; /** * Constructor. * * @param configuration the current configuration of the doclet - * @param releases list of releases + * @param since list of releases passed via --since option */ - public DeprecatedAPIListBuilder(BaseConfiguration configuration, List releases) { + public DeprecatedAPIListBuilder(BaseConfiguration configuration, List since) { super(configuration, configuration.utils::isDeprecated); - this.releases = releases; + this.foundReleases = new HashSet<>(); buildSummaryAPIInfo(); + // The releases list is set to the intersection of releases defined via `--since` option + // and actually occurring values of `Deprecated.since` in documented deprecated elements. + // If there are `Deprecated.since` values not contained in the `--since` option list + // an empty string is added to the releases list which causes the writer to generate + // a checkbox for other (unlisted) releases. + List releases = new ArrayList<>(since); + if (!releases.isEmpty()) { + releases.retainAll(foundReleases); + if (!releases.containsAll(foundReleases)) { + // Empty string is added for other releases, including the default value "" + releases.add(""); + } + } + this.releases = Collections.unmodifiableList(releases); } public SortedSet getForRemoval() { @@ -60,6 +75,7 @@ public SortedSet getForRemoval() { @Override protected void handleElement(Element e) { + foundReleases.add(utils.getDeprecatedSince(e)); if (utils.isDeprecatedForRemoval(e)) { getForRemoval().add(e); } diff --git a/test/langtools/jdk/javadoc/doclet/testNewApiList/TestNewApiList.java b/test/langtools/jdk/javadoc/doclet/testNewApiList/TestNewApiList.java index cd7278719a7..b8267947d85 100644 --- a/test/langtools/jdk/javadoc/doclet/testNewApiList/TestNewApiList.java +++ b/test/langtools/jdk/javadoc/doclet/testNewApiList/TestNewApiList.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8263468 8269401 8268422 + * @bug 8263468 8269401 8268422 8287524 * @summary New page for "recent" new API * @library ../../lib * @modules jdk.javadoc/jdk.javadoc.internal.tool @@ -476,10 +476,10 @@ private void checkMultiReleaseDeprecatedElements() {

Element
Deprecated in
Description
-
-
5
-
+
3.2
+
""", """
@@ -491,10 +491,10 @@ private void checkMultiReleaseDeprecatedElements() {
Method
Deprecated in
Description
- -
5
-
+
3.2
+
""", """
@@ -506,10 +506,10 @@ private void checkMultiReleaseDeprecatedElements() {
Constructor
Deprecated in
Description
- -
5
-
+ +
6
+
""", """
@@ -521,10 +521,10 @@ private void checkMultiReleaseDeprecatedElements() {
Enum Constant
Deprecated in
Description
-
<\ +
<\ a href="mdl/pkg/TestEnum.html#DEPRECATED">pkg.TestEnum.DEPRECATED
-
5
-
+
5
+
""", """
@@ -537,9 +537,9 @@ private void checkMultiReleaseDeprecatedElements() {
Deprecated in
Description
-
5
-
+ ion-interface-member-tab1">pkg.TestAnnotation.required()
+
3.2
+
"""); } @@ -553,53 +553,46 @@ private void checkSingleReleaseContents() {
  • Constructors
  • -
    Show API added in:
    -
    Show API deprecated in: