| /* | |
| * Copyright (c) 2018, 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 | |
| * java.base/sun.net java.base/sun.nio.ch:+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.lang.reflect.Method; | |
| import java.net.DatagramPacket; | |
| import java.net.DatagramSocket; | |
| import java.net.DatagramSocketImpl; | |
| import java.net.InetAddress; | |
| import java.net.InetSocketAddress; | |
| import java.net.MulticastSocket; | |
| import java.net.UnknownHostException; | |
| import java.nio.channels.DatagramChannel; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.nio.file.Paths; | |
| import java.security.AccessController; | |
| import java.security.PrivilegedAction; | |
| import java.util.ArrayDeque; | |
| import java.util.List; | |
| import java.util.Optional; | |
| import java.util.concurrent.Phaser; | |
| import java.util.concurrent.TimeUnit; | |
| import jdk.test.lib.net.IPSupport; | |
| import com.sun.management.UnixOperatingSystemMXBean; | |
| import sun.net.NetProperties; | |
| public class UnreferencedMulticastSockets { | |
| /** | |
| * The set of sockets we have to check up on. | |
| */ | |
| final static ArrayDeque<NamedWeak> pendingSockets = new ArrayDeque<>(5); | |
| /** | |
| * Queued objects when they are unreferenced. | |
| */ | |
| final static ReferenceQueue<Object> pendingQueue = new ReferenceQueue<>(); | |
| // Server to echo a datagram packet | |
| static class Server implements Runnable { | |
| MulticastSocket ss; | |
| final int port; | |
| final Phaser phaser = new Phaser(2); | |
| Server() throws IOException { | |
| InetAddress loopback = InetAddress.getLoopbackAddress(); | |
| InetSocketAddress serverAddress = | |
| new InetSocketAddress(loopback, 0); | |
| ss = new MulticastSocket(serverAddress); | |
| port = ss.getLocalPort(); | |
| 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.getLoopbackAddress(); | |
| return localhost; | |
| } | |
| int getPort() { | |
| return port; | |
| } | |
| // 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); | |
| System.out.printf("Server: ping received from: %s%n", p.getSocketAddress()); | |
| phaser.arriveAndAwaitAdvance(); // await the client... | |
| buffer[0] += 1; | |
| System.out.printf("Server: sending echo to: %s%n", p.getSocketAddress()); | |
| ss.send(p); // send back +1 | |
| System.out.printf("Server: awaiting client%n"); | |
| phaser.arriveAndAwaitAdvance(); // await the client... | |
| // do NOT close but 'forget' the socket reference | |
| System.out.printf("Server: forgetting socket...%n"); | |
| ss = null; | |
| } catch (Throwable ioe) { | |
| ioe.printStackTrace(); | |
| } | |
| } | |
| } | |
| public static void main(String args[]) throws Exception { | |
| IPSupport.throwSkippedExceptionIfNonOperational(); | |
| InetSocketAddress clientAddress = | |
| new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); | |
| // Create and close a MulticastSocket to warm up the FD count for side effects. | |
| try (MulticastSocket s = new MulticastSocket(clientAddress)) { | |
| // 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(); | |
| // It is possible under some circumstances that the client | |
| // might get bound to the same port than the server: this | |
| // would make the test fail - so if this happen we try to | |
| // bind to a specific port by incrementing the server port. | |
| MulticastSocket client = null; | |
| int serverPort = svr.getPort(); | |
| int maxtries = 20; | |
| for (int i = 0; i < maxtries; i++) { | |
| try { | |
| System.out.printf("Trying to bind client to: %s%n", clientAddress); | |
| client = new MulticastSocket(clientAddress); | |
| if (client.getLocalPort() != svr.getPort()) break; | |
| client.close(); | |
| } catch (IOException x) { | |
| System.out.printf("Couldn't create client after %d attempts: %s%n", i, x); | |
| if (i == maxtries) throw x; | |
| } | |
| if (i == maxtries) { | |
| String msg = String.format("Couldn't create client after %d attempts", i); | |
| System.out.println(msg); | |
| throw new AssertionError(msg); | |
| } | |
| clientAddress = new InetSocketAddress(clientAddress.getAddress(), serverPort + i); | |
| } | |
| System.out.printf(" client bound port: %s:%d%n", | |
| client.getLocalAddress(), client.getLocalPort()); | |
| 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); | |
| System.out.printf(" ping sent to: %s:%d%n", svr.getHost(), svr.getPort()); | |
| svr.phaser.arriveAndAwaitAdvance(); // wait until the server has received its packet | |
| 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]); | |
| } | |
| svr.phaser.arriveAndAwaitAdvance(); // let the server null out its socket | |
| // 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; | |
| } | |
| private static boolean usePlainDatagramSocketImpl() { | |
| PrivilegedAction<String> pa = () -> NetProperties.get("jdk.net.usePlainDatagramSocketImpl"); | |
| String s = AccessController.doPrivileged(pa); | |
| return (s != null) && (s.isEmpty() || s.equalsIgnoreCase("true")); | |
| } | |
| // Reflect to find references in the datagram implementation that will be gc'd | |
| private static void extractRefs(DatagramSocket s, String name) { | |
| try { | |
| Field datagramSocketField = DatagramSocket.class.getDeclaredField("delegate"); | |
| datagramSocketField.setAccessible(true); | |
| if (!usePlainDatagramSocketImpl()) { | |
| // MulticastSocket using DatagramSocketAdaptor | |
| Object MulticastSocket = datagramSocketField.get(s); | |
| Method m = DatagramSocket.class.getDeclaredMethod("getChannel"); | |
| m.setAccessible(true); | |
| DatagramChannel datagramChannel = (DatagramChannel) m.invoke(MulticastSocket); | |
| assert datagramChannel.getClass() == Class.forName("sun.nio.ch.DatagramChannelImpl"); | |
| Field fileDescriptorField = datagramChannel.getClass().getDeclaredField("fd"); | |
| fileDescriptorField.setAccessible(true); | |
| FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(datagramChannel); | |
| extractRefs(fileDescriptor, name); | |
| } else { | |
| // MulticastSocket using PlainDatagramSocketImpl | |
| Object MulticastSocket = datagramSocketField.get(s); | |
| assert MulticastSocket.getClass() == Class.forName("java.net.NetMulticastSocket"); | |
| Method m = MulticastSocket.getClass().getDeclaredMethod("getImpl"); | |
| m.setAccessible(true); | |
| DatagramSocketImpl datagramSocketImpl = (DatagramSocketImpl) m.invoke(MulticastSocket); | |
| Field fileDescriptorField = DatagramSocketImpl.class.getDeclaredField("fd"); | |
| fileDescriptorField.setAccessible(true); | |
| FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(datagramSocketImpl); | |
| extractRefs(fileDescriptor, name); | |
| } | |
| } catch (Exception 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<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(); | |
| } | |
| } | |
| } |