8232673: (dc) DatagramChannel socket adaptor issues

Reviewed-by: dfuchs, chegar
This commit is contained in:
Alan Bateman 2019-11-02 10:02:18 +00:00
parent 5dafc279a7
commit db4909bf99
8 changed files with 948 additions and 245 deletions

View File

@ -606,7 +606,6 @@ class DatagramSocket implements java.io.Closeable {
* @see #bind(SocketAddress)
* @since 1.4
*/
public SocketAddress getLocalSocketAddress() {
if (isClosed())
return null;
@ -853,7 +852,7 @@ class DatagramSocket implements java.io.Closeable {
public InetAddress getLocalAddress() {
if (isClosed())
return null;
InetAddress in = null;
InetAddress in;
try {
in = (InetAddress) getImpl().getOption(SocketOptions.SO_BINDADDR);
if (in.isAnyLocalAddress()) {
@ -874,8 +873,8 @@ class DatagramSocket implements java.io.Closeable {
* is bound.
*
* @return the port number on the local host to which this socket is bound,
{@code -1} if the socket is closed, or
{@code 0} if it is not bound yet.
* {@code -1} if the socket is closed, or
* {@code 0} if it is not bound yet.
*/
public int getLocalPort() {
if (isClosed())
@ -887,15 +886,16 @@ class DatagramSocket implements java.io.Closeable {
}
}
/** Enable/disable SO_TIMEOUT with the specified timeout, in
* milliseconds. With this option set to a positive timeout value,
* a call to receive() for this DatagramSocket
* will block for only this amount of time. If the timeout expires,
* a <B>java.net.SocketTimeoutException</B> is raised, though the
* DatagramSocket is still valid. A timeout of zero is interpreted
* as an infinite timeout.
* The option <B>must</B> be enabled prior to entering the blocking
* operation to have effect.
/**
* Enable/disable SO_TIMEOUT with the specified timeout, in
* milliseconds. With this option set to a positive timeout value,
* a call to receive() for this DatagramSocket
* will block for only this amount of time. If the timeout expires,
* a <B>java.net.SocketTimeoutException</B> is raised, though the
* DatagramSocket is still valid. A timeout of zero is interpreted
* as an infinite timeout.
* The option <B>must</B> be enabled prior to entering the blocking
* operation to have effect.
*
* @param timeout the specified timeout in milliseconds.
* @throws SocketException if there is an error in the underlying protocol, such as an UDP error.
@ -963,8 +963,7 @@ class DatagramSocket implements java.io.Closeable {
* negative.
* @see #getSendBufferSize()
*/
public synchronized void setSendBufferSize(int size)
throws SocketException{
public synchronized void setSendBufferSize(int size) throws SocketException {
if (!(size > 0)) {
throw new IllegalArgumentException("negative send size");
}
@ -1021,8 +1020,7 @@ class DatagramSocket implements java.io.Closeable {
* negative.
* @see #getReceiveBufferSize()
*/
public synchronized void setReceiveBufferSize(int size)
throws SocketException{
public synchronized void setReceiveBufferSize(int size) throws SocketException {
if (size <= 0) {
throw new IllegalArgumentException("invalid receive size");
}
@ -1039,8 +1037,7 @@ class DatagramSocket implements java.io.Closeable {
* @throws SocketException if there is an error in the underlying protocol, such as an UDP error.
* @see #setReceiveBufferSize(int)
*/
public synchronized int getReceiveBufferSize()
throws SocketException{
public synchronized int getReceiveBufferSize() throws SocketException {
if (isClosed())
throw new SocketException("Socket is closed");
int result = 0;

View File

@ -28,6 +28,8 @@ package sun.nio.ch;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.ref.Cleaner.Cleanable;
import java.net.DatagramSocket;
import java.net.Inet4Address;
@ -39,6 +41,7 @@ import java.net.PortUnreachableException;
import java.net.ProtocolFamily;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.net.SocketTimeoutException;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
@ -47,6 +50,7 @@ import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.IllegalBlockingModeException;
import java.nio.channels.MembershipKey;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
@ -113,8 +117,17 @@ class DatagramChannelImpl
private InetSocketAddress localAddress;
private InetSocketAddress remoteAddress;
// Our socket adaptor, if any
private DatagramSocket socket;
// Socket adaptor, created lazily
private static final VarHandle SOCKET;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
SOCKET = l.findVarHandle(DatagramChannelImpl.class, "socket", DatagramSocket.class);
} catch (Exception e) {
throw new InternalError(e);
}
}
private volatile DatagramSocket socket;
// Multicast support
private MembershipRegistry registry;
@ -199,11 +212,14 @@ class DatagramChannelImpl
@Override
public DatagramSocket socket() {
synchronized (stateLock) {
if (socket == null)
socket = DatagramSocketAdaptor.create(this);
return socket;
DatagramSocket socket = this.socket;
if (socket == null) {
socket = DatagramSocketAdaptor.create(this);
if (!SOCKET.compareAndSet(this, null, socket)) {
socket = this.socket;
}
}
return socket;
}
@Override
@ -408,62 +424,35 @@ class DatagramChannelImpl
public SocketAddress receive(ByteBuffer dst) throws IOException {
if (dst.isReadOnly())
throw new IllegalArgumentException("Read-only buffer");
readLock.lock();
try {
boolean blocking = isBlocking();
boolean completed = false;
int n = 0;
ByteBuffer bb = 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(fd, dst, connected);
n = receive(dst, connected);
if (blocking) {
while (IOStatus.okayToRetry(n) && isOpen()) {
park(Net.POLLIN);
n = receive(fd, dst, connected);
n = receive(dst, connected);
}
} else if (n == IOStatus.UNAVAILABLE) {
return null;
}
} else {
// Cannot receive into user's buffer when running with a
// security manager and not connected
bb = Util.getTemporaryDirectBuffer(dst.remaining());
for (;;) {
n = receive(fd, bb, connected);
if (blocking) {
while (IOStatus.okayToRetry(n) && isOpen()) {
park(Net.POLLIN);
n = receive(fd, bb, connected);
}
} else if (n == IOStatus.UNAVAILABLE) {
return null;
}
InetSocketAddress isa = (InetSocketAddress)sender;
try {
sm.checkAccept(isa.getAddress().getHostAddress(),
isa.getPort());
} catch (SecurityException se) {
// Ignore packet
bb.clear();
n = 0;
continue;
}
bb.flip();
dst.put(bb);
break;
}
// security manager and unconnected
n = untrustedReceive(dst);
}
assert sender != null;
if (n == IOStatus.UNAVAILABLE)
return null;
completed = (n > 0) || (n == 0 && isOpen());
return sender;
} finally {
if (bb != null)
Util.releaseTemporaryDirectBuffer(bb);
endRead(blocking, n > 0);
endRead(blocking, completed);
assert IOStatus.check(n);
}
} finally {
@ -471,15 +460,164 @@ class DatagramChannelImpl
}
}
private int receive(FileDescriptor fd, ByteBuffer dst, boolean connected)
/**
* Receives a datagram into an untrusted buffer. When there is a security
* manager set, and the socket is not connected, datagrams have to be received
* 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 {
SecurityManager sm = System.getSecurityManager();
assert readLock.isHeldByCurrentThread()
&& sm != null && remoteAddress == null;
ByteBuffer bb = Util.getTemporaryDirectBuffer(dst.remaining());
try {
boolean blocking = isBlocking();
for (;;) {
int n = receive(bb, false);
if (blocking) {
while (IOStatus.okayToRetry(n) && isOpen()) {
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();
}
}
} finally {
Util.releaseTemporaryDirectBuffer(bb);
}
}
/**
* Receives a datagram into the given buffer.
*
* @apiNote This method is for use by the socket adaptor. The buffer is
* assumed to be trusted, meaning it is not accessible to user code.
*
* @throws IllegalBlockingModeException if the channel is non-blocking
* @throws SocketTimeoutException if the timeout elapses
*/
SocketAddress blockingReceive(ByteBuffer dst, long nanos) throws IOException {
readLock.lock();
try {
ensureOpen();
if (!isBlocking())
throw new IllegalBlockingModeException();
SecurityManager sm = System.getSecurityManager();
boolean connected = isConnected();
SocketAddress sender;
do {
if (nanos > 0) {
sender = trustedBlockingReceive(dst, nanos);
} else {
sender = trustedBlockingReceive(dst);
}
// check sender when security manager set and not connected
if (sm != null && !connected) {
InetSocketAddress isa = (InetSocketAddress) sender;
try {
sm.checkAccept(isa.getAddress().getHostAddress(), isa.getPort());
} catch (SecurityException e) {
sender = null;
}
}
} while (sender == null);
return sender;
} finally {
readLock.unlock();
}
}
/**
* Receives a datagram into given buffer. This method is used to support
* the socket adaptor. The buffer is assumed to be trusted.
* @throws SocketTimeoutException if the timeout elapses
*/
private SocketAddress trustedBlockingReceive(ByteBuffer dst)
throws IOException
{
assert readLock.isHeldByCurrentThread() && isBlocking();
boolean completed = false;
int n = 0;
try {
SocketAddress remote = beginRead(true, false);
boolean connected = (remote != null);
n = receive(dst, connected);
while (n == IOStatus.UNAVAILABLE && isOpen()) {
park(Net.POLLIN);
n = receive(dst, connected);
}
completed = (n > 0) || (n == 0 && isOpen());
return sender;
} finally {
endRead(true, completed);
assert IOStatus.check(n);
}
}
/**
* Receives a datagram into given buffer with a timeout. This method is
* used to support the socket adaptor. The buffer is assumed to be trusted.
* @throws SocketTimeoutException if the timeout elapses
*/
private SocketAddress trustedBlockingReceive(ByteBuffer dst, long nanos)
throws IOException
{
assert readLock.isHeldByCurrentThread() && isBlocking();
boolean completed = false;
int n = 0;
try {
SocketAddress remote = beginRead(true, false);
boolean connected = (remote != null);
// change socket to non-blocking
lockedConfigureBlocking(false);
try {
long startNanos = System.nanoTime();
n = receive(dst, connected);
while (n == IOStatus.UNAVAILABLE && isOpen()) {
long remainingNanos = nanos - (System.nanoTime() - startNanos);
if (remainingNanos <= 0) {
throw new SocketTimeoutException("Receive timed out");
}
park(Net.POLLIN, remainingNanos);
n = receive(dst, connected);
}
completed = (n > 0) || (n == 0 && isOpen());
return sender;
} finally {
// restore socket to blocking mode (if channel is open)
tryLockedConfigureBlocking(true);
}
} finally {
endRead(true, completed);
assert IOStatus.check(n);
}
}
private int receive(ByteBuffer dst, boolean connected) throws IOException {
int pos = dst.position();
int lim = dst.limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (dst instanceof DirectBuffer && rem > 0)
return receiveIntoNativeBuffer(fd, dst, rem, pos, connected);
return receiveIntoNativeBuffer(dst, rem, pos, connected);
// Substitute a native buffer. If the supplied buffer is empty
// we must instead use a nonempty buffer, otherwise the call
@ -487,7 +625,7 @@ class DatagramChannelImpl
int newSize = Math.max(rem, 1);
ByteBuffer bb = Util.getTemporaryDirectBuffer(newSize);
try {
int n = receiveIntoNativeBuffer(fd, bb, newSize, 0, connected);
int n = receiveIntoNativeBuffer(bb, newSize, 0, connected);
bb.flip();
if (n > 0 && rem > 0)
dst.put(bb);
@ -497,8 +635,8 @@ class DatagramChannelImpl
}
}
private int receiveIntoNativeBuffer(FileDescriptor fd, ByteBuffer bb,
int rem, int pos, boolean connected)
private int receiveIntoNativeBuffer(ByteBuffer bb, int rem, int pos,
boolean connected)
throws IOException
{
int n = receive0(fd, ((DirectBuffer)bb).address() + pos, rem, connected);
@ -563,6 +701,25 @@ class DatagramChannelImpl
}
}
/**
* Sends a datagram from the bytes in given buffer.
*
* @apiNote This method is for use by the socket adaptor.
*
* @throws IllegalBlockingModeException if the channel is non-blocking
*/
void blockingSend(ByteBuffer src, SocketAddress target) throws IOException {
writeLock.lock();
try {
ensureOpen();
if (!isBlocking())
throw new IllegalBlockingModeException();
send(src, target);
} finally {
writeLock.unlock();
}
}
private int send(FileDescriptor fd, ByteBuffer src, InetSocketAddress target)
throws IOException
{
@ -785,10 +942,7 @@ class DatagramChannelImpl
try {
writeLock.lock();
try {
synchronized (stateLock) {
ensureOpen();
IOUtil.configureBlocking(fd, block);
}
lockedConfigureBlocking(block);
} finally {
writeLock.unlock();
}
@ -797,6 +951,36 @@ class DatagramChannelImpl
}
}
/**
* Adjusts the blocking mode. readLock or writeLock must already be held.
*/
private void lockedConfigureBlocking(boolean block) throws IOException {
assert readLock.isHeldByCurrentThread() || writeLock.isHeldByCurrentThread();
synchronized (stateLock) {
ensureOpen();
IOUtil.configureBlocking(fd, block);
}
}
/**
* Adjusts the blocking mode if the channel is open. readLock or writeLock
* must already be held.
*
* @return {@code true} if the blocking mode was adjusted, {@code false} if
* the blocking mode was not adjusted because the channel is closed
*/
private boolean tryLockedConfigureBlocking(boolean block) throws IOException {
assert readLock.isHeldByCurrentThread() || writeLock.isHeldByCurrentThread();
synchronized (stateLock) {
if (isOpen()) {
IOUtil.configureBlocking(fd, block);
return true;
} else {
return false;
}
}
}
InetSocketAddress localAddress() {
synchronized (stateLock) {
return localAddress;
@ -861,6 +1045,16 @@ class DatagramChannelImpl
@Override
public DatagramChannel connect(SocketAddress sa) throws IOException {
return connect(sa, true);
}
/**
* Connects the channel's socket.
*
* @param sa the remote address to which this channel is to be connected
* @param check true to check if the channel is already connected.
*/
DatagramChannel connect(SocketAddress sa, boolean check) throws IOException {
InetSocketAddress isa = Net.checkAddress(sa, family);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
@ -879,7 +1073,7 @@ class DatagramChannelImpl
try {
synchronized (stateLock) {
ensureOpen();
if (state == ST_CONNECTED)
if (check && state == ST_CONNECTED)
throw new AlreadyConnectedException();
// ensure that the socket is bound
@ -908,7 +1102,7 @@ class DatagramChannelImpl
}
try {
ByteBuffer buf = ByteBuffer.allocate(100);
while (receive(fd, buf, false) > 0) {
while (receive(buf, false) >= 0) {
buf.clear();
}
} finally {
@ -1331,30 +1525,6 @@ class DatagramChannelImpl
return translateReadyOps(ops, 0, ski);
}
/**
* Poll this channel's socket for reading up to the given timeout.
* @return {@code true} if the socket is polled
*/
boolean pollRead(long timeout) throws IOException {
boolean blocking = isBlocking();
assert Thread.holdsLock(blockingLock()) && blocking;
readLock.lock();
try {
boolean polled = false;
try {
beginRead(blocking, false);
int events = Net.poll(fd, Net.POLLIN, timeout);
polled = (events != 0);
} finally {
endRead(blocking, polled);
}
return polled;
} finally {
readLock.unlock();
}
}
/**
* Translates an interest operation set into a native poll event set
*/

View File

@ -26,6 +26,9 @@
package sun.nio.ch;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.VarHandle;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.DatagramSocketImpl;
@ -35,15 +38,16 @@ import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketOption;
import java.net.SocketTimeoutException;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.IllegalBlockingModeException;
import java.util.Objects;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Set;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
// Make a datagram-socket channel look like a datagram socket.
//
@ -61,13 +65,9 @@ class DatagramSocketAdaptor
// Timeout "option" value for receives
private volatile int timeout;
// ## super will create a useless impl
// create DatagramSocket with useless impl
private DatagramSocketAdaptor(DatagramChannelImpl dc) {
// Invoke the DatagramSocketAdaptor(SocketAddress) constructor,
// passing a dummy DatagramSocketImpl object to avoid any native
// resource allocation in super class and invoking our bind method
// before the dc field is initialized.
super(dummyDatagramSocket);
super(new DummyDatagramSocketImpl());
this.dc = dc;
}
@ -75,17 +75,9 @@ class DatagramSocketAdaptor
return new DatagramSocketAdaptor(dc);
}
private void connectInternal(SocketAddress remote)
throws SocketException
{
InetSocketAddress isa = Net.asInetSocketAddress(remote);
int port = isa.getPort();
if (port < 0 || port > 0xFFFF)
throw new IllegalArgumentException("connect: " + port);
if (remote == null)
throw new IllegalArgumentException("connect: null address");
private void connectInternal(SocketAddress remote) throws SocketException {
try {
dc.connect(remote);
dc.connect(remote, false); // skips check for already connected
} catch (ClosedChannelException e) {
// ignore
} catch (Exception x) {
@ -95,9 +87,12 @@ class DatagramSocketAdaptor
@Override
public void bind(SocketAddress local) throws SocketException {
if (local != null) {
local = Net.asInetSocketAddress(local);
} else {
local = new InetSocketAddress(0);
}
try {
if (local == null)
local = new InetSocketAddress(0);
dc.bind(local);
} catch (Exception x) {
Net.translateToSocketException(x);
@ -106,17 +101,20 @@ class DatagramSocketAdaptor
@Override
public void connect(InetAddress address, int port) {
if (address == null)
throw new IllegalArgumentException("Address can't be null");
try {
connectInternal(new InetSocketAddress(address, port));
} catch (SocketException x) {
// Yes, j.n.DatagramSocket really does this
throw new Error(x);
}
}
@Override
public void connect(SocketAddress remote) throws SocketException {
Objects.requireNonNull(remote, "Address can't be null");
connectInternal(remote);
if (remote == null)
throw new IllegalArgumentException("Address can't be null");
connectInternal(Net.asInetSocketAddress(remote));
}
@Override
@ -157,80 +155,84 @@ class DatagramSocketAdaptor
@Override
public SocketAddress getLocalSocketAddress() {
return dc.localAddress();
try {
return dc.getLocalAddress();
} catch (ClosedChannelException e) {
return null;
} catch (Exception x) {
throw new Error(x);
}
}
@Override
public void send(DatagramPacket p) throws IOException {
synchronized (dc.blockingLock()) {
if (!dc.isBlocking())
throw new IllegalBlockingModeException();
try {
synchronized (p) {
ByteBuffer bb = ByteBuffer.wrap(p.getData(),
p.getOffset(),
p.getLength());
if (dc.isConnected()) {
if (p.getAddress() == null) {
// Legacy DatagramSocket will send in this case
// and set address and port of the packet
InetSocketAddress isa = dc.remoteAddress();
p.setPort(isa.getPort());
p.setAddress(isa.getAddress());
dc.write(bb);
} else {
// Target address may not match connected address
dc.send(bb, p.getSocketAddress());
}
} else {
// Not connected so address must be valid or throw
dc.send(bb, p.getSocketAddress());
ByteBuffer bb = null;
try {
InetSocketAddress target;
synchronized (p) {
// copy bytes to temporary direct buffer
int len = p.getLength();
bb = Util.getTemporaryDirectBuffer(len);
bb.put(p.getData(), p.getOffset(), len);
bb.flip();
// target address
if (p.getAddress() == null) {
InetSocketAddress remote = dc.remoteAddress();
if (remote == null) {
// not specified by DatagramSocket
throw new IllegalArgumentException("Address not set");
}
// set address/port to maintain compatibility with DatagramSocket
p.setAddress(remote.getAddress());
p.setPort(remote.getPort());
target = remote;
} else {
// throws IllegalArgumentException if port not set
target = (InetSocketAddress) p.getSocketAddress();
}
} catch (IOException x) {
Net.translateException(x);
}
}
}
private SocketAddress receive(ByteBuffer bb) throws IOException {
assert Thread.holdsLock(dc.blockingLock()) && dc.isBlocking();
long to = this.timeout;
if (to == 0) {
return dc.receive(bb);
} else {
for (;;) {
if (!dc.isOpen())
throw new ClosedChannelException();
long st = System.currentTimeMillis();
if (dc.pollRead(to)) {
return dc.receive(bb);
}
to -= System.currentTimeMillis() - st;
if (to <= 0)
throw new SocketTimeoutException();
// send datagram
try {
dc.blockingSend(bb, target);
} catch (AlreadyConnectedException e) {
throw new IllegalArgumentException("Connected and packet address differ");
} catch (ClosedChannelException e) {
var exc = new SocketException("Socket closed");
exc.initCause(e);
throw exc;
}
} finally {
if (bb != null) {
Util.offerFirstTemporaryDirectBuffer(bb);
}
}
}
@Override
public void receive(DatagramPacket p) throws IOException {
synchronized (dc.blockingLock()) {
if (!dc.isBlocking())
throw new IllegalBlockingModeException();
try {
synchronized (p) {
ByteBuffer bb = ByteBuffer.wrap(p.getData(),
p.getOffset(),
p.getLength());
SocketAddress sender = receive(bb);
p.setSocketAddress(sender);
p.setLength(bb.position() - p.getOffset());
}
} catch (IOException x) {
Net.translateException(x);
// get temporary direct buffer with a capacity of p.bufLength
int bufLength = DatagramPackets.getBufLength(p);
ByteBuffer bb = Util.getTemporaryDirectBuffer(bufLength);
try {
long nanos = MILLISECONDS.toNanos(timeout);
SocketAddress sender = dc.blockingReceive(bb, nanos);
bb.flip();
synchronized (p) {
// copy bytes to the DatagramPacket and set length
int len = Math.min(bb.limit(), DatagramPackets.getBufLength(p));
bb.get(p.getData(), p.getOffset(), len);
DatagramPackets.setLength(p, len);
// sender address
p.setSocketAddress(sender);
}
} catch (ClosedChannelException e) {
var exc = new SocketException("Socket closed");
exc.initCause(e);
throw exc;
} finally {
Util.offerFirstTemporaryDirectBuffer(bb);
}
}
@ -257,19 +259,16 @@ class DatagramSocketAdaptor
public int getLocalPort() {
if (isClosed())
return -1;
try {
InetSocketAddress local = dc.localAddress();
if (local != null) {
return local.getPort();
}
} catch (Exception x) {
InetSocketAddress local = dc.localAddress();
if (local != null) {
return local.getPort();
}
return 0;
}
@Override
public void setSoTimeout(int timeout) throws SocketException {
if (!dc.isOpen())
if (isClosed())
throw new SocketException("Socket is closed");
if (timeout < 0)
throw new IllegalArgumentException("timeout < 0");
@ -278,7 +277,7 @@ class DatagramSocketAdaptor
@Override
public int getSoTimeout() throws SocketException {
if (!dc.isOpen())
if (isClosed())
throw new SocketException("Socket is closed");
return timeout;
}
@ -353,7 +352,6 @@ class DatagramSocketAdaptor
@Override
public boolean getReuseAddress() throws SocketException {
return getBooleanOption(StandardSocketOptions.SO_REUSEADDR);
}
@Override
@ -411,50 +409,157 @@ class DatagramSocketAdaptor
return dc.supportedOptions();
}
/*
* A dummy implementation of DatagramSocketImpl that can be passed to the
* DatagramSocket constructor so that no native resources are allocated in
* super class.
*/
private static final DatagramSocketImpl dummyDatagramSocket
= new DatagramSocketImpl()
{
protected void create() throws SocketException {}
protected void bind(int lport, InetAddress laddr) throws SocketException {}
/**
* DatagramSocketImpl implementation where all methods throw an error.
*/
private static class DummyDatagramSocketImpl extends DatagramSocketImpl {
private static <T> T shouldNotGetHere() {
throw new InternalError("Should not get here");
}
protected void send(DatagramPacket p) throws IOException {}
@Override
protected void create() {
shouldNotGetHere();
}
protected int peek(InetAddress i) throws IOException { return 0; }
@Override
protected void bind(int lport, InetAddress laddr) {
shouldNotGetHere();
}
protected int peekData(DatagramPacket p) throws IOException { return 0; }
@Override
protected void send(DatagramPacket p) {
shouldNotGetHere();
}
protected void receive(DatagramPacket p) throws IOException {}
@Override
protected int peek(InetAddress address) {
return shouldNotGetHere();
}
@Deprecated
protected void setTTL(byte ttl) throws IOException {}
@Override
protected int peekData(DatagramPacket p) {
return shouldNotGetHere();
}
@Deprecated
protected byte getTTL() throws IOException { return 0; }
@Override
protected void receive(DatagramPacket p) {
shouldNotGetHere();
}
protected void setTimeToLive(int ttl) throws IOException {}
@Deprecated
protected void setTTL(byte ttl) {
shouldNotGetHere();
}
protected int getTimeToLive() throws IOException { return 0;}
@Deprecated
protected byte getTTL() {
return shouldNotGetHere();
}
protected void join(InetAddress inetaddr) throws IOException {}
@Override
protected void setTimeToLive(int ttl) {
shouldNotGetHere();
}
protected void leave(InetAddress inetaddr) throws IOException {}
@Override
protected int getTimeToLive() {
return shouldNotGetHere();
}
protected void joinGroup(SocketAddress mcastaddr,
NetworkInterface netIf) throws IOException {}
@Override
protected void join(InetAddress group) {
shouldNotGetHere();
}
protected void leaveGroup(SocketAddress mcastaddr,
NetworkInterface netIf) throws IOException {}
@Override
protected void leave(InetAddress inetaddr) {
shouldNotGetHere();
}
protected void close() {}
@Override
protected void joinGroup(SocketAddress group, NetworkInterface netIf) {
shouldNotGetHere();
}
public Object getOption(int optID) throws SocketException { return null;}
@Override
protected void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) {
shouldNotGetHere();
}
public void setOption(int optID, Object value) throws SocketException {}
};
}
@Override
protected void close() {
shouldNotGetHere();
}
@Override
public Object getOption(int optID) {
return shouldNotGetHere();
}
@Override
public void setOption(int optID, Object value) {
shouldNotGetHere();
}
@Override
protected <T> void setOption(SocketOption<T> name, T value) {
shouldNotGetHere();
}
@Override
protected <T> T getOption(SocketOption<T> name) {
return shouldNotGetHere();
}
@Override
protected Set<SocketOption<?>> supportedOptions() {
return shouldNotGetHere();
}
}
/**
* Defines static methods to get/set DatagramPacket fields and workaround
* DatagramPacket deficiencies.
*/
private static class DatagramPackets {
private static final VarHandle LENGTH;
private static final VarHandle BUF_LENGTH;
static {
try {
PrivilegedAction<Lookup> pa = () -> {
try {
return MethodHandles.privateLookupIn(DatagramPacket.class, MethodHandles.lookup());
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
};
MethodHandles.Lookup l = AccessController.doPrivileged(pa);
LENGTH = l.findVarHandle(DatagramPacket.class, "length", int.class);
BUF_LENGTH = l.findVarHandle(DatagramPacket.class, "bufLength", int.class);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* Sets the DatagramPacket.length field. DatagramPacket.setLength cannot be
* used at this time because it sets both the length and bufLength fields.
*/
static void setLength(DatagramPacket p, int value) {
synchronized (p) {
LENGTH.set(p, value);
}
}
/**
* Returns the value of the DatagramPacket.bufLength field.
*/
static int getBufLength(DatagramPacket p) {
synchronized (p) {
return (int) BUF_LENGTH.get(p);
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 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
@ -22,20 +22,22 @@
*/
/* @test
* @bug 4313882 4981129 8143610
* @bug 4313882 4981129 8143610 8232673
* @summary Unit test for datagram-socket-channel adaptors
* @modules java.base/java.net:+open
* @library .. /test/lib
* @build jdk.test.lib.Utils TestServers
* @run main AdaptDatagramSocket
* @run main AdaptorBasic
* @key randomness
*/
import java.net.*;
import java.nio.channels.*;
import java.util.*;
import java.lang.reflect.Field;
public class AdaptDatagramSocket {
public class AdaptorBasic {
static java.io.PrintStream out = System.out;
static Random rand = new Random();
@ -46,13 +48,19 @@ public class AdaptDatagramSocket {
+ "]");
}
static void test(DatagramSocket ds, InetSocketAddress dst,
boolean shouldTimeout)
static int getBufLength(DatagramPacket p) throws Exception {
Field f = DatagramPacket.class.getDeclaredField("bufLength");
f.setAccessible(true);
return (int) f.get(p);
}
static void test(DatagramSocket ds, InetSocketAddress dst, boolean shouldTimeout)
throws Exception
{
DatagramPacket op = new DatagramPacket(new byte[100], 13, 42, dst);
rand.nextBytes(op.getData());
DatagramPacket ip = new DatagramPacket(new byte[100], 19, 100 - 19);
int bufLength = 100 - 19;
DatagramPacket ip = new DatagramPacket(new byte[100], 19, bufLength);
out.println("pre op: " + toString(op) + " ip: " + toString(ip));
long start = System.currentTimeMillis();
@ -61,10 +69,6 @@ public class AdaptDatagramSocket {
for (;;) {
try {
ds.receive(ip);
if (ip.getLength() == 0) { // ## Not sure why this happens
ip.setLength(100 - 19);
continue;
}
} catch (SocketTimeoutException x) {
if (shouldTimeout) {
out.println("Receive timed out, as expected");
@ -88,6 +92,10 @@ public class AdaptDatagramSocket {
throw new Exception("Incorrect sender address, expected: " + dst
+ " actual: " + ip.getSocketAddress());
}
if (getBufLength(ip) != bufLength) {
throw new Exception("DatagramPacket bufLength changed by receive!!!");
}
}
static void test(InetSocketAddress dst,

View File

@ -0,0 +1,83 @@
/*
* 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.
*
* 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
* @bug 8232673
* @summary Test DatagramChannel socket adaptor with concurrent send/receive
*/
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.DatagramChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class AdaptorConcurrentIO {
public static void main(String[] args) throws Exception {
testConcurrentSendReceive(0);
testConcurrentSendReceive(60_000);
}
/**
* Starts a task that blocks in the adaptor's receive method, then invokes
* the adaptor's send method to send a datagram. If the adaptor were using
* the channel's blockingLock then send without be blocked waiting for
* the receive to complete.
*/
static void testConcurrentSendReceive(int timeout) throws Exception {
try (DatagramChannel dc = DatagramChannel.open()) {
InetAddress lb = InetAddress.getLoopbackAddress();
dc.bind(new InetSocketAddress(lb, 0));
DatagramSocket s = dc.socket();
s.setSoTimeout(timeout);
ExecutorService pool = Executors.newSingleThreadExecutor();
try {
Future<String> result = pool.submit(() -> {
byte[] data = new byte[100];
DatagramPacket p = new DatagramPacket(data, 0, data.length);
s.receive(p);
return new String(p.getData(), p.getOffset(), p.getLength(), "UTF-8");
});
Thread.sleep(200); // give chance for thread to block
byte[] data = "hello".getBytes("UTF-8");
DatagramPacket p = new DatagramPacket(data, 0, data.length);
p.setSocketAddress(s.getLocalSocketAddress());
s.send(p);
String msg = result.get();
if (!msg.equals("hello"))
throw new RuntimeException("Unexpected message: " + msg);
} finally {
pool.shutdown();
}
}
}
}

View File

@ -0,0 +1,117 @@
/*
* 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.
*
* 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
* @bug 8232673
* @summary Test DatagramChannel socket adaptor connect method with illegal args
* @run testng AdaptorConnect
*/
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.DatagramChannel;
import static java.net.InetAddress.getLoopbackAddress;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
@Test
public class AdaptorConnect {
/**
* Invoke the given socket's connect method with illegal arguments.
*/
private void testConnectWithIllegalArguments(DatagramSocket s) {
assertThrows(IllegalArgumentException.class, () -> s.connect(null));
assertThrows(IllegalArgumentException.class, () -> s.connect(null, 7000));
assertThrows(IllegalArgumentException.class, () -> s.connect(getLoopbackAddress(), -1));
assertThrows(IllegalArgumentException.class, () -> s.connect(getLoopbackAddress(), 100_000));
SocketAddress sillyAddress = new SocketAddress() { };
assertThrows(IllegalArgumentException.class, () -> s.connect(sillyAddress));
SocketAddress unresolved = InetSocketAddress.createUnresolved("foo", 7777);
assertThrows(SocketException.class, () -> s.connect(unresolved));
}
/**
* Test connect method with an open socket.
*/
public void testOpenSocket() throws Exception {
try (DatagramChannel dc = DatagramChannel.open()) {
DatagramSocket s = dc.socket();
testConnectWithIllegalArguments(s);
// should not be bound or connected
assertTrue(s.getLocalSocketAddress() == null);
assertTrue(s.getRemoteSocketAddress() == null);
// connect(SocketAddress)
var remote1 = new InetSocketAddress(getLoopbackAddress(), 7001);
s.connect(remote1);
assertEquals(s.getRemoteSocketAddress(), remote1);
testConnectWithIllegalArguments(s);
assertEquals(s.getRemoteSocketAddress(), remote1);
// connect(SocketAddress)
var remote2 = new InetSocketAddress(getLoopbackAddress(), 7002);
s.connect(remote2);
assertEquals(s.getRemoteSocketAddress(), remote2);
testConnectWithIllegalArguments(s);
assertEquals(s.getRemoteSocketAddress(), remote2);
// connect(InetAddress, int)
var remote3 = new InetSocketAddress(getLoopbackAddress(), 7003);
s.connect(remote3.getAddress(), remote3.getPort());
assertEquals(s.getRemoteSocketAddress(), remote3);
testConnectWithIllegalArguments(s);
assertEquals(s.getRemoteSocketAddress(), remote3);
// connect(InetAddress, int)
var remote4 = new InetSocketAddress(getLoopbackAddress(), 7004);
s.connect(remote4.getAddress(), remote4.getPort());
assertEquals(s.getRemoteSocketAddress(), remote4);
testConnectWithIllegalArguments(s);
assertEquals(s.getRemoteSocketAddress(), remote4);
}
}
/**
* Test connect method with a closed socket.
*/
public void testClosedSocket() throws Exception {
DatagramChannel dc = DatagramChannel.open();
DatagramSocket s = dc.socket();
dc.close();
testConnectWithIllegalArguments(s);
// connect does not throw an exception when closed
var remote = new InetSocketAddress(getLoopbackAddress(), 7001);
s.connect(remote);
s.connect(remote.getAddress(), remote.getPort());
}
}

View File

@ -0,0 +1,223 @@
/*
* 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.
*
* 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
* @bug 8232673
* @summary Test the DatagramChannel socket adaptor getter methods
* @run testng AdaptorGetters
*/
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.DatagramChannel;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
@Test
public class AdaptorGetters {
/**
* Test getters on unbound socket, before and after it is closed.
*/
public void testUnboundSocket() throws Exception {
DatagramChannel dc = DatagramChannel.open();
DatagramSocket s = dc.socket();
try {
// state
assertFalse(s.isBound());
assertFalse(s.isConnected());
assertFalse(s.isClosed());
// local address
assertTrue(s.getLocalAddress().isAnyLocalAddress());
assertTrue(s.getLocalPort() == 0);
assertTrue(s.getLocalSocketAddress() == null);
// remote address
assertTrue(s.getInetAddress() == null);
assertTrue(s.getPort() == -1);
} finally {
dc.close();
}
// state
assertFalse(s.isBound());
assertFalse(s.isConnected());
assertTrue(s.isClosed());
// local address
assertTrue(s.getLocalAddress() == null);
assertTrue(s.getLocalPort() == -1);
assertTrue(s.getLocalSocketAddress() == null);
// remote address
assertTrue(s.getInetAddress() == null);
assertTrue(s.getPort() == -1);
assertTrue((s.getRemoteSocketAddress() == null));
}
/**
* Test getters on bound socket, before and after it is closed.
*/
public void testBoundSocket() throws Exception {
DatagramChannel dc = DatagramChannel.open();
DatagramSocket s = dc.socket();
try {
dc.bind(new InetSocketAddress(0));
var localAddress = (InetSocketAddress) dc.getLocalAddress();
// state
assertTrue(s.isBound());
assertFalse(s.isConnected());
assertFalse(s.isClosed());
// local address
assertEquals(s.getLocalAddress(), localAddress.getAddress());
assertTrue(s.getLocalPort() == localAddress.getPort());
assertEquals(s.getLocalSocketAddress(), localAddress);
// remote address
assertTrue(s.getInetAddress() == null);
assertTrue(s.getPort() == -1);
assertTrue((s.getRemoteSocketAddress() == null));
} finally {
dc.close();
}
// state
assertTrue(s.isBound());
assertFalse(s.isConnected());
assertTrue(s.isClosed());
// local address
assertTrue(s.getLocalAddress() == null);
assertTrue(s.getLocalPort() == -1);
assertTrue(s.getLocalSocketAddress() == null);
// remote address
assertTrue(s.getInetAddress() == null);
assertTrue(s.getPort() == -1);
assertTrue((s.getRemoteSocketAddress() == null));
}
/**
* Test getters on connected socket, before and after it is closed.
*/
public void testConnectedSocket() throws Exception {
var loopback = InetAddress.getLoopbackAddress();
var remoteAddress = new InetSocketAddress(loopback, 7777);
DatagramChannel dc = DatagramChannel.open();
DatagramSocket s = dc.socket();
try {
dc.connect(remoteAddress);
var localAddress = (InetSocketAddress) dc.getLocalAddress();
// state
assertTrue(s.isBound());
assertTrue(s.isConnected());
assertFalse(s.isClosed());
// local address
assertEquals(s.getLocalAddress(), localAddress.getAddress());
assertTrue(s.getLocalPort() == localAddress.getPort());
assertEquals(s.getLocalSocketAddress(), localAddress);
// remote address
assertEquals(s.getInetAddress(), remoteAddress.getAddress());
assertTrue(s.getPort() == remoteAddress.getPort());
assertEquals(s.getRemoteSocketAddress(), remoteAddress);
} finally {
dc.close();
}
// state
assertTrue(s.isBound());
assertTrue(s.isConnected());
assertTrue(s.isClosed());
// local address
assertTrue(s.getLocalAddress() == null);
assertTrue(s.getLocalPort() == -1);
assertTrue(s.getLocalSocketAddress() == null);
// remote address
assertEquals(s.getInetAddress(), remoteAddress.getAddress());
assertTrue(s.getPort() == remoteAddress.getPort());
assertEquals(s.getRemoteSocketAddress(), remoteAddress);
}
/**
* Test getters on disconnected socket, before and after it is closed.
*/
public void testDisconnectedSocket() throws Exception {
DatagramChannel dc = DatagramChannel.open();
DatagramSocket s = dc.socket();
try {
var loopback = InetAddress.getLoopbackAddress();
dc.connect(new InetSocketAddress(loopback, 7777));
dc.disconnect();
var localAddress = (InetSocketAddress) dc.getLocalAddress();
// state
assertTrue(s.isBound());
assertFalse(s.isConnected());
assertFalse(s.isClosed());
// local address
assertEquals(s.getLocalAddress(), localAddress.getAddress());
assertTrue(s.getLocalPort() == localAddress.getPort());
assertEquals(s.getLocalSocketAddress(), localAddress);
// remote address
assertTrue(s.getInetAddress() == null);
assertTrue(s.getPort() == -1);
assertTrue((s.getRemoteSocketAddress() == null));
} finally {
dc.close();
}
// state
assertTrue(s.isBound());
assertFalse(s.isConnected());
assertTrue(s.isClosed());
// local address
assertTrue(s.getLocalAddress() == null);
assertTrue(s.getLocalPort() == -1);
assertTrue(s.getLocalSocketAddress() == null);
// remote address
assertTrue(s.getInetAddress() == null);
assertTrue(s.getPort() == -1);
assertTrue((s.getRemoteSocketAddress() == null));
}
}

View File

@ -22,7 +22,7 @@
*/
/* @test
* @bug 7184932
* @bug 7184932 8232673
* @summary Test asynchronous close and interrupt of timed socket adapter methods
* @key randomness intermittent
*/
@ -78,8 +78,10 @@ public class AdaptorCloseAndInterrupt {
try (DatagramChannel peer = DatagramChannel.open()) {
peer.socket().bind(null);
new AdaptorCloseAndInterrupt(peer).dcReceiveAsyncClose();
new AdaptorCloseAndInterrupt(peer).dcReceiveAsyncInterrupt();
new AdaptorCloseAndInterrupt(peer).dcReceiveAsyncClose(0);
new AdaptorCloseAndInterrupt(peer).dcReceiveAsyncClose(30_000);
new AdaptorCloseAndInterrupt(peer).dcReceiveAsyncInterrupt(0);
new AdaptorCloseAndInterrupt(peer).dcReceiveAsyncInterrupt(30_000);
}
new AdaptorCloseAndInterrupt().ssAcceptAsyncClose();
@ -138,11 +140,10 @@ public class AdaptorCloseAndInterrupt {
}
}
void dcReceiveAsyncClose() throws IOException {
void dcReceiveAsyncClose(int timeout) throws IOException {
DatagramChannel dc = DatagramChannel.open();
dc.connect(new InetSocketAddress(
InetAddress.getLoopbackAddress(), port));
dc.socket().setSoTimeout(30*1000);
dc.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), port));
dc.socket().setSoTimeout(timeout);
doAsyncClose(dc);
@ -150,24 +151,23 @@ public class AdaptorCloseAndInterrupt {
dc.socket().receive(new DatagramPacket(new byte[100], 100));
System.err.format("close() was invoked: %s%n", isClosed.get());
throw new RuntimeException("receive should not have completed");
} catch (ClosedChannelException expected) {}
} catch (SocketException expected) { }
if (!dc.socket().isClosed())
throw new RuntimeException("socket is not closed");
}
void dcReceiveAsyncInterrupt() throws IOException {
void dcReceiveAsyncInterrupt(int timeout) throws IOException {
DatagramChannel dc = DatagramChannel.open();
dc.connect(new InetSocketAddress(
InetAddress.getLoopbackAddress(), port));
dc.socket().setSoTimeout(30*1000);
dc.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), port));
dc.socket().setSoTimeout(timeout);
doAsyncInterrupt();
try {
dc.socket().receive(new DatagramPacket(new byte[100], 100));
throw new RuntimeException("receive should not have completed");
} catch (ClosedByInterruptException expected) {
} catch (SocketException expected) {
System.out.format("interrupt() was invoked: %s%n",
isInterrupted.get());
System.out.format("dcReceiveAsyncInterrupt was interrupted: %s%n",