You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR was merged into the 5.4 branch.
Discussion
----------
[HttpClient] Lazily initialize CurlClientState
| Q | A
| ------------- | ---
| Branch? | 5.4
| Bug fix? | yes
| New feature? | no
| Deprecations? | no
| Issues | See below
| License | MIT
We have a dependency that recently started requiring symfony/http-client. Once that happened, our testing pipeline started to fail.
The failure turned out to be running out of open file descriptors.
It took a while to pinpoint the cause, but we identified the constructor of CurlClientState, specifically the `curl_multi_init` call in it, to cause an ever increasing number of open files (specifically, unix streams).
In our platform, we don't actually use HttpClient. But several services in Symfony have an "optional" dependency on it. Our examples were the TransportFactory-classes in the Mailer-package (including that for the NullTransport used in our testing pipeline) and the JsonManifestVersionStrategy.
I.e. as soon as the http-client package was installed, any time those services where requested, the container now first had to create the HttpClient-service. Which then selected CurlHttpClient as the best option. Which constructed its CurlClientState. And that in turn preemptively opened that stream.
We trigger the Mailer-service for every test, and given our 11k+ tests, that happened a lot of times. And apparently, the streams didn't get closed properly since we ran out of file descriptors after something like 9k tests done...
Unfortunately I was unable to create an isolated reproducer. My guess is that the reproducer was so much smaller, that PHP's memory cleanup could keep up and quickly identify and close abandoned resources (like the curl multihandle), whereas it could not in our much bigger application. But the issue (lots of 'STREAM' rows in the output of netstat or lsof) did not appear when an 'early return' was added to the CurlClientState's constructor (or when we explicitly specified null as httpclient for the mailer and our JsonManifestVersionStrategy).
This PR delays the (imo very heavy) construction of CurlClientState (including the initial reset) in CurlHttpClient to be a "just in time" occurrence.
Commits
-------
4966c75 [HttpClient] Lazily initialize CurlClientState
@@ -324,9 +333,11 @@ public function stream($responses, ?float $timeout = null): ResponseStreamInterf
324
333
thrownew \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of CurlResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
325
334
}
326
335
327
-
if (\is_resource($this->multi->handle) || $this->multi->handleinstanceof \CurlMultiHandle) {
336
+
$multi = $this->ensureState();
337
+
338
+
if (\is_resource($multi->handle) || $multi->handleinstanceof \CurlMultiHandle) {
328
339
$active = 0;
329
-
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active)) {
340
+
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($multi->handle, $active)) {
330
341
}
331
342
}
332
343
@@ -335,7 +346,9 @@ public function stream($responses, ?float $timeout = null): ResponseStreamInterf
0 commit comments