| /* | |
| * Copyright (c) 2000, 2025, 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.io.FileDescriptor; | |
| import java.io.IOException; | |
| import java.net.BindException; | |
| import java.net.InetSocketAddress; | |
| import java.net.ProtocolFamily; | |
| import java.net.ServerSocket; | |
| import java.net.SocketAddress; | |
| import java.net.SocketOption; | |
| import java.net.SocketTimeoutException; | |
| import java.net.StandardSocketOptions; | |
| import java.net.UnixDomainSocketAddress; | |
| import java.nio.channels.AlreadyBoundException; | |
| import java.nio.channels.AsynchronousCloseException; | |
| import java.nio.channels.ClosedChannelException; | |
| import java.nio.channels.IllegalBlockingModeException; | |
| import java.nio.channels.NotYetBoundException; | |
| import java.nio.channels.SelectionKey; | |
| import java.nio.channels.ServerSocketChannel; | |
| import java.nio.channels.SocketChannel; | |
| import java.nio.channels.spi.SelectorProvider; | |
| import java.nio.file.Path; | |
| import java.util.Collections; | |
| import java.util.HashSet; | |
| import java.util.Set; | |
| import java.util.Objects; | |
| import java.util.concurrent.locks.ReentrantLock; | |
| import static java.net.StandardProtocolFamily.INET; | |
| import static java.net.StandardProtocolFamily.INET6; | |
| import static java.net.StandardProtocolFamily.UNIX; | |
| import sun.net.NetHooks; | |
| import sun.net.ext.ExtendedSocketOptions; | |
| /** | |
| * An implementation of ServerSocketChannels | |
| */ | |
| class ServerSocketChannelImpl | |
| extends ServerSocketChannel | |
| implements SelChImpl | |
| { | |
| // Used to make native close and configure calls | |
| private static final NativeDispatcher nd = new SocketDispatcher(); | |
| // The protocol family of the socket | |
| private final ProtocolFamily family; | |
| // Our file descriptor | |
| private final FileDescriptor fd; | |
| private final int fdVal; | |
| // Lock held by thread currently blocked on this channel | |
| private final ReentrantLock acceptLock = new ReentrantLock(); | |
| // Lock held by any thread that modifies the state fields declared below | |
| // DO NOT invoke a blocking I/O operation while holding this lock! | |
| private final Object stateLock = new Object(); | |
| // -- The following fields are protected by stateLock | |
| // Channel state, increases monotonically | |
| private static final int ST_INUSE = 0; | |
| private static final int ST_CLOSING = 1; | |
| private static final int ST_CLOSED = 2; | |
| private int state; | |
| // ID of native thread currently blocked in this channel, for signalling | |
| private long thread; | |
| // Binding | |
| private SocketAddress localAddress; // null => unbound | |
| // set true when exclusive binding is on and SO_REUSEADDR is emulated | |
| private boolean isReuseAddress; | |
| // Our socket adaptor, if any | |
| private ServerSocket socket; | |
| // True if the channel's socket has been forced into non-blocking mode | |
| // by a virtual thread. It cannot be reset. When the channel is in | |
| // blocking mode and the channel's socket is in non-blocking mode then | |
| // operations that don't complete immediately will poll the socket and | |
| // preserve the semantics of blocking operations. | |
| private volatile boolean forcedNonBlocking; | |
| // -- End of fields protected by stateLock | |
| ServerSocketChannelImpl(SelectorProvider sp) throws IOException { | |
| this(sp, Net.isIPv6Available() ? INET6 : INET); | |
| } | |
| ServerSocketChannelImpl(SelectorProvider sp, ProtocolFamily family) | |
| throws IOException | |
| { | |
| super(sp); | |
| Objects.requireNonNull(family, "'family' is null"); | |
| if ((family != INET) && (family != INET6) && (family != UNIX)) { | |
| throw new UnsupportedOperationException("Protocol family not supported"); | |
| } | |
| if (family == INET6 && !Net.isIPv6Available()) { | |
| throw new UnsupportedOperationException("IPv6 not available"); | |
| } | |
| this.family = family; | |
| if (family == UNIX) { | |
| this.fd = UnixDomainSockets.socket(); | |
| } else { | |
| this.fd = Net.serverSocket(family); | |
| } | |
| this.fdVal = IOUtil.fdVal(fd); | |
| } | |
| ServerSocketChannelImpl(SelectorProvider sp, | |
| ProtocolFamily family, | |
| FileDescriptor fd, | |
| boolean bound) | |
| throws IOException | |
| { | |
| super(sp); | |
| if (family == UNIX) { | |
| this.family = UNIX; | |
| } else { | |
| this.family = Net.isIPv6Available() ? INET6 : INET; | |
| } | |
| this.fd = fd; | |
| this.fdVal = IOUtil.fdVal(fd); | |
| if (bound) { | |
| synchronized (stateLock) { | |
| if (family == UNIX) { | |
| localAddress = UnixDomainSockets.localAddress(fd); | |
| } else { | |
| localAddress = Net.localAddress(fd); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Returns true if this channel is to a INET or INET6 socket. | |
| */ | |
| private boolean isNetSocket() { | |
| return (family == INET) || (family == INET6); | |
| } | |
| /** | |
| * Returns true if this channel is to a UNIX socket. | |
| */ | |
| boolean isUnixSocket() { | |
| return (family == UNIX); | |
| } | |
| // @throws ClosedChannelException if channel is closed | |
| private void ensureOpen() throws ClosedChannelException { | |
| if (!isOpen()) | |
| throw new ClosedChannelException(); | |
| } | |
| @Override | |
| public ServerSocket socket() { | |
| synchronized (stateLock) { | |
| if (socket == null) { | |
| if (isNetSocket()) { | |
| socket = ServerSocketAdaptor.create(this); | |
| } else { | |
| throw new UnsupportedOperationException("Not supported"); | |
| } | |
| } | |
| return socket; | |
| } | |
| } | |
| @Override | |
| public SocketAddress getLocalAddress() throws IOException { | |
| synchronized (stateLock) { | |
| ensureOpen(); | |
| return localAddress; | |
| } | |
| } | |
| @Override | |
| public <T> ServerSocketChannel setOption(SocketOption<T> name, T value) | |
| throws IOException | |
| { | |
| Objects.requireNonNull(name); | |
| if (!supportedOptions().contains(name)) | |
| throw new UnsupportedOperationException("'" + name + "' not supported"); | |
| if (!name.type().isInstance(value)) | |
| throw new IllegalArgumentException("Invalid value '" + value + "'"); | |
| synchronized (stateLock) { | |
| ensureOpen(); | |
| if (isNetSocket() | |
| && name == StandardSocketOptions.SO_REUSEADDR | |
| && Net.useExclusiveBind()) { | |
| // SO_REUSEADDR emulated when using exclusive bind | |
| isReuseAddress = (Boolean) value; | |
| } else { | |
| // no options that require special handling | |
| Net.setSocketOption(fd, Net.UNSPEC, name, value); | |
| } | |
| return this; | |
| } | |
| } | |
| @Override | |
| @SuppressWarnings("unchecked") | |
| public <T> T getOption(SocketOption<T> name) | |
| throws IOException | |
| { | |
| Objects.requireNonNull(name); | |
| if (!supportedOptions().contains(name)) | |
| throw new UnsupportedOperationException("'" + name + "' not supported"); | |
| synchronized (stateLock) { | |
| ensureOpen(); | |
| if (isNetSocket() | |
| && name == StandardSocketOptions.SO_REUSEADDR | |
| && Net.useExclusiveBind()) { | |
| // SO_REUSEADDR emulated when using exclusive bind | |
| return (T) Boolean.valueOf(isReuseAddress); | |
| } else { | |
| // no options that require special handling | |
| return (T) Net.getSocketOption(fd, Net.UNSPEC, name); | |
| } | |
| } | |
| } | |
| private static class DefaultOptionsHolder { | |
| static final Set<SocketOption<?>> defaultInetOptions = defaultInetOptions(); | |
| static final Set<SocketOption<?>> defaultUnixDomainOptions = defaultUnixDomainOptions(); | |
| private static Set<SocketOption<?>> defaultInetOptions() { | |
| HashSet<SocketOption<?>> set = new HashSet<>(); | |
| set.add(StandardSocketOptions.SO_RCVBUF); | |
| set.add(StandardSocketOptions.SO_REUSEADDR); | |
| if (Net.isReusePortAvailable()) { | |
| set.add(StandardSocketOptions.SO_REUSEPORT); | |
| } | |
| set.addAll(ExtendedSocketOptions.serverSocketOptions()); | |
| return Collections.unmodifiableSet(set); | |
| } | |
| private static Set<SocketOption<?>> defaultUnixDomainOptions() { | |
| HashSet<SocketOption<?>> set = new HashSet<>(); | |
| set.add(StandardSocketOptions.SO_RCVBUF); | |
| return Collections.unmodifiableSet(set); | |
| } | |
| } | |
| @Override | |
| public final Set<SocketOption<?>> supportedOptions() { | |
| if (isUnixSocket()) { | |
| return DefaultOptionsHolder.defaultUnixDomainOptions; | |
| } else { | |
| return DefaultOptionsHolder.defaultInetOptions; | |
| } | |
| } | |
| @Override | |
| public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException { | |
| synchronized (stateLock) { | |
| ensureOpen(); | |
| if (localAddress != null) | |
| throw new AlreadyBoundException(); | |
| if (isUnixSocket()) { | |
| localAddress = unixBind(local, backlog); | |
| } else { | |
| localAddress = netBind(local, backlog); | |
| } | |
| } | |
| return this; | |
| } | |
| private SocketAddress unixBind(SocketAddress local, int backlog) throws IOException { | |
| if (local == null) { | |
| // Attempt up to 10 times to find an unused name in temp directory. | |
| // If local address supplied then bind called only once | |
| boolean bound = false; | |
| int attempts = 0; | |
| while (attempts < 10 && !bound) { | |
| try { | |
| Path path = UnixDomainSockets.generateTempName().getPath(); | |
| UnixDomainSockets.bind(fd, path); | |
| bound = true; | |
| } catch (BindException e) { } | |
| attempts++; | |
| } | |
| if (!bound) | |
| throw new BindException("Could not bind to temporary name"); | |
| } else { | |
| Path path = UnixDomainSockets.checkAddress(local).getPath(); | |
| UnixDomainSockets.bind(fd, path); | |
| } | |
| Net.listen(fd, backlog < 1 ? 50 : backlog); | |
| return UnixDomainSockets.localAddress(fd); | |
| } | |
| private SocketAddress netBind(SocketAddress local, int backlog) throws IOException { | |
| InetSocketAddress isa; | |
| if (local == null) { | |
| isa = new InetSocketAddress(Net.anyLocalAddress(family), 0); | |
| } else { | |
| isa = Net.checkAddress(local, family); | |
| } | |
| NetHooks.beforeTcpBind(fd, isa.getAddress(), isa.getPort()); | |
| Net.bind(family, fd, isa.getAddress(), isa.getPort()); | |
| Net.listen(fd, backlog < 1 ? 50 : backlog); | |
| return Net.localAddress(fd); | |
| } | |
| /** | |
| * Marks the beginning of an I/O operation that might block. | |
| * | |
| * @throws ClosedChannelException if the channel is closed | |
| * @throws NotYetBoundException if the channel's socket has not been bound yet | |
| */ | |
| private void begin(boolean blocking) throws ClosedChannelException { | |
| if (blocking) | |
| begin(); // set blocker to close channel if interrupted | |
| synchronized (stateLock) { | |
| ensureOpen(); | |
| if (localAddress == null) | |
| throw new NotYetBoundException(); | |
| if (blocking) | |
| thread = NativeThread.current(); | |
| } | |
| } | |
| /** | |
| * Marks the end of an I/O operation that may have blocked. | |
| * | |
| * @throws AsynchronousCloseException if the channel was closed due to this | |
| * thread being interrupted on a blocking I/O operation. | |
| */ | |
| private void end(boolean blocking, boolean completed) | |
| throws AsynchronousCloseException | |
| { | |
| if (blocking) { | |
| synchronized (stateLock) { | |
| thread = 0; | |
| if (state == ST_CLOSING) { | |
| tryFinishClose(); | |
| } | |
| } | |
| end(completed); | |
| } | |
| } | |
| @Override | |
| public SocketChannel accept() throws IOException { | |
| int n = 0; | |
| FileDescriptor newfd = new FileDescriptor(); | |
| SocketAddress[] saa = new SocketAddress[1]; | |
| acceptLock.lock(); | |
| try { | |
| ensureOpen(); | |
| boolean blocking = isBlocking(); | |
| try { | |
| begin(blocking); | |
| configureSocketNonBlockingIfVirtualThread(); | |
| n = implAccept(this.fd, newfd, saa); | |
| if (blocking) { | |
| while (IOStatus.okayToRetry(n) && isOpen()) { | |
| park(Net.POLLIN); | |
| n = implAccept(this.fd, newfd, saa); | |
| } | |
| } | |
| } finally { | |
| end(blocking, n > 0); | |
| assert IOStatus.check(n); | |
| } | |
| } finally { | |
| acceptLock.unlock(); | |
| } | |
| if (n > 0) { | |
| return finishAccept(newfd, saa[0]); | |
| } else { | |
| return null; | |
| } | |
| } | |
| private int implAccept(FileDescriptor fd, FileDescriptor newfd, SocketAddress[] saa) | |
| throws IOException | |
| { | |
| if (isUnixSocket()) { | |
| String[] pa = new String[1]; | |
| int n = UnixDomainSockets.accept(fd, newfd, pa); | |
| if (n > 0) | |
| saa[0] = UnixDomainSocketAddress.of(pa[0]); | |
| return n; | |
| } else { | |
| InetSocketAddress[] issa = new InetSocketAddress[1]; | |
| int n = Net.accept(fd, newfd, issa); | |
| if (n > 0) | |
| saa[0] = issa[0]; | |
| return n; | |
| } | |
| } | |
| /** | |
| * Accepts a new connection with a given timeout. This method requires the | |
| * channel to be configured in blocking mode. | |
| * | |
| * @apiNote This method is for use by the socket adaptor. | |
| * | |
| * @param nanos the timeout, in nanoseconds | |
| * @throws IllegalBlockingModeException if the channel is configured non-blocking | |
| * @throws SocketTimeoutException if the timeout expires | |
| */ | |
| SocketChannel blockingAccept(long nanos) throws IOException { | |
| int n = 0; | |
| FileDescriptor newfd = new FileDescriptor(); | |
| SocketAddress[] saa = new SocketAddress[1]; | |
| acceptLock.lock(); | |
| try { | |
| // check that channel is configured blocking | |
| if (!isBlocking()) | |
| throw new IllegalBlockingModeException(); | |
| try { | |
| begin(true); | |
| // change socket to non-blocking | |
| lockedConfigureBlocking(false); | |
| try { | |
| long startNanos = System.nanoTime(); | |
| n = implAccept(fd, newfd, saa); | |
| while (n == IOStatus.UNAVAILABLE && isOpen()) { | |
| long remainingNanos = nanos - (System.nanoTime() - startNanos); | |
| if (remainingNanos <= 0) { | |
| throw new SocketTimeoutException("Accept timed out"); | |
| } | |
| park(Net.POLLIN, remainingNanos); | |
| n = implAccept(fd, newfd, saa); | |
| } | |
| } finally { | |
| // restore socket to blocking mode (if channel is open) | |
| tryLockedConfigureBlocking(true); | |
| } | |
| } finally { | |
| end(true, n > 0); | |
| } | |
| } finally { | |
| acceptLock.unlock(); | |
| } | |
| assert n > 0; | |
| return finishAccept(newfd, saa[0]); | |
| } | |
| private SocketChannel finishAccept(FileDescriptor newfd, SocketAddress sa) | |
| throws IOException | |
| { | |
| try { | |
| // newly accepted socket is initially in blocking mode | |
| IOUtil.configureBlocking(newfd, true); | |
| return new SocketChannelImpl(provider(), family, newfd, sa); | |
| } catch (Exception e) { | |
| nd.close(newfd); | |
| throw e; | |
| } | |
| } | |
| @Override | |
| protected void implConfigureBlocking(boolean block) throws IOException { | |
| acceptLock.lock(); | |
| try { | |
| lockedConfigureBlocking(block); | |
| } finally { | |
| acceptLock.unlock(); | |
| } | |
| } | |
| /** | |
| * Adjusts the blocking mode. | |
| */ | |
| private void lockedConfigureBlocking(boolean block) throws IOException { | |
| assert acceptLock.isHeldByCurrentThread(); | |
| synchronized (stateLock) { | |
| ensureOpen(); | |
| // do nothing if virtual thread has forced the socket to be non-blocking | |
| if (!forcedNonBlocking) { | |
| IOUtil.configureBlocking(fd, block); | |
| } | |
| } | |
| } | |
| /** | |
| * Attempts to adjust the blocking mode if the channel is open. | |
| * @return {@code true} if the blocking mode was adjusted | |
| */ | |
| private boolean tryLockedConfigureBlocking(boolean block) throws IOException { | |
| assert acceptLock.isHeldByCurrentThread(); | |
| synchronized (stateLock) { | |
| // do nothing if virtual thread has forced the socket to be non-blocking | |
| if (!forcedNonBlocking && isOpen()) { | |
| IOUtil.configureBlocking(fd, block); | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| } | |
| /** | |
| * Ensures that the socket is configured non-blocking when on a virtual thread. | |
| */ | |
| private void configureSocketNonBlockingIfVirtualThread() throws IOException { | |
| assert acceptLock.isHeldByCurrentThread(); | |
| if (!forcedNonBlocking && Thread.currentThread().isVirtual()) { | |
| synchronized (stateLock) { | |
| ensureOpen(); | |
| IOUtil.configureBlocking(fd, false); | |
| forcedNonBlocking = true; | |
| } | |
| } | |
| } | |
| /** | |
| * Closes the socket if there are no accept in progress and the channel is | |
| * not registered with a Selector. | |
| */ | |
| private boolean tryClose() throws IOException { | |
| assert Thread.holdsLock(stateLock) && state == ST_CLOSING; | |
| if ((thread == 0) && !isRegistered()) { | |
| state = ST_CLOSED; | |
| nd.close(fd); | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| /** | |
| * Invokes tryClose to attempt to close the socket. | |
| * | |
| * This method is used for deferred closing by I/O and Selector operations. | |
| */ | |
| private void tryFinishClose() { | |
| try { | |
| tryClose(); | |
| } catch (IOException ignore) { } | |
| } | |
| /** | |
| * Closes this channel when configured in blocking mode. | |
| * | |
| * If there is an accept in progress then the socket is pre-closed and the | |
| * accept thread is signalled, in which case the final close is deferred | |
| * until the accept aborts. | |
| */ | |
| private void implCloseBlockingMode() throws IOException { | |
| synchronized (stateLock) { | |
| assert state < ST_CLOSING; | |
| state = ST_CLOSING; | |
| if (!tryClose()) { | |
| nd.preClose(fd, thread, 0); | |
| } | |
| } | |
| } | |
| /** | |
| * Closes this channel when configured in non-blocking mode. | |
| * | |
| * If the channel is registered with a Selector then the close is deferred | |
| * until the channel is flushed from all Selectors. | |
| */ | |
| private void implCloseNonBlockingMode() throws IOException { | |
| synchronized (stateLock) { | |
| assert state < ST_CLOSING; | |
| state = ST_CLOSING; | |
| } | |
| // wait for any accept to complete before trying to close | |
| acceptLock.lock(); | |
| acceptLock.unlock(); | |
| synchronized (stateLock) { | |
| if (state == ST_CLOSING) { | |
| tryClose(); | |
| } | |
| } | |
| } | |
| /** | |
| * Invoked by implCloseChannel to close the channel. | |
| */ | |
| @Override | |
| protected void implCloseSelectableChannel() throws IOException { | |
| assert !isOpen(); | |
| if (isBlocking()) { | |
| implCloseBlockingMode(); | |
| } else { | |
| implCloseNonBlockingMode(); | |
| } | |
| } | |
| @Override | |
| public void kill() { | |
| // wait for any accept operation to complete before trying to close | |
| acceptLock.lock(); | |
| acceptLock.unlock(); | |
| synchronized (stateLock) { | |
| if (state == ST_CLOSING) { | |
| tryFinishClose(); | |
| } | |
| } | |
| } | |
| /** | |
| * Returns true if channel's socket is bound | |
| */ | |
| boolean isBound() { | |
| synchronized (stateLock) { | |
| return localAddress != null; | |
| } | |
| } | |
| /** | |
| * Returns the local address, or null if not bound | |
| */ | |
| SocketAddress localAddress() { | |
| synchronized (stateLock) { | |
| return localAddress; | |
| } | |
| } | |
| /** | |
| * Translates native poll revent set into a ready operation set | |
| */ | |
| public boolean translateReadyOps(int ops, int initialOps, SelectionKeyImpl ski) { | |
| int intOps = ski.nioInterestOps(); | |
| int oldOps = ski.nioReadyOps(); | |
| int newOps = initialOps; | |
| if ((ops & Net.POLLNVAL) != 0) { | |
| // This should only happen if this channel is pre-closed while a | |
| // selection operation is in progress | |
| // ## Throw an error if this channel has not been pre-closed | |
| return false; | |
| } | |
| if ((ops & (Net.POLLERR | Net.POLLHUP)) != 0) { | |
| newOps = intOps; | |
| ski.nioReadyOps(newOps); | |
| return (newOps & ~oldOps) != 0; | |
| } | |
| if (((ops & Net.POLLIN) != 0) && | |
| ((intOps & SelectionKey.OP_ACCEPT) != 0)) | |
| newOps |= SelectionKey.OP_ACCEPT; | |
| ski.nioReadyOps(newOps); | |
| return (newOps & ~oldOps) != 0; | |
| } | |
| public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl ski) { | |
| return translateReadyOps(ops, ski.nioReadyOps(), ski); | |
| } | |
| public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl ski) { | |
| return translateReadyOps(ops, 0, ski); | |
| } | |
| /** | |
| * Translates an interest operation set into a native poll event set | |
| */ | |
| public int translateInterestOps(int ops) { | |
| int newOps = 0; | |
| if ((ops & SelectionKey.OP_ACCEPT) != 0) | |
| newOps |= Net.POLLIN; | |
| return newOps; | |
| } | |
| public FileDescriptor getFD() { | |
| return fd; | |
| } | |
| public int getFDVal() { | |
| return fdVal; | |
| } | |
| public String toString() { | |
| StringBuilder sb = new StringBuilder(); | |
| sb.append(this.getClass().getName()); | |
| sb.append('['); | |
| if (!isOpen()) { | |
| sb.append("closed"); | |
| } else { | |
| synchronized (stateLock) { | |
| SocketAddress addr = localAddress; | |
| if (addr == null) { | |
| sb.append("unbound"); | |
| } else if (isUnixSocket()) { | |
| sb.append(addr); | |
| } | |
| } | |
| } | |
| sb.append(']'); | |
| return sb.toString(); | |
| } | |
| } |