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

Prevent cURL transport from leaking on Exception #319

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
16 changes: 10 additions & 6 deletions src/Transport/Curl.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,16 @@ public function request($url, $headers = [], $data = [], $options = []) {
$response = $this->response_data;
}

$this->process_response($response, $options);

// Need to remove the $this reference from the curl handle.
// Otherwise \WpOrg\Requests\Transport\Curl won't be garbage collected and the curl_close() will never be called.
curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null);
curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null);
try {
$this->process_response($response, $options);
} finally {
if (isset($options['blocking']) && $options['blocking'] === true) {
// Need to remove the $this reference from the curl handle.
// Otherwise \WpOrg\Requests\Transport\Curl won't be garbage collected and the curl_close() will never be called.
curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null);
curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null);
}
}

return $this->headers;
}
Expand Down
70 changes: 70 additions & 0 deletions tests/Transport/Curl/CurlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,84 @@

namespace WpOrg\Requests\Tests\Transport\Curl;

use CurlHandle;
use WeakReference;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Hooks;
use WpOrg\Requests\Requests;
use WpOrg\Requests\Tests\Transport\BaseTestCase;
use WpOrg\Requests\Transport\Curl;

final class CurlTest extends BaseTestCase {
protected $transport = Curl::class;

/**
* Temporary storage of the cURL handle to assert against.
*
* The handle is stored as a weak reference in order to avoid the test itself
* becoming the source of the memory leak due to locking the resource.
*
* @var null|WeakReference
*/
protected $curl_handle;

/**
* Get the options to use for the cURL tests.
*
* This adds a hook on curl.before_request to store the cURL handle. This is
* needed for asserting after the test scenarios that the cURL handle was
* correctly closed.
*
* @param array $other
* @return array
*/
protected function getOptions($other = []) {
$options = parent::getOptions($other);

$this->curl_handle = null;

// On PHP < 7.2, we lack the capability to store weak references.
// In this case, we just skip the memory leak testing.
if (version_compare(PHP_VERSION, '7.4.0') < 0) {
return $options;
}

if (!array_key_exists('hooks', $options)) {
$options['hooks'] = new Hooks();
}

$options['hooks']->register(
'curl.before_request',
function ($handle) {
$this->curl_handle = WeakReference::create($handle);
}
);

return $options;
}

/**
* Post condition asserts to run after each scenario.
*
* This is used for asserting that cURL handles are not leaking memory.
*/
protected function assert_post_conditions() {
if (version_compare(PHP_VERSION, '7.4.0') < 0 || !$this->curl_handle instanceof WeakReference) {
// No cURL handle was used during this particular test scenario.
return;
}

if ($this->curl_handle->get() instanceof CurlHandle) {
// CURL handles have been changed from resources into CurlHandle
// objects starting with PHP 8.0, which don;t need to be closed.
return;
}

if ($this->shouldClosedResourceAssertionBeSkipped($this->curl_handle->get()) === false) {
$this->assertIsClosedResource($this->curl_handle->get());
}
}

public function testBadIP() {
$this->expectException(Exception::class);
$this->expectExceptionMessage('t resolve host');
Expand Down