1
0
Fork 0

Add support for aborting running promises cleanly

pull/8952/head
Jordi Boggiano 2020-06-05 09:06:49 +02:00
parent 0dad963cd8
commit 8f6e82f562
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC
6 changed files with 62 additions and 18 deletions

View File

@ -184,6 +184,8 @@ class InstallationManager
$runCleanup = function () use (&$cleanupPromises, $loop) {
$promises = array();
$loop->abortJobs();
foreach ($cleanupPromises as $cleanup) {
$promises[] = new \React\Promise\Promise(function ($resolve, $reject) use ($cleanup) {
$promise = $cleanup();

View File

@ -23,6 +23,7 @@ use Composer\Util\HttpDownloader;
use React\Promise\Promise;
/**
* @internal
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Nicolas Grekas <p@tchwork.com>
*/
@ -90,6 +91,9 @@ class CurlDownloader
$this->authHelper = new AuthHelper($io, $config);
}
/**
* @return int internal job id
*/
public function download($resolve, $reject, $origin, $url, $options, $copyTo = null)
{
$attributes = array();
@ -101,6 +105,9 @@ class CurlDownloader
return $this->initDownload($resolve, $reject, $origin, $url, $options, $copyTo, $attributes);
}
/**
* @return int internal job id
*/
private function initDownload($resolve, $reject, $origin, $url, $options, $copyTo = null, array $attributes = array())
{
$attributes = array_merge(array(
@ -199,8 +206,29 @@ class CurlDownloader
}
$this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle));
// TODO progress
// TODO progress
//$params['notification'](STREAM_NOTIFY_RESOLVE, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0, false);
return (int) $curlHandle;
}
public function abortRequest($id)
{
if (isset($this->jobs[$id]) && isset($this->jobs[$id]['handle'])) {
$job = $this->jobs[$id];
curl_multi_remove_handle($this->multiHandle, $job['handle']);
curl_close($job['handle']);
if (is_resource($job['headerHandle'])) {
fclose($job['headerHandle']);
}
if (is_resource($job['bodyHandle'])) {
fclose($job['bodyHandle']);
}
if ($job['filename']) {
@unlink($job['filename'].'~');
}
unset($this->jobs[$id]);
}
}
public function tick()
@ -235,7 +263,7 @@ class CurlDownloader
$statusCode = null;
$response = null;
try {
// TODO progress
// TODO progress
//$this->onProgress($curlHandle, $job['callback'], $progress, $job['progress']);
if (CURLE_OK !== $errno || $error) {
throw new TransportException($error);
@ -285,8 +313,6 @@ class CurlDownloader
// fail 4xx and 5xx responses and capture the response
if ($statusCode >= 400 && $statusCode <= 599) {
throw $this->failResponse($job, $response, $response->getStatusMessage());
// TODO progress
// $this->io->overwriteError("Downloading (<error>failed</error>)", false);
}
if ($job['attributes']['storeAuth']) {

View File

@ -31,6 +31,7 @@ class HttpDownloader
const STATUS_STARTED = 2;
const STATUS_COMPLETED = 3;
const STATUS_FAILED = 4;
const STATUS_ABORTED = 5;
private $io;
private $config;
@ -184,8 +185,20 @@ class HttpDownloader
$downloader = $this;
$io = $this->io;
$curl = $this->curl;
$canceler = function () {};
$canceler = function () use (&$job, $curl) {
if ($job['status'] === self::STATUS_QUEUED) {
$job['status'] = self::STATUS_ABORTED;
}
if ($job['status'] !== self::STATUS_STARTED) {
return;
}
$job['status'] = self::STATUS_ABORTED;
if (isset($job['curl_id'])) {
$curl->abortRequest($job['curl_id']);
}
};
$promise = new Promise($resolver, $canceler);
$promise->then(function ($response) use (&$job, $downloader) {
@ -242,9 +255,9 @@ class HttpDownloader
}
if ($job['request']['copyTo']) {
$this->curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']);
$job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']);
} else {
$this->curl->download($resolve, $reject, $origin, $url, $options);
$job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options);
}
}
@ -294,14 +307,11 @@ class HttpDownloader
}
if (null !== $index) {
if ($this->jobs[$index]['status'] === self::STATUS_COMPLETED || $this->jobs[$index]['status'] === self::STATUS_FAILED) {
return false;
}
return true;
return $this->jobs[$index]['status'] < self::STATUS_COMPLETED;
}
foreach ($this->jobs as $job) {
if (!in_array($job['status'], array(self::STATUS_COMPLETED, self::STATUS_FAILED), true)) {
if ($job['status'] < self::STATUS_COMPLETED) {
return true;
} elseif (!$job['sync']) {
unset($this->jobs[$job['id']]);

View File

@ -76,4 +76,13 @@ class Loop
throw $uncaught;
}
}
public function abortJobs()
{
if ($this->currentPromises) {
foreach ($this->currentPromises as $promise) {
$promise->cancel();
}
}
}
}

View File

@ -281,15 +281,11 @@ class ProcessExecutor
}
if (null !== $index) {
if ($this->jobs[$index]['status'] === self::STATUS_COMPLETED || $this->jobs[$index]['status'] === self::STATUS_FAILED || $this->jobs[$index]['status'] === self::STATUS_ABORTED) {
return false;
}
return true;
return $this->jobs[$index]['status'] < self::STATUS_COMPLETED;
}
foreach ($this->jobs as $job) {
if (!in_array($job['status'], array(self::STATUS_COMPLETED, self::STATUS_FAILED, self::STATUS_ABORTED), true)) {
if ($job['status'] < self::STATUS_COMPLETED) {
return true;
} else {
unset($this->jobs[$job['id']]);

View File

@ -20,6 +20,7 @@ use Composer\Util\HttpDownloader;
use Composer\Util\Http\Response;
/**
* @internal
* @author François Pluchino <francois.pluchino@opendisplay.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Nils Adermann <naderman@naderman.de>