# HG changeset patch # User rriggs # Date 1517599027 18000 # Node ID 7c12219870fdbc0eab057bb96abbe1d72a2cc98f # Parent 0454688cc319137caa809c14374f907f4a2b9b9d 8195059: Update java.net Socket and DatagramSocket implementations to use Cleaner Reviewed-by: chegar, plevart diff -r 0454688cc319 -r 7c12219870fd make/mapfiles/libnet/mapfile-vers --- a/make/mapfiles/libnet/mapfile-vers Fri Feb 02 10:32:59 2018 -0800 +++ b/make/mapfiles/libnet/mapfile-vers Fri Feb 02 14:17:07 2018 -0500 @@ -1,5 +1,5 @@ # -# Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1997, 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 @@ -89,6 +89,7 @@ Java_java_net_PlainDatagramSocketImpl_setTimeToLive; Java_java_net_AbstractPlainSocketImpl_isReusePortAvailable0; Java_java_net_AbstractPlainDatagramSocketImpl_isReusePortAvailable0; + Java_java_net_SocketCleanable_cleanupClose0; Java_jdk_net_Sockets_isReusePortAvailable0; Java_sun_net_PortConfig_getUpper0; Java_sun_net_PortConfig_getLower0; diff -r 0454688cc319 -r 7c12219870fd src/java.base/share/classes/java/net/AbstractPlainDatagramSocketImpl.java --- a/src/java.base/share/classes/java/net/AbstractPlainDatagramSocketImpl.java Fri Feb 02 10:32:59 2018 -0800 +++ b/src/java.base/share/classes/java/net/AbstractPlainDatagramSocketImpl.java Fri Feb 02 14:17:07 2018 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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 @@ -26,11 +26,11 @@ import java.io.FileDescriptor; import java.io.IOException; -import java.security.AccessController; -import sun.net.ResourceManager; +import java.util.Collections; +import java.util.HashSet; import java.util.Set; -import java.util.HashSet; -import java.util.Collections; + +import sun.net.ResourceManager; import sun.security.action.GetPropertyAction; /** @@ -115,6 +115,7 @@ fd = new FileDescriptor(); try { datagramSocketCreate(); + SocketCleanable.register(fd); } catch (SocketException ioe) { ResourceManager.afterUdpClose(); fd = null; @@ -265,6 +266,7 @@ */ protected void close() { if (fd != null) { + SocketCleanable.unregister(fd); datagramSocketClose(); ResourceManager.afterUdpClose(); fd = null; @@ -275,11 +277,6 @@ return (fd == null) ? true : false; } - @SuppressWarnings("deprecation") - protected void finalize() { - close(); - } - /** * set a value - since we only support (setting) binary options * here, o must be a Boolean diff -r 0454688cc319 -r 7c12219870fd src/java.base/share/classes/java/net/AbstractPlainSocketImpl.java --- a/src/java.base/share/classes/java/net/AbstractPlainSocketImpl.java Fri Feb 02 10:32:59 2018 -0800 +++ b/src/java.base/share/classes/java/net/AbstractPlainSocketImpl.java Fri Feb 02 14:17:07 2018 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -25,17 +25,18 @@ package java.net; +import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.FileDescriptor; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import sun.net.ConnectionResetException; import sun.net.NetHooks; import sun.net.ResourceManager; -import java.util.Set; -import java.util.HashSet; -import java.util.Collections; /** * Default Socket Implementation. This implementation does @@ -136,6 +137,7 @@ fd = new FileDescriptor(); try { socketCreate(false); + SocketCleanable.register(fd); } catch (IOException ioe) { ResourceManager.afterUdpClose(); fd = null; @@ -144,6 +146,7 @@ } else { fd = new FileDescriptor(); socketCreate(true); + SocketCleanable.register(fd); } if (socket != null) socket.setCreated(); @@ -643,14 +646,6 @@ socketSendUrgentData (data); } - /** - * Cleans up if the user forgets to close it. - */ - @SuppressWarnings("deprecation") - protected void finalize() throws IOException { - close(); - } - /* * "Acquires" and returns the FileDescriptor for this impl * @@ -748,6 +743,7 @@ * Close the socket (and release the file descriptor). */ protected void socketClose() throws IOException { + SocketCleanable.unregister(fd); socketClose0(false); } diff -r 0454688cc319 -r 7c12219870fd src/java.base/share/classes/java/net/ServerSocket.java --- a/src/java.base/share/classes/java/net/ServerSocket.java Fri Feb 02 10:32:59 2018 -0800 +++ b/src/java.base/share/classes/java/net/ServerSocket.java Fri Feb 02 14:17:07 2018 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -549,6 +549,7 @@ si.address = new InetAddress(); si.fd = new FileDescriptor(); getImpl().accept(si); + SocketCleanable.register(si.fd); // raw fd has been set SecurityManager security = System.getSecurityManager(); if (security != null) { diff -r 0454688cc319 -r 7c12219870fd src/java.base/share/classes/java/net/SocketCleanable.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/net/SocketCleanable.java Fri Feb 02 14:17:07 2018 -0500 @@ -0,0 +1,107 @@ +/* + * 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. 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 java.net; + +import jdk.internal.misc.JavaIOFileDescriptorAccess; +import jdk.internal.misc.SharedSecrets; +import jdk.internal.ref.CleanerFactory; +import jdk.internal.ref.PhantomCleanable; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.ref.Cleaner; + + +/** + * Cleanup for a socket/datagramsocket FileDescriptor when it becomes phantom reachable. + * Create a cleanup if the raw fd != -1. Windows closes sockets using the fd. + * Subclassed from {@code PhantomCleanable} so that {@code clear} can be + * called to disable the cleanup when the socket fd is closed by any means + * other than calling {@link FileDescriptor#close}. + * Otherwise, it would incorrectly close the handle or fd after it has been reused. + */ +final class SocketCleanable extends PhantomCleanable { + + // Access to FileDescriptor internals + private static final JavaIOFileDescriptorAccess fdAccess = + SharedSecrets.getJavaIOFileDescriptorAccess(); + + // Native function to call NET_SocketClose(fd) + private static native void cleanupClose0(int fd) throws IOException; + + // The raw fd to close + private final int fd; + + /** + * Register a socket specific Cleanable with the FileDescriptor + * if the FileDescriptor is non-null and the raw fd is != -1. + * + * @param fdo the FileDescriptor; may be null + */ + static void register(FileDescriptor fdo) { + if (fdo != null) { + int fd = fdAccess.get(fdo); + if (fd != -1) { + fdAccess.registerCleanup(fdo, + new SocketCleanable(fdo, CleanerFactory.cleaner(), fd)); + } + } + } + + /** + * Unregister a Cleanable from the FileDescriptor. + * @param fdo the FileDescriptor; may be null + */ + static void unregister(FileDescriptor fdo) { + if (fdo != null) { + fdAccess.unregisterCleanup(fdo); + } + } + + /** + * Constructor for a phantom cleanable reference. + * + * @param obj the object to monitor + * @param cleaner the cleaner + * @param fd file descriptor to close + */ + private SocketCleanable(Object obj, Cleaner cleaner, int fd) { + super(obj, cleaner); + this.fd = fd; + } + + /** + * Close the native handle or fd. + */ + @Override + protected void performCleanup() { + try { + cleanupClose0(fd); + } catch (IOException ioe) { + throw new UncheckedIOException("close", ioe); + } + } +} diff -r 0454688cc319 -r 7c12219870fd src/java.base/share/classes/jdk/internal/misc/JavaIOFileDescriptorAccess.java --- a/src/java.base/share/classes/jdk/internal/misc/JavaIOFileDescriptorAccess.java Fri Feb 02 10:32:59 2018 -0800 +++ b/src/java.base/share/classes/jdk/internal/misc/JavaIOFileDescriptorAccess.java Fri Feb 02 14:17:07 2018 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 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 @@ -27,6 +27,8 @@ import java.io.FileDescriptor; import java.io.IOException; +import jdk.internal.ref.PhantomCleanable; + /* * @author Chris Hegarty */ @@ -38,6 +40,8 @@ public boolean getAppend(FileDescriptor fdo); public void close(FileDescriptor fdo) throws IOException; public void registerCleanup(FileDescriptor fdo); + public void registerCleanup(FileDescriptor fdo, PhantomCleanable cleanable); + public void unregisterCleanup(FileDescriptor fdo); // Only valid on Windows public void setHandle(FileDescriptor fdo, long handle); diff -r 0454688cc319 -r 7c12219870fd src/java.base/unix/classes/java/io/FileDescriptor.java --- a/src/java.base/unix/classes/java/io/FileDescriptor.java Fri Feb 02 10:32:59 2018 -0800 +++ b/src/java.base/unix/classes/java/io/FileDescriptor.java Fri Feb 02 14:17:07 2018 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -91,6 +91,14 @@ fdo.registerCleanup(); } + public void registerCleanup(FileDescriptor fdo, PhantomCleanable cleanup) { + fdo.registerCleanup(cleanup); + } + + public void unregisterCleanup(FileDescriptor fdo) { + fdo.unregisterCleanup(); + } + public void setHandle(FileDescriptor fdo, long handle) { throw new UnsupportedOperationException(); } @@ -105,7 +113,7 @@ /** * Cleanup in case FileDescriptor is not explicitly closed. */ - private FDCleanup cleanup; + private PhantomCleanable cleanup; /** * Constructs an (invalid) FileDescriptor @@ -206,16 +214,44 @@ } /** - * Register a cleanup for the current raw fd. + * Register a cleanup for the current handle. * Used directly in java.io and indirectly via fdAccess. - * The cleanup should be registered after the fd is set in the FileDescriptor. + * The cleanup should be registered after the handle is set in the FileDescriptor. */ @SuppressWarnings("unchecked") - synchronized void registerCleanup() { + void registerCleanup() { + registerCleanup(null); + } + + /** + * Register a cleanup for the current handle. + * Used directly in java.io and indirectly via fdAccess. + * The cleanup should be registered after the handle is set in the FileDescriptor. + * @param newCleanable a PhantomCleanable to register + */ + @SuppressWarnings("unchecked") + synchronized void registerCleanup(PhantomCleanable newCleanable) { if (cleanup != null) { cleanup.clear(); } - cleanup = FDCleanup.create(this); + cleanup = (newCleanable == null) ? FDCleanup.create(this) : newCleanable; + } + + /** + * Unregister a cleanup for the current raw fd. + * Used directly in java.io and indirectly via fdAccess. + * Normally {@link #close()} should be used except in cases where + * it is certain the caller will close the raw fd and the cleanup + * must not close the raw fd. {@link #unregisterCleanup()} must be + * called before the raw fd is closed to prevent a race that makes + * it possible for the fd to be reallocated to another use and later + * the cleanup might be invoked. + */ + synchronized void unregisterCleanup() { + if (cleanup != null) { + cleanup.clear(); + } + cleanup = null; } /** diff -r 0454688cc319 -r 7c12219870fd src/java.base/unix/native/libnet/SocketImpl.c --- a/src/java.base/unix/native/libnet/SocketImpl.c Fri Feb 02 10:32:59 2018 -0800 +++ b/src/java.base/unix/native/libnet/SocketImpl.c Fri Feb 02 14:17:07 2018 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -27,6 +27,7 @@ #include #include "net_util.h" +#include "java_net_SocketCleanable.h" JNIEXPORT jboolean JNICALL Java_java_net_AbstractPlainSocketImpl_isReusePortAvailable0(JNIEnv* env, jclass c1) @@ -45,3 +46,15 @@ { return (reuseport_available()) ? JNI_TRUE : JNI_FALSE; } + +/* + * Class: java_net_SocketCleanable + * Method: cleanupClose0 + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_java_net_SocketCleanable_cleanupClose0(JNIEnv *env, jclass c1, jint fd) +{ + NET_SocketClose(fd); +} + diff -r 0454688cc319 -r 7c12219870fd src/java.base/windows/classes/java/io/FileDescriptor.java --- a/src/java.base/windows/classes/java/io/FileDescriptor.java Fri Feb 02 10:32:59 2018 -0800 +++ b/src/java.base/windows/classes/java/io/FileDescriptor.java Fri Feb 02 14:17:07 2018 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -90,7 +90,15 @@ } public void registerCleanup(FileDescriptor fdo) { - fdo.registerCleanup(); + fdo.registerCleanup(null); + } + + public void registerCleanup(FileDescriptor fdo, PhantomCleanable cleanup) { + fdo.registerCleanup(cleanup); + } + + public void unregisterCleanup(FileDescriptor fdo) { + fdo.unregisterCleanup(); } public void setHandle(FileDescriptor fdo, long handle) { @@ -107,7 +115,7 @@ /** * Cleanup in case FileDescriptor is not explicitly closed. */ - private FDCleanup cleanup; + private PhantomCleanable cleanup; /** * Constructs an (invalid) FileDescriptor @@ -217,11 +225,39 @@ * The cleanup should be registered after the handle is set in the FileDescriptor. */ @SuppressWarnings("unchecked") - synchronized void registerCleanup() { + void registerCleanup() { + registerCleanup(null); + } + + /** + * Register a cleanup for the current handle. + * Used directly in java.io and indirectly via fdAccess. + * The cleanup should be registered after the handle is set in the FileDescriptor. + * @param newCleanable a PhantomCleanable to register + */ + @SuppressWarnings("unchecked") + synchronized void registerCleanup(PhantomCleanable newCleanable) { if (cleanup != null) { cleanup.clear(); } - cleanup = FDCleanup.create(this); + cleanup = (newCleanable == null) ? FDCleanup.create(this) : newCleanable; + } + + /** + * Unregister a cleanup for the current raw fd. + * Used directly in java.io and indirectly via fdAccess. + * Normally {@link #close()} should be used except in cases where + * it is certain the caller will close the raw fd and the cleanup + * must not close the raw fd. {@link #unregisterCleanup()} must be + * called before the raw fd is closed to prevent a race that makes + * it possible for the fd to be reallocated to another use and later + * the cleanup might be invoked. + */ + synchronized void unregisterCleanup() { + if (cleanup != null) { + cleanup.clear(); + } + cleanup = null; } /** @@ -319,16 +355,21 @@ /** * Cleanup for a FileDescriptor when it becomes phantom reachable. * Create a cleanup if handle != -1. + * Windows closes files using handles and sockets via the fd. + * Network FileDescriptors using socket fd must provide their + * own PhantomCleanable to {@link #registerCleanup}. + * This implementation only clears thehandles. + *

* Subclassed from {@code PhantomCleanable} so that {@code clear} can be - * called to disable the cleanup when the fd is closed by any means other + * called to disable the cleanup when the handle is closed by any means other * than calling {@link FileDescriptor#close}. - * Otherwise, it may close the handle after it has been reused. + * Otherwise, it may incorrectly close the handle after it has been reused. */ static final class FDCleanup extends PhantomCleanable { private final long handle; static FDCleanup create(FileDescriptor fdo) { - return fdo.handle == -1 + return fdo.handle == -1L ? null : new FDCleanup(fdo, CleanerFactory.cleaner(), fdo.handle); } diff -r 0454688cc319 -r 7c12219870fd src/java.base/windows/classes/java/net/TwoStacksPlainDatagramSocketImpl.java --- a/src/java.base/windows/classes/java/net/TwoStacksPlainDatagramSocketImpl.java Fri Feb 02 10:32:59 2018 -0800 +++ b/src/java.base/windows/classes/java/net/TwoStacksPlainDatagramSocketImpl.java Fri Feb 02 14:17:07 2018 -0500 @@ -86,6 +86,7 @@ fd1 = new FileDescriptor(); try { super.create(); + SocketCleanable.register(fd1); } catch (SocketException e) { fd1 = null; throw e; @@ -160,6 +161,8 @@ protected void close() { if (fd != null || fd1 != null) { + SocketCleanable.unregister(fd); + SocketCleanable.unregister(fd1); datagramSocketClose(); ResourceManager.afterUdpClose(); fd = null; diff -r 0454688cc319 -r 7c12219870fd src/java.base/windows/native/libnet/SocketImpl.c --- a/src/java.base/windows/native/libnet/SocketImpl.c Fri Feb 02 10:32:59 2018 -0800 +++ b/src/java.base/windows/native/libnet/SocketImpl.c Fri Feb 02 14:17:07 2018 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -24,6 +24,8 @@ */ #include +#include "net_util.h" +#include "java_net_SocketCleanable.h" JNIEXPORT jboolean JNICALL Java_java_net_AbstractPlainSocketImpl_isReusePortAvailable0(JNIEnv* env, jclass c1) @@ -45,3 +47,15 @@ // SO_REUSEPORT is not supported on Windows return JNI_FALSE; } + +/* + * Class: java_net_SocketCleanable + * Method: cleanupClose0 + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_java_net_SocketCleanable_cleanupClose0(JNIEnv *env, jclass c1, jint fd) +{ + NET_SocketClose(fd); +} + diff -r 0454688cc319 -r 7c12219870fd test/jdk/java/net/DatagramSocket/SendSize.java --- a/test/jdk/java/net/DatagramSocket/SendSize.java Fri Feb 02 10:32:59 2018 -0800 +++ b/test/jdk/java/net/DatagramSocket/SendSize.java Fri Feb 02 14:17:07 2018 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -24,7 +24,7 @@ /* @test * * @bug 4095393 - * + * @test main SendSize * @summary this tests a regression in 1.2, beta 2 and earlier where * the DatagramPackets sent the entire buffer, not the buffer length * of the packet. @@ -43,8 +43,12 @@ public static void main(String[] args) throws Exception { DatagramSocket serverSocket = new DatagramSocket(); - new ServerThread(serverSocket).start(); - new ClientThread(serverSocket.getLocalPort()).start(); + Thread server = new ServerThread(serverSocket); + server.start(); + Thread client = new ClientThread(serverSocket.getLocalPort()); + client.start(); + server.join(); + client.join(); } static class ServerThread extends Thread { @@ -58,24 +62,26 @@ try { System.err.println("started server thread: " + server); byte[] buf = new byte[1024]; - DatagramPacket receivePacket = new DatagramPacket(buf, - buf.length); - server.receive(receivePacket); - int len = receivePacket.getLength(); - switch(len) { - case packetLength: - System.out.println("receive length is: " + len); - break; - default: - throw new RuntimeException( - "receive length is: " + len + - ", send length: " + packetLength + - ", buffer length: " + buf.length); + for (int i = 0; i < 10; i++) { + DatagramPacket receivePacket = new DatagramPacket(buf, + buf.length); + server.receive(receivePacket); + int len = receivePacket.getLength(); + switch (len) { + case packetLength: + System.out.println("receive length is: " + len); + break; + default: + throw new RuntimeException( + "receive length is: " + len + + ", send length: " + packetLength + + ", buffer length: " + buf.length); + } } return; } catch (Exception e) { e.printStackTrace(); - throw new RuntimeException("caugth: " + e); + throw new RuntimeException("caught: " + e); } finally { if (server != null) { server.close(); } } diff -r 0454688cc319 -r 7c12219870fd test/jdk/java/net/DatagramSocket/UnreferencedDatagramSockets.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/net/DatagramSocket/UnreferencedDatagramSockets.java Fri Feb 02 14:17:07 2018 -0500 @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2017, 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. + */ + +/** + * @test + * @modules java.management java.base/java.io:+open java.base/java.net:+open + * @run main/othervm UnreferencedDatagramSockets + * @run main/othervm -Djava.net.preferIPv4Stack=true UnreferencedDatagramSockets + * @summary Check that unreferenced datagram sockets are closed + */ + +import java.io.FileDescriptor; +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.DatagramPacket; +import java.net.DatagramSocket; +import java.net.DatagramSocketImpl; +import java.net.InetAddress; +import java.net.UnknownHostException; +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; + +public class UnreferencedDatagramSockets { + + /** + * The set of sockets we have to check up on. + */ + final static ArrayDeque pendingSockets = new ArrayDeque<>(5); + + /** + * Queued objects when they are unreferenced. + */ + final static ReferenceQueue pendingQueue = new ReferenceQueue<>(); + + // Server to echo a datagram packet + static class Server implements Runnable { + + DatagramSocket ss; + + Server() throws IOException { + ss = new DatagramSocket(0); + System.out.printf(" DatagramServer addr: %s: %d%n", + this.getHost(), this.getPort()); + pendingSockets.add(new NamedWeak(ss, pendingQueue, "serverDatagramSocket")); + extractRefs(ss, "serverDatagramSocket"); + } + + InetAddress getHost() throws UnknownHostException { + InetAddress localhost = InetAddress.getByName("localhost"); //.getLocalHost(); + return localhost; + } + + int getPort() { + return ss.getLocalPort(); + } + + // Receive a byte and send back a byte + public void run() { + try { + byte[] buffer = new byte[50]; + DatagramPacket p = new DatagramPacket(buffer, buffer.length); + ss.receive(p); + buffer[0] += 1; + ss.send(p); // send back +1 + + // do NOT close but 'forget' the datagram socket reference + ss = null; + } catch (Exception ioe) { + ioe.printStackTrace(); + } + } + } + + public static void main(String args[]) throws Exception { + + // Create and close a DatagramSocket to warm up the FD count for side effects. + try (DatagramSocket s = new DatagramSocket(0)) { + // no-op; close immediately + s.getLocalPort(); // no-op + } + + long fdCount0 = getFdCount(); + listProcFD(); + + // start a server + Server svr = new Server(); + Thread thr = new Thread(svr); + thr.start(); + + DatagramSocket client = new DatagramSocket(0); + client.connect(svr.getHost(), svr.getPort()); + pendingSockets.add(new NamedWeak(client, pendingQueue, "clientDatagramSocket")); + extractRefs(client, "clientDatagramSocket"); + + byte[] msg = new byte[1]; + msg[0] = 1; + DatagramPacket p = new DatagramPacket(msg, msg.length, svr.getHost(), svr.getPort()); + client.send(p); + + p = new DatagramPacket(msg, msg.length); + client.receive(p); + + System.out.printf("echo received from: %s%n", p.getSocketAddress()); + if (msg[0] != 2) { + throw new AssertionError("incorrect data received: expected: 2, actual: " + msg[0]); + } + + // Do NOT close the DatagramSocket; 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 freed: %s, remaining: %d%n", ref, pendingSockets.size()); + } else { + client = null; + p = null; + msg = 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 datagram implementation that will be gc'd + private static void extractRefs(DatagramSocket s, String name) { + try { + + Field socketImplField = DatagramSocket.class.getDeclaredField("impl"); + socketImplField.setAccessible(true); + Object socketImpl = socketImplField.get(s); + + Field fileDescriptorField = DatagramSocketImpl.class.getDeclaredField("fd"); + fileDescriptorField.setAccessible(true); + FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl); + extractRefs(fileDescriptor, name); + + Class socketImplClass = socketImpl.getClass(); + System.out.printf("socketImplClass: %s%n", socketImplClass); + if (socketImplClass.getName().equals("java.net.TwoStacksPlainDatagramSocketImpl")) { + Field fileDescriptor1Field = socketImplClass.getDeclaredField("fd1"); + fileDescriptor1Field.setAccessible(true); + FileDescriptor fileDescriptor1 = (FileDescriptor) fileDescriptor1Field.get(socketImpl); + extractRefs(fileDescriptor1, name + "::twoStacksFd1"); + + } else { + System.out.printf("socketImpl class name not matched: %s != %s%n", + socketImplClass.getName(), "java.net.TwoStacksPlainDatagramSocketImpl"); + } + } 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(" %s:: fd: %s, fd: %d, cleanup: %s%n", + name, fileDescriptor, rawfd, cleanup)); + } + } + + /** + * Method to list the open file descriptors (if supported by the 'lsof' command). + */ + static void listProcFD() { + List lsofDirs = List.of("/usr/bin", "/usr/sbin"); + Optional 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 { + private final String name; + + NamedWeak(Object o, ReferenceQueue queue, String name) { + super(o, queue); + this.name = name; + } + + public String toString() { + return name + "; " + super.toString(); + } + } +} diff -r 0454688cc319 -r 7c12219870fd test/jdk/java/net/MulticastSocket/UnreferencedMulticastSockets.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/net/MulticastSocket/UnreferencedMulticastSockets.java Fri Feb 02 14:17:07 2018 -0500 @@ -0,0 +1,271 @@ +/* + * 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. + */ + +/** + * @test + * @modules java.management java.base/java.io:+open java.base/java.net:+open + * @run main/othervm -Djava.net.preferIPv4Stack=true UnreferencedMulticastSockets + * @run main/othervm UnreferencedMulticastSockets + * @summary Check that unreferenced multicast sockets are closed + */ + +import java.io.FileDescriptor; +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.DatagramPacket; +import java.net.DatagramSocket; +import java.net.DatagramSocketImpl; +import java.net.InetAddress; +import java.net.MulticastSocket; +import java.net.UnknownHostException; +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; + +public class UnreferencedMulticastSockets { + + /** + * The set of sockets we have to check up on. + */ + final static ArrayDeque pendingSockets = new ArrayDeque<>(5); + + /** + * Queued objects when they are unreferenced. + */ + final static ReferenceQueue pendingQueue = new ReferenceQueue<>(); + + // Server to echo a datagram packet + static class Server implements Runnable { + + MulticastSocket ss; + + Server() throws IOException { + ss = new MulticastSocket(0); + System.out.printf(" DatagramServer addr: %s: %d%n", + this.getHost(), this.getPort()); + pendingSockets.add(new NamedWeak(ss, pendingQueue, "serverMulticastSocket")); + extractRefs(ss, "serverMulticastSocket"); + } + + InetAddress getHost() throws UnknownHostException { + InetAddress localhost = InetAddress.getByName("localhost"); //.getLocalHost(); + return localhost; + } + + int getPort() { + return ss.getLocalPort(); + } + + // Receive a byte and send back a byte + public void run() { + try { + byte[] buffer = new byte[50]; + DatagramPacket p = new DatagramPacket(buffer, buffer.length); + ss.receive(p); + buffer[0] += 1; + ss.send(p); // send back +1 + + // do NOT close but 'forget' the socket reference + ss = null; + } catch (Exception ioe) { + ioe.printStackTrace(); + } + } + } + + public static void main(String args[]) throws Exception { + + // Create and close a MulticastSocket to warm up the FD count for side effects. + try (MulticastSocket s = new MulticastSocket(0)) { + // no-op; close immediately + s.getLocalPort(); // no-op + } + + long fdCount0 = getFdCount(); + listProcFD(); + + // start a server + Server svr = new Server(); + Thread thr = new Thread(svr); + thr.start(); + + MulticastSocket client = new MulticastSocket(0); + client.connect(svr.getHost(), svr.getPort()); + pendingSockets.add(new NamedWeak(client, pendingQueue, "clientMulticastSocket")); + extractRefs(client, "clientMulticastSocket"); + + byte[] msg = new byte[1]; + msg[0] = 1; + DatagramPacket p = new DatagramPacket(msg, msg.length, svr.getHost(), svr.getPort()); + client.send(p); + + p = new DatagramPacket(msg, msg.length); + client.receive(p); + + System.out.printf("echo received from: %s%n", p.getSocketAddress()); + if (msg[0] != 2) { + throw new AssertionError("incorrect data received: expected: 2, actual: " + msg[0]); + } + + // Do NOT close the MulticastSocket; 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 freed: %s, remaining: %d%n", ref, pendingSockets.size()); + } else { + client = null; + p = null; + msg = 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(MulticastSocket s, String name) { + try { + + Field socketImplField = DatagramSocket.class.getDeclaredField("impl"); + socketImplField.setAccessible(true); + Object socketImpl = socketImplField.get(s); + + Field fileDescriptorField = DatagramSocketImpl.class.getDeclaredField("fd"); + fileDescriptorField.setAccessible(true); + FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl); + extractRefs(fileDescriptor, name); + + Class socketImplClass = socketImpl.getClass(); + System.out.printf("socketImplClass: %s%n", socketImplClass); + if (socketImplClass.getName().equals("java.net.TwoStacksPlainDatagramSocketImpl")) { + Field fileDescriptor1Field = socketImplClass.getDeclaredField("fd1"); + fileDescriptor1Field.setAccessible(true); + FileDescriptor fileDescriptor1 = (FileDescriptor) fileDescriptor1Field.get(socketImpl); + extractRefs(fileDescriptor1, name + "::twoStacksFd1"); + + } else { + System.out.printf("socketImpl class name not matched: %s != %s%n", + socketImplClass.getName(), "java.net.TwoStacksPlainDatagramSocketImpl"); + } + } 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(" %s:: fd: %s, fd: %d, cleanup: %s%n", + name, fileDescriptor, rawfd, cleanup)); + } + } + + /** + * Method to list the open file descriptors (if supported by the 'lsof' command). + */ + static void listProcFD() { + List lsofDirs = List.of("/usr/bin", "/usr/sbin"); + Optional 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 { + private final String name; + + NamedWeak(Object o, ReferenceQueue queue, String name) { + super(o, queue); + this.name = name; + } + + public String toString() { + return name + "; " + super.toString(); + } + } +} diff -r 0454688cc319 -r 7c12219870fd test/jdk/java/net/ServerSocket/UnreferencedSockets.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/net/ServerSocket/UnreferencedSockets.java Fri Feb 02 14:17:07 2018 -0500 @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2017, 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. + */ + +/** + * @test + * @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.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; + +public class UnreferencedSockets { + + /** + * The set of sockets we have to check up on. + */ + final static ArrayDeque pendingSockets = new ArrayDeque<>(100); + + /** + * Queued sockets when they are unreferenced. + */ + final static ReferenceQueue pendingQueue = new ReferenceQueue<>(); + + // Server to echo a stream + static class Server implements Runnable { + + ServerSocket ss; + + Server() throws IOException { + ss = new ServerSocket(0); + 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 { + + // Create and close a ServerSocket to warm up the FD count for side effects. + try (ServerSocket s = new ServerSocket(0)) { + // no-op; close immediately + s.getLocalPort(); // no-op + } + + long fdCount0 = getFdCount(); + listProcFD(); + + // start a server + Server svr = new Server(); + Thread thr = new Thread(svr); + thr.start(); + + Socket s = new Socket("localhost", 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); + + Class socketImplClass = socketImpl.getClass(); + System.out.printf("socketImplClass: %s%n", socketImplClass); + if (socketImplClass.getClass().getName().equals("java.net.TwoStacksPlainSocketImpl")) { + Field fileDescriptor1Field = socketImplClass.getDeclaredField("fd1"); + fileDescriptor1Field.setAccessible(true); + FileDescriptor fileDescriptor1 = (FileDescriptor) fileDescriptor1Field.get(socketImpl); + extractRefs(fileDescriptor1, name + "::twoStacksFd1"); + + } + } 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 lsofDirs = List.of("/usr/bin", "/usr/sbin"); + Optional 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 { + private final String name; + + NamedWeak(Object o, ReferenceQueue queue, String name) { + super(o, queue); + this.name = name; + } + + public String toString() { + return name + "; " + super.toString(); + } + } +}