8212151: jdi/ExclusiveBind.java times out due to "bind failed: Address already in use" on Solaris-X64
authoramenkov
Thu, 25 Oct 2018 11:48:18 -0700
changeset 52291 fa61165a3f2b
parent 52290 db83eceba962
child 52292 39f8fa3a7be8
8212151: jdi/ExclusiveBind.java times out due to "bind failed: Address already in use" on Solaris-X64 Reviewed-by: sspitsyn, jcbeyler
test/jdk/com/sun/jdi/ExclusiveBind.java
test/jdk/com/sun/jdi/lib/jdb/Debuggee.java
test/jdk/com/sun/jdi/lib/jdb/JdbTest.java
--- a/test/jdk/com/sun/jdi/ExclusiveBind.java	Thu Oct 25 11:18:24 2018 -0700
+++ b/test/jdk/com/sun/jdi/ExclusiveBind.java	Thu Oct 25 11:48:18 2018 -0700
@@ -27,113 +27,39 @@
  *          at the same time.
  * @library /test/lib
  *
- * @modules java.management
- *          jdk.jdi
- * @build VMConnection ExclusiveBind HelloWorld
+ * @build ExclusiveBind HelloWorld
  * @run driver ExclusiveBind
  */
-import java.net.ServerSocket;
-import com.sun.jdi.Bootstrap;
-import com.sun.jdi.VirtualMachine;
-import com.sun.jdi.connect.Connector;
-import com.sun.jdi.connect.AttachingConnector;
-
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.List;
-import java.util.Iterator;
-import java.util.concurrent.TimeUnit;
 
 import jdk.test.lib.process.ProcessTools;
-import jdk.test.lib.Utils;
+import lib.jdb.Debuggee;
 
 public class ExclusiveBind {
     /*
-     * Find a connector by name
-     */
-    private static Connector findConnector(String name) {
-        List connectors = Bootstrap.virtualMachineManager().allConnectors();
-        Iterator iter = connectors.iterator();
-        while (iter.hasNext()) {
-            Connector connector = (Connector)iter.next();
-            if (connector.name().equals(name)) {
-                return connector;
-            }
-        }
-        return null;
-    }
-
-    /*
-     * Launch (in server mode) a debuggee with the given address and
-     * suspend mode.
-     */
-    private static ProcessBuilder prepareLauncher(String address, boolean suspend, String class_name) throws Exception {
-        List<String> args = new ArrayList<>();
-        for(String dbgOption : VMConnection.getDebuggeeVMOptions().split(" ")) {
-            if (!dbgOption.trim().isEmpty()) {
-                args.add(dbgOption);
-            }
-        }
-        String lib = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=";
-        if (suspend) {
-            lib += "y";
-        } else {
-            lib += "n";
-        }
-        lib += ",address=" + address;
-
-        args.add(lib);
-        args.add(class_name);
-
-        return ProcessTools.createJavaProcessBuilder(args.toArray(new String[args.size()]));
-    }
-
-    /*
-     * - pick a TCP port
-     * - Launch a debuggee in server=y,suspend=y,address=${port}
-     * - Launch a second debuggee in server=y,suspend=n with the same port
+     * - Launch a debuggee with server=y,suspend=y
+     * - Parse listening port
+     * - Launch a second debuggee in server=y,suspend=n with the parsed port
      * - Second debuggee should fail with an error (address already in use)
      * - For clean-up we attach to the first debuggee and resume it.
      */
     public static void main(String args[]) throws Exception {
-        // find a free port
-        ServerSocket ss = new ServerSocket(0);
-        int port = ss.getLocalPort();
-        ss.close();
-
-        String address = String.valueOf(port);
-
         // launch the first debuggee
-        ProcessBuilder process1 = prepareLauncher(address, true, "HelloWorld");
-        // start the debuggee and wait for the "ready" message
-        Process p = ProcessTools.startProcess(
-                "process1",
-                process1,
-                line -> line.equals("Listening for transport dt_socket at address: " + address),
-                Utils.adjustTimeout(5000),
-                TimeUnit.MILLISECONDS
-        );
+        try (Debuggee process1 = Debuggee.launcher("HelloWorld").launch("process1")) {
+            // launch a second debuggee with the same address
+            ProcessBuilder process2 = Debuggee.launcher("HelloWorld")
+                    .setSuspended(false)
+                    .setAddress(process1.getAddress())
+                    .prepare();
 
-        // launch a second debuggee with the same address
-        ProcessBuilder process2 = prepareLauncher(address, false, "HelloWorld");
-
-        // get exit status from second debuggee
-        int exitCode = ProcessTools.startProcess("process2", process2).waitFor();
+            // get exit status from second debuggee
+            int exitCode = ProcessTools.startProcess("process2", process2).waitFor();
 
-        // clean-up - attach to first debuggee and resume it
-        AttachingConnector conn = (AttachingConnector)findConnector("com.sun.jdi.SocketAttach");
-        Map conn_args = conn.defaultArguments();
-        Connector.IntegerArgument port_arg =
-            (Connector.IntegerArgument)conn_args.get("port");
-        port_arg.setValue(port);
-        VirtualMachine vm = conn.attach(conn_args);
-        vm.resume();
-
-        // if the second debuggee ran to completion then we've got a problem
-        if (exitCode == 0) {
-            throw new RuntimeException("Test failed - second debuggee didn't fail to bind");
-        } else {
-            System.out.println("Test passed - second debuggee correctly failed to bind");
+            // if the second debuggee ran to completion then we've got a problem
+            if (exitCode == 0) {
+                throw new RuntimeException("Test failed - second debuggee didn't fail to bind");
+            } else {
+                System.out.println("Test passed - second debuggee correctly failed to bind");
+            }
         }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jdi/lib/jdb/Debuggee.java	Thu Oct 25 11:48:18 2018 -0700
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2018, 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.
+ */
+
+package lib.jdb;
+
+import jdk.test.lib.Utils;
+import jdk.test.lib.process.ProcessTools;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * Helper class to run java debuggee and parse agent listening transport/address.
+ * Usage:
+ *   1)
+ *      Debugee debugee = Debuggee.launcher("MyClass").setTransport("dt_shmem").launch();
+ *      try {
+ *          String transport = debuggee.getTransport();
+ *          String addr = debuggee.getAddress();
+ *      } finally {
+ *          debuggee.shutdown();
+ *      }
+ *   2) (using try-with-resource)
+ *      try (Debugee debugee = Debuggee.launcher("MyClass").launch()) {
+ *          String transport = debuggee.getTransport();
+ *          String addr = debuggee.getAddress();
+ *      }
+ *   3)
+ *      ProcessBuilder pb = Debuggee.launcher("MyClass").setSuspended(false).prepare();
+ *      ProcessTools.executeProcess(pb);
+ */
+public class Debuggee implements Closeable {
+
+    public static Launcher launcher(String mainClass) {
+        return new Launcher(mainClass);
+    }
+
+    public static class Launcher {
+        private final String mainClass;
+        private final List<String> options = new LinkedList<>();
+        private String transport = "dt_socket";
+        private String address = null;
+        private boolean suspended = true;
+        private boolean addTestVmAndJavaOptions = true;
+
+        private Launcher(String mainClass) {
+            this.mainClass = mainClass;
+        }
+        public Launcher addOption(String option) {
+            options.add(option);
+            return this;
+        }
+        public Launcher addOptions(List<String> options) {
+            this.options.addAll(options);
+            return this;
+        }
+        // default is "dt_socket"
+        public Launcher setTransport(String value) {
+            transport = value;
+            return this;
+        }
+        // default is "null" (auto-generate)
+        public Launcher setAddress(String value) {
+            address = value;
+            return this;
+        }
+        // default is "true"
+        public Launcher setSuspended(boolean value) {
+            suspended = value;
+            return this;
+        }
+        // default is "true"
+        public Launcher addTestVmAndJavaOptions(boolean value) {
+            addTestVmAndJavaOptions = value;
+            return this;
+        }
+
+        public ProcessBuilder prepare() {
+            List<String> debuggeeArgs = new LinkedList<>();
+            debuggeeArgs.add("-agentlib:jdwp=transport=" + transport
+                    + (address == null ? "" : ",address=" + address)
+                    + ",server=y,suspend=" + (suspended ? "y" : "n"));
+            debuggeeArgs.addAll(options);
+            debuggeeArgs.add(mainClass);
+            return ProcessTools.createJavaProcessBuilder(addTestVmAndJavaOptions,
+                    debuggeeArgs.toArray(new String[0]));
+        }
+
+        public Debuggee launch(String name) {
+            return new Debuggee(prepare(), name);
+        }
+        public Debuggee launch() {
+            return launch("debuggee");
+        }
+    }
+
+    // starts the process, waits for "Listening for transport" output and detects transport/address
+    private Debuggee(ProcessBuilder pb, String name) {
+        // debuggeeListen[0] - transport, debuggeeListen[1] - address
+        String[] debuggeeListen = new String[2];
+        Pattern listenRegexp = Pattern.compile("Listening for transport \\b(.+)\\b at address: \\b(.+)\\b");
+        try {
+            p = ProcessTools.startProcess(name, pb,
+                    s -> output.add(s),  // output consumer
+                    s -> {  // warm-up predicate
+                        Matcher m = listenRegexp.matcher(s);
+                        if (!m.matches()) {
+                            return false;
+                        }
+                        debuggeeListen[0] = m.group(1);
+                        debuggeeListen[1] = m.group(2);
+                        return true;
+                    },
+                    30, TimeUnit.SECONDS);
+            transport = debuggeeListen[0];
+            address = debuggeeListen[1];
+        } catch (IOException | InterruptedException | TimeoutException ex) {
+            throw new RuntimeException("failed to launch debuggee", ex);
+        }
+    }
+
+    private final Process p;
+    private final List<String> output = new LinkedList<>();
+    private final String transport;
+    private final String address;
+
+    public void shutdown() {
+        try {
+            close();
+        } catch (IOException ex) {
+            // ignore
+        }
+    }
+
+    // waits until the process shutdown or crash
+    public boolean waitFor(long timeout, TimeUnit unit) {
+        try {
+            return p.waitFor(Utils.adjustTimeout(timeout), unit);
+        } catch (InterruptedException e) {
+            return false;
+        }
+    }
+
+    // returns the whole debuggee output as a string
+    public String getOutput() {
+        return output.stream().collect(Collectors.joining(Utils.NEW_LINE));
+    }
+
+    String getTransport() {
+        return transport;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (p.isAlive()) {
+            p.destroy();
+        }
+    }
+
+}
--- a/test/jdk/com/sun/jdi/lib/jdb/JdbTest.java	Thu Oct 25 11:18:24 2018 -0700
+++ b/test/jdk/com/sun/jdi/lib/jdb/JdbTest.java	Thu Oct 25 11:48:18 2018 -0700
@@ -23,9 +23,7 @@
 
 package lib.jdb;
 
-import jdk.test.lib.Utils;
 import jdk.test.lib.process.OutputAnalyzer;
-import jdk.test.lib.process.ProcessTools;
 
 import java.io.IOException;
 import java.nio.file.Files;
@@ -34,9 +32,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 public abstract class JdbTest {
@@ -76,8 +71,7 @@
     }
 
     protected Jdb jdb;
-    protected Process debuggee;
-    private final List<String> debuggeeOutput = new LinkedList<>();
+    protected Debuggee debuggee;
     private final LaunchOptions launchOptions;
 
     // returns the whole jdb output as a string
@@ -87,7 +81,7 @@
 
     // returns the whole debuggee output as a string
     public String getDebuggeeOutput() {
-        return debuggeeOutput.stream().collect(Collectors.joining(lineSeparator));
+        return debuggee == null ? "" : debuggee.getOutput();
     }
 
     public void run() {
@@ -106,45 +100,22 @@
 
     protected void setup() {
         /* run debuggee as:
-            java -agentlib:jdwp=transport=dt_socket,address=0,server=n,suspend=y <debuggeeClass>
+            java -agentlib:jdwp=transport=dt_socket,server=n,suspend=y <debuggeeClass>
         it reports something like : Listening for transport dt_socket at address: 60810
         after that connect jdb by:
             jdb -connect com.sun.jdi.SocketAttach:port=60810
         */
         // launch debuggee
-        List<String> debuggeeArgs = new LinkedList<>();
-        // specify address=0 to automatically select free port
-        debuggeeArgs.add("-agentlib:jdwp=transport=dt_socket,address=0,server=y,suspend=y");
-        debuggeeArgs.addAll(launchOptions.debuggeeOptions);
-        debuggeeArgs.add(launchOptions.debuggeeClass);
-        ProcessBuilder pbDebuggee = ProcessTools.createJavaProcessBuilder(true, debuggeeArgs.toArray(new String[0]));
-
-        // debuggeeListen[0] - transport, debuggeeListen[1] - address
-        String[] debuggeeListen = new String[2];
-        Pattern listenRegexp = Pattern.compile("Listening for transport \\b(.+)\\b at address: \\b(\\d+)\\b");
-        try {
-            debuggee = ProcessTools.startProcess("debuggee", pbDebuggee,
-                    s -> debuggeeOutput.add(s),  // output consumer
-                    s -> {  // warm-up predicate
-                        Matcher m = listenRegexp.matcher(s);
-                        if (!m.matches()) {
-                            return false;
-                        }
-                        debuggeeListen[0] = m.group(1);
-                        debuggeeListen[1] = m.group(2);
-                        return true;
-                    },
-                    30, TimeUnit.SECONDS);
-        } catch (IOException | InterruptedException | TimeoutException ex) {
-            throw new RuntimeException("failed to launch debuggee", ex);
-        }
+        debuggee = Debuggee.launcher(launchOptions.debuggeeClass)
+                .addOptions(launchOptions.debuggeeOptions)
+                .launch();
 
         // launch jdb
         try {
-            jdb = new Jdb("-connect", "com.sun.jdi.SocketAttach:port=" + debuggeeListen[1]);
+            jdb = new Jdb("-connect", "com.sun.jdi.SocketAttach:port=" + debuggee.getAddress());
         } catch (Throwable ex) {
             // terminate debuggee if something went wrong
-            debuggee.destroy();
+            debuggee.shutdown();
             throw ex;
         }
         // wait while jdb is initialized
@@ -158,15 +129,9 @@
             jdb.shutdown();
         }
         // shutdown debuggee
-        if (debuggee != null && debuggee.isAlive()) {
-            try {
-                debuggee.waitFor(Utils.adjustTimeout(10), TimeUnit.SECONDS);
-            } catch (InterruptedException e) {
-                // ignore
-            } finally {
-                if (debuggee.isAlive()) {
-                    debuggee.destroy();
-                }
+        if (debuggee != null) {
+            if (!debuggee.waitFor(10, TimeUnit.SECONDS)) {
+                debuggee.shutdown();
             }
         }
     }