-
-
Notifications
You must be signed in to change notification settings - Fork 66
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
[WIP] Connection Pool Refactor #182
Conversation
src/ClientBuilder.php
Outdated
{ | ||
$this->socketPool = $socketPool ?? new UnlimitedSocketPool; | ||
$this->connector = $connector ?? new DnsConnector; |
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 should fallback to connector()
instead.
public function createConnection(Socket $socket): Connection | ||
{ | ||
$connection = new Http1Connection($socket); | ||
$this->connections[$socket->getRemoteAddress()->toString()] = $connection; |
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 wanted to say this only allows one concurrent connection per host, but actually this works, because it has the port in 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.
So it allows one concurrent connection per host per port, which isn't really enough for HTTP/1.x, but works fine for HTTP/2. This is one of the areas where I'm not exactly sure how I want to approach the design.
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.
Ugh, well, right. I think I'd add Connection::isBusy
and ConnectionPool
chooses the next connection then or creates a new one if none is available. This also allows limiting the number of requests per HTTP/2 connection then, so the connection manager creates a separate HTTP/2 connection at some point.
The major issue here is that we can't use the remote address as key, because different sites might be hosted on the same IP, but due to SNI there might be different TLS certificates served.
|
||
$address = $uri->getHost(); | ||
$port = $uri->getPort(); | ||
$authority = $request->getUri()->getHost() . ':' . ($request->getUri()->getPort() ?: $defaultPort); |
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.
We should probably error here if we didn't get an absolute URI.
if ($port !== null) { | ||
$address .= ':' . $port; | ||
} | ||
if (isset($this->connections[$authority])) { |
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.
We should also include the scheme in the key here, there are very rare cases where requests with http
and https
are supported on the same port.
src/Connection/Http1Connection.php
Outdated
return yield from $this->doRead($request, $cancellation, $readingCancellation, $completionDeferred); | ||
$response = yield from $this->doRead($request, $cancellation, $readingCancellation, $completionDeferred); | ||
|
||
$this->busy = false; |
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 is too early. You need to subscribe to the completion promise in Response
instead, because at this point we have only read the header, not the body.
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 was intentional, since another request can be made while a prior response body is being streamed… but that could cause another request to be severely delayed, so I'll use the completion promise 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.
As far as I know no browser supports pipelining, so we probably shouldn't either.
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.
We do in http-server
(because we should to comply with the RFC I think…), but in the client, I agree.
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.
Yes, for http-server
we need to support it.
src/Response.php
Outdated
@@ -33,7 +36,7 @@ public function __construct( | |||
array $headers, | |||
InputStream $body, | |||
Request $request, | |||
ConnectionInfo $connectionInfo, | |||
Connection $connection, |
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.
Response shouldn't have a reference to the connection itself.
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 suppose not… I was hoping to just drop ConnectionInfo
, but maybe I'll have to keep it after all. Does it make sense to pass a Connection
to NetworkInterceptor
too?
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.
NetworkInterceptor
can receive Connection
, that's fine. We'll have to keep ConnectionInfo
for Response
or remove support for it in Response
and if you want to access it, you have to implement a NetworkInterceptor
.
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 think I like the second option, since 99.9% of the time you don't care about that information.
unset($this->connections[$key]); | ||
} | ||
return; | ||
} |
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.
We only push the connection to the array two lines below, so there shouldn't be any entry here to delete.
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.
The promise is pushed to the array when it's created, so that needs to be deleted.
return 0; | ||
} | ||
|
||
if (\strcasecmp($responseConnHeader, 'keep-alive')) { |
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 be checking against close
, too, as keep-alive
is the default for HTTP/1.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.
If the value is anything other than 'keep-alive'
the connection will be closed.
@@ -75,6 +75,9 @@ final class Http2Connection implements Connection | |||
public const INADEQUATE_SECURITY = 0xc; | |||
public const HTTP_1_1_REQUIRED = 0xd; | |||
|
|||
public const DEFAULT_MAX_HEADER_SIZE = 1 << 20; |
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.
1MB for headers is a bit much, no?
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.
Probably. We'll have to look at if and how we want these settings to be configured and pick some defaults.
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 not matter too much as a client. TBH, if you do random requests to not-somewhat-known sites, then you need sanity checks at every layer.
A WIP at this point, but it sort of works.
Duplicate pseudo-headers are checked above.
src/Connection/Connection.php
Outdated
* | ||
* @return Promise<Response> | ||
*/ | ||
public function request(Request $request, ?CancellationToken $token = null): Promise; |
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.
CancellationToken
isn't optional in ConnectionPool
, so probably can be required here, too?
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, the Client
object should take care of creating one.
bf2f820
to
b70f00b
Compare
} | ||
} | ||
|
||
++$index; |
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 isn't safe. Suppose we have 3 concurrent connections which all allow just one request. Due to the yielding
above, the second and third both have $index = 0
and hang in the yield
until the connection is established. Both push to $index = 1
then.
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.
Ah, right, since the new connections won't be added to the list used by foreach. Hmm… might have to use a queue or something where that would be the case.
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'd just do a keyless push and query the last key.
Also actually use it in Http2Connection. :-)
Currently a WIP. I still need to refine the structure a bit more and add some needed functionality to how the pool handles returning connections (e.g.: keep-alive vs. close connections).