4ffe96a857
Reviewed-by: dfuchs
344 lines
13 KiB
Java
344 lines
13 KiB
Java
/*
|
|
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/**
|
|
* @test
|
|
* @library /test/lib
|
|
* @modules java.management java.base/java.io:+open java.base/java.net:+open
|
|
* java.base/sun.net java.base/sun.nio.ch:+open
|
|
* @run main/othervm -Djava.net.preferIPv4Stack=true UnreferencedMulticastSockets
|
|
* @run main/othervm UnreferencedMulticastSockets
|
|
* @summary Check that unreferenced multicast sockets are closed
|
|
*/
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.lang.management.ManagementFactory;
|
|
import java.lang.management.OperatingSystemMXBean;
|
|
import java.lang.ref.ReferenceQueue;
|
|
import java.lang.ref.WeakReference;
|
|
import java.lang.reflect.Field;
|
|
import java.io.IOException;
|
|
import java.lang.reflect.Method;
|
|
import java.net.DatagramPacket;
|
|
import java.net.DatagramSocket;
|
|
import java.net.DatagramSocketImpl;
|
|
import java.net.InetAddress;
|
|
import java.net.InetSocketAddress;
|
|
import java.net.MulticastSocket;
|
|
import java.net.UnknownHostException;
|
|
import java.nio.channels.DatagramChannel;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.security.AccessController;
|
|
import java.security.PrivilegedAction;
|
|
import java.util.ArrayDeque;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
import java.util.concurrent.Phaser;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import jdk.test.lib.net.IPSupport;
|
|
|
|
import com.sun.management.UnixOperatingSystemMXBean;
|
|
import sun.net.NetProperties;
|
|
|
|
public class UnreferencedMulticastSockets {
|
|
|
|
/**
|
|
* The set of sockets we have to check up on.
|
|
*/
|
|
final static ArrayDeque<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;
|
|
final int port;
|
|
final Phaser phaser = new Phaser(2);
|
|
Server() throws IOException {
|
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
|
InetSocketAddress serverAddress =
|
|
new InetSocketAddress(loopback, 0);
|
|
ss = new MulticastSocket(serverAddress);
|
|
port = ss.getLocalPort();
|
|
System.out.printf(" DatagramServer addr: %s: %d%n",
|
|
this.getHost(), this.getPort());
|
|
pendingSockets.add(new NamedWeak(ss, pendingQueue, "serverMulticastSocket"));
|
|
extractRefs(ss, "serverMulticastSocket");
|
|
}
|
|
|
|
InetAddress getHost() throws UnknownHostException {
|
|
InetAddress localhost = InetAddress.getLoopbackAddress();
|
|
return localhost;
|
|
}
|
|
|
|
int getPort() {
|
|
return port;
|
|
}
|
|
|
|
// Receive a byte and send back a byte
|
|
public void run() {
|
|
try {
|
|
byte[] buffer = new byte[50];
|
|
DatagramPacket p = new DatagramPacket(buffer, buffer.length);
|
|
ss.receive(p);
|
|
System.out.printf("Server: ping received from: %s%n", p.getSocketAddress());
|
|
phaser.arriveAndAwaitAdvance(); // await the client...
|
|
buffer[0] += 1;
|
|
System.out.printf("Server: sending echo to: %s%n", p.getSocketAddress());
|
|
ss.send(p); // send back +1
|
|
|
|
System.out.printf("Server: awaiting client%n");
|
|
phaser.arriveAndAwaitAdvance(); // await the client...
|
|
// do NOT close but 'forget' the socket reference
|
|
System.out.printf("Server: forgetting socket...%n");
|
|
ss = null;
|
|
} catch (Throwable ioe) {
|
|
ioe.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void main(String args[]) throws Exception {
|
|
IPSupport.throwSkippedExceptionIfNonOperational();
|
|
|
|
InetSocketAddress clientAddress =
|
|
new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
|
|
|
|
// Create and close a MulticastSocket to warm up the FD count for side effects.
|
|
try (MulticastSocket s = new MulticastSocket(clientAddress)) {
|
|
// no-op; close immediately
|
|
s.getLocalPort(); // no-op
|
|
}
|
|
|
|
long fdCount0 = getFdCount();
|
|
listProcFD();
|
|
|
|
// start a server
|
|
Server svr = new Server();
|
|
Thread thr = new Thread(svr);
|
|
thr.start();
|
|
|
|
// It is possible under some circumstances that the client
|
|
// might get bound to the same port than the server: this
|
|
// would make the test fail - so if this happen we try to
|
|
// bind to a specific port by incrementing the server port.
|
|
MulticastSocket client = null;
|
|
int serverPort = svr.getPort();
|
|
int maxtries = 20;
|
|
for (int i = 0; i < maxtries; i++) {
|
|
try {
|
|
System.out.printf("Trying to bind client to: %s%n", clientAddress);
|
|
client = new MulticastSocket(clientAddress);
|
|
if (client.getLocalPort() != svr.getPort()) break;
|
|
client.close();
|
|
} catch (IOException x) {
|
|
System.out.printf("Couldn't create client after %d attempts: %s%n", i, x);
|
|
if (i == maxtries) throw x;
|
|
}
|
|
if (i == maxtries) {
|
|
String msg = String.format("Couldn't create client after %d attempts", i);
|
|
System.out.println(msg);
|
|
throw new AssertionError(msg);
|
|
}
|
|
clientAddress = new InetSocketAddress(clientAddress.getAddress(), serverPort + i);
|
|
}
|
|
|
|
System.out.printf(" client bound port: %s:%d%n",
|
|
client.getLocalAddress(), client.getLocalPort());
|
|
client.connect(svr.getHost(), svr.getPort());
|
|
pendingSockets.add(new NamedWeak(client, pendingQueue, "clientMulticastSocket"));
|
|
extractRefs(client, "clientMulticastSocket");
|
|
|
|
byte[] msg = new byte[1];
|
|
msg[0] = 1;
|
|
DatagramPacket p = new DatagramPacket(msg, msg.length, svr.getHost(), svr.getPort());
|
|
client.send(p);
|
|
System.out.printf(" ping sent to: %s:%d%n", svr.getHost(), svr.getPort());
|
|
svr.phaser.arriveAndAwaitAdvance(); // wait until the server has received its packet
|
|
|
|
p = new DatagramPacket(msg, msg.length);
|
|
client.receive(p);
|
|
|
|
System.out.printf(" echo received from: %s%n", p.getSocketAddress());
|
|
if (msg[0] != 2) {
|
|
throw new AssertionError("incorrect data received: expected: 2, actual: " + msg[0]);
|
|
}
|
|
svr.phaser.arriveAndAwaitAdvance(); // let the server null out its socket
|
|
|
|
// Do NOT close the MulticastSocket; forget it
|
|
|
|
Object ref;
|
|
int loops = 20;
|
|
while (!pendingSockets.isEmpty() && loops-- > 0) {
|
|
ref = pendingQueue.remove(1000L);
|
|
if (ref != null) {
|
|
pendingSockets.remove(ref);
|
|
System.out.printf(" ref freed: %s, remaining: %d%n", ref, pendingSockets.size());
|
|
} else {
|
|
client = null;
|
|
p = null;
|
|
msg = null;
|
|
System.gc();
|
|
}
|
|
}
|
|
|
|
thr.join();
|
|
|
|
// List the open file descriptors
|
|
long fdCount = getFdCount();
|
|
System.out.printf("Initial fdCount: %d, final fdCount: %d%n", fdCount0, fdCount);
|
|
listProcFD();
|
|
|
|
if (loops == 0) {
|
|
throw new AssertionError("Not all references reclaimed");
|
|
}
|
|
}
|
|
|
|
// Get the count of open file descriptors, or -1 if not available
|
|
private static long getFdCount() {
|
|
OperatingSystemMXBean mxBean = ManagementFactory.getOperatingSystemMXBean();
|
|
return (mxBean instanceof UnixOperatingSystemMXBean)
|
|
? ((UnixOperatingSystemMXBean) mxBean).getOpenFileDescriptorCount()
|
|
: -1L;
|
|
}
|
|
|
|
private static boolean usePlainDatagramSocketImpl() {
|
|
PrivilegedAction<String> pa = () -> NetProperties.get("jdk.net.usePlainDatagramSocketImpl");
|
|
String s = AccessController.doPrivileged(pa);
|
|
return (s != null) && (s.isEmpty() || s.equalsIgnoreCase("true"));
|
|
}
|
|
|
|
// Reflect to find references in the datagram implementation that will be gc'd
|
|
private static void extractRefs(DatagramSocket s, String name) {
|
|
|
|
try {
|
|
Field datagramSocketField = DatagramSocket.class.getDeclaredField("delegate");
|
|
datagramSocketField.setAccessible(true);
|
|
|
|
if (!usePlainDatagramSocketImpl()) {
|
|
// MulticastSocket using DatagramSocketAdaptor
|
|
Object MulticastSocket = datagramSocketField.get(s);
|
|
|
|
Method m = DatagramSocket.class.getDeclaredMethod("getChannel");
|
|
m.setAccessible(true);
|
|
DatagramChannel datagramChannel = (DatagramChannel) m.invoke(MulticastSocket);
|
|
|
|
assert datagramChannel.getClass() == Class.forName("sun.nio.ch.DatagramChannelImpl");
|
|
|
|
Field fileDescriptorField = datagramChannel.getClass().getDeclaredField("fd");
|
|
fileDescriptorField.setAccessible(true);
|
|
FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(datagramChannel);
|
|
extractRefs(fileDescriptor, name);
|
|
|
|
} else {
|
|
// MulticastSocket using PlainDatagramSocketImpl
|
|
Object MulticastSocket = datagramSocketField.get(s);
|
|
assert MulticastSocket.getClass() == Class.forName("java.net.NetMulticastSocket");
|
|
|
|
Method m = MulticastSocket.getClass().getDeclaredMethod("getImpl");
|
|
m.setAccessible(true);
|
|
DatagramSocketImpl datagramSocketImpl = (DatagramSocketImpl) m.invoke(MulticastSocket);
|
|
|
|
Field fileDescriptorField = DatagramSocketImpl.class.getDeclaredField("fd");
|
|
fileDescriptorField.setAccessible(true);
|
|
FileDescriptor fileDescriptor = (FileDescriptor) fileDescriptorField.get(datagramSocketImpl);
|
|
extractRefs(fileDescriptor, name);
|
|
}
|
|
} catch (Exception ex) {
|
|
ex.printStackTrace();
|
|
throw new AssertionError("missing field", ex);
|
|
}
|
|
}
|
|
|
|
private static void extractRefs(FileDescriptor fileDescriptor, String name) {
|
|
Object cleanup = null;
|
|
int rawfd = -1;
|
|
try {
|
|
if (fileDescriptor != null) {
|
|
Field fd1Field = FileDescriptor.class.getDeclaredField("fd");
|
|
fd1Field.setAccessible(true);
|
|
rawfd = fd1Field.getInt(fileDescriptor);
|
|
|
|
Field cleanupfdField = FileDescriptor.class.getDeclaredField("cleanup");
|
|
cleanupfdField.setAccessible(true);
|
|
cleanup = cleanupfdField.get(fileDescriptor);
|
|
pendingSockets.add(new NamedWeak(fileDescriptor, pendingQueue,
|
|
name + "::fileDescriptor: " + rawfd));
|
|
pendingSockets.add(new NamedWeak(cleanup, pendingQueue, name + "::fdCleanup: " + rawfd));
|
|
|
|
}
|
|
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
|
ex.printStackTrace();
|
|
throw new AssertionError("missing field", ex);
|
|
} finally {
|
|
System.out.print(String.format(" %s:: fd: %s, fd: %d, cleanup: %s%n",
|
|
name, fileDescriptor, rawfd, cleanup));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to list the open file descriptors (if supported by the 'lsof' command).
|
|
*/
|
|
static void listProcFD() {
|
|
List<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();
|
|
}
|
|
}
|
|
}
|