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

pkgs/ok_http: Close and remove all idle connections from the resource pool on response #1223

Closed

Conversation

Anikate-De
Copy link
Contributor

Items in this PR:

  • Generated JNI Bindings for okhttp3.ConnectionPool (allows us to use the evictAll() method)
  • Close all idle connections in the resource pool onResponse()
    • WHY?: During testRequestCookies(), the server determines the requests only by binding on the socket. And, OkHttp never frees or closes connections for all enqueue() calls. This led to the server assuming multiple requests as a single entity (as OkHttp never stopped communicating with the socket,) and parsing both cookie headers as one. This gave a cookies list with a length of 2, which failed the 2nd test case multiple cookies semicolon separated. By explicitly closing idle connections, we protect consecutive requests from being overlapped.
  • Add testRequestCookies()
  • Add testResponseHeaders() since all TCs are passing now that [http_client_conformance_tests] Add flag to skip tests for folded headers #1219 is closed (see pkgs/http_client_conformance_tests: add boolean flag supportsFoldedHeaders #1222)

  • I’ve reviewed the contributor guide and applied the relevant portions to this PR.
Contribution guidelines:

Note that many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback.

@@ -126,6 +126,9 @@ class OkHttpClient extends BaseClient {
request: request,
contentLength: contentLength,
));

// Close and remove all idle connections from the resource pool.
_client.connectionPool().evictAll();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read your explanation but I still don't understand why this is necessary. Since the socket is closed by the server after receiving the headers, how can it be reused?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is that even if the socket is closed, the server keeps listening to the request

request.listen((line)) {

and even when you encounter an empty line, it only adds the cookies list to the channel sink.

This seemed very peculiar to me, but I confirmed that it was indeed the problem by doing:
If you create a StreamSubscription and cancel it when an empty line is encountered.

late StreamSubscription reqSubscription;
reqSubscription = request.listen((line) {
  if (line.toLowerCase().startsWith('cookie:')) {
    cookies.add(line);
  }

  if (line.isEmpty) {
    // A blank line indicates the end of the headers.
    channel.sink.add(cookies);
    reqSubscription.cancel(); // <- here
  }
});

then all works well, without the need of explicitly removing idle connections.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... the test seems racy. Does this formulation work:

await for (final line in request) {
        if (line.toLowerCase().startsWith('cookie:')) {
          cookies.add(line);
        }

        if (line.isEmpty) {
          // A blank line indicates the end of the headers.
          channel.sink.add(cookies);
          break;
        }
}

socket.writeAll(...)
await socket.close()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it does!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you suggest changing the test case instead? I didn't go that route because CronetClient seems to pass both cases without a problem.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you suggest changing the test case instead? I didn't go that route because CronetClient seems to pass both cases without a problem.

Yeah, I think that the test is incorrect and it is a coincidence that it works ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, then it's good to close this PR. We would still need okhttp3.ConnectionPool and evictAll() for properly closing the client if I'm not wrong.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, then it's good to close this PR. We would still need okhttp3.ConnectionPool and evictAll() for properly closing the client if I'm not wrong.

You probably want to empty the connection pool when in response to Client.close(). From the docs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's exactly what I was going to follow.

  1. call shutdown on ExecutorService
  2. empty the connection pool
  3. release client object (JNI)

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.

2 participants