/* * Copyright (c) 2018, 2020, 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 -Djdk.net.usePlainDatagramSocketImpl 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 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; 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 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); Class socketImplClass = datagramSocketImpl.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(datagramSocketImpl); extractRefs(fileDescriptor1, name + "::twoStacksFd1"); } else { System.out.printf("socketImpl class name not matched: %s != %s%n", socketImplClass.getName(), "java.net.TwoStacksPlainDatagramSocketImpl"); } } } 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 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(); } } }