diff --git a/composer.json b/composer.json
index 7962557..0147b1d 100644
--- a/composer.json
+++ b/composer.json
@@ -16,6 +16,9 @@
],
"prefer-stable": true,
"minimum-stability": "dev",
+ "config": {
+ "secure-http": true
+ },
"require": {
"php": "^7.1",
"ext-curl": "*",
@@ -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": {
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 3f3b615..2c9938e 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -23,6 +23,10 @@
tests/Unit
+
+ tests/Integration
+
+
tests/Functional
diff --git a/src/MultiRunner.php b/src/MultiRunner.php
index 7a2c50c..d56cb28 100644
--- a/src/MultiRunner.php
+++ b/src/MultiRunner.php
@@ -14,6 +14,11 @@
*/
class MultiRunner
{
+ /**
+ * Timeout for curl_multi_select in seconds.
+ */
+ private const SELECT_TIMEOUT = 0.1;
+
/**
* cURL multi handle.
*
@@ -84,18 +89,27 @@ public function remove(PromiseCore $core): void
public function wait(PromiseCore $targetCore = null): void
{
do {
- $status = curl_multi_exec($this->multiHandle, $active);
+ 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 (false !== $info) {
+ if ($info !== false) {
$core = $this->findCoreByHandle($info['handle']);
- if (null === $core) {
+ if ($core === null) {
// We have no promise for this handle. Drop it.
curl_multi_remove_handle($this->multiHandle, $info['handle']);
continue;
}
- if (CURLE_OK === $info['result']) {
+ if ($info['result'] === CURLM_OK) {
$core->fulfill();
} else {
$error = curl_error($core->getHandle());
@@ -108,7 +122,7 @@ public function wait(PromiseCore $targetCore = null): void
return;
}
}
- } while ($status === CURLM_CALL_MULTI_PERFORM || $active);
+ } while ($active);
}
/**
diff --git a/tests/Integration/MultiRunnerTest.php b/tests/Integration/MultiRunnerTest.php
new file mode 100644
index 0000000..b0d554f
--- /dev/null
+++ b/tests/Integration/MultiRunnerTest.php
@@ -0,0 +1,91 @@
+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;
+ }
+}