test/jdk/java/net/MulticastSocket/UnreferencedMulticastSockets.java
changeset 48737 7c12219870fd
child 51830 d2c72de3cf83
equal deleted inserted replaced
48736:0454688cc319 48737:7c12219870fd
       
     1 /*
       
     2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 /**
       
    25  * @test
       
    26  * @modules java.management java.base/java.io:+open java.base/java.net:+open
       
    27  * @run main/othervm -Djava.net.preferIPv4Stack=true UnreferencedMulticastSockets
       
    28  * @run main/othervm UnreferencedMulticastSockets
       
    29  * @summary Check that unreferenced multicast sockets are closed
       
    30  */
       
    31 
       
    32 import java.io.FileDescriptor;
       
    33 import java.lang.management.ManagementFactory;
       
    34 import java.lang.management.OperatingSystemMXBean;
       
    35 import java.lang.ref.ReferenceQueue;
       
    36 import java.lang.ref.WeakReference;
       
    37 import java.lang.reflect.Field;
       
    38 import java.io.IOException;
       
    39 import java.net.DatagramPacket;
       
    40 import java.net.DatagramSocket;
       
    41 import java.net.DatagramSocketImpl;
       
    42 import java.net.InetAddress;
       
    43 import java.net.MulticastSocket;
       
    44 import java.net.UnknownHostException;
       
    45 import java.nio.file.Files;
       
    46 import java.nio.file.Path;
       
    47 import java.nio.file.Paths;
       
    48 import java.util.ArrayDeque;
       
    49 import java.util.List;
       
    50 import java.util.Optional;
       
    51 import java.util.concurrent.TimeUnit;
       
    52 
       
    53 import com.sun.management.UnixOperatingSystemMXBean;
       
    54 
       
    55 public class UnreferencedMulticastSockets {
       
    56 
       
    57     /**
       
    58      * The set of sockets we have to check up on.
       
    59      */
       
    60     final static ArrayDeque<NamedWeak> pendingSockets = new ArrayDeque<>(5);
       
    61 
       
    62     /**
       
    63      * Queued objects when they are unreferenced.
       
    64      */
       
    65     final static ReferenceQueue<Object> pendingQueue = new ReferenceQueue<>();
       
    66 
       
    67     // Server to echo a datagram packet
       
    68     static class Server implements Runnable {
       
    69 
       
    70         MulticastSocket ss;
       
    71 
       
    72         Server() throws IOException {
       
    73             ss = new MulticastSocket(0);
       
    74             System.out.printf("  DatagramServer addr: %s: %d%n",
       
    75                     this.getHost(), this.getPort());
       
    76             pendingSockets.add(new NamedWeak(ss, pendingQueue, "serverMulticastSocket"));
       
    77             extractRefs(ss, "serverMulticastSocket");
       
    78         }
       
    79 
       
    80         InetAddress getHost() throws UnknownHostException {
       
    81             InetAddress localhost = InetAddress.getByName("localhost"); //.getLocalHost();
       
    82             return localhost;
       
    83         }
       
    84 
       
    85         int getPort() {
       
    86             return ss.getLocalPort();
       
    87         }
       
    88 
       
    89         // Receive a byte and send back a byte
       
    90         public void run() {
       
    91             try {
       
    92                 byte[] buffer = new byte[50];
       
    93                 DatagramPacket p = new DatagramPacket(buffer, buffer.length);
       
    94                 ss.receive(p);
       
    95                 buffer[0] += 1;
       
    96                 ss.send(p);         // send back +1
       
    97 
       
    98                 // do NOT close but 'forget' the socket reference
       
    99                 ss = null;
       
   100             } catch (Exception ioe) {
       
   101                 ioe.printStackTrace();
       
   102             }
       
   103         }
       
   104     }
       
   105 
       
   106     public static void main(String args[]) throws Exception {
       
   107 
       
   108         // Create and close a MulticastSocket to warm up the FD count for side effects.
       
   109         try (MulticastSocket s = new MulticastSocket(0)) {
       
   110             // no-op; close immediately
       
   111             s.getLocalPort();   // no-op
       
   112         }
       
   113 
       
   114         long fdCount0 = getFdCount();
       
   115         listProcFD();
       
   116 
       
   117         // start a server
       
   118         Server svr = new Server();
       
   119         Thread thr = new Thread(svr);
       
   120         thr.start();
       
   121 
       
   122         MulticastSocket client = new MulticastSocket(0);
       
   123         client.connect(svr.getHost(), svr.getPort());
       
   124         pendingSockets.add(new NamedWeak(client, pendingQueue, "clientMulticastSocket"));
       
   125         extractRefs(client, "clientMulticastSocket");
       
   126 
       
   127         byte[] msg = new byte[1];
       
   128         msg[0] = 1;
       
   129         DatagramPacket p = new DatagramPacket(msg, msg.length, svr.getHost(), svr.getPort());
       
   130         client.send(p);
       
   131 
       
   132         p = new DatagramPacket(msg, msg.length);
       
   133         client.receive(p);
       
   134 
       
   135         System.out.printf("echo received from: %s%n", p.getSocketAddress());
       
   136         if (msg[0] != 2) {
       
   137             throw new AssertionError("incorrect data received: expected: 2, actual: " + msg[0]);
       
   138         }
       
   139 
       
   140         // Do NOT close the MulticastSocket; forget it
       
   141 
       
   142         Object ref;
       
   143         int loops = 20;
       
   144         while (!pendingSockets.isEmpty() && loops-- > 0) {
       
   145             ref = pendingQueue.remove(1000L);
       
   146             if (ref != null) {
       
   147                 pendingSockets.remove(ref);
       
   148                 System.out.printf("  ref freed: %s, remaining: %d%n", ref, pendingSockets.size());
       
   149             } else {
       
   150                 client = null;
       
   151                 p = null;
       
   152                 msg = null;
       
   153                 System.gc();
       
   154             }
       
   155         }
       
   156 
       
   157         thr.join();
       
   158 
       
   159         // List the open file descriptors
       
   160         long fdCount = getFdCount();
       
   161         System.out.printf("Initial fdCount: %d, final fdCount: %d%n", fdCount0, fdCount);
       
   162         listProcFD();
       
   163 
       
   164         if (loops == 0) {
       
   165             throw new AssertionError("Not all references reclaimed");
       
   166         }
       
   167     }
       
   168 
       
   169     // Get the count of open file descriptors, or -1 if not available
       
   170     private static long getFdCount() {
       
   171         OperatingSystemMXBean mxBean = ManagementFactory.getOperatingSystemMXBean();
       
   172         return (mxBean instanceof UnixOperatingSystemMXBean)
       
   173                 ? ((UnixOperatingSystemMXBean) mxBean).getOpenFileDescriptorCount()
       
   174                 : -1L;
       
   175     }
       
   176 
       
   177     // Reflect to find references in the socket implementation that will be gc'd
       
   178     private static void extractRefs(MulticastSocket s, String name) {
       
   179         try {
       
   180 
       
   181             Field socketImplField = DatagramSocket.class.getDeclaredField("impl");
       
   182             socketImplField.setAccessible(true);
       
   183             Object socketImpl = socketImplField.get(s);
       
   184 
       
   185             Field fileDescriptorField = DatagramSocketImpl.class.getDeclaredField("fd");
       
   186             fileDescriptorField.setAccessible(true);
       
   187             FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl);
       
   188             extractRefs(fileDescriptor, name);
       
   189 
       
   190             Class<?> socketImplClass = socketImpl.getClass();
       
   191             System.out.printf("socketImplClass: %s%n", socketImplClass);
       
   192             if (socketImplClass.getName().equals("java.net.TwoStacksPlainDatagramSocketImpl")) {
       
   193                 Field fileDescriptor1Field = socketImplClass.getDeclaredField("fd1");
       
   194                 fileDescriptor1Field.setAccessible(true);
       
   195                 FileDescriptor fileDescriptor1 = (FileDescriptor) fileDescriptor1Field.get(socketImpl);
       
   196                 extractRefs(fileDescriptor1, name + "::twoStacksFd1");
       
   197 
       
   198             } else {
       
   199                 System.out.printf("socketImpl class name not matched: %s != %s%n",
       
   200                         socketImplClass.getName(), "java.net.TwoStacksPlainDatagramSocketImpl");
       
   201             }
       
   202         } catch (NoSuchFieldException | IllegalAccessException ex) {
       
   203             ex.printStackTrace();
       
   204             throw new AssertionError("missing field", ex);
       
   205         }
       
   206     }
       
   207 
       
   208     private static void extractRefs(FileDescriptor fileDescriptor, String name) {
       
   209         Object cleanup = null;
       
   210         int rawfd = -1;
       
   211         try {
       
   212             if (fileDescriptor != null) {
       
   213                 Field fd1Field = FileDescriptor.class.getDeclaredField("fd");
       
   214                 fd1Field.setAccessible(true);
       
   215                 rawfd = fd1Field.getInt(fileDescriptor);
       
   216 
       
   217                 Field cleanupfdField = FileDescriptor.class.getDeclaredField("cleanup");
       
   218                 cleanupfdField.setAccessible(true);
       
   219                 cleanup = cleanupfdField.get(fileDescriptor);
       
   220                 pendingSockets.add(new NamedWeak(fileDescriptor, pendingQueue,
       
   221                         name + "::fileDescriptor: " + rawfd));
       
   222                 pendingSockets.add(new NamedWeak(cleanup, pendingQueue, name + "::fdCleanup: " + rawfd));
       
   223 
       
   224             }
       
   225         } catch (NoSuchFieldException | IllegalAccessException ex) {
       
   226             ex.printStackTrace();
       
   227             throw new AssertionError("missing field", ex);
       
   228         } finally {
       
   229             System.out.print(String.format("  %s:: fd: %s, fd: %d, cleanup: %s%n",
       
   230                     name, fileDescriptor, rawfd, cleanup));
       
   231         }
       
   232     }
       
   233 
       
   234     /**
       
   235      * Method to list the open file descriptors (if supported by the 'lsof' command).
       
   236      */
       
   237     static void listProcFD() {
       
   238         List<String> lsofDirs = List.of("/usr/bin", "/usr/sbin");
       
   239         Optional<Path> lsof = lsofDirs.stream()
       
   240                 .map(s -> Paths.get(s, "lsof"))
       
   241                 .filter(f -> Files.isExecutable(f))
       
   242                 .findFirst();
       
   243         lsof.ifPresent(exe -> {
       
   244             try {
       
   245                 System.out.printf("Open File Descriptors:%n");
       
   246                 long pid = ProcessHandle.current().pid();
       
   247                 ProcessBuilder pb = new ProcessBuilder(exe.toString(), "-p", Integer.toString((int) pid));
       
   248                 pb.inheritIO();
       
   249                 Process p = pb.start();
       
   250                 p.waitFor(10, TimeUnit.SECONDS);
       
   251             } catch (IOException | InterruptedException ie) {
       
   252                 ie.printStackTrace();
       
   253             }
       
   254         });
       
   255     }
       
   256 
       
   257 
       
   258     // Simple class to identify which refs have been queued
       
   259     static class NamedWeak extends WeakReference<Object> {
       
   260         private final String name;
       
   261 
       
   262         NamedWeak(Object o, ReferenceQueue<Object> queue, String name) {
       
   263             super(o, queue);
       
   264             this.name = name;
       
   265         }
       
   266 
       
   267         public String toString() {
       
   268             return name + "; " + super.toString();
       
   269         }
       
   270     }
       
   271 }