8278326: Socket close is not thread safe and other cleanup

Reviewed-by: jpai
This commit is contained in:
Alan Bateman 2023-01-12 09:38:31 +00:00
parent 036c80844e
commit 4b573343a6
5 changed files with 835 additions and 232 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1995, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1995, 2023, 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
@ -98,31 +98,11 @@ import java.util.Collections;
* @since 1.0
*/
public class Socket implements java.io.Closeable {
/**
* Various states of this socket.
*/
private boolean created = false;
private boolean bound = false;
private boolean connected = false;
private boolean closed = false;
private Object closeLock = new Object();
private boolean shutIn = false;
private boolean shutOut = false;
/**
* The implementation of this Socket.
*/
SocketImpl impl;
/**
* Socket input/output streams
*/
private volatile InputStream in;
private volatile OutputStream out;
private static final VarHandle IN, OUT;
private static final VarHandle STATE, IN, OUT;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
STATE = l.findVarHandle(Socket.class, "state", int.class);
IN = l.findVarHandle(Socket.class, "in", InputStream.class);
OUT = l.findVarHandle(Socket.class, "out", OutputStream.class);
} catch (Exception e) {
@ -130,6 +110,63 @@ public class Socket implements java.io.Closeable {
}
}
// the underlying SocketImpl, may be null, may be swapped when connecting
private volatile SocketImpl impl;
// state bits
private static final int SOCKET_CREATED = 1 << 0; // impl.create(boolean) called
private static final int BOUND = 1 << 1;
private static final int CONNECTED = 1 << 2;
private static final int CLOSED = 1 << 3;
private static final int SHUT_IN = 1 << 9;
private static final int SHUT_OUT = 1 << 10;
private volatile int state;
// used to coordinate creating and closing underlying socket
private final Object socketLock = new Object();
// input/output streams
private volatile InputStream in;
private volatile OutputStream out;
/**
* Atomically sets state to the result of a bitwise OR of the current value
* and the given mask.
* @return the previous state value
*/
private int getAndBitwiseOrState(int mask) {
return (int) STATE.getAndBitwiseOr(this, mask);
}
private static boolean isBound(int s) {
return (s & BOUND) != 0;
}
private static boolean isConnected(int s) {
return (s & CONNECTED) != 0;
}
private static boolean isClosed(int s) {
return (s & CLOSED) != 0;
}
private static boolean isInputShutdown(int s) {
return (s & SHUT_IN) != 0;
}
private static boolean isOutputShutdown(int s) {
return (s & SHUT_OUT) != 0;
}
/**
* Creates an unconnected socket with the given {@code SocketImpl}.
*/
private Socket(Void unused, SocketImpl impl) {
if (impl != null) {
this.impl = impl;
}
}
/**
* Creates an unconnected Socket.
* <p>
@ -142,7 +179,7 @@ public class Socket implements java.io.Closeable {
* @revised 1.4
*/
public Socket() {
setImpl();
this.impl = createImpl();
}
/**
@ -232,18 +269,16 @@ public class Socket implements java.io.Closeable {
* @since 1.1
*/
protected Socket(SocketImpl impl) throws SocketException {
checkPermission(impl);
this.impl = impl;
this(checkPermission(impl), impl);
}
private static Void checkPermission(SocketImpl impl) {
if (impl == null) {
return null;
}
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SecurityConstants.SET_SOCKETIMPL_PERMISSION);
if (impl != null) {
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SecurityConstants.SET_SOCKETIMPL_PERMISSION);
}
}
return null;
}
@ -494,16 +529,27 @@ public class Socket implements java.io.Closeable {
new InetSocketAddress(0), stream);
}
private Socket(SocketAddress address, SocketAddress localAddr,
boolean stream) throws IOException {
setImpl();
/**
* Initialize a new Socket that is connected to the given remote address.
* The socket is optionally bound to a local address before connecting.
*
* @param address the remote address to connect to
* @param localAddr the local address to bind to, can be null
* @param stream true for a stream socket, false for a datagram socket
*/
private Socket(SocketAddress address, SocketAddress localAddr, boolean stream)
throws IOException
{
Objects.requireNonNull(address);
// backward compatibility
if (address == null)
throw new NullPointerException();
// create the SocketImpl and the underlying socket
SocketImpl impl = createImpl();
impl.create(stream);
this.impl = impl;
this.state = SOCKET_CREATED;
try {
createImpl(stream);
if (localAddr != null)
bind(localAddr);
connect(address);
@ -518,59 +564,97 @@ public class Socket implements java.io.Closeable {
}
/**
* Creates the socket implementation.
*
* @param stream a {@code boolean} value : {@code true} for a TCP socket,
* {@code false} for UDP.
* @throws SocketException if creation fails
* @since 1.4
* Create a new SocketImpl for a connecting/client socket. The SocketImpl
* is created without an underlying socket.
*/
void createImpl(boolean stream) throws SocketException {
if (impl == null)
setImpl();
try {
impl.create(stream);
created = true;
} catch (SocketException e) {
throw e;
} catch (IOException e) {
throw new SocketException(e.getMessage(), e);
}
}
void setImpl(SocketImpl si) {
impl = si;
}
/**
* Sets impl to the system-default type of SocketImpl.
* @since 1.4
*/
void setImpl() {
private static SocketImpl createImpl() {
SocketImplFactory factory = Socket.factory;
if (factory != null) {
impl = factory.createSocketImpl();
return factory.createSocketImpl();
} else {
// create a SOCKS SocketImpl that delegates to a platform SocketImpl
SocketImpl delegate = SocketImpl.createPlatformSocketImpl(false);
impl = new SocksSocketImpl(delegate);
return new SocksSocketImpl(delegate);
}
}
/**
* Get the {@code SocketImpl} attached to this socket, creating
* it if necessary.
*
* @return the {@code SocketImpl} attached to that ServerSocket.
* @throws SocketException if creation fails
* @since 1.4
* Returns the {@code SocketImpl} for this Socket, creating it, and the
* underlying socket, if required.
* @throws SocketException if creating the underlying socket fails
*/
SocketImpl getImpl() throws SocketException {
if (!created)
createImpl(true);
private SocketImpl getImpl() throws SocketException {
if ((state & SOCKET_CREATED) == 0) {
synchronized (socketLock) {
int s = state; // re-read state
if ((s & SOCKET_CREATED) == 0) {
if (isClosed(s)) {
throw new SocketException("Socket is closed");
}
SocketImpl impl = this.impl;
if (impl == null) {
this.impl = impl = createImpl();
}
try {
impl.create(true);
} catch (SocketException e) {
throw e;
} catch (IOException e) {
throw new SocketException(e.getMessage(), e);
}
getAndBitwiseOrState(SOCKET_CREATED);
}
}
}
return impl;
}
/**
* Returns the SocketImpl, may be null.
*/
SocketImpl impl() {
return impl;
}
/**
* Sets the SocketImpl. The SocketImpl is connected to a peer. The behavior for
* the case that the Socket was not a newly created Socket is unspecified. If
* there is an existing SocketImpl then it closed to avoid leaking resources.
* @throws SocketException if the Socket is closed
* @apiNote For ServerSocket use when accepting connections
*/
void setConnectedImpl(SocketImpl si) throws SocketException {
synchronized (socketLock) {
if ((state & CLOSED) != 0) {
throw new SocketException("Socket is closed");
}
SocketImpl previous = impl;
impl = si;
state = (SOCKET_CREATED | BOUND | CONNECTED);
if (previous != null) {
in = null;
out = null;
previous.closeQuietly();
}
}
}
/**
* Sets the SocketImpl.
* @apiNote For ServerSocket use when accepting connections with a custom SocketImpl
*/
void setImpl(SocketImpl si) {
impl = si;
}
/**
* Sets to Socket state for a newly accepted connection.
* @apiNote For ServerSocket use when accepting connections with a custom SocketImpl
*/
void setConnected() {
getAndBitwiseOrState(SOCKET_CREATED | BOUND | CONNECTED);
}
/**
* Connects this socket to the server.
*
@ -639,18 +723,18 @@ public class Socket implements java.io.Closeable {
throw new IllegalArgumentException("connect: The address can't be null");
if (timeout < 0)
throw new IllegalArgumentException("connect: timeout can't be negative");
throw new IllegalArgumentException("connect: timeout can't be negative");
if (isClosed())
int s = state;
if (isClosed(s))
throw new SocketException("Socket is closed");
if (isConnected())
if (isConnected(s))
throw new SocketException("already connected");
if (!(endpoint instanceof InetSocketAddress epoint))
throw new IllegalArgumentException("Unsupported address type");
InetAddress addr = epoint.getAddress ();
InetAddress addr = epoint.getAddress();
int port = epoint.getPort();
checkAddress(addr, "connect");
@ -662,10 +746,9 @@ public class Socket implements java.io.Closeable {
else
security.checkConnect(addr.getHostAddress(), port);
}
if (!created)
createImpl(true);
try {
impl.connect(epoint, timeout);
getImpl().connect(epoint, timeout);
} catch (SocketTimeoutException e) {
throw e;
} catch (InterruptedIOException e) {
@ -676,12 +759,9 @@ public class Socket implements java.io.Closeable {
}
throw e;
}
connected = true;
/*
* If the socket was not bound before the connect, it is now because
* the kernel will have picked an ephemeral port & a local address
*/
bound = true;
// connect will bind the socket if not previously bound
getAndBitwiseOrState(BOUND | CONNECTED);
}
/**
@ -703,9 +783,10 @@ public class Socket implements java.io.Closeable {
* @see #isBound
*/
public void bind(SocketAddress bindpoint) throws IOException {
if (isClosed())
int s = state;
if (isClosed(s))
throw new SocketException("Socket is closed");
if (isBound())
if (isBound(s))
throw new SocketException("Already bound");
if (bindpoint != null && (!(bindpoint instanceof InetSocketAddress)))
@ -724,11 +805,11 @@ public class Socket implements java.io.Closeable {
if (security != null) {
security.checkListen(port);
}
getImpl().bind (addr, port);
bound = true;
getImpl().bind(addr, port);
getAndBitwiseOrState(BOUND);
}
private void checkAddress (InetAddress addr, String op) {
private void checkAddress(InetAddress addr, String op) {
if (addr == null) {
return;
}
@ -737,15 +818,6 @@ public class Socket implements java.io.Closeable {
}
}
/**
* set the flags after an accept() call.
*/
final void postAccept() {
connected = true;
created = true;
bound = true;
}
/**
* Returns the address to which the socket is connected.
* <p>
@ -896,7 +968,6 @@ public class Socket implements java.io.Closeable {
* @see SecurityManager#checkConnect
* @since 1.4
*/
public SocketAddress getLocalSocketAddress() {
if (!isBound())
return null;
@ -984,11 +1055,12 @@ public class Socket implements java.io.Closeable {
* @revised 1.4
*/
public InputStream getInputStream() throws IOException {
if (isClosed())
int s = state;
if (isClosed(s))
throw new SocketException("Socket is closed");
if (!isConnected())
if (!isConnected(s))
throw new SocketException("Socket is not connected");
if (isInputShutdown())
if (isInputShutdown(s))
throw new SocketException("Socket input is shutdown");
InputStream in = this.in;
if (in == null) {
@ -1080,11 +1152,12 @@ public class Socket implements java.io.Closeable {
* @revised 1.4
*/
public OutputStream getOutputStream() throws IOException {
if (isClosed())
int s = state;
if (isClosed(s))
throw new SocketException("Socket is closed");
if (!isConnected())
if (!isConnected(s))
throw new SocketException("Socket is not connected");
if (isOutputShutdown())
if (isOutputShutdown(s))
throw new SocketException("Socket output is shutdown");
OutputStream out = this.out;
if (out == null) {
@ -1218,8 +1291,8 @@ public class Socket implements java.io.Closeable {
if (isClosed())
throw new SocketException("Socket is closed");
Object o = getImpl().getOption(SocketOptions.SO_LINGER);
if (o instanceof Integer) {
return ((Integer) o).intValue();
if (o instanceof Integer i) {
return i.intValue();
} else {
return -1;
}
@ -1235,8 +1308,8 @@ public class Socket implements java.io.Closeable {
* sending the data.
* @since 1.4
*/
public void sendUrgentData (int data) throws IOException {
if (!getImpl().supportsUrgentData ()) {
public void sendUrgentData(int data) throws IOException {
if (!getImpl().supportsUrgentData()) {
throw new SocketException ("Urgent data not supported");
}
getImpl().sendUrgentData (data);
@ -1307,12 +1380,11 @@ public class Socket implements java.io.Closeable {
* @since 1.1
* @see #getSoTimeout()
*/
public synchronized void setSoTimeout(int timeout) throws SocketException {
public void setSoTimeout(int timeout) throws SocketException {
if (isClosed())
throw new SocketException("Socket is closed");
if (timeout < 0)
throw new IllegalArgumentException("timeout can't be negative");
throw new IllegalArgumentException("timeout can't be negative");
getImpl().setOption(SocketOptions.SO_TIMEOUT, timeout);
}
@ -1327,13 +1399,13 @@ public class Socket implements java.io.Closeable {
* @since 1.1
* @see #setSoTimeout(int)
*/
public synchronized int getSoTimeout() throws SocketException {
public int getSoTimeout() throws SocketException {
if (isClosed())
throw new SocketException("Socket is closed");
Object o = getImpl().getOption(SocketOptions.SO_TIMEOUT);
/* extra type safety */
if (o instanceof Integer) {
return ((Integer) o).intValue();
if (o instanceof Integer i) {
return i.intValue();
} else {
return 0;
}
@ -1362,11 +1434,9 @@ public class Socket implements java.io.Closeable {
* @see #getSendBufferSize()
* @since 1.2
*/
public synchronized void setSendBufferSize(int size)
throws SocketException{
if (!(size > 0)) {
public void setSendBufferSize(int size) throws SocketException {
if (size <= 0)
throw new IllegalArgumentException("negative send size");
}
if (isClosed())
throw new SocketException("Socket is closed");
getImpl().setOption(SocketOptions.SO_SNDBUF, size);
@ -1385,13 +1455,13 @@ public class Socket implements java.io.Closeable {
* @see #setSendBufferSize(int)
* @since 1.2
*/
public synchronized int getSendBufferSize() throws SocketException {
public int getSendBufferSize() throws SocketException {
if (isClosed())
throw new SocketException("Socket is closed");
int result = 0;
Object o = getImpl().getOption(SocketOptions.SO_SNDBUF);
if (o instanceof Integer) {
result = ((Integer)o).intValue();
if (o instanceof Integer i) {
result = i.intValue();
}
return result;
}
@ -1436,11 +1506,9 @@ public class Socket implements java.io.Closeable {
* @see ServerSocket#setReceiveBufferSize(int)
* @since 1.2
*/
public synchronized void setReceiveBufferSize(int size)
throws SocketException{
if (size <= 0) {
public void setReceiveBufferSize(int size) throws SocketException {
if (size <= 0)
throw new IllegalArgumentException("invalid receive size");
}
if (isClosed())
throw new SocketException("Socket is closed");
getImpl().setOption(SocketOptions.SO_RCVBUF, size);
@ -1458,14 +1526,13 @@ public class Socket implements java.io.Closeable {
* @see #setReceiveBufferSize(int)
* @since 1.2
*/
public synchronized int getReceiveBufferSize()
throws SocketException{
public int getReceiveBufferSize() throws SocketException {
if (isClosed())
throw new SocketException("Socket is closed");
int result = 0;
Object o = getImpl().getOption(SocketOptions.SO_RCVBUF);
if (o instanceof Integer) {
result = ((Integer)o).intValue();
if (o instanceof Integer i) {
result = i.intValue();
}
return result;
}
@ -1550,17 +1617,9 @@ public class Socket implements java.io.Closeable {
public void setTrafficClass(int tc) throws SocketException {
if (tc < 0 || tc > 255)
throw new IllegalArgumentException("tc is not in range 0 -- 255");
if (isClosed())
throw new SocketException("Socket is closed");
try {
getImpl().setOption(SocketOptions.IP_TOS, tc);
} catch (SocketException se) {
// not supported if socket already connected
// Solaris returns error in such cases
if(!isConnected())
throw se;
}
getImpl().setOption(SocketOptions.IP_TOS, tc);
}
/**
@ -1661,13 +1720,15 @@ public class Socket implements java.io.Closeable {
* @revised 1.4
* @see #isClosed
*/
public synchronized void close() throws IOException {
synchronized(closeLock) {
if (isClosed())
return;
if (created)
impl.close();
closed = true;
public void close() throws IOException {
synchronized (socketLock) {
if ((state & CLOSED) == 0) {
int s = getAndBitwiseOrState(CLOSED);
if ((s & (SOCKET_CREATED | CLOSED)) == SOCKET_CREATED) {
// close underlying socket if created
impl.close();
}
}
}
}
@ -1689,16 +1750,16 @@ public class Socket implements java.io.Closeable {
* @see java.net.Socket#setSoLinger(boolean, int)
* @see #isInputShutdown
*/
public void shutdownInput() throws IOException
{
if (isClosed())
public void shutdownInput() throws IOException {
int s = state;
if (isClosed(s))
throw new SocketException("Socket is closed");
if (!isConnected())
if (!isConnected(s))
throw new SocketException("Socket is not connected");
if (isInputShutdown())
if (isInputShutdown(s))
throw new SocketException("Socket input is already shutdown");
getImpl().shutdownInput();
shutIn = true;
getAndBitwiseOrState(SHUT_IN);
}
/**
@ -1719,16 +1780,16 @@ public class Socket implements java.io.Closeable {
* @see java.net.Socket#setSoLinger(boolean, int)
* @see #isOutputShutdown
*/
public void shutdownOutput() throws IOException
{
if (isClosed())
public void shutdownOutput() throws IOException {
int s = state;
if (isClosed(s))
throw new SocketException("Socket is closed");
if (!isConnected())
if (!isConnected(s))
throw new SocketException("Socket is not connected");
if (isOutputShutdown())
if (isOutputShutdown(s))
throw new SocketException("Socket output is already shutdown");
getImpl().shutdownOutput();
shutOut = true;
getAndBitwiseOrState(SHUT_OUT);
}
/**
@ -1759,7 +1820,7 @@ public class Socket implements java.io.Closeable {
* @since 1.4
*/
public boolean isConnected() {
return connected;
return isConnected(state);
}
/**
@ -1775,7 +1836,7 @@ public class Socket implements java.io.Closeable {
* @see #bind
*/
public boolean isBound() {
return bound;
return isBound(state);
}
/**
@ -1786,9 +1847,7 @@ public class Socket implements java.io.Closeable {
* @see #close
*/
public boolean isClosed() {
synchronized(closeLock) {
return closed;
}
return isClosed(state);
}
/**
@ -1799,7 +1858,7 @@ public class Socket implements java.io.Closeable {
* @see #shutdownInput
*/
public boolean isInputShutdown() {
return shutIn;
return isInputShutdown(state);
}
/**
@ -1810,7 +1869,7 @@ public class Socket implements java.io.Closeable {
* @see #shutdownOutput
*/
public boolean isOutputShutdown() {
return shutOut;
return isOutputShutdown(state);
}
/**