| /* | |
| * Copyright (c) 2017, 2021, 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 | |
| * @library /test/lib | |
| * @modules java.management java.base/java.io:+open java.base/java.net:+open | |
| * @run main/othervm UnreferencedSockets | |
| * @run main/othervm -Djava.net.preferIPv4Stack=true UnreferencedSockets | |
| * @summary Check that unreferenced sockets are closed | |
| */ | |
| import java.io.FileDescriptor; | |
| import java.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.lang.management.ManagementFactory; | |
| import java.lang.management.OperatingSystemMXBean; | |
| import java.lang.ref.ReferenceQueue; | |
| import java.lang.ref.WeakReference; | |
| import java.lang.reflect.Field; | |
| import java.io.IOException; | |
| import java.net.InetAddress; | |
| import java.net.ServerSocket; | |
| import java.net.Socket; | |
| import java.net.SocketImpl; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.nio.file.Paths; | |
| import java.util.ArrayDeque; | |
| import java.util.List; | |
| import java.util.Optional; | |
| import java.util.concurrent.TimeUnit; | |
| import com.sun.management.UnixOperatingSystemMXBean; | |
| import jdk.test.lib.net.IPSupport; | |
| public class UnreferencedSockets { | |
| /** | |
| * The set of sockets we have to check up on. | |
| */ | |
| final static ArrayDeque<NamedWeak> pendingSockets = new ArrayDeque<>(100); | |
| /** | |
| * Queued sockets when they are unreferenced. | |
| */ | |
| final static ReferenceQueue<Object> pendingQueue = new ReferenceQueue<>(); | |
| // Server to echo a stream | |
| static class Server implements Runnable { | |
| ServerSocket ss; | |
| Server(InetAddress address) throws IOException { | |
| ss = new ServerSocket(0, 0, address); | |
| pendingSockets.add(new NamedWeak(ss, pendingQueue, "serverSocket")); | |
| extractRefs(ss, "serverSocket"); | |
| } | |
| public int localPort() { | |
| return ss.getLocalPort(); | |
| } | |
| public void run() { | |
| try { | |
| Socket s = ss.accept(); | |
| pendingSockets.add(new NamedWeak(s, pendingQueue, "acceptedSocket")); | |
| extractRefs(s, "acceptedSocket"); | |
| InputStream in = s.getInputStream(); | |
| int b = in.read(); | |
| OutputStream out = s.getOutputStream(); | |
| out.write(b); | |
| // do NOT close but 'forget' the socket reference | |
| out = null; | |
| in = null; | |
| s = null; | |
| } catch (Exception ioe) { | |
| ioe.printStackTrace(); | |
| } finally { | |
| try { | |
| ss.close(); | |
| ss = null; | |
| } catch (IOException x) { | |
| x.printStackTrace(); | |
| } | |
| } | |
| } | |
| } | |
| public static void main(String args[]) throws Exception { | |
| IPSupport.throwSkippedExceptionIfNonOperational(); | |
| InetAddress lba = InetAddress.getLoopbackAddress(); | |
| // Create and close a ServerSocket to warm up the FD count for side effects. | |
| try (ServerSocket s = new ServerSocket(0, 0, lba)) { | |
| // no-op; close immediately | |
| s.getLocalPort(); // no-op | |
| } | |
| long fdCount0 = getFdCount(); | |
| listProcFD(); | |
| // start a server | |
| Server svr = new Server(lba); | |
| Thread thr = new Thread(svr); | |
| thr.start(); | |
| Socket s = new Socket(lba, svr.localPort()); | |
| pendingSockets.add(new NamedWeak(s, pendingQueue, "clientSocket")); | |
| extractRefs(s, "clientSocket"); | |
| OutputStream out = s.getOutputStream(); | |
| out.write('x'); | |
| out.flush(); | |
| InputStream in = s.getInputStream(); | |
| int b = in.read(); // wait for it back | |
| System.out.printf(" data sent and received%n"); | |
| // Do NOT close the Socket; forget it | |
| Object ref; | |
| int loops = 20; | |
| while (!pendingSockets.isEmpty() && loops-- > 0) { | |
| ref = pendingQueue.remove(1000L); | |
| if (ref != null) { | |
| pendingSockets.remove(ref); | |
| System.out.printf(" ref queued: %s, remaining: %d%n", ref, pendingSockets.size()); | |
| } else { | |
| s = null; | |
| out = null; | |
| in = null; | |
| System.gc(); | |
| } | |
| } | |
| thr.join(); | |
| // List the open file descriptors | |
| long fdCount = getFdCount(); | |
| System.out.printf("Initial fdCount: %d, final fdCount: %d%n", fdCount0, fdCount); | |
| listProcFD(); | |
| if (loops == 0) { | |
| throw new AssertionError("Not all references reclaimed"); | |
| } | |
| } | |
| // Get the count of open file descriptors, or -1 if not available | |
| private static long getFdCount() { | |
| OperatingSystemMXBean mxBean = ManagementFactory.getOperatingSystemMXBean(); | |
| return (mxBean instanceof UnixOperatingSystemMXBean) | |
| ? ((UnixOperatingSystemMXBean) mxBean).getOpenFileDescriptorCount() | |
| : -1L; | |
| } | |
| // Reflect to find references in the socket implementation that will be gc'd | |
| private static void extractRefs(Socket s, String name) { | |
| try { | |
| Field socketImplField = Socket.class.getDeclaredField("impl"); | |
| socketImplField.setAccessible(true); | |
| Object socketImpl = socketImplField.get(s); | |
| Field fileDescriptorField = SocketImpl.class.getDeclaredField("fd"); | |
| fileDescriptorField.setAccessible(true); | |
| FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl); | |
| extractRefs(fileDescriptor, name); | |
| } catch (NoSuchFieldException | IllegalAccessException ex) { | |
| ex.printStackTrace(); | |
| throw new AssertionError("missing field", ex); | |
| } | |
| } | |
| private static void extractRefs(FileDescriptor fileDescriptor, String name) { | |
| Object cleanup = null; | |
| int rawfd = -1; | |
| try { | |
| if (fileDescriptor != null) { | |
| Field fd1Field = FileDescriptor.class.getDeclaredField("fd"); | |
| fd1Field.setAccessible(true); | |
| rawfd = fd1Field.getInt(fileDescriptor); | |
| Field cleanupfdField = FileDescriptor.class.getDeclaredField("cleanup"); | |
| cleanupfdField.setAccessible(true); | |
| cleanup = cleanupfdField.get(fileDescriptor); | |
| pendingSockets.add(new NamedWeak(fileDescriptor, pendingQueue, | |
| name + "::fileDescriptor: " + rawfd)); | |
| pendingSockets.add(new NamedWeak(cleanup, pendingQueue, name + "::fdCleanup: " + rawfd)); | |
| } | |
| } catch (NoSuchFieldException | IllegalAccessException ex) { | |
| ex.printStackTrace(); | |
| throw new AssertionError("missing field", ex); | |
| } finally { | |
| System.out.print(String.format(" fd: %s, fd: %d, cleanup: %s%n", | |
| fileDescriptor, rawfd, cleanup)); | |
| } | |
| } | |
| private static void extractRefs(ServerSocket s, String name) { | |
| try { | |
| Field socketImplField = ServerSocket.class.getDeclaredField("impl"); | |
| socketImplField.setAccessible(true); | |
| Object socketImpl = socketImplField.get(s); | |
| Field fileDescriptorField = SocketImpl.class.getDeclaredField("fd"); | |
| fileDescriptorField.setAccessible(true); | |
| FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl); | |
| Field fdField = FileDescriptor.class.getDeclaredField("fd"); | |
| fdField.setAccessible(true); | |
| int rawfd = fdField.getInt(fileDescriptor); | |
| Field cleanupField = FileDescriptor.class.getDeclaredField("cleanup"); | |
| cleanupField.setAccessible(true); | |
| Object cleanup = cleanupField.get(fileDescriptor); | |
| System.out.print(String.format(" fd: %s, fd: %d, cleanup: %s, socket: %s%n", | |
| fileDescriptor, rawfd, cleanup, s)); | |
| pendingSockets.add(new NamedWeak(fileDescriptor, pendingQueue, | |
| name + "::fileDescriptor: " + rawfd)); | |
| pendingSockets.add(new NamedWeak(cleanup, pendingQueue, name + "::fdCleanup: " + rawfd)); | |
| } catch (NoSuchFieldException | IllegalAccessException ex) { | |
| ex.printStackTrace(); | |
| throw new AssertionError("missing field", ex); | |
| } | |
| } | |
| /** | |
| * Method to list the open file descriptors (if supported by the 'lsof' command). | |
| */ | |
| static void listProcFD() { | |
| List<String> lsofDirs = List.of("/usr/bin", "/usr/sbin"); | |
| Optional<Path> lsof = lsofDirs.stream() | |
| .map(s -> Paths.get(s, "lsof")) | |
| .filter(f -> Files.isExecutable(f)) | |
| .findFirst(); | |
| lsof.ifPresent(exe -> { | |
| try { | |
| System.out.printf("Open File Descriptors:%n"); | |
| long pid = ProcessHandle.current().pid(); | |
| ProcessBuilder pb = new ProcessBuilder(exe.toString(), "-p", Integer.toString((int) pid)); | |
| pb.inheritIO(); | |
| Process p = pb.start(); | |
| p.waitFor(10, TimeUnit.SECONDS); | |
| } catch (IOException | InterruptedException ie) { | |
| ie.printStackTrace(); | |
| } | |
| }); | |
| } | |
| // Simple class to identify which refs have been queued | |
| static class NamedWeak extends WeakReference<Object> { | |
| private final String name; | |
| NamedWeak(Object o, ReferenceQueue<Object> queue, String name) { | |
| super(o, queue); | |
| this.name = name; | |
| } | |
| public String toString() { | |
| return name + "; " + super.toString(); | |
| } | |
| } | |
| } |