Skip to content
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

Merged
merged 16 commits into from
May 8, 2024

Conversation

samvaity
Copy link
Member

@samvaity samvaity commented Mar 14, 2024

Perf Results:

Simple Http GET

image

Request Throughput

image

Simple Http Download

image

Request Throughput

image

Simple Http PATCH

image

Request Throughput

image

05/1 perf update:

Simple Http GET:

Okhttp and Default Client have comparable results.
Memory usage is better with DefaultCLient
image

Simple Http PATCH

image

Request Throughput

image

@samvaity samvaity linked an issue Apr 2, 2024 that may be closed by this pull request
@samvaity samvaity self-assigned this Apr 3, 2024
@samvaity samvaity force-pushed the connectio-pool-2 branch 7 times, most recently from d79bc5e to 8365f95 Compare April 22, 2024 14:43
@samvaity samvaity marked this pull request as ready for review April 22, 2024 14:48
Comment on lines +65 to +70
if (socketInputStream != null) {
socketInputStream.close();
}
if (socketOutputStream != null) {
socketOutputStream.close();
}
Copy link
Member

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

* @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,
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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) {
Copy link
Member

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;
Copy link
Member

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;
Copy link
Member

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') {
Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member

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);
Copy link
Member

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?

throws IOException {
int contentLength;
if (contentLengthHeader == null || contentLengthHeader.getValue() == null) {
contentLength = -1;
Copy link
Member

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
Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member

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;
Copy link
Member

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.

Copy link
Member

@alzimmermsft alzimmermsft left a 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.

@samvaity samvaity merged commit 9319fa1 into Azure:feature/generic-sdk-core-2 May 8, 2024
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add connection caching for socket-based PATCH requests
2 participants