# HG changeset patch # User alanb # Date 1554212177 -3600 # Node ID 00d475a13e297cdfed81cde79b1ed5037d84cfa2 # Parent c1126b592df94cbdae4937c7367a87bf68508dda Adjust timeout when several threads are doing timed accepts at around the same time diff -r c1126b592df9 -r 00d475a13e29 src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java --- a/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java Sun Mar 31 12:11:12 2019 +0100 +++ b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java Tue Apr 02 14:36:17 2019 +0100 @@ -194,13 +194,11 @@ } /** - * Ensures that the socket is configured non-blocking when a timeout is specified. + * Configures the socket to be non-blocking (if not already non-blocking) * @throws IOException if there is an I/O error changing the blocking mode */ - private void configureNonBlockingIfNeeded(FileDescriptor fd, int timeout) - throws IOException - { - if (timeout > 0 && !nonBlocking) { + private void configureNonBlocking(FileDescriptor fd) throws IOException { + if (!nonBlocking) { assert readLock.isHeldByCurrentThread() || writeLock.isHeldByCurrentThread(); IOUtil.configureBlocking(fd, false); nonBlocking = true; @@ -257,11 +255,10 @@ * Reads bytes from the socket into the given byte array with a timeout. * @throws SocketTimeoutException if the read timeout elapses */ - private int timedRead(FileDescriptor fd, byte[] b, int off, int len, int millis) + private int timedRead(FileDescriptor fd, byte[] b, int off, int len, long nanos) throws IOException { assert nonBlocking; - long nanos = NANOSECONDS.convert(millis, TimeUnit.MILLISECONDS); long remainingNanos = nanos; long startNanos = System.nanoTime(); int n; @@ -293,12 +290,14 @@ if (isInputClosed) return -1; int timeout = this.timeout; - configureNonBlockingIfNeeded(fd, timeout); + if (timeout > 0) + configureNonBlocking(fd); n = tryRead(fd, b, off, len); if (IOStatus.okayToRetry(n) && isOpen()) { if (timeout > 0) { // read with timeout - n = timedRead(fd, b, off, len, timeout); + long nanos = NANOSECONDS.convert(timeout, MILLISECONDS); + n = timedRead(fd, b, off, len, nanos); } else { // read, no timeout do { @@ -526,8 +525,7 @@ * Waits for a connection attempt to finish with a timeout * @throws SocketTimeoutException if the connect timeout elapses */ - private void timedFinishConnect(FileDescriptor fd, int millis) throws IOException { - long nanos = NANOSECONDS.convert(millis, TimeUnit.MILLISECONDS); + private void timedFinishConnect(FileDescriptor fd, long nanos) throws IOException { long remainingNanos = nanos; long startNanos = System.nanoTime(); boolean polled; @@ -571,7 +569,8 @@ boolean connected = false; FileDescriptor fd = beginConnect(address, port); try { - configureNonBlockingIfNeeded(fd, millis); + if (millis > 0) + configureNonBlocking(fd); int n = Net.connect(fd, address, port); if (isOpen()) { if (n > 0) { @@ -581,7 +580,8 @@ // not established or interrupted if (millis > 0) { // finish connect with timeout - timedFinishConnect(fd, millis); + long nanos = NANOSECONDS.convert(millis, MILLISECONDS); + timedFinishConnect(fd, nanos); } else { // finish connect, no timeout boolean polled; @@ -679,11 +679,10 @@ private int timedAccept(FileDescriptor fd, FileDescriptor newfd, InetSocketAddress[] isaa, - int millis) + long nanos) throws IOException { assert nonBlocking; - long nanos = NANOSECONDS.convert(millis, TimeUnit.MILLISECONDS); long remainingNanos = nanos; long startNanos = System.nanoTime(); int n; @@ -706,23 +705,36 @@ */ @Override protected void accept(SocketImpl si) throws IOException { - // accept a connection FileDescriptor newfd = new FileDescriptor(); InetSocketAddress[] isaa = new InetSocketAddress[1]; + // acquire the lock, adjusting the timeout for cases where several + // threads are accepting connections and there is a timeout set ReentrantLock acceptLock = readLock; - acceptLock.lock(); + int timeout = this.timeout; + long remainingNanos = 0; + if (timeout > 0) { + remainingNanos = tryLock(acceptLock, timeout, MILLISECONDS); + if (remainingNanos <= 0) { + assert !acceptLock.isHeldByCurrentThread(); + throw new SocketTimeoutException("Accept timed out"); + } + } else { + acceptLock.lock(); + } + + // accept a connection try { int n = 0; FileDescriptor fd = beginAccept(); try { - int timeout = this.timeout; - configureNonBlockingIfNeeded(fd, timeout); + if (remainingNanos > 0) + configureNonBlocking(fd); n = Net.accept(fd, newfd, isaa); if (IOStatus.okayToRetry(n) && isOpen()) { - if (timeout > 0) { + if (remainingNanos > 0) { // accept with timeout - n = timedAccept(fd, newfd, isaa, timeout); + n = timedAccept(fd, newfd, isaa, remainingNanos); } else { // accept, no timeout do { @@ -1219,6 +1231,33 @@ } /** + * Attempts to acquire the given lock within the given waiting time. + * @return the remaining time in nanoseconds when the lock is acquired, zero + * or less if the lock was not acquired before the timeout expired + */ + private static long tryLock(ReentrantLock lock, long timeout, TimeUnit unit) { + assert timeout > 0; + boolean interrupted = false; + long nanos = NANOSECONDS.convert(timeout, unit); + long remainingNanos = nanos; + long startNanos = System.nanoTime(); + boolean acquired = false; + while (!acquired && (remainingNanos > 0)) { + try { + acquired = lock.tryLock(remainingNanos, NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + } + remainingNanos = nanos - (System.nanoTime() - startNanos); + } + if (acquired && remainingNanos <= 0L) + lock.unlock(); // release lock if timeout has expired + if (interrupted) + Thread.currentThread().interrupt(); + return remainingNanos; + } + + /** * Returns the socket protocol family. */ private static ProtocolFamily family() {