8195059: Update java.net Socket and DatagramSocket implementations to use Cleaner

Reviewed-by: chegar, plevart
This commit is contained in:
Roger Riggs 2018-02-02 14:17:07 -05:00
parent 89c2e03b1d
commit 0b8689b331
15 changed files with 1113 additions and 60 deletions

View file

@ -1,5 +1,5 @@
# #
# Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. # Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
# #
# This code is free software; you can redistribute it and/or modify it # This code is free software; you can redistribute it and/or modify it
@ -89,6 +89,7 @@ SUNWprivate_1.1 {
Java_java_net_PlainDatagramSocketImpl_setTimeToLive; Java_java_net_PlainDatagramSocketImpl_setTimeToLive;
Java_java_net_AbstractPlainSocketImpl_isReusePortAvailable0; Java_java_net_AbstractPlainSocketImpl_isReusePortAvailable0;
Java_java_net_AbstractPlainDatagramSocketImpl_isReusePortAvailable0; Java_java_net_AbstractPlainDatagramSocketImpl_isReusePortAvailable0;
Java_java_net_SocketCleanable_cleanupClose0;
Java_jdk_net_Sockets_isReusePortAvailable0; Java_jdk_net_Sockets_isReusePortAvailable0;
Java_sun_net_PortConfig_getUpper0; Java_sun_net_PortConfig_getUpper0;
Java_sun_net_PortConfig_getLower0; Java_sun_net_PortConfig_getLower0;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -26,11 +26,11 @@ package java.net;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.security.AccessController;
import sun.net.ResourceManager;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import sun.net.ResourceManager;
import sun.security.action.GetPropertyAction; import sun.security.action.GetPropertyAction;
/** /**
@ -115,6 +115,7 @@ abstract class AbstractPlainDatagramSocketImpl extends DatagramSocketImpl
fd = new FileDescriptor(); fd = new FileDescriptor();
try { try {
datagramSocketCreate(); datagramSocketCreate();
SocketCleanable.register(fd);
} catch (SocketException ioe) { } catch (SocketException ioe) {
ResourceManager.afterUdpClose(); ResourceManager.afterUdpClose();
fd = null; fd = null;
@ -265,6 +266,7 @@ abstract class AbstractPlainDatagramSocketImpl extends DatagramSocketImpl
*/ */
protected void close() { protected void close() {
if (fd != null) { if (fd != null) {
SocketCleanable.unregister(fd);
datagramSocketClose(); datagramSocketClose();
ResourceManager.afterUdpClose(); ResourceManager.afterUdpClose();
fd = null; fd = null;
@ -275,11 +277,6 @@ abstract class AbstractPlainDatagramSocketImpl extends DatagramSocketImpl
return (fd == null) ? true : false; return (fd == null) ? true : false;
} }
@SuppressWarnings("deprecation")
protected void finalize() {
close();
}
/** /**
* set a value - since we only support (setting) binary options * set a value - since we only support (setting) binary options
* here, o must be a Boolean * here, o must be a Boolean

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1995, 2017, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1995, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,17 +25,18 @@
package java.net; package java.net;
import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.FileDescriptor;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import sun.net.ConnectionResetException; import sun.net.ConnectionResetException;
import sun.net.NetHooks; import sun.net.NetHooks;
import sun.net.ResourceManager; import sun.net.ResourceManager;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
/** /**
* Default Socket Implementation. This implementation does * Default Socket Implementation. This implementation does
@ -136,6 +137,7 @@ abstract class AbstractPlainSocketImpl extends SocketImpl
fd = new FileDescriptor(); fd = new FileDescriptor();
try { try {
socketCreate(false); socketCreate(false);
SocketCleanable.register(fd);
} catch (IOException ioe) { } catch (IOException ioe) {
ResourceManager.afterUdpClose(); ResourceManager.afterUdpClose();
fd = null; fd = null;
@ -144,6 +146,7 @@ abstract class AbstractPlainSocketImpl extends SocketImpl
} else { } else {
fd = new FileDescriptor(); fd = new FileDescriptor();
socketCreate(true); socketCreate(true);
SocketCleanable.register(fd);
} }
if (socket != null) if (socket != null)
socket.setCreated(); socket.setCreated();
@ -643,14 +646,6 @@ abstract class AbstractPlainSocketImpl extends SocketImpl
socketSendUrgentData (data); socketSendUrgentData (data);
} }
/**
* Cleans up if the user forgets to close it.
*/
@SuppressWarnings("deprecation")
protected void finalize() throws IOException {
close();
}
/* /*
* "Acquires" and returns the FileDescriptor for this impl * "Acquires" and returns the FileDescriptor for this impl
* *
@ -748,6 +743,7 @@ abstract class AbstractPlainSocketImpl extends SocketImpl
* Close the socket (and release the file descriptor). * Close the socket (and release the file descriptor).
*/ */
protected void socketClose() throws IOException { protected void socketClose() throws IOException {
SocketCleanable.unregister(fd);
socketClose0(false); socketClose0(false);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1995, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -549,6 +549,7 @@ class ServerSocket implements java.io.Closeable {
si.address = new InetAddress(); si.address = new InetAddress();
si.fd = new FileDescriptor(); si.fd = new FileDescriptor();
getImpl().accept(si); getImpl().accept(si);
SocketCleanable.register(si.fd); // raw fd has been set
SecurityManager security = System.getSecurityManager(); SecurityManager security = System.getSecurityManager();
if (security != null) { if (security != null) {

View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2018, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package java.net;
import jdk.internal.misc.JavaIOFileDescriptorAccess;
import jdk.internal.misc.SharedSecrets;
import jdk.internal.ref.CleanerFactory;
import jdk.internal.ref.PhantomCleanable;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.ref.Cleaner;
/**
* Cleanup for a socket/datagramsocket FileDescriptor when it becomes phantom reachable.
* Create a cleanup if the raw fd != -1. Windows closes sockets using the fd.
* Subclassed from {@code PhantomCleanable} so that {@code clear} can be
* called to disable the cleanup when the socket fd is closed by any means
* other than calling {@link FileDescriptor#close}.
* Otherwise, it would incorrectly close the handle or fd after it has been reused.
*/
final class SocketCleanable extends PhantomCleanable<Object> {
// Access to FileDescriptor internals
private static final JavaIOFileDescriptorAccess fdAccess =
SharedSecrets.getJavaIOFileDescriptorAccess();
// Native function to call NET_SocketClose(fd)
private static native void cleanupClose0(int fd) throws IOException;
// The raw fd to close
private final int fd;
/**
* Register a socket specific Cleanable with the FileDescriptor
* if the FileDescriptor is non-null and the raw fd is != -1.
*
* @param fdo the FileDescriptor; may be null
*/
static void register(FileDescriptor fdo) {
if (fdo != null) {
int fd = fdAccess.get(fdo);
if (fd != -1) {
fdAccess.registerCleanup(fdo,
new SocketCleanable(fdo, CleanerFactory.cleaner(), fd));
}
}
}
/**
* Unregister a Cleanable from the FileDescriptor.
* @param fdo the FileDescriptor; may be null
*/
static void unregister(FileDescriptor fdo) {
if (fdo != null) {
fdAccess.unregisterCleanup(fdo);
}
}
/**
* Constructor for a phantom cleanable reference.
*
* @param obj the object to monitor
* @param cleaner the cleaner
* @param fd file descriptor to close
*/
private SocketCleanable(Object obj, Cleaner cleaner, int fd) {
super(obj, cleaner);
this.fd = fd;
}
/**
* Close the native handle or fd.
*/
@Override
protected void performCleanup() {
try {
cleanupClose0(fd);
} catch (IOException ioe) {
throw new UncheckedIOException("close", ioe);
}
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -27,6 +27,8 @@ package jdk.internal.misc;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import jdk.internal.ref.PhantomCleanable;
/* /*
* @author Chris Hegarty * @author Chris Hegarty
*/ */
@ -38,6 +40,8 @@ public interface JavaIOFileDescriptorAccess {
public boolean getAppend(FileDescriptor fdo); public boolean getAppend(FileDescriptor fdo);
public void close(FileDescriptor fdo) throws IOException; public void close(FileDescriptor fdo) throws IOException;
public void registerCleanup(FileDescriptor fdo); public void registerCleanup(FileDescriptor fdo);
public void registerCleanup(FileDescriptor fdo, PhantomCleanable<Object> cleanable);
public void unregisterCleanup(FileDescriptor fdo);
// Only valid on Windows // Only valid on Windows
public void setHandle(FileDescriptor fdo, long handle); public void setHandle(FileDescriptor fdo, long handle);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1995, 2017, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1995, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -91,6 +91,14 @@ public final class FileDescriptor {
fdo.registerCleanup(); fdo.registerCleanup();
} }
public void registerCleanup(FileDescriptor fdo, PhantomCleanable<Object> cleanup) {
fdo.registerCleanup(cleanup);
}
public void unregisterCleanup(FileDescriptor fdo) {
fdo.unregisterCleanup();
}
public void setHandle(FileDescriptor fdo, long handle) { public void setHandle(FileDescriptor fdo, long handle) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -105,7 +113,7 @@ public final class FileDescriptor {
/** /**
* Cleanup in case FileDescriptor is not explicitly closed. * Cleanup in case FileDescriptor is not explicitly closed.
*/ */
private FDCleanup cleanup; private PhantomCleanable<Object> cleanup;
/** /**
* Constructs an (invalid) FileDescriptor * Constructs an (invalid) FileDescriptor
@ -206,16 +214,44 @@ public final class FileDescriptor {
} }
/** /**
* Register a cleanup for the current raw fd. * Register a cleanup for the current handle.
* Used directly in java.io and indirectly via fdAccess. * Used directly in java.io and indirectly via fdAccess.
* The cleanup should be registered after the fd is set in the FileDescriptor. * The cleanup should be registered after the handle is set in the FileDescriptor.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
synchronized void registerCleanup() { void registerCleanup() {
registerCleanup(null);
}
/**
* Register a cleanup for the current handle.
* Used directly in java.io and indirectly via fdAccess.
* The cleanup should be registered after the handle is set in the FileDescriptor.
* @param newCleanable a PhantomCleanable to register
*/
@SuppressWarnings("unchecked")
synchronized void registerCleanup(PhantomCleanable<Object> newCleanable) {
if (cleanup != null) { if (cleanup != null) {
cleanup.clear(); cleanup.clear();
} }
cleanup = FDCleanup.create(this); cleanup = (newCleanable == null) ? FDCleanup.create(this) : newCleanable;
}
/**
* Unregister a cleanup for the current raw fd.
* Used directly in java.io and indirectly via fdAccess.
* Normally {@link #close()} should be used except in cases where
* it is certain the caller will close the raw fd and the cleanup
* must not close the raw fd. {@link #unregisterCleanup()} must be
* called before the raw fd is closed to prevent a race that makes
* it possible for the fd to be reallocated to another use and later
* the cleanup might be invoked.
*/
synchronized void unregisterCleanup() {
if (cleanup != null) {
cleanup.clear();
}
cleanup = null;
} }
/** /**

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -27,6 +27,7 @@
#include <string.h> #include <string.h>
#include "net_util.h" #include "net_util.h"
#include "java_net_SocketCleanable.h"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_java_net_AbstractPlainSocketImpl_isReusePortAvailable0(JNIEnv* env, jclass c1) Java_java_net_AbstractPlainSocketImpl_isReusePortAvailable0(JNIEnv* env, jclass c1)
@ -45,3 +46,15 @@ Java_jdk_net_Sockets_isReusePortAvailable0(JNIEnv* env, jclass c1)
{ {
return (reuseport_available()) ? JNI_TRUE : JNI_FALSE; return (reuseport_available()) ? JNI_TRUE : JNI_FALSE;
} }
/*
* Class: java_net_SocketCleanable
* Method: cleanupClose0
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_java_net_SocketCleanable_cleanupClose0(JNIEnv *env, jclass c1, jint fd)
{
NET_SocketClose(fd);
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -90,7 +90,15 @@ public final class FileDescriptor {
} }
public void registerCleanup(FileDescriptor fdo) { public void registerCleanup(FileDescriptor fdo) {
fdo.registerCleanup(); fdo.registerCleanup(null);
}
public void registerCleanup(FileDescriptor fdo, PhantomCleanable<Object> cleanup) {
fdo.registerCleanup(cleanup);
}
public void unregisterCleanup(FileDescriptor fdo) {
fdo.unregisterCleanup();
} }
public void setHandle(FileDescriptor fdo, long handle) { public void setHandle(FileDescriptor fdo, long handle) {
@ -107,7 +115,7 @@ public final class FileDescriptor {
/** /**
* Cleanup in case FileDescriptor is not explicitly closed. * Cleanup in case FileDescriptor is not explicitly closed.
*/ */
private FDCleanup cleanup; private PhantomCleanable<Object> cleanup;
/** /**
* Constructs an (invalid) FileDescriptor * Constructs an (invalid) FileDescriptor
@ -217,11 +225,39 @@ public final class FileDescriptor {
* The cleanup should be registered after the handle is set in the FileDescriptor. * The cleanup should be registered after the handle is set in the FileDescriptor.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
synchronized void registerCleanup() { void registerCleanup() {
registerCleanup(null);
}
/**
* Register a cleanup for the current handle.
* Used directly in java.io and indirectly via fdAccess.
* The cleanup should be registered after the handle is set in the FileDescriptor.
* @param newCleanable a PhantomCleanable to register
*/
@SuppressWarnings("unchecked")
synchronized void registerCleanup(PhantomCleanable<Object> newCleanable) {
if (cleanup != null) { if (cleanup != null) {
cleanup.clear(); cleanup.clear();
} }
cleanup = FDCleanup.create(this); cleanup = (newCleanable == null) ? FDCleanup.create(this) : newCleanable;
}
/**
* Unregister a cleanup for the current raw fd.
* Used directly in java.io and indirectly via fdAccess.
* Normally {@link #close()} should be used except in cases where
* it is certain the caller will close the raw fd and the cleanup
* must not close the raw fd. {@link #unregisterCleanup()} must be
* called before the raw fd is closed to prevent a race that makes
* it possible for the fd to be reallocated to another use and later
* the cleanup might be invoked.
*/
synchronized void unregisterCleanup() {
if (cleanup != null) {
cleanup.clear();
}
cleanup = null;
} }
/** /**
@ -319,16 +355,21 @@ public final class FileDescriptor {
/** /**
* Cleanup for a FileDescriptor when it becomes phantom reachable. * Cleanup for a FileDescriptor when it becomes phantom reachable.
* Create a cleanup if handle != -1. * Create a cleanup if handle != -1.
* Windows closes files using handles and sockets via the fd.
* Network FileDescriptors using socket fd must provide their
* own PhantomCleanable to {@link #registerCleanup}.
* This implementation only clears thehandles.
* <p>
* Subclassed from {@code PhantomCleanable} so that {@code clear} can be * Subclassed from {@code PhantomCleanable} so that {@code clear} can be
* called to disable the cleanup when the fd is closed by any means other * called to disable the cleanup when the handle is closed by any means other
* than calling {@link FileDescriptor#close}. * than calling {@link FileDescriptor#close}.
* Otherwise, it may close the handle after it has been reused. * Otherwise, it may incorrectly close the handle after it has been reused.
*/ */
static final class FDCleanup extends PhantomCleanable<Object> { static final class FDCleanup extends PhantomCleanable<Object> {
private final long handle; private final long handle;
static FDCleanup create(FileDescriptor fdo) { static FDCleanup create(FileDescriptor fdo) {
return fdo.handle == -1 return fdo.handle == -1L
? null ? null
: new FDCleanup(fdo, CleanerFactory.cleaner(), fdo.handle); : new FDCleanup(fdo, CleanerFactory.cleaner(), fdo.handle);
} }

View file

@ -86,6 +86,7 @@ class TwoStacksPlainDatagramSocketImpl extends AbstractPlainDatagramSocketImpl
fd1 = new FileDescriptor(); fd1 = new FileDescriptor();
try { try {
super.create(); super.create();
SocketCleanable.register(fd1);
} catch (SocketException e) { } catch (SocketException e) {
fd1 = null; fd1 = null;
throw e; throw e;
@ -160,6 +161,8 @@ class TwoStacksPlainDatagramSocketImpl extends AbstractPlainDatagramSocketImpl
protected void close() { protected void close() {
if (fd != null || fd1 != null) { if (fd != null || fd1 != null) {
SocketCleanable.unregister(fd);
SocketCleanable.unregister(fd1);
datagramSocketClose(); datagramSocketClose();
ResourceManager.afterUdpClose(); ResourceManager.afterUdpClose();
fd = null; fd = null;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -24,6 +24,8 @@
*/ */
#include <jni.h> #include <jni.h>
#include "net_util.h"
#include "java_net_SocketCleanable.h"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_java_net_AbstractPlainSocketImpl_isReusePortAvailable0(JNIEnv* env, jclass c1) Java_java_net_AbstractPlainSocketImpl_isReusePortAvailable0(JNIEnv* env, jclass c1)
@ -45,3 +47,15 @@ Java_jdk_net_Sockets_isReusePortAvailable0(JNIEnv* env, jclass c1)
// SO_REUSEPORT is not supported on Windows // SO_REUSEPORT is not supported on Windows
return JNI_FALSE; return JNI_FALSE;
} }
/*
* Class: java_net_SocketCleanable
* Method: cleanupClose0
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_java_net_SocketCleanable_cleanupClose0(JNIEnv *env, jclass c1, jint fd)
{
NET_SocketClose(fd);
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -24,7 +24,7 @@
/* @test /* @test
* *
* @bug 4095393 * @bug 4095393
* * @test main SendSize
* @summary this tests a regression in 1.2, beta 2 and earlier where * @summary this tests a regression in 1.2, beta 2 and earlier where
* the DatagramPackets sent the entire buffer, not the buffer length * the DatagramPackets sent the entire buffer, not the buffer length
* of the packet. * of the packet.
@ -43,8 +43,12 @@ public class SendSize {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
DatagramSocket serverSocket = new DatagramSocket(); DatagramSocket serverSocket = new DatagramSocket();
new ServerThread(serverSocket).start(); Thread server = new ServerThread(serverSocket);
new ClientThread(serverSocket.getLocalPort()).start(); server.start();
Thread client = new ClientThread(serverSocket.getLocalPort());
client.start();
server.join();
client.join();
} }
static class ServerThread extends Thread { static class ServerThread extends Thread {
@ -58,24 +62,26 @@ public class SendSize {
try { try {
System.err.println("started server thread: " + server); System.err.println("started server thread: " + server);
byte[] buf = new byte[1024]; byte[] buf = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(buf, for (int i = 0; i < 10; i++) {
buf.length); DatagramPacket receivePacket = new DatagramPacket(buf,
server.receive(receivePacket); buf.length);
int len = receivePacket.getLength(); server.receive(receivePacket);
switch(len) { int len = receivePacket.getLength();
case packetLength: switch (len) {
System.out.println("receive length is: " + len); case packetLength:
break; System.out.println("receive length is: " + len);
default: break;
throw new RuntimeException( default:
"receive length is: " + len + throw new RuntimeException(
", send length: " + packetLength + "receive length is: " + len +
", buffer length: " + buf.length); ", send length: " + packetLength +
", buffer length: " + buf.length);
}
} }
return; return;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
throw new RuntimeException("caugth: " + e); throw new RuntimeException("caught: " + e);
} finally { } finally {
if (server != null) { server.close(); } if (server != null) { server.close(); }
} }

View file

@ -0,0 +1,269 @@
/*
* Copyright (c) 2017, 2018, 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
* @modules java.management java.base/java.io:+open java.base/java.net:+open
* @run main/othervm UnreferencedDatagramSockets
* @run main/othervm -Djava.net.preferIPv4Stack=true UnreferencedDatagramSockets
* @summary Check that unreferenced datagram 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.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.DatagramSocketImpl;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import com.sun.management.UnixOperatingSystemMXBean;
public class UnreferencedDatagramSockets {
/**
* 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 {
DatagramSocket ss;
Server() throws IOException {
ss = new DatagramSocket(0);
System.out.printf(" DatagramServer addr: %s: %d%n",
this.getHost(), this.getPort());
pendingSockets.add(new NamedWeak(ss, pendingQueue, "serverDatagramSocket"));
extractRefs(ss, "serverDatagramSocket");
}
InetAddress getHost() throws UnknownHostException {
InetAddress localhost = InetAddress.getByName("localhost"); //.getLocalHost();
return localhost;
}
int getPort() {
return ss.getLocalPort();
}
// 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);
buffer[0] += 1;
ss.send(p); // send back +1
// do NOT close but 'forget' the datagram socket reference
ss = null;
} catch (Exception ioe) {
ioe.printStackTrace();
}
}
}
public static void main(String args[]) throws Exception {
// Create and close a DatagramSocket to warm up the FD count for side effects.
try (DatagramSocket s = new DatagramSocket(0)) {
// 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();
DatagramSocket client = new DatagramSocket(0);
client.connect(svr.getHost(), svr.getPort());
pendingSockets.add(new NamedWeak(client, pendingQueue, "clientDatagramSocket"));
extractRefs(client, "clientDatagramSocket");
byte[] msg = new byte[1];
msg[0] = 1;
DatagramPacket p = new DatagramPacket(msg, msg.length, svr.getHost(), svr.getPort());
client.send(p);
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]);
}
// Do NOT close the DatagramSocket; 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;
}
// Reflect to find references in the datagram implementation that will be gc'd
private static void extractRefs(DatagramSocket s, String name) {
try {
Field socketImplField = DatagramSocket.class.getDeclaredField("impl");
socketImplField.setAccessible(true);
Object socketImpl = socketImplField.get(s);
Field fileDescriptorField = DatagramSocketImpl.class.getDeclaredField("fd");
fileDescriptorField.setAccessible(true);
FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl);
extractRefs(fileDescriptor, name);
Class<?> socketImplClass = socketImpl.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(socketImpl);
extractRefs(fileDescriptor1, name + "::twoStacksFd1");
} else {
System.out.printf("socketImpl class name not matched: %s != %s%n",
socketImplClass.getName(), "java.net.TwoStacksPlainDatagramSocketImpl");
}
} catch (NoSuchFieldException | IllegalAccessException 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();
}
}
}

View file

@ -0,0 +1,271 @@
/*
* Copyright (c) 2018, 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
* @modules java.management java.base/java.io:+open java.base/java.net:+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.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.DatagramSocketImpl;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import com.sun.management.UnixOperatingSystemMXBean;
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;
Server() throws IOException {
ss = new MulticastSocket(0);
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.getByName("localhost"); //.getLocalHost();
return localhost;
}
int getPort() {
return ss.getLocalPort();
}
// 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);
buffer[0] += 1;
ss.send(p); // send back +1
// do NOT close but 'forget' the socket reference
ss = null;
} catch (Exception ioe) {
ioe.printStackTrace();
}
}
}
public static void main(String args[]) throws Exception {
// Create and close a MulticastSocket to warm up the FD count for side effects.
try (MulticastSocket s = new MulticastSocket(0)) {
// 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();
MulticastSocket client = new MulticastSocket(0);
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);
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]);
}
// 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;
}
// Reflect to find references in the socket implementation that will be gc'd
private static void extractRefs(MulticastSocket s, String name) {
try {
Field socketImplField = DatagramSocket.class.getDeclaredField("impl");
socketImplField.setAccessible(true);
Object socketImpl = socketImplField.get(s);
Field fileDescriptorField = DatagramSocketImpl.class.getDeclaredField("fd");
fileDescriptorField.setAccessible(true);
FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl);
extractRefs(fileDescriptor, name);
Class<?> socketImplClass = socketImpl.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(socketImpl);
extractRefs(fileDescriptor1, name + "::twoStacksFd1");
} else {
System.out.printf("socketImpl class name not matched: %s != %s%n",
socketImplClass.getName(), "java.net.TwoStacksPlainDatagramSocketImpl");
}
} catch (NoSuchFieldException | IllegalAccessException 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();
}
}
}

View file

@ -0,0 +1,294 @@
/*
* Copyright (c) 2017, 2018, 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
* @modules java.management java.base/java.io:+open java.base/java.net:+open
* @run main/othervm UnreferencedSockets
* @run main/othervm -Djava.net.preferIPv4Stack=true UnreferencedSockets
* @summary Check that unreferenced sockets are closed
*/
import java.io.FileDescriptor;
import java.io.InputStream;
import java.io.OutputStream;
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.net.ServerSocket;
import java.net.Socket;
import java.net.SocketImpl;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import com.sun.management.UnixOperatingSystemMXBean;
public class UnreferencedSockets {
/**
* The set of sockets we have to check up on.
*/
final static ArrayDeque<NamedWeak> pendingSockets = new ArrayDeque<>(100);
/**
* Queued sockets when they are unreferenced.
*/
final static ReferenceQueue<Object> pendingQueue = new ReferenceQueue<>();
// Server to echo a stream
static class Server implements Runnable {
ServerSocket ss;
Server() throws IOException {
ss = new ServerSocket(0);
pendingSockets.add(new NamedWeak(ss, pendingQueue, "serverSocket"));
extractRefs(ss, "serverSocket");
}
public int localPort() {
return ss.getLocalPort();
}
public void run() {
try {
Socket s = ss.accept();
pendingSockets.add(new NamedWeak(s, pendingQueue, "acceptedSocket"));
extractRefs(s, "acceptedSocket");
InputStream in = s.getInputStream();
int b = in.read();
OutputStream out = s.getOutputStream();
out.write(b);
// do NOT close but 'forget' the socket reference
out = null;
in = null;
s = null;
} catch (Exception ioe) {
ioe.printStackTrace();
} finally {
try {
ss.close();
ss = null;
} catch (IOException x) {
x.printStackTrace();
}
}
}
}
public static void main(String args[]) throws Exception {
// Create and close a ServerSocket to warm up the FD count for side effects.
try (ServerSocket s = new ServerSocket(0)) {
// 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();
Socket s = new Socket("localhost", svr.localPort());
pendingSockets.add(new NamedWeak(s, pendingQueue, "clientSocket"));
extractRefs(s, "clientSocket");
OutputStream out = s.getOutputStream();
out.write('x');
out.flush();
InputStream in = s.getInputStream();
int b = in.read(); // wait for it back
System.out.printf(" data sent and received%n");
// Do NOT close the Socket; 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 queued: %s, remaining: %d%n", ref, pendingSockets.size());
} else {
s = null;
out = null;
in = 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;
}
// Reflect to find references in the socket implementation that will be gc'd
private static void extractRefs(Socket s, String name) {
try {
Field socketImplField = Socket.class.getDeclaredField("impl");
socketImplField.setAccessible(true);
Object socketImpl = socketImplField.get(s);
Field fileDescriptorField = SocketImpl.class.getDeclaredField("fd");
fileDescriptorField.setAccessible(true);
FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl);
extractRefs(fileDescriptor, name);
Class<?> socketImplClass = socketImpl.getClass();
System.out.printf("socketImplClass: %s%n", socketImplClass);
if (socketImplClass.getClass().getName().equals("java.net.TwoStacksPlainSocketImpl")) {
Field fileDescriptor1Field = socketImplClass.getDeclaredField("fd1");
fileDescriptor1Field.setAccessible(true);
FileDescriptor fileDescriptor1 = (FileDescriptor) fileDescriptor1Field.get(socketImpl);
extractRefs(fileDescriptor1, name + "::twoStacksFd1");
}
} catch (NoSuchFieldException | IllegalAccessException 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(" fd: %s, fd: %d, cleanup: %s%n",
fileDescriptor, rawfd, cleanup));
}
}
private static void extractRefs(ServerSocket s, String name) {
try {
Field socketImplField = ServerSocket.class.getDeclaredField("impl");
socketImplField.setAccessible(true);
Object socketImpl = socketImplField.get(s);
Field fileDescriptorField = SocketImpl.class.getDeclaredField("fd");
fileDescriptorField.setAccessible(true);
FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(socketImpl);
Field fdField = FileDescriptor.class.getDeclaredField("fd");
fdField.setAccessible(true);
int rawfd = fdField.getInt(fileDescriptor);
Field cleanupField = FileDescriptor.class.getDeclaredField("cleanup");
cleanupField.setAccessible(true);
Object cleanup = cleanupField.get(fileDescriptor);
System.out.print(String.format(" fd: %s, fd: %d, cleanup: %s, socket: %s%n",
fileDescriptor, rawfd, cleanup, s));
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);
}
}
/**
* 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();
}
}
}