Skip to content

Commit aeb4ddf

Browse files
HypeMCfabpot
authored andcommitted
[HttpClient] Fix CurlHttpClient memory leak
1 parent 744e245 commit aeb4ddf

File tree

5 files changed

+36
-16
lines changed

5 files changed

+36
-16
lines changed

src/Symfony/Component/HttpClient/Response/CurlResponse.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use Symfony\Component\HttpClient\Exception\TransportException;
1818
use Symfony\Component\HttpClient\Internal\ClientState;
1919
use Symfony\Component\HttpClient\Internal\CurlClientState;
20-
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
2120
use Symfony\Contracts\HttpClient\ResponseInterface;
2221

2322
/**
@@ -205,14 +204,9 @@ public function __destruct()
205204
return; // Unused pushed response
206205
}
207206

208-
$e = null;
209207
$this->doDestruct();
210-
} catch (HttpExceptionInterface $e) {
211-
throw $e;
212208
} finally {
213-
if ($e ?? false) {
214-
throw $e;
215-
}
209+
$multi = clone $this->multi;
216210

217211
$this->close();
218212

@@ -221,6 +215,8 @@ public function __destruct()
221215
$this->multi->dnsCache->evictions = $this->multi->dnsCache->evictions ?: $this->multi->dnsCache->removals;
222216
$this->multi->dnsCache->removals = $this->multi->dnsCache->hostnames = [];
223217
}
218+
219+
$this->multi = $multi;
224220
}
225221
}
226222

src/Symfony/Component/HttpClient/Response/NativeResponse.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use Symfony\Component\HttpClient\Exception\TransportException;
1717
use Symfony\Component\HttpClient\Internal\ClientState;
1818
use Symfony\Component\HttpClient\Internal\NativeClientState;
19-
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
2019
use Symfony\Contracts\HttpClient\ResponseInterface;
2120

2221
/**
@@ -87,14 +86,9 @@ public function getInfo(string $type = null)
8786
public function __destruct()
8887
{
8988
try {
90-
$e = null;
9189
$this->doDestruct();
92-
} catch (HttpExceptionInterface $e) {
93-
throw $e;
9490
} finally {
95-
if ($e ?? false) {
96-
throw $e;
97-
}
91+
$multi = clone $this->multi;
9892

9993
$this->close();
10094

@@ -103,6 +97,8 @@ public function __destruct()
10397
$this->multi->responseCount = 0;
10498
$this->multi->dnsCache = [];
10599
}
100+
101+
$this->multi = $multi;
106102
}
107103
}
108104

src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
use Symfony\Component\HttpClient\Exception\ClientException;
1515
use Symfony\Component\HttpClient\Exception\TransportException;
16+
use Symfony\Component\HttpClient\Internal\ClientState;
17+
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
1618
use Symfony\Contracts\HttpClient\Test\HttpClientTestCase as BaseHttpClientTestCase;
1719

1820
abstract class HttpClientTestCase extends BaseHttpClientTestCase
@@ -120,4 +122,26 @@ public function testTimeoutIsNotAFatalError()
120122
throw $e;
121123
}
122124
}
125+
126+
public function testHandleIsRemovedOnException()
127+
{
128+
$client = $this->getHttpClient(__FUNCTION__);
129+
130+
try {
131+
$client->request('GET', 'http://localhost:8057/304');
132+
$this->fail(RedirectionExceptionInterface::class.' expected');
133+
} catch (RedirectionExceptionInterface $e) {
134+
// The response content-type mustn't be json as that calls getContent
135+
// @see src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php:58
136+
$this->assertStringNotContainsString('json', $e->getResponse()->getHeaders(false)['content-type'][0] ?? '');
137+
138+
$r = new \ReflectionProperty($client, 'multi');
139+
$r->setAccessible(true);
140+
/** @var ClientState $clientState */
141+
$clientState = $r->getValue($client);
142+
143+
$this->assertCount(0, $clientState->handlesActivity);
144+
$this->assertCount(0, $clientState->openHandles);
145+
}
146+
}
123147
}

src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ protected function getHttpClient(string $testCase): HttpClientInterface
119119
$this->markTestSkipped("MockHttpClient doesn't timeout on destruct");
120120
break;
121121

122+
case 'testHandleIsRemovedOnException':
123+
$this->markTestSkipped("MockHttpClient doesn't cache handles");
124+
break;
125+
122126
case 'testGetRequest':
123127
array_unshift($headers, 'HTTP/1.1 200 OK');
124128
$responses[] = new MockResponse($body, ['response_headers' => $headers]);

src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@
157157
exit;
158158

159159
case '/json':
160-
header("Content-Type: application/json");
160+
header('Content-Type: application/json');
161161
echo json_encode([
162162
'documents' => [
163163
['id' => '/json/1'],
@@ -170,7 +170,7 @@
170170
case '/json/1':
171171
case '/json/2':
172172
case '/json/3':
173-
header("Content-Type: application/json");
173+
header('Content-Type: application/json');
174174
echo json_encode([
175175
'title' => $vars['REQUEST_URI'],
176176
]);

0 commit comments

Comments
 (0)