Skip to content

Commit

Permalink
fix(async): MultiRunner burn CPU
Browse files Browse the repository at this point in the history
Fixes #55.
  • Loading branch information
mekras committed Apr 10, 2019
1 parent 3b201af commit 469bbb3
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 5 deletions.
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
],
"prefer-stable": true,
"minimum-stability": "dev",
"config": {
"secure-http": true
},
"require": {
"php": "^7.1",
"ext-curl": "*",
Expand All @@ -30,7 +33,8 @@
"guzzlehttp/psr7": "^1.0",
"php-http/client-integration-tests": "^2.0",
"phpunit/phpunit": "^7.5",
"zendframework/zend-diactoros": "^2.0"
"zendframework/zend-diactoros": "^2.0",
"donatj/mock-webserver": "^2.0"
},
"autoload": {
"psr-4": {
Expand Down
4 changes: 4 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
<directory>tests/Unit</directory>
</testsuite>

<testsuite name="Integration">
<directory>tests/Integration</directory>
</testsuite>

<testsuite name="Functional">
<directory>tests/Functional</directory>
<!-- Exclude till https://github.com/php-http/message/issues/105 resolved. -->
Expand Down
21 changes: 17 additions & 4 deletions src/MultiRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
*/
class MultiRunner
{
/**
* Timeout for curl_multi_select in seconds.
*/
private const SELECT_TIMEOUT = 0.1;

/**
* cURL multi handle.
*
Expand Down Expand Up @@ -84,8 +89,16 @@ public function remove(PromiseCore $core): void
public function wait(PromiseCore $targetCore = null): void
{
do {
$status = curl_multi_exec($this->multiHandle, $active);
curl_multi_select($this->multiHandle, 0.1);
if (curl_multi_select($this->multiHandle, self::SELECT_TIMEOUT) === -1) {
// See https://bugs.php.net/bug.php?id=61141
usleep(250);
}

do {
$status = curl_multi_exec($this->multiHandle, $active);
// TODO CURLM_CALL_MULTI_PERFORM never returned since cURL 7.20.
} while ($status === CURLM_CALL_MULTI_PERFORM);

$info = curl_multi_info_read($this->multiHandle);
if ($info !== false) {
$core = $this->findCoreByHandle($info['handle']);
Expand All @@ -96,7 +109,7 @@ public function wait(PromiseCore $targetCore = null): void
continue;
}

if ($info['result'] === CURLE_OK) {
if ($info['result'] === CURLM_OK) {
$core->fulfill();
} else {
$error = curl_error($core->getHandle());
Expand All @@ -109,7 +122,7 @@ public function wait(PromiseCore $targetCore = null): void
return;
}
}
} while ($status === CURLM_CALL_MULTI_PERFORM || $active);
} while ($active);
}

/**
Expand Down
91 changes: 91 additions & 0 deletions tests/Integration/MultiRunnerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace Http\Client\Curl\Tests\Integration;

use donatj\MockWebServer\MockWebServer;
use Http\Client\Curl\MultiRunner;
use Http\Client\Curl\PromiseCore;
use PHPUnit\Framework\TestCase;

/**
* Tests for Http\Client\Curl\MultiRunner.
*
* @covers \Http\Client\Curl\MultiRunner
*/
class MultiRunnerTest extends TestCase
{
/**
* Test HTTP server.
*
* @var MockWebServer
*/
private static $server;

/**
* Prepare environment for all tests.
*/
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();

self::$server = new MockWebServer();
self::$server->start();
}

/**
* Cleanup environment after all tests.
*/
public static function tearDownAfterClass(): void
{
self::$server->stop();

parent::tearDownAfterClass();
}

public function testWait(): void
{
$runner = new MultiRunner();

$handle = $this->createCurlHandle('/');
$core1 = $this->createConfiguredMock(PromiseCore::class, ['getHandle' => $handle]);
$core1
->expects(self::once())
->method('fulfill');

$handle = $this->createCurlHandle('/');
$core2 = $this->createConfiguredMock(PromiseCore::class, ['getHandle' => $handle]);
$core2
->expects(self::once())
->method('fulfill');

$runner->add($core1);
$runner->add($core2);

$runner->wait($core1);
$runner->wait($core2);
}

/**
* Create cURL handle with given parameters.
*
* @param string $url Request URL relative to server root.
*
* @return resource
*/
private function createCurlHandle(string $url)
{
$handle = curl_init();
self::assertNotFalse($handle);

curl_setopt_array(
$handle,
[
CURLOPT_URL => self::$server->getServerRoot() . $url
]
);

return $handle;
}
}

0 comments on commit 469bbb3

Please sign in to comment.