-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add connection caching using System properties #39229
Add connection caching using System properties #39229
Conversation
686d9bc
to
d3b803a
Compare
d3b803a
to
2c94e73
Compare
d79bc5e
to
8365f95
Compare
sdk/client-core/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClient.java
Outdated
Show resolved
Hide resolved
sdk/client-core/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClient.java
Show resolved
Hide resolved
sdk/client-core/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClient.java
Outdated
Show resolved
Hide resolved
sdk/client-core/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClient.java
Outdated
Show resolved
Hide resolved
sdk/client-core/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClient.java
Outdated
Show resolved
Hide resolved
sdk/client-core/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClient.java
Outdated
Show resolved
Hide resolved
sdk/client-core/core/src/main/java/io/clientcore/core/models/SocketConnection.java
Outdated
Show resolved
Hide resolved
if (socketInputStream != null) { | ||
socketInputStream.close(); | ||
} | ||
if (socketOutputStream != null) { | ||
socketOutputStream.close(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Closing the socket will also close the streams
sdk/client-core/core/src/main/java/io/clientcore/core/models/SocketConnection.java
Outdated
Show resolved
Hide resolved
sdk/client-core/http-stress/src/main/java/io/clientcore/http/stress/App.java
Outdated
Show resolved
Hide resolved
sdk/client-core/core/src/test/java/io/clientcore/core/models/SocketConnectionCacheTest.java
Outdated
Show resolved
Hide resolved
a0c314d
to
7daed8e
Compare
9086dd5
to
0938cec
Compare
* @param readTimeout read timeout for the connection | ||
* @return the instance of the SocketConnectionCache if it exists, else create a new one | ||
*/ | ||
public static synchronized SocketConnectionCache getInstance(boolean connectionKeepAlive, int maximumConnections, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few thoughts on the design
- If
connectionKeepAlive
is false, why would we want to create or get this? I'd say let's simplify some stuff where whatever keep alive and maximum connections is as the start of the HttpClient we use those for the lifetime and only need to call this once as a static singleton. - Why do we take
readTimeout
? This is something that may change HttpClient to HttpClient and is only used when reading from the network, nothing to do with the connection pool itself. Unless this was supposed to be a connection idle timeout.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readTimeout
is unrelated from the connection pooling work actually, noticed we were missing doing this in the patch so went ahead and added it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, adding the read timeout is good, just not sure this is the right place for it.
public SocketConnection get(SocketConnectionProperties socketConnectionProperties) throws IOException { | ||
SocketConnection connection = null; | ||
// try to get a connection from the cache | ||
synchronized (CONNECTION_POOL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we shouldn't be using synchronized
, this reduces scalability when using virtual threads. We should look into using things like ReentrantLock
or Semaphore
for concurrency control.
private final int readTimeout; | ||
private final boolean keepConnectionAlive; | ||
private final int maxConnections; | ||
private long readPos = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose of this? Shouldn't this be something specific to an individual connection and not the cache itself
public static final class SocketConnectionProperties { | ||
private final URL requestUrl; | ||
private final String host; | ||
private final String port; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we convert the port to a String?
private static int readStatusCode(InputStream inputStream) throws IOException { | ||
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); | ||
int b; | ||
while ((b = inputStream.read()) != -1 && b != '\n') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing we may need to look at in the future for performance is reading in chunks, reading byte-by-byte off the network will be costly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are dealing with persistent connections and inputstreams, we should be better off reading byte-by-byte rather than buffering readers or readLines I was thinking.
What could be the alternative otherwise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the Socket stream will return -1
even if the connection can be reused later.
} | ||
String[] parts = statusLine.split(" "); | ||
if (parts.length < 2) { | ||
throw new IllegalStateException("Unexpected response from server. Status : " + statusLine); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When exceptions are thrown during processing, are we closing the connection?
sdk/client-core/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClient.java
Show resolved
Hide resolved
throws IOException { | ||
int contentLength; | ||
if (contentLengthHeader == null || contentLengthHeader.getValue() == null) { | ||
contentLength = -1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason we don't just return null here?
int bytesRead; | ||
int totalBytesRead = 0; | ||
|
||
while (totalBytesRead < contentLength |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be a problem if there is an invalid response where the amount of data differs from the Content-Length value. We probably should just read all the data and then if the read amount differs from what the Content-Length header specified throw an exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we rather throw when the "amount of data differs from the Content-Length value" instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but I'd do it after completing the reading
this.requestUrl = requestUrl; | ||
this.host = host; | ||
this.port = port; | ||
this.sslSocketFactory = sslSocketFactory; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should include the SSLSocketFactory in the equivalence check and hash code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's get this merged in so we can begin iterating on focus areas.
Perf Results:
Simple Http GET
Request Throughput
Simple Http Download
Request Throughput
Simple Http PATCH
Request Throughput
05/1 perf update:
Simple Http GET:
Okhttp and Default Client have comparable results.
Memory usage is better with DefaultCLient
Simple Http PATCH
Request Throughput