diff --git a/src/java.base/share/classes/sun/net/www/MessageHeader.java b/src/java.base/share/classes/sun/net/www/MessageHeader.java index 95b82cda283..22b16407dd2 100644 --- a/src/java.base/share/classes/sun/net/www/MessageHeader.java +++ b/src/java.base/share/classes/sun/net/www/MessageHeader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2020, 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 @@ -294,7 +294,7 @@ public synchronized Map> filterAndAddHeaders( * @param line the line to check. * @return true if the line might be a request line. */ - private boolean isRequestline(String line) { + private static boolean isRequestline(String line) { String k = line.trim(); int i = k.lastIndexOf(' '); if (i <= 0) return false; @@ -311,12 +311,23 @@ private boolean isRequestline(String line) { return (k.substring(i+1, len-3).equalsIgnoreCase("HTTP/")); } + /** Prints the key-value pairs represented by this + header. Also prints the RFC required blank line + at the end. Omits pairs with a null key. Omits + colon if key-value pair is the requestline. */ + public void print(PrintStream p) { + // no synchronization: use cloned arrays instead. + String[] k; String[] v; int n; + synchronized (this) { n = nkeys; k = keys.clone(); v = values.clone(); } + print(n, k, v, p); + } + /** Prints the key-value pairs represented by this header. Also prints the RFC required blank line at the end. Omits pairs with a null key. Omits colon if key-value pair is the requestline. */ - public synchronized void print(PrintStream p) { + private static void print(int nkeys, String[] keys, String[] values, PrintStream p) { for (int i = 0; i < nkeys; i++) if (keys[i] != null) { StringBuilder sb = new StringBuilder(keys[i]); diff --git a/src/java.base/share/classes/sun/net/www/MeteredStream.java b/src/java.base/share/classes/sun/net/www/MeteredStream.java index 08ff179457a..049b16c03c6 100644 --- a/src/java.base/share/classes/sun/net/www/MeteredStream.java +++ b/src/java.base/share/classes/sun/net/www/MeteredStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2020, 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 @@ -25,9 +25,8 @@ package sun.net.www; -import java.net.URL; -import java.util.*; import java.io.*; +import java.util.concurrent.locks.ReentrantLock; import sun.net.ProgressSource; import sun.net.www.http.ChunkedInputStream; @@ -44,6 +43,7 @@ public class MeteredStream extends FilterInputStream { protected long markedCount = 0; protected int markLimit = -1; protected ProgressSource pi; + private final ReentrantLock readLock = new ReentrantLock(); public MeteredStream(InputStream is, ProgressSource pi, long expected) { @@ -57,7 +57,9 @@ public MeteredStream(InputStream is, ProgressSource pi, long expected) } } - private final void justRead(long n) throws IOException { + private final void justRead(long n) throws IOException { + assert isLockHeldByCurrentThread(); + if (n == -1) { /* @@ -99,6 +101,7 @@ private final void justRead(long n) throws IOException { * Returns true if the mark is valid, false otherwise */ private boolean isMarked() { + assert isLockHeldByCurrentThread(); if (markLimit < 0) { return false; @@ -113,94 +116,130 @@ private boolean isMarked() { return true; } - public synchronized int read() throws java.io.IOException { - if (closed) { - return -1; - } - int c = in.read(); - if (c != -1) { - justRead(1); - } else { - justRead(c); + public int read() throws java.io.IOException { + lock(); + try { + if (closed) return -1; + int c = in.read(); + if (c != -1) { + justRead(1); + } else { + justRead(c); + } + return c; + } finally { + unlock(); } - return c; } - public synchronized int read(byte b[], int off, int len) + public int read(byte b[], int off, int len) throws java.io.IOException { - if (closed) { - return -1; - } - int n = in.read(b, off, len); - justRead(n); - return n; - } - - public synchronized long skip(long n) throws IOException { + lock(); + try { + if (closed) return -1; - // REMIND: what does skip do on EOF???? - if (closed) { - return 0; + int n = in.read(b, off, len); + justRead(n); + return n; + } finally { + unlock(); } + } - if (in instanceof ChunkedInputStream) { - n = in.skip(n); - } - else { - // just skip min(n, num_bytes_left) - long min = (n > expected - count) ? expected - count: n; - n = in.skip(min); + public long skip(long n) throws IOException { + lock(); + try { + // REMIND: what does skip do on EOF???? + if (closed) return 0; + + if (in instanceof ChunkedInputStream) { + n = in.skip(n); + } else { + // just skip min(n, num_bytes_left) + long min = (n > expected - count) ? expected - count : n; + n = in.skip(min); + } + justRead(n); + return n; + } finally { + unlock(); } - justRead(n); - return n; } public void close() throws IOException { - if (closed) { - return; - } - if (pi != null) - pi.finishTracking(); + lock(); + try { + if (closed) return; + if (pi != null) + pi.finishTracking(); - closed = true; - in.close(); + closed = true; + in.close(); + } finally { + unlock(); + } } - public synchronized int available() throws IOException { - return closed ? 0: in.available(); + public int available() throws IOException { + lock(); + try { + return closed ? 0 : in.available(); + } finally { + unlock(); + } } - public synchronized void mark(int readLimit) { - if (closed) { - return; - } - super.mark(readLimit); + public void mark(int readLimit) { + lock(); + try { + if (closed) return; + super.mark(readLimit); - /* - * mark the count to restore upon reset - */ - markedCount = count; - markLimit = readLimit; + /* + * mark the count to restore upon reset + */ + markedCount = count; + markLimit = readLimit; + } finally { + unlock(); + } } - public synchronized void reset() throws IOException { - if (closed) { - return; - } + public void reset() throws IOException { + lock(); + try { + if (closed) return; + if (!isMarked()) { + throw new IOException("Resetting to an invalid mark"); + } - if (!isMarked()) { - throw new IOException ("Resetting to an invalid mark"); + count = markedCount; + super.reset(); + } finally { + unlock(); } - - count = markedCount; - super.reset(); } public boolean markSupported() { - if (closed) { - return false; + lock(); + try { + if (closed) return false; + return super.markSupported(); + } finally { + unlock(); } - return super.markSupported(); + } + + public final void lock() { + readLock.lock(); + } + + public final void unlock() { + readLock.unlock(); + } + + public final boolean isLockHeldByCurrentThread() { + return readLock.isHeldByCurrentThread(); } @SuppressWarnings("deprecation") diff --git a/src/java.base/share/classes/sun/net/www/http/ChunkedInputStream.java b/src/java.base/share/classes/sun/net/www/http/ChunkedInputStream.java index 2c3d8ab2490..63dea6406dc 100644 --- a/src/java.base/share/classes/sun/net/www/http/ChunkedInputStream.java +++ b/src/java.base/share/classes/sun/net/www/http/ChunkedInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2020, 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 @@ -25,9 +25,7 @@ package sun.net.www.http; import java.io.*; -import java.util.*; - -import sun.net.*; +import java.util.concurrent.locks.ReentrantLock; import sun.net.www.*; import sun.nio.cs.US_ASCII; @@ -41,8 +39,7 @@ * can be hurried to the end of the stream if the bytes are available on * the underlying stream. */ -public -class ChunkedInputStream extends InputStream implements Hurryable { +public class ChunkedInputStream extends InputStream implements Hurryable { /** * The underlying stream @@ -126,6 +123,8 @@ class ChunkedInputStream extends InputStream implements Hurryable { */ private boolean closed; + private final ReentrantLock readLock = new ReentrantLock(); + /* * Maximum chunk header size of 2KB + 2 bytes for CRLF */ @@ -648,14 +647,19 @@ public ChunkedInputStream(InputStream in, HttpClient hc, MessageHeader responses * @exception IOException if an I/O error occurs. * @see java.io.FilterInputStream#in */ - public synchronized int read() throws IOException { - ensureOpen(); - if (chunkPos >= chunkCount) { - if (readAhead(true) <= 0) { - return -1; + public int read() throws IOException { + readLock.lock(); + try { + ensureOpen(); + if (chunkPos >= chunkCount) { + if (readAhead(true) <= 0) { + return -1; + } } + return chunkData[chunkPos++] & 0xff; + } finally { + readLock.unlock(); } - return chunkData[chunkPos++] & 0xff; } @@ -670,42 +674,47 @@ public synchronized int read() throws IOException { * the stream has been reached. * @exception IOException if an I/O error occurs. */ - public synchronized int read(byte b[], int off, int len) + public int read(byte b[], int off, int len) throws IOException { - ensureOpen(); - if ((off < 0) || (off > b.length) || (len < 0) || - ((off + len) > b.length) || ((off + len) < 0)) { - throw new IndexOutOfBoundsException(); - } else if (len == 0) { - return 0; - } - - int avail = chunkCount - chunkPos; - if (avail <= 0) { - /* - * Optimization: if we're in the middle of the chunk read - * directly from the underlying stream into the caller's - * buffer - */ - if (state == STATE_READING_CHUNK) { - return fastRead( b, off, len ); + readLock.lock(); + try { + ensureOpen(); + if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) > b.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; } - /* - * We're not in the middle of a chunk so we must read ahead - * until there is some chunk data available. - */ - avail = readAhead(true); - if (avail < 0) { - return -1; /* EOF */ + int avail = chunkCount - chunkPos; + if (avail <= 0) { + /* + * Optimization: if we're in the middle of the chunk read + * directly from the underlying stream into the caller's + * buffer + */ + if (state == STATE_READING_CHUNK) { + return fastRead(b, off, len); + } + + /* + * We're not in the middle of a chunk so we must read ahead + * until there is some chunk data available. + */ + avail = readAhead(true); + if (avail < 0) { + return -1; /* EOF */ + } } - } - int cnt = (avail < len) ? avail : len; - System.arraycopy(chunkData, chunkPos, b, off, cnt); - chunkPos += cnt; + int cnt = (avail < len) ? avail : len; + System.arraycopy(chunkData, chunkPos, b, off, cnt); + chunkPos += cnt; - return cnt; + return cnt; + } finally { + readLock.unlock(); + } } /** @@ -717,20 +726,25 @@ public synchronized int read(byte b[], int off, int len) * @exception IOException if an I/O error occurs. * @see java.io.FilterInputStream#in */ - public synchronized int available() throws IOException { - ensureOpen(); + public int available() throws IOException { + readLock.lock(); + try { + ensureOpen(); - int avail = chunkCount - chunkPos; - if(avail > 0) { - return avail; - } + int avail = chunkCount - chunkPos; + if (avail > 0) { + return avail; + } - avail = readAhead(false); + avail = readAhead(false); - if (avail < 0) { - return 0; - } else { - return avail; + if (avail < 0) { + return 0; + } else { + return avail; + } + } finally { + readLock.unlock(); } } @@ -745,12 +759,18 @@ public synchronized int available() throws IOException { * * @exception IOException if an I/O error occurs. */ - public synchronized void close() throws IOException { - if (closed) { - return; + public void close() throws IOException { + if (closed) return; + readLock.lock(); + try { + if (closed) { + return; + } + closeUnderlying(); + closed = true; + } finally { + readLock.unlock(); } - closeUnderlying(); - closed = true; } /** @@ -762,22 +782,27 @@ public synchronized void close() throws IOException { * without blocking then this stream can't be hurried and should be * closed. */ - public synchronized boolean hurry() { - if (in == null || error) { - return false; - } - + public boolean hurry() { + readLock.lock(); try { - readAhead(false); - } catch (Exception e) { - return false; - } + if (in == null || error) { + return false; + } - if (error) { - return false; - } + try { + readAhead(false); + } catch (Exception e) { + return false; + } - return (state == STATE_DONE); + if (error) { + return false; + } + + return (state == STATE_DONE); + } finally { + readLock.unlock(); + } } } diff --git a/src/java.base/share/classes/sun/net/www/http/ChunkedOutputStream.java b/src/java.base/share/classes/sun/net/www/http/ChunkedOutputStream.java index f21bfbf6f65..4cf485cc33e 100644 --- a/src/java.base/share/classes/sun/net/www/http/ChunkedOutputStream.java +++ b/src/java.base/share/classes/sun/net/www/http/ChunkedOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2020, 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 @@ -25,6 +25,8 @@ package sun.net.www.http; import java.io.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import sun.nio.cs.US_ASCII; @@ -32,7 +34,7 @@ * OutputStream that sends the output to the underlying stream using chunked * encoding as specified in RFC 2068. */ -public class ChunkedOutputStream extends PrintStream { +public class ChunkedOutputStream extends OutputStream { /* Default chunk size (including chunk header) if not specified */ static final int DEFAULT_CHUNK_SIZE = 4096; @@ -63,6 +65,8 @@ public class ChunkedOutputStream extends PrintStream { /* header for a complete Chunk */ private byte[] completeHeader; + private final Lock writeLock = new ReentrantLock(); + /* return the size of the header for a particular chunk size */ private static int getHeaderSize(int size) { return (Integer.toHexString(size)).length() + CRLF_SIZE; @@ -85,7 +89,6 @@ public ChunkedOutputStream(PrintStream o) { } public ChunkedOutputStream(PrintStream o, int size) { - super(o); out = o; if (size <= 0) { @@ -144,7 +147,7 @@ private void flush(boolean flushAll) { reset(); } else if (flushAll){ /* complete the last chunk and flush it to underlying stream */ - if (size > 0){ + if (size > 0) { /* adjust a header start index in case the header of the last * chunk is shorter then preferedHeaderSize */ @@ -168,18 +171,18 @@ private void flush(boolean flushAll) { out.flush(); reset(); - } + } } - @Override public boolean checkError() { - return out.checkError(); + var out = this.out; + return out == null || out.checkError(); } /* Check that the output stream is still open */ - private void ensureOpen() { + private void ensureOpen() throws IOException { if (out == null) - setError(); + throw new IOException("closed"); } /* @@ -194,77 +197,92 @@ private void ensureOpen() { * The size of the data is of course smaller than preferredChunkSize. */ @Override - public synchronized void write(byte b[], int off, int len) { - ensureOpen(); - if ((off < 0) || (off > b.length) || (len < 0) || - ((off + len) > b.length) || ((off + len) < 0)) { - throw new IndexOutOfBoundsException(); - } else if (len == 0) { - return; - } - - /* if b[] contains enough data then one loop cycle creates one complete - * data chunk with a header, body and a footer, and then flushes the - * chunk to the underlying stream. Otherwise, the last loop cycle - * creates incomplete data chunk with empty header and with no footer - * and stores this incomplete chunk in an internal buffer buf[] - */ - int bytesToWrite = len; - int inputIndex = off; /* the index of the byte[] currently being written */ - - do { - /* enough data to complete a chunk */ - if (bytesToWrite >= spaceInCurrentChunk) { - - /* header */ - for (int i=0; i b.length) || (len < 0) || + ((off + len) > b.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } - flush(false); - if (checkError()){ - break; + /* if b[] contains enough data then one loop cycle creates one complete + * data chunk with a header, body and a footer, and then flushes the + * chunk to the underlying stream. Otherwise, the last loop cycle + * creates incomplete data chunk with empty header and with no footer + * and stores this incomplete chunk in an internal buffer buf[] + */ + int bytesToWrite = len; + int inputIndex = off; /* the index of the byte[] currently being written */ + + do { + /* enough data to complete a chunk */ + if (bytesToWrite >= spaceInCurrentChunk) { + + /* header */ + for (int i = 0; i < completeHeader.length; i++) + buf[i] = completeHeader[i]; + + /* data */ + System.arraycopy(b, inputIndex, buf, count, spaceInCurrentChunk); + inputIndex += spaceInCurrentChunk; + bytesToWrite -= spaceInCurrentChunk; + count += spaceInCurrentChunk; + + /* footer */ + buf[count++] = FOOTER[0]; + buf[count++] = FOOTER[1]; + spaceInCurrentChunk = 0; //chunk is complete + + flush(false); + if (checkError()) { + break; + } } - } - /* not enough data to build a chunk */ - else { - /* header */ - /* do not write header if not enough bytes to build a chunk yet */ + /* not enough data to build a chunk */ + else { + /* header */ + /* do not write header if not enough bytes to build a chunk yet */ - /* data */ - System.arraycopy(b, inputIndex, buf, count, bytesToWrite); - count += bytesToWrite; - size += bytesToWrite; - spaceInCurrentChunk -= bytesToWrite; - bytesToWrite = 0; + /* data */ + System.arraycopy(b, inputIndex, buf, count, bytesToWrite); + count += bytesToWrite; + size += bytesToWrite; + spaceInCurrentChunk -= bytesToWrite; + bytesToWrite = 0; - /* footer */ - /* do not write header if not enough bytes to build a chunk yet */ - } - } while (bytesToWrite > 0); + /* footer */ + /* do not write header if not enough bytes to build a chunk yet */ + } + } while (bytesToWrite > 0); + } finally { + writeLock.unlock(); + } } @Override - public synchronized void write(int _b) { - byte b[] = {(byte)_b}; - write(b, 0, 1); + public void write(int _b) throws IOException { + writeLock.lock(); + try { + byte b[] = {(byte) _b}; + write(b, 0, 1); + } finally { + writeLock.unlock(); + } } - public synchronized void reset() { - count = preferedHeaderSize; - size = 0; - spaceInCurrentChunk = preferredChunkDataSize; + public void reset() { + writeLock.lock(); + try { + count = preferedHeaderSize; + size = 0; + spaceInCurrentChunk = preferredChunkDataSize; + } finally { + writeLock.unlock(); + } } public int size() { @@ -272,26 +290,36 @@ public int size() { } @Override - public synchronized void close() { - ensureOpen(); + public void close() { + writeLock.lock(); + try { + if (out == null) return; + + /* if we have buffer a chunked send it */ + if (size > 0) { + flush(true); + } - /* if we have buffer a chunked send it */ - if (size > 0) { + /* send a zero length chunk */ flush(true); - } - - /* send a zero length chunk */ - flush(true); - /* don't close the underlying stream */ - out = null; + /* don't close the underlying stream */ + out = null; + } finally { + writeLock.unlock(); + } } @Override - public synchronized void flush() { - ensureOpen(); - if (size > 0) { - flush(true); + public void flush() throws IOException { + writeLock.lock(); + try { + ensureOpen(); + if (size > 0) { + flush(true); + } + } finally { + writeLock.unlock(); } } } diff --git a/src/java.base/share/classes/sun/net/www/http/HttpCapture.java b/src/java.base/share/classes/sun/net/www/http/HttpCapture.java index 13ede95ac41..2ddd5289023 100644 --- a/src/java.base/share/classes/sun/net/www/http/HttpCapture.java +++ b/src/java.base/share/classes/sun/net/www/http/HttpCapture.java @@ -54,6 +54,8 @@ * @author jccollet */ public class HttpCapture { + // HttpCapture does blocking I/O operations while holding monitors. + // This is not a concern because it is rarely used. private File file; private boolean incoming = true; private BufferedWriter out; diff --git a/src/java.base/share/classes/sun/net/www/http/HttpClient.java b/src/java.base/share/classes/sun/net/www/http/HttpClient.java index be1b7916762..85325d6c884 100644 --- a/src/java.base/share/classes/sun/net/www/http/HttpClient.java +++ b/src/java.base/share/classes/sun/net/www/http/HttpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2020, 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 @@ -30,6 +30,8 @@ import java.util.Locale; import java.util.Objects; import java.util.Properties; +import java.util.concurrent.locks.ReentrantLock; + import sun.net.NetworkClient; import sun.net.ProgressSource; import sun.net.www.MessageHeader; @@ -47,6 +49,8 @@ * @author Dave Brown */ public class HttpClient extends NetworkClient { + private final ReentrantLock clientLock = new ReentrantLock(); + // whether this httpclient comes from the cache protected boolean cachedHttpClient = false; @@ -349,22 +353,28 @@ public static HttpClient New(URL url, Proxy p, int to, boolean useCache, boolean compatible = Objects.equals(ret.proxy, p) && Objects.equals(ret.getAuthenticatorKey(), ak); if (compatible) { - synchronized (ret) { + ret.lock(); + try { ret.cachedHttpClient = true; assert ret.inCache; ret.inCache = false; if (httpuc != null && ret.needsTunneling()) httpuc.setTunnelState(TUNNELING); logFinest("KeepAlive stream retrieved from the cache, " + ret); + } finally { + ret.unlock(); } } else { // We cannot return this connection to the cache as it's // KeepAliveTimeout will get reset. We simply close the connection. // This should be fine as it is very rare that a connection // to the same host will not use the same proxy. - synchronized(ret) { + ret.lock(); + try { ret.inCache = false; ret.closeServer(); + } finally { + ret.unlock(); } ret = null; } @@ -442,10 +452,11 @@ public void finished() { } } - protected synchronized boolean available() { + protected boolean available() { boolean available = true; int old = -1; + lock(); try { try { old = serverSocket.getSoTimeout(); @@ -469,21 +480,33 @@ protected synchronized boolean available() { logFinest("HttpClient.available(): " + "SocketException: not available"); available = false; + } finally { + unlock(); } return available; } - protected synchronized void putInKeepAliveCache() { - if (inCache) { - assert false : "Duplicate put to keep alive cache"; - return; + protected void putInKeepAliveCache() { + lock(); + try { + if (inCache) { + assert false : "Duplicate put to keep alive cache"; + return; + } + inCache = true; + kac.put(url, null, this); + } finally { + unlock(); } - inCache = true; - kac.put(url, null, this); } - protected synchronized boolean isInKeepAliveCache() { - return inCache; + protected boolean isInKeepAliveCache() { + lock(); + try { + return inCache; + } finally { + unlock(); + } } /* @@ -530,8 +553,13 @@ public boolean needsTunneling() { /* * Returns true if this httpclient is from cache */ - public synchronized boolean isCachedConnection() { - return cachedHttpClient; + public boolean isCachedConnection() { + lock(); + try { + return cachedHttpClient; + } finally { + unlock(); + } } /* @@ -549,9 +577,10 @@ public void afterConnect() throws IOException, UnknownHostException { /* * call openServer in a privileged block */ - private synchronized void privilegedOpenServer(final InetSocketAddress server) + private void privilegedOpenServer(final InetSocketAddress server) throws IOException { + assert clientLock.isHeldByCurrentThread(); try { java.security.AccessController.doPrivileged( new java.security.PrivilegedExceptionAction<>() { @@ -577,48 +606,53 @@ private void superOpenServer(final String proxyHost, /* */ - protected synchronized void openServer() throws IOException { + protected void openServer() throws IOException { SecurityManager security = System.getSecurityManager(); - if (security != null) { - security.checkConnect(host, port); - } - - if (keepingAlive) { // already opened - return; - } - - if (url.getProtocol().equals("http") || - url.getProtocol().equals("https") ) { + lock(); + try { + if (security != null) { + security.checkConnect(host, port); + } - if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) { - sun.net.www.URLConnection.setProxiedHost(host); - privilegedOpenServer((InetSocketAddress) proxy.address()); - usingProxy = true; - return; - } else { - // make direct connection - openServer(host, port); - usingProxy = false; + if (keepingAlive) { // already opened return; } - } else { - /* we're opening some other kind of url, most likely an - * ftp url. - */ - if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) { - sun.net.www.URLConnection.setProxiedHost(host); - privilegedOpenServer((InetSocketAddress) proxy.address()); - usingProxy = true; - return; + if (url.getProtocol().equals("http") || + url.getProtocol().equals("https")) { + + if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) { + sun.net.www.URLConnection.setProxiedHost(host); + privilegedOpenServer((InetSocketAddress) proxy.address()); + usingProxy = true; + return; + } else { + // make direct connection + openServer(host, port); + usingProxy = false; + return; + } + } else { - // make direct connection - super.openServer(host, port); - usingProxy = false; - return; + /* we're opening some other kind of url, most likely an + * ftp url. + */ + if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) { + sun.net.www.URLConnection.setProxiedHost(host); + privilegedOpenServer((InetSocketAddress) proxy.address()); + usingProxy = true; + return; + } else { + // make direct connection + super.openServer(host, port); + usingProxy = false; + return; + } } + } finally { + unlock(); } } @@ -1043,8 +1077,13 @@ else if (cl == -1) { return ret; } - public synchronized InputStream getInputStream() { - return serverInput; + public InputStream getInputStream() { + lock(); + try { + return serverInput; + } finally { + unlock(); + } } public OutputStream getOutputStream() { @@ -1121,4 +1160,12 @@ public int getProxyPortUsed() { return ((InetSocketAddress)proxy.address()).getPort(); return -1; } + + public final void lock() { + clientLock.lock(); + } + + public final void unlock() { + clientLock.unlock(); + } } diff --git a/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java b/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java index 10766f4ccf0..5beaef24d58 100644 --- a/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java +++ b/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2020, 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 @@ -36,6 +36,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import jdk.internal.misc.InnocuousThread; import sun.security.action.GetIntegerAction; @@ -101,6 +103,8 @@ static int getMaxConnections() { static final int LIFETIME = 5000; + // This class is never serialized (see writeObject/readObject). + private final ReentrantLock cacheLock = new ReentrantLock(); private Thread keepAliveTimer = null; /** @@ -113,63 +117,49 @@ public KeepAliveCache() {} * @param url The URL contains info about the host and port * @param http The HttpClient to be cached */ - public synchronized void put(final URL url, Object obj, HttpClient http) { - boolean startThread = (keepAliveTimer == null); - if (!startThread) { - if (!keepAliveTimer.isAlive()) { - startThread = true; - } - } - if (startThread) { - clear(); - /* Unfortunately, we can't always believe the keep-alive timeout we got - * back from the server. If I'm connected through a Netscape proxy - * to a server that sent me a keep-alive - * time of 15 sec, the proxy unilaterally terminates my connection - * The robustness to get around this is in HttpClient.parseHTTP() - */ - final KeepAliveCache cache = this; - AccessController.doPrivileged(new PrivilegedAction<>() { - public Void run() { - keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache); - keepAliveTimer.setDaemon(true); - keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2); - keepAliveTimer.start(); - return null; + public void put(final URL url, Object obj, HttpClient http) { + cacheLock.lock(); + try { + boolean startThread = (keepAliveTimer == null); + if (!startThread) { + if (!keepAliveTimer.isAlive()) { + startThread = true; } - }); - } + } + if (startThread) { + clear(); + /* Unfortunately, we can't always believe the keep-alive timeout we got + * back from the server. If I'm connected through a Netscape proxy + * to a server that sent me a keep-alive + * time of 15 sec, the proxy unilaterally terminates my connection + * The robustness to get around this is in HttpClient.parseHTTP() + */ + final KeepAliveCache cache = this; + AccessController.doPrivileged(new PrivilegedAction<>() { + public Void run() { + keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache); + keepAliveTimer.setDaemon(true); + keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2); + keepAliveTimer.start(); + return null; + } + }); + } - KeepAliveKey key = new KeepAliveKey(url, obj); - ClientVector v = super.get(key); + KeepAliveKey key = new KeepAliveKey(url, obj); + ClientVector v = super.get(key); - if (v == null) { - int keepAliveTimeout = http.getKeepAliveTimeout(); - if (keepAliveTimeout == 0) { - keepAliveTimeout = getUserKeepAlive(http.getUsingProxy()); - if (keepAliveTimeout == -1) { - // same default for server and proxy - keepAliveTimeout = 5; - } - } else if (keepAliveTimeout == -1) { - keepAliveTimeout = getUserKeepAlive(http.getUsingProxy()); - if (keepAliveTimeout == -1) { - // different default for server and proxy - keepAliveTimeout = http.getUsingProxy() ? 60 : 5; - } - } - // at this point keepAliveTimeout is the number of seconds to keep - // alive, which could be 0, if the user specified 0 for the property - assert keepAliveTimeout >= 0; - if (keepAliveTimeout == 0) { - http.closeServer(); - } else { - v = new ClientVector(keepAliveTimeout * 1000); - v.put(http); - super.put(key, v); - } - } else { - v.put(http); + if (v == null) { + int keepAliveTimeout = http.getKeepAliveTimeout(); + v = new ClientVector(keepAliveTimeout > 0 ? + keepAliveTimeout * 1000 : LIFETIME); + v.put(http); + super.put(key, v); + } else { + v.put(http); + } + } finally { + cacheLock.unlock(); } } @@ -179,34 +169,45 @@ private static int getUserKeepAlive(boolean isProxy) { } /* remove an obsolete HttpClient from its VectorCache */ - public synchronized void remove(HttpClient h, Object obj) { - KeepAliveKey key = new KeepAliveKey(h.url, obj); - ClientVector v = super.get(key); - if (v != null) { - v.remove(h); - if (v.isEmpty()) { - removeVector(key); + public void remove(HttpClient h, Object obj) { + cacheLock.lock(); + try { + KeepAliveKey key = new KeepAliveKey(h.url, obj); + ClientVector v = super.get(key); + if (v != null) { + v.remove(h); + if (v.isEmpty()) { + removeVector(key); + } } + } finally { + cacheLock.unlock(); } } /* called by a clientVector thread when all its connections have timed out * and that vector of connections should be removed. */ - synchronized void removeVector(KeepAliveKey k) { + private void removeVector(KeepAliveKey k) { + assert cacheLock.isHeldByCurrentThread(); super.remove(k); } /** * Check to see if this URL has a cached HttpClient */ - public synchronized HttpClient get(URL url, Object obj) { - KeepAliveKey key = new KeepAliveKey(url, obj); - ClientVector v = super.get(key); - if (v == null) { // nothing in cache yet - return null; + public HttpClient get(URL url, Object obj) { + cacheLock.lock(); + try { + KeepAliveKey key = new KeepAliveKey(url, obj); + ClientVector v = super.get(key); + if (v == null) { // nothing in cache yet + return null; + } + return v.get(); + } finally { + cacheLock.unlock(); } - return v.get(); } /* Sleeps for an alloted timeout, then checks for timed out connections. @@ -221,13 +222,15 @@ public void run() { } catch (InterruptedException e) {} // Remove all outdated HttpClients. - synchronized (this) { + cacheLock.lock(); + try { long currentTime = System.currentTimeMillis(); List keysToRemove = new ArrayList<>(); for (KeepAliveKey key : keySet()) { ClientVector v = get(key); - synchronized (v) { + v.lock(); + try { KeepAliveEntry e = v.peek(); while (e != null) { if ((currentTime - e.idleStartTime) > v.nap) { @@ -242,12 +245,16 @@ public void run() { if (v.isEmpty()) { keysToRemove.add(key); } + } finally { + v.unlock(); } } for (KeepAliveKey key : keysToRemove) { removeVector(key); } + } finally { + cacheLock.unlock(); } } while (!isEmpty()); } @@ -271,6 +278,7 @@ private void readObject(ObjectInputStream stream) */ class ClientVector extends ArrayDeque { private static final long serialVersionUID = -8680532108106489459L; + private final ReentrantLock lock = new ReentrantLock(); // sleep time in milliseconds, before cache clear int nap; @@ -279,47 +287,65 @@ class ClientVector extends ArrayDeque { this.nap = nap; } - synchronized HttpClient get() { - if (isEmpty()) { - return null; - } + HttpClient get() { + lock(); + try { + if (isEmpty()) { + return null; + } - // Loop until we find a connection that has not timed out - HttpClient hc = null; - long currentTime = System.currentTimeMillis(); - do { - KeepAliveEntry e = pop(); - if ((currentTime - e.idleStartTime) > nap) { - e.hc.closeServer(); - } else { - hc = e.hc; - if (KeepAliveCache.logger.isLoggable(PlatformLogger.Level.FINEST)) { - String msg = "cached HttpClient was idle for " - + Long.toString(currentTime - e.idleStartTime); - KeepAliveCache.logger.finest(msg); + // Loop until we find a connection that has not timed out + HttpClient hc = null; + long currentTime = System.currentTimeMillis(); + do { + KeepAliveEntry e = pop(); + if ((currentTime - e.idleStartTime) > nap) { + e.hc.closeServer(); + } else { + hc = e.hc; } - } - } while ((hc == null) && (!isEmpty())); - return hc; + } while ((hc == null) && (!isEmpty())); + return hc; + } finally { + unlock(); + } } /* return a still valid, unused HttpClient */ - synchronized void put(HttpClient h) { - if (size() >= KeepAliveCache.getMaxConnections()) { - h.closeServer(); // otherwise the connection remains in limbo - } else { - push(new KeepAliveEntry(h, System.currentTimeMillis())); + void put(HttpClient h) { + lock(); + try { + if (size() >= KeepAliveCache.getMaxConnections()) { + h.closeServer(); // otherwise the connection remains in limbo + } else { + push(new KeepAliveEntry(h, System.currentTimeMillis())); + } + } finally { + unlock(); } } /* remove an HttpClient */ - synchronized boolean remove(HttpClient h) { - for (KeepAliveEntry curr : this) { - if (curr.hc == h) { - return super.remove(curr); + boolean remove(HttpClient h) { + lock(); + try { + for (KeepAliveEntry curr : this) { + if (curr.hc == h) { + return super.remove(curr); + } } + return false; + } finally { + unlock(); } - return false; + } + + final void lock() { + lock.lock(); + } + + final void unlock() { + lock.unlock(); } /* diff --git a/src/java.base/share/classes/sun/net/www/http/KeepAliveStream.java b/src/java.base/share/classes/sun/net/www/http/KeepAliveStream.java index 3af7c555a5a..7963e4c856f 100644 --- a/src/java.base/share/classes/sun/net/www/http/KeepAliveStream.java +++ b/src/java.base/share/classes/sun/net/www/http/KeepAliveStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2020, 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 @@ -47,7 +47,8 @@ class KeepAliveStream extends MeteredStream implements Hurryable { boolean hurried; // has this KeepAliveStream been put on the queue for asynchronous cleanup. - protected boolean queuedForCleanup = false; + // This flag is read from within KeepAliveCleanerEntry outside of any lock. + protected volatile boolean queuedForCleanup = false; private static final KeepAliveStreamCleaner queue = new KeepAliveStreamCleaner(); private static Thread cleanerThread; // null @@ -64,15 +65,8 @@ public KeepAliveStream(InputStream is, ProgressSource pi, long expected, HttpCli * Attempt to cache this connection */ public void close() throws IOException { - // If the inputstream is closed already, just return. - if (closed) { - return; - } - - // If this stream has already been queued for cleanup. - if (queuedForCleanup) { - return; - } + // If the inputstream is queued for cleanup, just return. + if (queuedForCleanup) return; // Skip past the data that's left in the Inputstream because // some sort of error may have occurred. @@ -81,34 +75,45 @@ public void close() throws IOException { // to hang around for nothing. So if we can't skip without blocking // we just close the socket and, therefore, terminate the keepAlive // NOTE: Don't close super class + // For consistency, access to `expected` and `count` should be + // protected by readLock + lock(); try { - if (expected > count) { - long nskip = expected - count; - if (nskip <= available()) { - do {} while ((nskip = (expected - count)) > 0L - && skip(Math.min(nskip, available())) > 0L); - } else if (expected <= KeepAliveStreamCleaner.MAX_DATA_REMAINING && !hurried) { - //put this KeepAliveStream on the queue so that the data remaining - //on the socket can be cleanup asyncronously. - queueForCleanup(new KeepAliveCleanerEntry(this, hc)); - } else { - hc.closeServer(); + // If the inputstream is closed already, or if this stream + // has already been queued for cleanup, just return. + if (closed || queuedForCleanup) return; + try { + if (expected > count) { + long nskip = expected - count; + if (nskip <= available()) { + do { + } while ((nskip = (expected - count)) > 0L + && skip(Math.min(nskip, available())) > 0L); + } else if (expected <= KeepAliveStreamCleaner.MAX_DATA_REMAINING && !hurried) { + //put this KeepAliveStream on the queue so that the data remaining + //on the socket can be cleanup asyncronously. + queueForCleanup(new KeepAliveCleanerEntry(this, hc)); + } else { + hc.closeServer(); + } + } + if (!closed && !hurried && !queuedForCleanup) { + hc.finished(); + } + } finally { + if (pi != null) + pi.finishTracking(); + + if (!queuedForCleanup) { + // nulling out the underlying inputstream as well as + // httpClient to let gc collect the memories faster + in = null; + hc = null; + closed = true; } - } - if (!closed && !hurried && !queuedForCleanup) { - hc.finished(); } } finally { - if (pi != null) - pi.finishTracking(); - - if (!queuedForCleanup) { - // nulling out the underlying inputstream as well as - // httpClient to let gc collect the memories faster - in = null; - hc = null; - closed = true; - } + unlock(); } } @@ -124,7 +129,8 @@ public void reset() throws IOException { throw new IOException("mark/reset not supported"); } - public synchronized boolean hurry() { + public boolean hurry() { + lock(); try { /* CASE 0: we're actually already done */ if (closed || count >= expected) { @@ -147,11 +153,14 @@ public synchronized boolean hurry() { } catch (IOException e) { // e.printStackTrace(); return false; + } finally { + unlock(); } } private static void queueForCleanup(KeepAliveCleanerEntry kace) { - synchronized(queue) { + queue.lock(); + try { if(!kace.getQueuedForCleanup()) { if (!queue.offer(kace)) { kace.getHttpClient().closeServer(); @@ -159,7 +168,7 @@ private static void queueForCleanup(KeepAliveCleanerEntry kace) { } kace.setQueuedForCleanup(); - queue.notifyAll(); + queue.signalAll(); } boolean startCleanupThread = (cleanerThread == null); @@ -181,14 +190,20 @@ public Void run() { } }); } - } // queue + } finally { + queue.unlock(); + } } + // Only called from KeepAliveStreamCleaner protected long remainingToRead() { + assert isLockHeldByCurrentThread(); return expected - count; } + // Only called from KeepAliveStreamCleaner protected void setClosed() { + assert isLockHeldByCurrentThread(); in = null; hc = null; closed = true; diff --git a/src/java.base/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java b/src/java.base/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java index 87f0d09ea62..5ad38485453 100644 --- a/src/java.base/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java +++ b/src/java.base/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2020, 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 @@ -30,6 +30,8 @@ import sun.net.NetProperties; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; /** * This class is used to cleanup any remaining data that may be on a KeepAliveStream @@ -78,6 +80,20 @@ public Integer run() { } + private final ReentrantLock queueLock = new ReentrantLock(); + private final Condition waiter = queueLock.newCondition(); + + final void signalAll() { + waiter.signalAll(); + } + + final void lock() { + queueLock.lock(); + } + + final void unlock() { + queueLock.unlock(); + } @Override public boolean offer(KeepAliveCleanerEntry e) { @@ -94,11 +110,12 @@ public void run() do { try { - synchronized(this) { + lock(); + try { long before = System.currentTimeMillis(); long timeout = TIMEOUT; while ((kace = poll()) == null) { - this.wait(timeout); + waiter.wait(timeout); long after = System.currentTimeMillis(); long elapsed = after - before; @@ -110,6 +127,8 @@ public void run() before = after; timeout -= elapsed; } + } finally { + unlock(); } if(kace == null) @@ -118,7 +137,8 @@ public void run() KeepAliveStream kas = kace.getKeepAliveStream(); if (kas != null) { - synchronized(kas) { + kas.lock(); + try { HttpClient hc = kace.getHttpClient(); try { if (hc != null && !hc.isInKeepAliveCache()) { @@ -147,6 +167,8 @@ public void run() } finally { kas.setClosed(); } + } finally { + kas.unlock(); } } } catch (InterruptedException ie) { } diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/AuthCacheImpl.java b/src/java.base/share/classes/sun/net/www/protocol/http/AuthCacheImpl.java index 45a5a0eaf9b..a35dfc17ebb 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/AuthCacheImpl.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/AuthCacheImpl.java @@ -32,8 +32,10 @@ /** * @author Michael McMahon */ - public class AuthCacheImpl implements AuthCache { + // No blocking IO is performed within the synchronized code blocks + // in this class, so there is no need to convert this class to using + // java.util.concurrent.locks HashMap> hashtable; public AuthCacheImpl () { @@ -46,7 +48,6 @@ public void setMap (HashMap> map) { // put a value in map according to primary key + secondary key which // is the path field of AuthenticationInfo - public synchronized void put (String pkey, AuthCacheValue value) { LinkedList list = hashtable.get (pkey); String skey = value.getPath(); diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java b/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java index f4547a2c47a..d4f6aa4f3f0 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2020, 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 @@ -31,6 +31,8 @@ import java.net.URL; import java.util.HashMap; import java.util.Objects; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import sun.net.www.HeaderParser; @@ -124,8 +126,9 @@ protected boolean useAuthCache() { * at the same time, then all but the first will block until * the first completes its authentication. */ - private static HashMap requests = new HashMap<>(); - + private static final HashMap requests = new HashMap<>(); + private static final ReentrantLock requestLock = new ReentrantLock(); + private static final Condition requestFinished = requestLock.newCondition(); /* * check if AuthenticationInfo is available in the cache. * If not, check if a request for this destination is in progress @@ -141,8 +144,9 @@ private static AuthenticationInfo requestAuthentication(String key, Function schemesListToSet(String list) { * - getOutputStream() * - getInputStream()) * - connect() - * Access synchronized on this. + * Access is protected by connectionLock. */ private boolean connecting = false; @@ -431,6 +433,22 @@ public enum TunnelState { private static final PlatformLogger logger = PlatformLogger.getLogger("sun.net.www.protocol.http.HttpURLConnection"); + /* Lock */ + private final ReentrantLock connectionLock = new ReentrantLock(); + + private final void lock() { + connectionLock.lock(); + } + + private final void unlock() { + connectionLock.unlock(); + } + + public final boolean isLockHeldByCurrentThread() { + return connectionLock.isHeldByCurrentThread(); + } + + /* * privileged request password authentication * @@ -513,13 +531,18 @@ public void authObj(Object authObj) { } @Override - public synchronized void setAuthenticator(Authenticator auth) { - if (connecting || connected) { - throw new IllegalStateException( - "Authenticator must be set before connecting"); + public void setAuthenticator(Authenticator auth) { + lock(); + try { + if (connecting || connected) { + throw new IllegalStateException( + "Authenticator must be set before connecting"); + } + authenticator = Objects.requireNonNull(auth); + authenticatorKey = AuthenticatorKeys.getKey(authenticator); + } finally { + unlock(); } - authenticator = Objects.requireNonNull(auth); - authenticatorKey = AuthenticatorKeys.getKey(authenticator); } public String getAuthenticatorKey() { @@ -562,18 +585,25 @@ private void checkMessageHeader(String key, String value) { } } - public synchronized void setRequestMethod(String method) + public void setRequestMethod(String method) throws ProtocolException { - if (connecting) { - throw new IllegalStateException("connect in progress"); + lock(); + try { + if (connecting) { + throw new IllegalStateException("connect in progress"); + } + super.setRequestMethod(method); + } finally { + unlock(); } - super.setRequestMethod(method); } /* adds the standard key/val pairs to reqests if necessary & write to * given PrintStream */ private void writeRequests() throws IOException { + assert isLockHeldByCurrentThread(); + /* print all message headers in the MessageHeader * onto the wire - all the ones we've set and any * others that have been set @@ -682,6 +712,8 @@ private void writeRequests() throws IOException { } } else if (poster != null) { /* add Content-Length & POST/PUT data */ + // safe to synchronize on poster: this is + // a simple subclass of ByteArrayOutputStream synchronized (poster) { /* close it, so no more data can be added */ poster.close(); @@ -1009,8 +1041,11 @@ public Void run() { // overridden in HTTPS subclass public void connect() throws IOException { - synchronized (this) { + lock(); + try { connecting = true; + } finally { + unlock(); } plainConnect(); } @@ -1056,11 +1091,14 @@ public String run() throws IOException { return host + ":" + Integer.toString(port); } - protected void plainConnect() throws IOException { - synchronized (this) { + protected void plainConnect() throws IOException { + lock(); + try { if (connected) { return; } + } finally { + unlock(); } SocketPermission p = URLtoSocketPermission(this.url); if (p != null) { @@ -1322,28 +1360,34 @@ private void expect100Continue() throws IOException { */ @Override - public synchronized OutputStream getOutputStream() throws IOException { - connecting = true; - SocketPermission p = URLtoSocketPermission(this.url); + public OutputStream getOutputStream() throws IOException { + lock(); + try { + connecting = true; + SocketPermission p = URLtoSocketPermission(this.url); - if (p != null) { - try { - return AccessController.doPrivilegedWithCombiner( - new PrivilegedExceptionAction<>() { - public OutputStream run() throws IOException { - return getOutputStream0(); - } - }, null, p - ); - } catch (PrivilegedActionException e) { - throw (IOException) e.getException(); + if (p != null) { + try { + return AccessController.doPrivilegedWithCombiner( + new PrivilegedExceptionAction<>() { + public OutputStream run() throws IOException { + return getOutputStream0(); + } + }, null, p + ); + } catch (PrivilegedActionException e) { + throw (IOException) e.getException(); + } + } else { + return getOutputStream0(); } - } else { - return getOutputStream0(); + } finally { + unlock(); } } - private synchronized OutputStream getOutputStream0() throws IOException { + private OutputStream getOutputStream0() throws IOException { + assert isLockHeldByCurrentThread(); try { if (!doOutput) { throw new ProtocolException("cannot write to a URLConnection" @@ -1433,16 +1477,19 @@ private void setCookieHeader() throws IOException { // we only want to capture the user defined Cookies once, as // they cannot be changed by user code after we are connected, // only internally. - synchronized (this) { - if (setUserCookies) { - int k = requests.getKey("Cookie"); - if (k != -1) - userCookies = requests.getValue(k); - k = requests.getKey("Cookie2"); - if (k != -1) - userCookies2 = requests.getValue(k); - setUserCookies = false; - } + + // we should only reach here when called from + // writeRequest, which in turn is only called by + // getInputStream0 + assert isLockHeldByCurrentThread(); + if (setUserCookies) { + int k = requests.getKey("Cookie"); + if (k != -1) + userCookies = requests.getValue(k); + k = requests.getKey("Cookie2"); + if (k != -1) + userCookies2 = requests.getValue(k); + setUserCookies = false; } // remove old Cookie header before setting new one. @@ -1500,30 +1547,36 @@ private void setCookieHeader() throws IOException { } @Override - public synchronized InputStream getInputStream() throws IOException { - connecting = true; - SocketPermission p = URLtoSocketPermission(this.url); + public InputStream getInputStream() throws IOException { + lock(); + try { + connecting = true; + SocketPermission p = URLtoSocketPermission(this.url); - if (p != null) { - try { - return AccessController.doPrivilegedWithCombiner( - new PrivilegedExceptionAction<>() { - public InputStream run() throws IOException { - return getInputStream0(); - } - }, null, p - ); - } catch (PrivilegedActionException e) { - throw (IOException) e.getException(); + if (p != null) { + try { + return AccessController.doPrivilegedWithCombiner( + new PrivilegedExceptionAction<>() { + public InputStream run() throws IOException { + return getInputStream0(); + } + }, null, p + ); + } catch (PrivilegedActionException e) { + throw (IOException) e.getException(); + } + } else { + return getInputStream0(); } - } else { - return getInputStream0(); + } finally { + unlock(); } } @SuppressWarnings("empty-statement") - private synchronized InputStream getInputStream0() throws IOException { + private InputStream getInputStream0() throws IOException { + assert isLockHeldByCurrentThread(); if (!doInput) { throw new ProtocolException("Cannot read from URLConnection" + " if doInput=false (call setDoInput(true))"); @@ -2002,6 +2055,10 @@ public InputStream getErrorStream() { */ private AuthenticationInfo resetProxyAuthentication(AuthenticationInfo proxyAuthentication, AuthenticationHeader auth) throws IOException { + + // Only called from getInputStream0 and doTunneling0 + assert isLockHeldByCurrentThread(); + if ((proxyAuthentication != null )&& proxyAuthentication.getAuthScheme() != NTLM) { String raw = auth.raw(); @@ -2052,7 +2109,16 @@ public void setTunnelState(TunnelState tunnelState) { /** * establish a tunnel through proxy server */ - public synchronized void doTunneling() throws IOException { + public void doTunneling() throws IOException { + lock(); + try { + doTunneling0(); + } finally{ + unlock(); + } + } + + private void doTunneling0() throws IOException { int retryTunnel = 0; String statusLine = ""; int respCode = 0; @@ -2060,6 +2126,8 @@ public synchronized void doTunneling() throws IOException { String proxyHost = null; int proxyPort = -1; + assert isLockHeldByCurrentThread(); + // save current requests so that they can be restored after tunnel is setup. MessageHeader savedRequests = requests; requests = new MessageHeader(); @@ -2280,7 +2348,10 @@ private void setPreemptiveProxyAuthentication(MessageHeader requests) throws IOE * the connection. */ @SuppressWarnings("fallthrough") - private AuthenticationInfo getHttpProxyAuthentication (AuthenticationHeader authhdr) { + private AuthenticationInfo getHttpProxyAuthentication(AuthenticationHeader authhdr) { + + assert isLockHeldByCurrentThread(); + /* get authorization from authenticator */ AuthenticationInfo ret = null; String raw = authhdr.raw(); @@ -2439,11 +2510,15 @@ public InetAddress run() /** * Gets the authentication for an HTTP server, and applies it to * the connection. - * @param authHdr the AuthenticationHeader which tells what auth scheme is + * @param authhdr the AuthenticationHeader which tells what auth scheme is * preferred. */ @SuppressWarnings("fallthrough") - private AuthenticationInfo getServerAuthentication (AuthenticationHeader authhdr) { + private AuthenticationInfo getServerAuthentication(AuthenticationHeader authhdr) { + + // Only called from getInputStream0 + assert isLockHeldByCurrentThread(); + /* get authorization from authenticator */ AuthenticationInfo ret = null; String raw = authhdr.raw(); @@ -2719,6 +2794,8 @@ public Boolean run() throws IOException { private boolean followRedirect0(String loc, int stat, URL locUrl) throws IOException { + assert isLockHeldByCurrentThread(); + disconnectInternal(); if (streaming()) { throw new HttpRetryException (RETRY_MSG3, stat, loc); @@ -3180,17 +3257,22 @@ public String getHeaderFieldKey(int n) { * @param value the value to be set */ @Override - public synchronized void setRequestProperty(String key, String value) { - if (connected || connecting) - throw new IllegalStateException("Already connected"); - if (key == null) - throw new NullPointerException ("key is null"); + public void setRequestProperty(String key, String value) { + lock(); + try { + if (connected || connecting) + throw new IllegalStateException("Already connected"); + if (key == null) + throw new NullPointerException("key is null"); - if (isExternalMessageHeaderAllowed(key, value)) { - requests.set(key, value); - if (!key.equalsIgnoreCase("Content-Type")) { - userHeaders.set(key, value); + if (isExternalMessageHeaderAllowed(key, value)) { + requests.set(key, value); + if (!key.equalsIgnoreCase("Content-Type")) { + userHeaders.set(key, value); + } } + } finally { + unlock(); } } @@ -3206,21 +3288,26 @@ MessageHeader getUserSetHeaders() { * @param key the keyword by which the request is known * (e.g., "accept"). * @param value the value associated with it. - * @see #getRequestProperties(java.lang.String) + * @see #getRequestProperty(java.lang.String) * @since 1.4 */ @Override - public synchronized void addRequestProperty(String key, String value) { - if (connected || connecting) - throw new IllegalStateException("Already connected"); - if (key == null) - throw new NullPointerException ("key is null"); - - if (isExternalMessageHeaderAllowed(key, value)) { - requests.add(key, value); - if (!key.equalsIgnoreCase("Content-Type")) { + public void addRequestProperty(String key, String value) { + lock(); + try { + if (connected || connecting) + throw new IllegalStateException("Already connected"); + if (key == null) + throw new NullPointerException("key is null"); + + if (isExternalMessageHeaderAllowed(key, value)) { + requests.add(key, value); + if (!key.equalsIgnoreCase("Content-Type")) { userHeaders.add(key, value); + } } + } finally { + unlock(); } } @@ -3229,31 +3316,41 @@ public synchronized void addRequestProperty(String key, String value) { // the connected test. // public void setAuthenticationProperty(String key, String value) { + // Only called by the implementation of AuthenticationInfo::setHeaders(...) + // in AuthenticationInfo subclasses, which is only called from + // methods from HttpURLConnection protected by the connectionLock. + assert isLockHeldByCurrentThread(); + checkMessageHeader(key, value); requests.set(key, value); } @Override - public synchronized String getRequestProperty (String key) { - if (key == null) { - return null; - } - - // don't return headers containing security sensitive information - for (int i=0; i < EXCLUDE_HEADERS.length; i++) { - if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) { + public String getRequestProperty (String key) { + lock(); + try { + if (key == null) { return null; } - } - if (!setUserCookies) { - if (key.equalsIgnoreCase("Cookie")) { - return userCookies; + + // don't return headers containing security sensitive information + for (int i = 0; i < EXCLUDE_HEADERS.length; i++) { + if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) { + return null; + } } - if (key.equalsIgnoreCase("Cookie2")) { - return userCookies2; + if (!setUserCookies) { + if (key.equalsIgnoreCase("Cookie")) { + return userCookies; + } + if (key.equalsIgnoreCase("Cookie2")) { + return userCookies2; + } } + return requests.findValue(key); + } finally { + unlock(); } - return requests.findValue(key); } /** @@ -3269,29 +3366,34 @@ public synchronized String getRequestProperty (String key) { * @since 1.4 */ @Override - public synchronized Map> getRequestProperties() { - if (connected) - throw new IllegalStateException("Already connected"); + public Map> getRequestProperties() { + lock(); + try { + if (connected) + throw new IllegalStateException("Already connected"); - // exclude headers containing security-sensitive info - if (setUserCookies) { - return requests.getHeaders(EXCLUDE_HEADERS); - } - /* - * The cookies in the requests message headers may have - * been modified. Use the saved user cookies instead. - */ - Map> userCookiesMap = null; - if (userCookies != null || userCookies2 != null) { - userCookiesMap = new HashMap<>(); - if (userCookies != null) { - userCookiesMap.put("Cookie", Arrays.asList(userCookies)); + // exclude headers containing security-sensitive info + if (setUserCookies) { + return requests.getHeaders(EXCLUDE_HEADERS); } - if (userCookies2 != null) { - userCookiesMap.put("Cookie2", Arrays.asList(userCookies2)); + /* + * The cookies in the requests message headers may have + * been modified. Use the saved user cookies instead. + */ + Map> userCookiesMap = null; + if (userCookies != null || userCookies2 != null) { + userCookiesMap = new HashMap<>(); + if (userCookies != null) { + userCookiesMap.put("Cookie", Arrays.asList(userCookies)); + } + if (userCookies2 != null) { + userCookiesMap.put("Cookie2", Arrays.asList(userCookies2)); + } } + return requests.filterAndAddHeaders(EXCLUDE_HEADERS2, userCookiesMap); + } finally { + unlock(); } - return requests.filterAndAddHeaders(EXCLUDE_HEADERS2, userCookiesMap); } @Override @@ -3335,7 +3437,7 @@ public int getConnectTimeout() { * value to be used in milliseconds * @throws IllegalArgumentException if the timeout parameter is negative * - * @see java.net.URLConnectiongetReadTimeout() + * @see java.net.URLConnection#getReadTimeout() * @see java.io.InputStream#read() * @since 1.5 */ @@ -3454,6 +3556,9 @@ public HttpInputStream (InputStream is, CacheRequest cacheRequest) { * @see java.io.FilterInputStream#in * @see java.io.FilterInputStream#reset() */ + // safe to use synchronized here: super method is synchronized too + // and involves no blocking operation; only mark & reset are + // synchronized in the super class hierarchy. @Override public synchronized void mark(int readlimit) { super.mark(readlimit); @@ -3484,6 +3589,9 @@ public synchronized void mark(int readlimit) { * @see java.io.FilterInputStream#in * @see java.io.FilterInputStream#mark(int) */ + // safe to use synchronized here: super method is synchronized too + // and involves no blocking operation; only mark & reset are + // synchronized in the super class hierarchy. @Override public synchronized void reset() throws IOException { super.reset(); @@ -3664,8 +3772,14 @@ void checkError () throws IOException { if (error) { throw errorExcp; } - if (((PrintStream)out).checkError()) { - throw new IOException("Error writing request body to server"); + if (out instanceof PrintStream) { + if (((PrintStream) out).checkError()) { + throw new IOException("Error writing request body to server"); + } + } else if (out instanceof ChunkedOutputStream) { + if (((ChunkedOutputStream) out).checkError()) { + throw new IOException("Error writing request body to server"); + } } } diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.java b/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.java index f3b9f94fa1a..b37cd6efc19 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2020, 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 @@ -30,6 +30,8 @@ import java.net.Authenticator.RequestorType; import java.util.Base64; import java.util.HashMap; +import java.util.concurrent.locks.ReentrantLock; + import sun.net.www.HeaderParser; import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE; import static sun.net.www.protocol.http.AuthScheme.KERBEROS; @@ -55,6 +57,8 @@ class NegotiateAuthentication extends AuthenticationInfo { // the cache can be used only once, so after the first use, it's cleaned. static HashMap supported = null; static ThreadLocal > cache = null; + private static final ReentrantLock negotiateLock = new ReentrantLock(); + /* Whether cache is enabled for Negotiate/Kerberos */ private static final boolean cacheSPNEGO; static { @@ -98,40 +102,50 @@ public boolean supportsPreemptiveAuthorization() { * * @return true if supported */ - synchronized public static boolean isSupported(HttpCallerInfo hci) { - if (supported == null) { - supported = new HashMap<>(); - } - String hostname = hci.host; - hostname = hostname.toLowerCase(); - if (supported.containsKey(hostname)) { - return supported.get(hostname); - } + public static boolean isSupported(HttpCallerInfo hci) { + negotiateLock.lock(); + try { + if (supported == null) { + supported = new HashMap<>(); + } + String hostname = hci.host; + hostname = hostname.toLowerCase(); + if (supported.containsKey(hostname)) { + return supported.get(hostname); + } - Negotiator neg = Negotiator.getNegotiator(hci); - if (neg != null) { - supported.put(hostname, true); - // the only place cache.put is called. here we can make sure - // the object is valid and the oneToken inside is not null - if (cache == null) { - cache = new ThreadLocal<>() { - @Override - protected HashMap initialValue() { - return new HashMap<>(); - } - }; + Negotiator neg = Negotiator.getNegotiator(hci); + if (neg != null) { + supported.put(hostname, true); + // the only place cache.put is called. here we can make sure + // the object is valid and the oneToken inside is not null + if (cache == null) { + cache = new ThreadLocal<>() { + @Override + protected HashMap initialValue() { + return new HashMap<>(); + } + }; + } + cache.get().put(hostname, neg); + return true; + } else { + supported.put(hostname, false); + return false; } - cache.get().put(hostname, neg); - return true; - } else { - supported.put(hostname, false); - return false; + } finally { + negotiateLock.unlock(); } } - private static synchronized HashMap getCache() { - if (cache == null) return null; - return cache.get(); + private static HashMap getCache() { + negotiateLock.lock(); + try { + if (cache == null) return null; + return cache.get(); + } finally { + negotiateLock.unlock(); + } } @Override @@ -169,7 +183,10 @@ public boolean isAuthorizationStale (String header) { * @return true if all goes well, false if no headers were set. */ @Override - public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + // no need to synchronize here: + // already locked by s.n.w.p.h.HttpURLConnection + assert conn.isLockHeldByCurrentThread(); try { String response; diff --git a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java index 72286b1c884..02bae43f658 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java +++ b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java @@ -342,8 +342,10 @@ static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, boolean compatible = ((ret.proxy != null && ret.proxy.equals(p)) || (ret.proxy == null && p == Proxy.NO_PROXY)) && Objects.equals(ret.getAuthenticatorKey(), ak); + if (compatible) { - synchronized (ret) { + ret.lock(); + try { ret.cachedHttpClient = true; assert ret.inCache; ret.inCache = false; @@ -352,18 +354,23 @@ static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, if (logger.isLoggable(PlatformLogger.Level.FINEST)) { logger.finest("KeepAlive stream retrieved from the cache, " + ret); } + } finally { + ret.unlock(); } } else { // We cannot return this connection to the cache as it's // KeepAliveTimeout will get reset. We simply close the connection. // This should be fine as it is very rare that a connection // to the same host will not use the same proxy. - synchronized(ret) { + ret.lock(); + try { if (logger.isLoggable(PlatformLogger.Level.FINEST)) { logger.finest("Not returning this connection to cache: " + ret); } ret.inCache = false; ret.closeServer(); + } finally { + ret.unlock(); } ret = null; } diff --git a/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java b/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java index 9901d799d1c..5b521101a22 100644 --- a/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java +++ b/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2020, 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 @@ -225,7 +225,10 @@ public boolean isAuthorizationStale (String header) { * @return true if all goes well, false if no headers were set. */ @Override - public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + // no need to synchronize here: + // already locked by s.n.w.p.h.HttpURLConnection + assert conn.isLockHeldByCurrentThread(); try { String response; diff --git a/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java b/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java index 1fec7349274..8433fabae46 100644 --- a/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java +++ b/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2020, 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 @@ -244,7 +244,11 @@ public boolean isAuthorizationStale (String header) { * @return true if all goes well, false if no headers were set. */ @Override - public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + + // no need to synchronize here: + // already locked by s.n.w.p.h.HttpURLConnection + assert conn.isLockHeldByCurrentThread(); try { NTLMAuthSequence seq = (NTLMAuthSequence)conn.authObj();