Path: blob/master/test/jdk/java/net/MulticastSocket/UnreferencedMulticastSockets.java
41149 views
/*1* Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/**24* @test25* @library /test/lib26* @modules java.management java.base/java.io:+open java.base/java.net:+open27* java.base/sun.net java.base/sun.nio.ch:+open28* @run main/othervm -Djava.net.preferIPv4Stack=true UnreferencedMulticastSockets29* @run main/othervm -Djdk.net.usePlainDatagramSocketImpl UnreferencedMulticastSockets30* @run main/othervm UnreferencedMulticastSockets31* @summary Check that unreferenced multicast sockets are closed32*/3334import java.io.FileDescriptor;35import java.lang.management.ManagementFactory;36import java.lang.management.OperatingSystemMXBean;37import java.lang.ref.ReferenceQueue;38import java.lang.ref.WeakReference;39import java.lang.reflect.Field;40import java.io.IOException;41import java.lang.reflect.Method;42import java.net.DatagramPacket;43import java.net.DatagramSocket;44import java.net.DatagramSocketImpl;45import java.net.InetAddress;46import java.net.InetSocketAddress;47import java.net.MulticastSocket;48import java.net.UnknownHostException;49import java.nio.channels.DatagramChannel;50import java.nio.file.Files;51import java.nio.file.Path;52import java.nio.file.Paths;53import java.security.AccessController;54import java.security.PrivilegedAction;55import java.util.ArrayDeque;56import java.util.List;57import java.util.Optional;58import java.util.concurrent.Phaser;59import java.util.concurrent.TimeUnit;6061import jdk.test.lib.net.IPSupport;6263import com.sun.management.UnixOperatingSystemMXBean;64import sun.net.NetProperties;6566public class UnreferencedMulticastSockets {6768/**69* The set of sockets we have to check up on.70*/71final static ArrayDeque<NamedWeak> pendingSockets = new ArrayDeque<>(5);7273/**74* Queued objects when they are unreferenced.75*/76final static ReferenceQueue<Object> pendingQueue = new ReferenceQueue<>();7778// Server to echo a datagram packet79static class Server implements Runnable {8081MulticastSocket ss;82final int port;83final Phaser phaser = new Phaser(2);84Server() throws IOException {85InetAddress loopback = InetAddress.getLoopbackAddress();86InetSocketAddress serverAddress =87new InetSocketAddress(loopback, 0);88ss = new MulticastSocket(serverAddress);89port = ss.getLocalPort();90System.out.printf(" DatagramServer addr: %s: %d%n",91this.getHost(), this.getPort());92pendingSockets.add(new NamedWeak(ss, pendingQueue, "serverMulticastSocket"));93extractRefs(ss, "serverMulticastSocket");94}9596InetAddress getHost() throws UnknownHostException {97InetAddress localhost = InetAddress.getLoopbackAddress();98return localhost;99}100101int getPort() {102return port;103}104105// Receive a byte and send back a byte106public void run() {107try {108byte[] buffer = new byte[50];109DatagramPacket p = new DatagramPacket(buffer, buffer.length);110ss.receive(p);111System.out.printf("Server: ping received from: %s%n", p.getSocketAddress());112phaser.arriveAndAwaitAdvance(); // await the client...113buffer[0] += 1;114System.out.printf("Server: sending echo to: %s%n", p.getSocketAddress());115ss.send(p); // send back +1116117System.out.printf("Server: awaiting client%n");118phaser.arriveAndAwaitAdvance(); // await the client...119// do NOT close but 'forget' the socket reference120System.out.printf("Server: forgetting socket...%n");121ss = null;122} catch (Throwable ioe) {123ioe.printStackTrace();124}125}126}127128public static void main(String args[]) throws Exception {129IPSupport.throwSkippedExceptionIfNonOperational();130131InetSocketAddress clientAddress =132new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);133134// Create and close a MulticastSocket to warm up the FD count for side effects.135try (MulticastSocket s = new MulticastSocket(clientAddress)) {136// no-op; close immediately137s.getLocalPort(); // no-op138}139140long fdCount0 = getFdCount();141listProcFD();142143// start a server144Server svr = new Server();145Thread thr = new Thread(svr);146thr.start();147148// It is possible under some circumstances that the client149// might get bound to the same port than the server: this150// would make the test fail - so if this happen we try to151// bind to a specific port by incrementing the server port.152MulticastSocket client = null;153int serverPort = svr.getPort();154int maxtries = 20;155for (int i = 0; i < maxtries; i++) {156try {157System.out.printf("Trying to bind client to: %s%n", clientAddress);158client = new MulticastSocket(clientAddress);159if (client.getLocalPort() != svr.getPort()) break;160client.close();161} catch (IOException x) {162System.out.printf("Couldn't create client after %d attempts: %s%n", i, x);163if (i == maxtries) throw x;164}165if (i == maxtries) {166String msg = String.format("Couldn't create client after %d attempts", i);167System.out.println(msg);168throw new AssertionError(msg);169}170clientAddress = new InetSocketAddress(clientAddress.getAddress(), serverPort + i);171}172173System.out.printf(" client bound port: %s:%d%n",174client.getLocalAddress(), client.getLocalPort());175client.connect(svr.getHost(), svr.getPort());176pendingSockets.add(new NamedWeak(client, pendingQueue, "clientMulticastSocket"));177extractRefs(client, "clientMulticastSocket");178179byte[] msg = new byte[1];180msg[0] = 1;181DatagramPacket p = new DatagramPacket(msg, msg.length, svr.getHost(), svr.getPort());182client.send(p);183System.out.printf(" ping sent to: %s:%d%n", svr.getHost(), svr.getPort());184svr.phaser.arriveAndAwaitAdvance(); // wait until the server has received its packet185186p = new DatagramPacket(msg, msg.length);187client.receive(p);188189System.out.printf(" echo received from: %s%n", p.getSocketAddress());190if (msg[0] != 2) {191throw new AssertionError("incorrect data received: expected: 2, actual: " + msg[0]);192}193svr.phaser.arriveAndAwaitAdvance(); // let the server null out its socket194195// Do NOT close the MulticastSocket; forget it196197Object ref;198int loops = 20;199while (!pendingSockets.isEmpty() && loops-- > 0) {200ref = pendingQueue.remove(1000L);201if (ref != null) {202pendingSockets.remove(ref);203System.out.printf(" ref freed: %s, remaining: %d%n", ref, pendingSockets.size());204} else {205client = null;206p = null;207msg = null;208System.gc();209}210}211212thr.join();213214// List the open file descriptors215long fdCount = getFdCount();216System.out.printf("Initial fdCount: %d, final fdCount: %d%n", fdCount0, fdCount);217listProcFD();218219if (loops == 0) {220throw new AssertionError("Not all references reclaimed");221}222}223224// Get the count of open file descriptors, or -1 if not available225private static long getFdCount() {226OperatingSystemMXBean mxBean = ManagementFactory.getOperatingSystemMXBean();227return (mxBean instanceof UnixOperatingSystemMXBean)228? ((UnixOperatingSystemMXBean) mxBean).getOpenFileDescriptorCount()229: -1L;230}231232private static boolean usePlainDatagramSocketImpl() {233PrivilegedAction<String> pa = () -> NetProperties.get("jdk.net.usePlainDatagramSocketImpl");234String s = AccessController.doPrivileged(pa);235return (s != null) && (s.isEmpty() || s.equalsIgnoreCase("true"));236}237238// Reflect to find references in the datagram implementation that will be gc'd239private static void extractRefs(DatagramSocket s, String name) {240241try {242Field datagramSocketField = DatagramSocket.class.getDeclaredField("delegate");243datagramSocketField.setAccessible(true);244245if (!usePlainDatagramSocketImpl()) {246// MulticastSocket using DatagramSocketAdaptor247Object MulticastSocket = datagramSocketField.get(s);248249Method m = DatagramSocket.class.getDeclaredMethod("getChannel");250m.setAccessible(true);251DatagramChannel datagramChannel = (DatagramChannel) m.invoke(MulticastSocket);252253assert datagramChannel.getClass() == Class.forName("sun.nio.ch.DatagramChannelImpl");254255Field fileDescriptorField = datagramChannel.getClass().getDeclaredField("fd");256fileDescriptorField.setAccessible(true);257FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(datagramChannel);258extractRefs(fileDescriptor, name);259260} else {261// MulticastSocket using PlainDatagramSocketImpl262Object MulticastSocket = datagramSocketField.get(s);263assert MulticastSocket.getClass() == Class.forName("java.net.NetMulticastSocket");264265Method m = MulticastSocket.getClass().getDeclaredMethod("getImpl");266m.setAccessible(true);267DatagramSocketImpl datagramSocketImpl = (DatagramSocketImpl) m.invoke(MulticastSocket);268269Field fileDescriptorField = DatagramSocketImpl.class.getDeclaredField("fd");270fileDescriptorField.setAccessible(true);271FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(datagramSocketImpl);272extractRefs(fileDescriptor, name);273274Class<?> socketImplClass = datagramSocketImpl.getClass();275System.out.printf("socketImplClass: %s%n", socketImplClass);276if (socketImplClass.getName().equals("java.net.TwoStacksPlainDatagramSocketImpl")) {277Field fileDescriptor1Field = socketImplClass.getDeclaredField("fd1");278fileDescriptor1Field.setAccessible(true);279FileDescriptor fileDescriptor1 = (FileDescriptor) fileDescriptor1Field.get(datagramSocketImpl);280extractRefs(fileDescriptor1, name + "::twoStacksFd1");281282} else {283System.out.printf("socketImpl class name not matched: %s != %s%n",284socketImplClass.getName(), "java.net.TwoStacksPlainDatagramSocketImpl");285}286}287} catch (Exception ex) {288ex.printStackTrace();289throw new AssertionError("missing field", ex);290}291}292293private static void extractRefs(FileDescriptor fileDescriptor, String name) {294Object cleanup = null;295int rawfd = -1;296try {297if (fileDescriptor != null) {298Field fd1Field = FileDescriptor.class.getDeclaredField("fd");299fd1Field.setAccessible(true);300rawfd = fd1Field.getInt(fileDescriptor);301302Field cleanupfdField = FileDescriptor.class.getDeclaredField("cleanup");303cleanupfdField.setAccessible(true);304cleanup = cleanupfdField.get(fileDescriptor);305pendingSockets.add(new NamedWeak(fileDescriptor, pendingQueue,306name + "::fileDescriptor: " + rawfd));307pendingSockets.add(new NamedWeak(cleanup, pendingQueue, name + "::fdCleanup: " + rawfd));308309}310} catch (NoSuchFieldException | IllegalAccessException ex) {311ex.printStackTrace();312throw new AssertionError("missing field", ex);313} finally {314System.out.print(String.format(" %s:: fd: %s, fd: %d, cleanup: %s%n",315name, fileDescriptor, rawfd, cleanup));316}317}318319/**320* Method to list the open file descriptors (if supported by the 'lsof' command).321*/322static void listProcFD() {323List<String> lsofDirs = List.of("/usr/bin", "/usr/sbin");324Optional<Path> lsof = lsofDirs.stream()325.map(s -> Paths.get(s, "lsof"))326.filter(f -> Files.isExecutable(f))327.findFirst();328lsof.ifPresent(exe -> {329try {330System.out.printf("Open File Descriptors:%n");331long pid = ProcessHandle.current().pid();332ProcessBuilder pb = new ProcessBuilder(exe.toString(), "-p", Integer.toString((int) pid));333pb.inheritIO();334Process p = pb.start();335p.waitFor(10, TimeUnit.SECONDS);336} catch (IOException | InterruptedException ie) {337ie.printStackTrace();338}339});340}341342343// Simple class to identify which refs have been queued344static class NamedWeak extends WeakReference<Object> {345private final String name;346347NamedWeak(Object o, ReferenceQueue<Object> queue, String name) {348super(o, queue);349this.name = name;350}351352public String toString() {353return name + "; " + super.toString();354}355}356}357358359