test/jdk/java/net/ServerSocket/UnreferencedSockets.java
changeset 48737 7c12219870fd
child 54770 62b6e7587b1f
child 57308 f22d38b23756
equal deleted inserted replaced
48736:0454688cc319 48737:7c12219870fd
       
     1 /*
       
     2  * Copyright (c) 2017, 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 UnreferencedSockets
       
    28  * @run main/othervm -Djava.net.preferIPv4Stack=true UnreferencedSockets
       
    29  * @summary Check that unreferenced sockets are closed
       
    30  */
       
    31 
       
    32 import java.io.FileDescriptor;
       
    33 import java.io.InputStream;
       
    34 import java.io.OutputStream;
       
    35 import java.lang.management.ManagementFactory;
       
    36 import java.lang.management.OperatingSystemMXBean;
       
    37 import java.lang.ref.ReferenceQueue;
       
    38 import java.lang.ref.WeakReference;
       
    39 import java.lang.reflect.Field;
       
    40 import java.io.IOException;
       
    41 import java.net.ServerSocket;
       
    42 import java.net.Socket;
       
    43 import java.net.SocketImpl;
       
    44 import java.nio.file.Files;
       
    45 import java.nio.file.Path;
       
    46 import java.nio.file.Paths;
       
    47 import java.util.ArrayDeque;
       
    48 import java.util.List;
       
    49 import java.util.Optional;
       
    50 import java.util.concurrent.TimeUnit;
       
    51 
       
    52 import com.sun.management.UnixOperatingSystemMXBean;
       
    53 
       
    54 public class UnreferencedSockets {
       
    55 
       
    56     /**
       
    57      * The set of sockets we have to check up on.
       
    58      */
       
    59     final static ArrayDeque<NamedWeak> pendingSockets = new ArrayDeque<>(100);
       
    60 
       
    61     /**
       
    62      * Queued sockets when they are unreferenced.
       
    63      */
       
    64     final static ReferenceQueue<Object> pendingQueue = new ReferenceQueue<>();
       
    65 
       
    66     // Server to echo a stream
       
    67     static class Server implements Runnable {
       
    68 
       
    69         ServerSocket ss;
       
    70 
       
    71         Server() throws IOException {
       
    72             ss = new ServerSocket(0);
       
    73             pendingSockets.add(new NamedWeak(ss, pendingQueue, "serverSocket"));
       
    74             extractRefs(ss, "serverSocket");
       
    75         }
       
    76 
       
    77         public int localPort() {
       
    78             return ss.getLocalPort();
       
    79         }
       
    80 
       
    81 
       
    82         public void run() {
       
    83             try {
       
    84                 Socket s = ss.accept();
       
    85                 pendingSockets.add(new NamedWeak(s, pendingQueue, "acceptedSocket"));
       
    86                 extractRefs(s, "acceptedSocket");
       
    87 
       
    88                 InputStream in = s.getInputStream();
       
    89                 int b = in.read();
       
    90                 OutputStream out = s.getOutputStream();
       
    91                 out.write(b);
       
    92                 // do NOT close but 'forget' the socket reference
       
    93                 out = null;
       
    94                 in = null;
       
    95                 s = null;
       
    96             } catch (Exception ioe) {
       
    97                 ioe.printStackTrace();
       
    98             } finally {
       
    99                 try {
       
   100                     ss.close();
       
   101                     ss = null;
       
   102                 } catch (IOException x) {
       
   103                     x.printStackTrace();
       
   104                 }
       
   105             }
       
   106         }
       
   107     }
       
   108 
       
   109     public static void main(String args[]) throws Exception {
       
   110 
       
   111         // Create and close a ServerSocket to warm up the FD count for side effects.
       
   112         try (ServerSocket s = new ServerSocket(0)) {
       
   113             // no-op; close immediately
       
   114             s.getLocalPort();   // no-op
       
   115         }
       
   116 
       
   117         long fdCount0 = getFdCount();
       
   118         listProcFD();
       
   119 
       
   120         // start a server
       
   121         Server svr = new Server();
       
   122         Thread thr = new Thread(svr);
       
   123         thr.start();
       
   124 
       
   125         Socket s = new Socket("localhost", svr.localPort());
       
   126         pendingSockets.add(new NamedWeak(s, pendingQueue, "clientSocket"));
       
   127         extractRefs(s, "clientSocket");
       
   128 
       
   129         OutputStream out = s.getOutputStream();
       
   130         out.write('x');
       
   131         out.flush();
       
   132         InputStream in = s.getInputStream();
       
   133         int b = in.read();  // wait for it back
       
   134         System.out.printf("  data sent and received%n");
       
   135         // Do NOT close the Socket; forget it
       
   136 
       
   137         Object ref;
       
   138         int loops = 20;
       
   139         while (!pendingSockets.isEmpty() && loops-- > 0) {
       
   140             ref = pendingQueue.remove(1000L);
       
   141             if (ref != null) {
       
   142                 pendingSockets.remove(ref);
       
   143                 System.out.printf("  ref queued: %s, remaining: %d%n", ref, pendingSockets.size());
       
   144             } else {
       
   145                 s = null;
       
   146                 out = null;
       
   147                 in = null;
       
   148                 System.gc();
       
   149             }
       
   150         }
       
   151 
       
   152         thr.join();
       
   153 
       
   154         // List the open file descriptors
       
   155         long fdCount = getFdCount();
       
   156         System.out.printf("Initial fdCount: %d, final fdCount: %d%n", fdCount0, fdCount);
       
   157         listProcFD();
       
   158 
       
   159         if (loops == 0) {
       
   160             throw new AssertionError("Not all references reclaimed");
       
   161         }
       
   162     }
       
   163 
       
   164     // Get the count of open file descriptors, or -1 if not available
       
   165     private static long getFdCount() {
       
   166         OperatingSystemMXBean mxBean = ManagementFactory.getOperatingSystemMXBean();
       
   167         return (mxBean instanceof UnixOperatingSystemMXBean)
       
   168                 ? ((UnixOperatingSystemMXBean) mxBean).getOpenFileDescriptorCount()
       
   169                 : -1L;
       
   170     }
       
   171 
       
   172     // Reflect to find references in the socket implementation that will be gc'd
       
   173     private static void extractRefs(Socket s, String name) {
       
   174         try {
       
   175 
       
   176             Field socketImplField = Socket.class.getDeclaredField("impl");
       
   177             socketImplField.setAccessible(true);
       
   178             Object socketImpl = socketImplField.get(s);
       
   179 
       
   180             Field fileDescriptorField = SocketImpl.class.getDeclaredField("fd");
       
   181             fileDescriptorField.setAccessible(true);
       
   182             FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl);
       
   183             extractRefs(fileDescriptor, name);
       
   184 
       
   185             Class<?> socketImplClass = socketImpl.getClass();
       
   186             System.out.printf("socketImplClass: %s%n", socketImplClass);
       
   187             if (socketImplClass.getClass().getName().equals("java.net.TwoStacksPlainSocketImpl")) {
       
   188                 Field fileDescriptor1Field = socketImplClass.getDeclaredField("fd1");
       
   189                 fileDescriptor1Field.setAccessible(true);
       
   190                 FileDescriptor fileDescriptor1 = (FileDescriptor) fileDescriptor1Field.get(socketImpl);
       
   191                 extractRefs(fileDescriptor1, name + "::twoStacksFd1");
       
   192 
       
   193             }
       
   194         } catch (NoSuchFieldException | IllegalAccessException ex) {
       
   195             ex.printStackTrace();
       
   196             throw new AssertionError("missing field", ex);
       
   197         }
       
   198     }
       
   199 
       
   200     private static void extractRefs(FileDescriptor fileDescriptor, String name) {
       
   201         Object cleanup = null;
       
   202         int rawfd = -1;
       
   203         try {
       
   204             if (fileDescriptor != null) {
       
   205                 Field fd1Field = FileDescriptor.class.getDeclaredField("fd");
       
   206                 fd1Field.setAccessible(true);
       
   207                 rawfd = fd1Field.getInt(fileDescriptor);
       
   208 
       
   209                 Field cleanupfdField = FileDescriptor.class.getDeclaredField("cleanup");
       
   210                 cleanupfdField.setAccessible(true);
       
   211                 cleanup = cleanupfdField.get(fileDescriptor);
       
   212                 pendingSockets.add(new NamedWeak(fileDescriptor, pendingQueue,
       
   213                         name + "::fileDescriptor: " + rawfd));
       
   214                 pendingSockets.add(new NamedWeak(cleanup, pendingQueue, name + "::fdCleanup: " + rawfd));
       
   215 
       
   216             }
       
   217         } catch (NoSuchFieldException | IllegalAccessException ex) {
       
   218             ex.printStackTrace();
       
   219             throw new AssertionError("missing field", ex);
       
   220         } finally {
       
   221             System.out.print(String.format("  fd: %s, fd: %d, cleanup: %s%n",
       
   222                     fileDescriptor, rawfd, cleanup));
       
   223         }
       
   224     }
       
   225 
       
   226     private static void extractRefs(ServerSocket s, String name) {
       
   227         try {
       
   228 
       
   229             Field socketImplField = ServerSocket.class.getDeclaredField("impl");
       
   230             socketImplField.setAccessible(true);
       
   231             Object socketImpl = socketImplField.get(s);
       
   232 
       
   233             Field fileDescriptorField = SocketImpl.class.getDeclaredField("fd");
       
   234             fileDescriptorField.setAccessible(true);
       
   235             FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl);
       
   236 
       
   237             Field fdField = FileDescriptor.class.getDeclaredField("fd");
       
   238             fdField.setAccessible(true);
       
   239             int rawfd = fdField.getInt(fileDescriptor);
       
   240 
       
   241             Field cleanupField = FileDescriptor.class.getDeclaredField("cleanup");
       
   242             cleanupField.setAccessible(true);
       
   243             Object cleanup = cleanupField.get(fileDescriptor);
       
   244 
       
   245             System.out.print(String.format("  fd: %s, fd: %d, cleanup: %s, socket: %s%n",
       
   246                     fileDescriptor, rawfd, cleanup, s));
       
   247 
       
   248             pendingSockets.add(new NamedWeak(fileDescriptor, pendingQueue,
       
   249                     name + "::fileDescriptor: " + rawfd));
       
   250             pendingSockets.add(new NamedWeak(cleanup, pendingQueue, name + "::fdCleanup: " + rawfd));
       
   251 
       
   252         } catch (NoSuchFieldException | IllegalAccessException ex) {
       
   253             ex.printStackTrace();
       
   254             throw new AssertionError("missing field", ex);
       
   255         }
       
   256     }
       
   257 
       
   258     /**
       
   259      * Method to list the open file descriptors (if supported by the 'lsof' command).
       
   260      */
       
   261     static void listProcFD() {
       
   262         List<String> lsofDirs = List.of("/usr/bin", "/usr/sbin");
       
   263         Optional<Path> lsof = lsofDirs.stream()
       
   264                 .map(s -> Paths.get(s, "lsof"))
       
   265                 .filter(f -> Files.isExecutable(f))
       
   266                 .findFirst();
       
   267         lsof.ifPresent(exe -> {
       
   268             try {
       
   269                 System.out.printf("Open File Descriptors:%n");
       
   270                 long pid = ProcessHandle.current().pid();
       
   271                 ProcessBuilder pb = new ProcessBuilder(exe.toString(), "-p", Integer.toString((int) pid));
       
   272                 pb.inheritIO();
       
   273                 Process p = pb.start();
       
   274                 p.waitFor(10, TimeUnit.SECONDS);
       
   275             } catch (IOException | InterruptedException ie) {
       
   276                 ie.printStackTrace();
       
   277             }
       
   278         });
       
   279     }
       
   280 
       
   281     // Simple class to identify which refs have been queued
       
   282     static class NamedWeak extends WeakReference<Object> {
       
   283         private final String name;
       
   284 
       
   285         NamedWeak(Object o, ReferenceQueue<Object> queue, String name) {
       
   286             super(o, queue);
       
   287             this.name = name;
       
   288         }
       
   289 
       
   290         public String toString() {
       
   291             return name + "; " + super.toString();
       
   292         }
       
   293     }
       
   294 }