8234805: (dc) Remove JNI upcall from DatagramChannel.receive implementation

Reviewed-by: dfuchs, chegar
This commit is contained in:
Alan Bateman 2019-11-30 16:21:19 +00:00
parent b08944dd96
commit f3e90595aa
6 changed files with 535 additions and 226 deletions

View file

@ -90,12 +90,12 @@ class DatagramChannelImpl
// Our file descriptor
private final FileDescriptor fd;
private final int fdVal;
private final Cleanable cleaner;
// Cached InetAddress and port for unconnected DatagramChannels
// used by receive0
private InetAddress cachedSenderInetAddress;
private int cachedSenderPort;
// Native buffer for socket address used by receive0, protected by readLock
private final NativeSocketAddress socketAddress;
// Cleaner to close file descriptor and free native socket address
private final Cleanable cleaner;
// Lock held by current reading or connecting thread
private final ReentrantLock readLock = new ReentrantLock();
@ -154,6 +154,9 @@ class DatagramChannelImpl
throws IOException
{
super(sp);
this.socketAddress = new NativeSocketAddress();
ResourceManager.beforeUdpCreate();
try {
this.family = Net.isIPv6Available()
@ -163,9 +166,12 @@ class DatagramChannelImpl
this.fdVal = IOUtil.fdVal(fd);
} catch (IOException ioe) {
ResourceManager.afterUdpClose();
socketAddress.free();
throw ioe;
}
this.cleaner = CleanerFactory.cleaner().register(this, closerFor(fd));
Runnable releaser = releaserFor(fd, socketAddress);
this.cleaner = CleanerFactory.cleaner().register(this, releaser);
}
public DatagramChannelImpl(SelectorProvider sp, ProtocolFamily family)
@ -183,6 +189,8 @@ class DatagramChannelImpl
}
}
this.socketAddress = new NativeSocketAddress();
ResourceManager.beforeUdpCreate();
try {
this.family = family;
@ -190,9 +198,12 @@ class DatagramChannelImpl
this.fdVal = IOUtil.fdVal(fd);
} catch (IOException ioe) {
ResourceManager.afterUdpClose();
socketAddress.free();
throw ioe;
}
this.cleaner = CleanerFactory.cleaner().register(this, closerFor(fd));
Runnable releaser = releaserFor(fd, socketAddress);
this.cleaner = CleanerFactory.cleaner().register(this, releaser);
}
public DatagramChannelImpl(SelectorProvider sp, FileDescriptor fd)
@ -200,6 +211,13 @@ class DatagramChannelImpl
{
super(sp);
try {
this.socketAddress = new NativeSocketAddress();
} catch (OutOfMemoryError e) {
nd.close(fd);
throw e;
}
// increment UDP count to match decrement when closing
ResourceManager.beforeUdpCreate();
@ -208,7 +226,10 @@ class DatagramChannelImpl
: StandardProtocolFamily.INET;
this.fd = fd;
this.fdVal = IOUtil.fdVal(fd);
this.cleaner = CleanerFactory.cleaner().register(this, closerFor(fd));
Runnable releaser = releaserFor(fd, socketAddress);
this.cleaner = CleanerFactory.cleaner().register(this, releaser);
synchronized (stateLock) {
this.localAddress = Net.localAddress(fd);
}
@ -450,8 +471,6 @@ class DatagramChannelImpl
}
}
private SocketAddress sender; // Set by receive0 (## ugh)
@Override
public SocketAddress receive(ByteBuffer dst) throws IOException {
if (dst.isReadOnly())
@ -459,33 +478,31 @@ class DatagramChannelImpl
readLock.lock();
try {
boolean blocking = isBlocking();
boolean completed = false;
int n = 0;
SocketAddress sender = null;
try {
SocketAddress remote = beginRead(blocking, false);
boolean connected = (remote != null);
SecurityManager sm = System.getSecurityManager();
if (connected || (sm == null)) {
// connected or no security manager
n = receive(dst, connected);
int n = receive(dst, connected);
if (blocking) {
while (IOStatus.okayToRetry(n) && isOpen()) {
park(Net.POLLIN);
n = receive(dst, connected);
}
}
if (n >= 0) {
// sender address is in socket address buffer
sender = socketAddress.toInetSocketAddress();
}
} else {
// security manager and unconnected
n = untrustedReceive(dst);
sender = untrustedReceive(dst);
}
if (n == IOStatus.UNAVAILABLE)
return null;
completed = (n > 0) || (n == 0 && isOpen());
return sender;
} finally {
endRead(blocking, completed);
assert IOStatus.check(n);
endRead(blocking, (sender != null));
}
} finally {
readLock.unlock();
@ -498,10 +515,8 @@ class DatagramChannelImpl
* into a buffer that is not accessible to the user. The datagram is copied
* into the user's buffer when the sender address is accepted by the security
* manager.
*
* @return the size of the datagram or IOStatus.UNAVAILABLE
*/
private int untrustedReceive(ByteBuffer dst) throws IOException {
private SocketAddress untrustedReceive(ByteBuffer dst) throws IOException {
SecurityManager sm = System.getSecurityManager();
assert readLock.isHeldByCurrentThread()
&& sm != null && remoteAddress == null;
@ -516,18 +531,21 @@ class DatagramChannelImpl
park(Net.POLLIN);
n = receive(bb, false);
}
} else if (n == IOStatus.UNAVAILABLE) {
return n;
}
InetSocketAddress isa = (InetSocketAddress) sender;
try {
sm.checkAccept(isa.getAddress().getHostAddress(), isa.getPort());
bb.flip();
dst.put(bb);
return n;
} catch (SecurityException se) {
// ignore datagram
bb.clear();
if (n >= 0) {
// sender address is in socket address buffer
InetSocketAddress isa = socketAddress.toInetSocketAddress();
try {
sm.checkAccept(isa.getAddress().getHostAddress(), isa.getPort());
bb.flip();
dst.put(bb);
return isa;
} catch (SecurityException se) {
// ignore datagram
bb.clear();
}
} else {
return null;
}
}
} finally {
@ -584,21 +602,22 @@ class DatagramChannelImpl
throws IOException
{
assert readLock.isHeldByCurrentThread() && isBlocking();
boolean completed = false;
int n = 0;
SocketAddress sender = null;
try {
SocketAddress remote = beginRead(true, false);
boolean connected = (remote != null);
n = receive(dst, connected);
while (n == IOStatus.UNAVAILABLE && isOpen()) {
int n = receive(dst, connected);
while (IOStatus.okayToRetry(n) && isOpen()) {
park(Net.POLLIN);
n = receive(dst, connected);
}
completed = (n > 0) || (n == 0 && isOpen());
if (n >= 0) {
// sender address is in socket address buffer
sender = socketAddress.toInetSocketAddress();
}
return sender;
} finally {
endRead(true, completed);
assert IOStatus.check(n);
endRead(true, (sender != null));
}
}
@ -611,8 +630,7 @@ class DatagramChannelImpl
throws IOException
{
assert readLock.isHeldByCurrentThread() && isBlocking();
boolean completed = false;
int n = 0;
SocketAddress sender = null;
try {
SocketAddress remote = beginRead(true, false);
boolean connected = (remote != null);
@ -621,7 +639,7 @@ class DatagramChannelImpl
lockedConfigureBlocking(false);
try {
long startNanos = System.nanoTime();
n = receive(dst, connected);
int n = receive(dst, connected);
while (n == IOStatus.UNAVAILABLE && isOpen()) {
long remainingNanos = nanos - (System.nanoTime() - startNanos);
if (remainingNanos <= 0) {
@ -630,16 +648,17 @@ class DatagramChannelImpl
park(Net.POLLIN, remainingNanos);
n = receive(dst, connected);
}
completed = (n > 0) || (n == 0 && isOpen());
if (n >= 0) {
// sender address is in socket address buffer
sender = socketAddress.toInetSocketAddress();
}
return sender;
} finally {
// restore socket to blocking mode (if channel is open)
tryLockedConfigureBlocking(true);
}
} finally {
endRead(true, completed);
assert IOStatus.check(n);
endRead(true, (sender != null));
}
}
@ -671,7 +690,10 @@ class DatagramChannelImpl
boolean connected)
throws IOException
{
int n = receive0(fd, ((DirectBuffer)bb).address() + pos, rem, connected);
int n = receive0(fd,
((DirectBuffer)bb).address() + pos, rem,
socketAddress.address(),
connected);
if (n > 0)
bb.position(pos + n);
return n;
@ -1709,38 +1731,37 @@ class DatagramChannelImpl
}
/**
* Returns an action to close the given file descriptor.
* Returns an action to release a the given file descriptor and free the
* given native socket address.
*/
private static Runnable closerFor(FileDescriptor fd) {
private static Runnable releaserFor(FileDescriptor fd, NativeSocketAddress sa) {
return () -> {
try {
nd.close(fd);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
} finally {
// decrement
// decrement socket count and release memory
ResourceManager.afterUdpClose();
sa.free();
}
};
}
// -- Native methods --
private static native void initIDs();
private static native void disconnect0(FileDescriptor fd, boolean isIPv6)
throws IOException;
private native int receive0(FileDescriptor fd, long address, int len,
boolean connected)
private static native int receive0(FileDescriptor fd, long address, int len,
long senderAddress, boolean connected)
throws IOException;
private native int send0(boolean preferIPv6, FileDescriptor fd, long address,
int len, InetAddress addr, int port)
private static native int send0(boolean preferIPv6, FileDescriptor fd,
long address, int len, InetAddress addr, int port)
throws IOException;
static {
IOUtil.load();
initIDs();
}
}

View file

@ -0,0 +1,213 @@
/*
* Copyright (c) 2019, 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 sun.nio.ch;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import jdk.internal.misc.Unsafe;
import jdk.internal.util.ArraysSupport;
/**
* A native socket address that is the union of struct sockaddr, struct sockaddr_in,
* and struct sockaddr_in6.
*
* This class is not thread safe.
*/
class NativeSocketAddress {
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
private static final long ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
private static final int AF_INET = AFINET();
private static final int AF_INET6 = AFINET6();
private static final int SIZEOF_SOCKETADDRESS = sizeofSOCKETADDRESS();
private static final int SIZEOF_FAMILY = sizeofFamily();
private static final int OFFSET_FAMILY = offsetFamily();
private static final int OFFSET_SIN4_PORT = offsetSin4Port();
private static final int OFFSET_SIN4_ADDR = offsetSin4Addr();
private static final int OFFSET_SIN6_PORT = offsetSin6Port();
private static final int OFFSET_SIN6_ADDR = offsetSin6Addr();
private static final int OFFSET_SIN6_SCOPE_ID = offsetSin6ScopeId();
// SOCKETADDRESS
private final long address;
// cached copy of SOCKETADDRESS and the corresponding InetSocketAddress
private final long cachedSocketAddress;
private InetSocketAddress cachedInetSocketAddress;
NativeSocketAddress() {
// allocate 2 * SOCKETADDRESS
int size = SIZEOF_SOCKETADDRESS << 1;
long base = UNSAFE.allocateMemory(size);
UNSAFE.setMemory(base, size, (byte) 0);
this.address = base;
this.cachedSocketAddress = base + SIZEOF_SOCKETADDRESS;
}
long address() {
return address;
}
void free() {
UNSAFE.freeMemory(address);
}
/**
* Return an InetSocketAddress to represent the socket address in this buffer.
* @throws SocketException if the socket address is not AF_INET or AF_INET6
*/
InetSocketAddress toInetSocketAddress() throws SocketException {
// return cached InetSocketAddress if the SOCKETADDRESS bytes match
if (cachedInetSocketAddress != null && mismatch() < 0) {
return cachedInetSocketAddress;
}
// decode SOCKETADDRESS to InetSocketAddress
int family = family();
if (family != AF_INET && family != AF_INET6)
throw new SocketException("Socket family not recognized");
var isa = new InetSocketAddress(address(family), port(family));
// copy SOCKETADDRESS and InetSocketAddress
UNSAFE.copyMemory(null, address, null, cachedSocketAddress, SIZEOF_SOCKETADDRESS);
this.cachedInetSocketAddress = isa;
return isa;
}
/**
* Find a mismatch between the SOCKETADDRESS structures stored at address
* and cachedSocketAddress.
* @return the byte offset of the first mismatch or -1 if no mismatch
*/
private int mismatch() {
int i = ArraysSupport.vectorizedMismatch(null,
address,
null,
cachedSocketAddress,
SIZEOF_SOCKETADDRESS,
ArraysSupport.LOG2_ARRAY_BYTE_INDEX_SCALE);
if (i >= 0)
return i;
i = SIZEOF_SOCKETADDRESS - ~i;
for (; i < SIZEOF_SOCKETADDRESS; i++) {
if (UNSAFE.getByte(address + i) != UNSAFE.getByte(cachedSocketAddress + i)) {
return i;
}
}
return -1;
}
@Override
public String toString() {
int family = family();
if (family == AF_INET || family == AF_INET6) {
return ((family == AF_INET) ? "AF_INET" : "AF_INET6")
+ ", address=" + address(family) + ", port=" + port(family);
} else {
return "<unknown>";
}
}
/**
* Return the value of the sa_family field.
*/
private int family() {
if (SIZEOF_FAMILY == 1) {
return UNSAFE.getByte(address + OFFSET_FAMILY);
} else if (SIZEOF_FAMILY == 2) {
return UNSAFE.getShort(address + OFFSET_FAMILY);
} else {
throw new InternalError();
}
}
/**
* Return the value of the sin4_port or sin6_port field. These fields are
* stored in network order.
*/
private int port(int family) {
byte b1, b2;
if (family == AF_INET) {
b1 = UNSAFE.getByte(address + OFFSET_SIN4_PORT);
b2 = UNSAFE.getByte(address + OFFSET_SIN4_PORT + 1);
} else {
b1 = UNSAFE.getByte(address + OFFSET_SIN6_PORT);
b2 = UNSAFE.getByte(address + OFFSET_SIN6_PORT + 1);
}
return (Byte.toUnsignedInt(b1) << 8) + Byte.toUnsignedInt(b2);
}
/**
* Return an InetAddress to represent the value of the address in the
* sin4_addr or sin6_addr fields.
*/
private InetAddress address(int family) {
int len;
int offset;
int scope_id;
if (family == AF_INET) {
len = 4;
offset = OFFSET_SIN4_ADDR;
scope_id = 0;
} else {
len = 16;
offset = OFFSET_SIN6_ADDR;
scope_id = UNSAFE.getInt(address + OFFSET_SIN6_SCOPE_ID);
}
byte[] bytes = new byte[len];
UNSAFE.copyMemory(null, address + offset, bytes, ARRAY_BASE_OFFSET, len);
try {
if (scope_id == 0) {
return InetAddress.getByAddress(bytes);
} else {
return Inet6Address.getByAddress(null, bytes, scope_id);
}
} catch (UnknownHostException e) {
throw new InternalError(e);
}
}
private static native int AFINET();
private static native int AFINET6();
private static native int sizeofSOCKETADDRESS();
private static native int sizeofFamily();
private static native int offsetFamily();
private static native int offsetSin4Port();
private static native int offsetSin4Addr();
private static native int offsetSin6Port();
private static native int offsetSin6Addr();
private static native int offsetSin6ScopeId();
static {
IOUtil.load();
}
}