mirror of
https://github.com/DanielnetoDotCom/YouPHPTube
synced 2025-10-03 09:49:28 +02:00
amphp/parallel
This commit is contained in:
parent
81bb55acc4
commit
d2c2721633
1255 changed files with 74242 additions and 6356 deletions
|
@ -43,6 +43,7 @@
|
||||||
"norkunas/onesignal-php-api": "^2.7",
|
"norkunas/onesignal-php-api": "^2.7",
|
||||||
"stripe/stripe-php": "^9.1",
|
"stripe/stripe-php": "^9.1",
|
||||||
"symfony/translation": "^5.3",
|
"symfony/translation": "^5.3",
|
||||||
"amphp/amp": "^2.6"
|
"amphp/amp": "^2.6",
|
||||||
|
"amphp/parallel": "^1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
399
composer.lock
generated
399
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "727a796a171b460f46c5b8ce390fdb0c",
|
"content-hash": "cdae481593f0665c871360069580b3d1",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "abraham/twitteroauth",
|
"name": "abraham/twitteroauth",
|
||||||
|
@ -156,6 +156,403 @@
|
||||||
],
|
],
|
||||||
"time": "2022-02-20T17:52:18+00:00"
|
"time": "2022-02-20T17:52:18+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "amphp/byte-stream",
|
||||||
|
"version": "v1.8.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/amphp/byte-stream.git",
|
||||||
|
"reference": "acbd8002b3536485c997c4e019206b3f10ca15bd"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd",
|
||||||
|
"reference": "acbd8002b3536485c997c4e019206b3f10ca15bd",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"amphp/amp": "^2",
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"amphp/php-cs-fixer-config": "dev-master",
|
||||||
|
"amphp/phpunit-util": "^1.4",
|
||||||
|
"friendsofphp/php-cs-fixer": "^2.3",
|
||||||
|
"jetbrains/phpstorm-stubs": "^2019.3",
|
||||||
|
"phpunit/phpunit": "^6 || ^7 || ^8",
|
||||||
|
"psalm/phar": "^3.11.4"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"lib/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Amp\\ByteStream\\": "lib"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Aaron Piotrowski",
|
||||||
|
"email": "aaron@trowski.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Niklas Keller",
|
||||||
|
"email": "me@kelunik.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A stream abstraction to make working with non-blocking I/O simple.",
|
||||||
|
"homepage": "http://amphp.org/byte-stream",
|
||||||
|
"keywords": [
|
||||||
|
"amp",
|
||||||
|
"amphp",
|
||||||
|
"async",
|
||||||
|
"io",
|
||||||
|
"non-blocking",
|
||||||
|
"stream"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"irc": "irc://irc.freenode.org/amphp",
|
||||||
|
"issues": "https://github.com/amphp/byte-stream/issues",
|
||||||
|
"source": "https://github.com/amphp/byte-stream/tree/v1.8.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/amphp",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-03-30T17:13:30+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amphp/parallel",
|
||||||
|
"version": "v1.4.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/amphp/parallel.git",
|
||||||
|
"reference": "fbc128383c1ffb3823866f71b88d8c4722a25ce9"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/amphp/parallel/zipball/fbc128383c1ffb3823866f71b88d8c4722a25ce9",
|
||||||
|
"reference": "fbc128383c1ffb3823866f71b88d8c4722a25ce9",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"amphp/amp": "^2",
|
||||||
|
"amphp/byte-stream": "^1.6.1",
|
||||||
|
"amphp/parser": "^1",
|
||||||
|
"amphp/process": "^1",
|
||||||
|
"amphp/serialization": "^1",
|
||||||
|
"amphp/sync": "^1.0.1",
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"amphp/php-cs-fixer-config": "dev-master",
|
||||||
|
"amphp/phpunit-util": "^1.1",
|
||||||
|
"phpunit/phpunit": "^8 || ^7"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"lib/Context/functions.php",
|
||||||
|
"lib/Sync/functions.php",
|
||||||
|
"lib/Worker/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Amp\\Parallel\\": "lib"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Aaron Piotrowski",
|
||||||
|
"email": "aaron@trowski.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Stephen Coakley",
|
||||||
|
"email": "me@stephencoakley.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Parallel processing component for Amp.",
|
||||||
|
"homepage": "https://github.com/amphp/parallel",
|
||||||
|
"keywords": [
|
||||||
|
"async",
|
||||||
|
"asynchronous",
|
||||||
|
"concurrent",
|
||||||
|
"multi-processing",
|
||||||
|
"multi-threading"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/amphp/parallel/issues",
|
||||||
|
"source": "https://github.com/amphp/parallel/tree/v1.4.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/amphp",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-10-25T19:16:02+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amphp/parser",
|
||||||
|
"version": "v1.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/amphp/parser.git",
|
||||||
|
"reference": "f83e68f03d5b8e8e0365b8792985a7f341c57ae1"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/amphp/parser/zipball/f83e68f03d5b8e8e0365b8792985a7f341c57ae1",
|
||||||
|
"reference": "f83e68f03d5b8e8e0365b8792985a7f341c57ae1",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^2.3",
|
||||||
|
"phpunit/phpunit": "^6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Amp\\Parser\\": "lib"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Niklas Keller",
|
||||||
|
"email": "me@kelunik.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Aaron Piotrowski",
|
||||||
|
"email": "aaron@trowski.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A generator parser to make streaming parsers simple.",
|
||||||
|
"homepage": "https://github.com/amphp/parser",
|
||||||
|
"keywords": [
|
||||||
|
"async",
|
||||||
|
"non-blocking",
|
||||||
|
"parser",
|
||||||
|
"stream"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/amphp/parser/issues",
|
||||||
|
"source": "https://github.com/amphp/parser/tree/is-valid"
|
||||||
|
},
|
||||||
|
"time": "2017-06-06T05:29:10+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amphp/process",
|
||||||
|
"version": "v1.1.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/amphp/process.git",
|
||||||
|
"reference": "76e9495fd6818b43a20167cb11d8a67f7744ee0f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/amphp/process/zipball/76e9495fd6818b43a20167cb11d8a67f7744ee0f",
|
||||||
|
"reference": "76e9495fd6818b43a20167cb11d8a67f7744ee0f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"amphp/amp": "^2",
|
||||||
|
"amphp/byte-stream": "^1.4",
|
||||||
|
"php": ">=7"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"amphp/php-cs-fixer-config": "dev-master",
|
||||||
|
"amphp/phpunit-util": "^1",
|
||||||
|
"phpunit/phpunit": "^6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"lib/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Amp\\Process\\": "lib"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Bob Weinand",
|
||||||
|
"email": "bobwei9@hotmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Aaron Piotrowski",
|
||||||
|
"email": "aaron@trowski.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Niklas Keller",
|
||||||
|
"email": "me@kelunik.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Asynchronous process manager.",
|
||||||
|
"homepage": "https://github.com/amphp/process",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/amphp/process/issues",
|
||||||
|
"source": "https://github.com/amphp/process/tree/v1.1.4"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/amphp",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2022-07-06T23:50:12+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amphp/serialization",
|
||||||
|
"version": "v1.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/amphp/serialization.git",
|
||||||
|
"reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1",
|
||||||
|
"reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"amphp/php-cs-fixer-config": "dev-master",
|
||||||
|
"phpunit/phpunit": "^9 || ^8 || ^7"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Amp\\Serialization\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Aaron Piotrowski",
|
||||||
|
"email": "aaron@trowski.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Niklas Keller",
|
||||||
|
"email": "me@kelunik.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Serialization tools for IPC and data storage in PHP.",
|
||||||
|
"homepage": "https://github.com/amphp/serialization",
|
||||||
|
"keywords": [
|
||||||
|
"async",
|
||||||
|
"asynchronous",
|
||||||
|
"serialization",
|
||||||
|
"serialize"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/amphp/serialization/issues",
|
||||||
|
"source": "https://github.com/amphp/serialization/tree/master"
|
||||||
|
},
|
||||||
|
"time": "2020-03-25T21:39:07+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amphp/sync",
|
||||||
|
"version": "v1.4.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/amphp/sync.git",
|
||||||
|
"reference": "85ab06764f4f36d63b1356b466df6111cf4b89cf"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/amphp/sync/zipball/85ab06764f4f36d63b1356b466df6111cf4b89cf",
|
||||||
|
"reference": "85ab06764f4f36d63b1356b466df6111cf4b89cf",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"amphp/amp": "^2.2",
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"amphp/php-cs-fixer-config": "dev-master",
|
||||||
|
"amphp/phpunit-util": "^1.1",
|
||||||
|
"phpunit/phpunit": "^9 || ^8 || ^7"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions.php",
|
||||||
|
"src/ConcurrentIterator/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Amp\\Sync\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Aaron Piotrowski",
|
||||||
|
"email": "aaron@trowski.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Stephen Coakley",
|
||||||
|
"email": "me@stephencoakley.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Mutex, Semaphore, and other synchronization tools for Amp.",
|
||||||
|
"homepage": "https://github.com/amphp/sync",
|
||||||
|
"keywords": [
|
||||||
|
"async",
|
||||||
|
"asynchronous",
|
||||||
|
"mutex",
|
||||||
|
"semaphore",
|
||||||
|
"synchronization"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/amphp/sync/issues",
|
||||||
|
"source": "https://github.com/amphp/sync/tree/v1.4.2"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/amphp",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-10-25T18:29:10+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
"version": "v1.0.2",
|
"version": "v1.0.2",
|
||||||
|
|
|
@ -59,7 +59,7 @@ function runLoop() {
|
||||||
if (empty($videos_id)) {
|
if (empty($videos_id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
download($videos_id)->onResolve(function (Throwable $error = null, $response = null) use ($info) {
|
download($videos_id)->onResolve(function (Throwable $error = null, $response = null) {
|
||||||
if ($error) {
|
if ($error) {
|
||||||
_error_log("download: asyncOperation1 fail -> " . $error->getMessage());
|
_error_log("download: asyncOperation1 fail -> " . $error->getMessage());
|
||||||
} else {
|
} else {
|
||||||
|
@ -71,9 +71,9 @@ function runLoop() {
|
||||||
|
|
||||||
Loop::run(function () {
|
Loop::run(function () {
|
||||||
_error_log("download: runLoop 1 ");
|
_error_log("download: runLoop 1 ");
|
||||||
Amp\call(runLoop());
|
Worker\enqueueCallable(runLoop());
|
||||||
_error_log("download: runLoop 2 ");
|
_error_log("download: runLoop 2 ");
|
||||||
Amp\call(runLoop());
|
Worker\enqueueCallable(runLoop());
|
||||||
});
|
});
|
||||||
|
|
||||||
echo "StatusNotActive=$countStatusNotActive; Moved=$countMoved;" . PHP_EOL;
|
echo "StatusNotActive=$countStatusNotActive; Moved=$countMoved;" . PHP_EOL;
|
||||||
|
|
|
@ -167,7 +167,11 @@ class Gallery extends PluginAbstract {
|
||||||
public function getFirstPage() {
|
public function getFirstPage() {
|
||||||
global $global;
|
global $global;
|
||||||
if (!AVideoPlugin::isEnabledByName("YouPHPFlix2")) {
|
if (!AVideoPlugin::isEnabledByName("YouPHPFlix2")) {
|
||||||
return $global['systemRootPath'] . 'plugin/Gallery/view/modeGallery.php';
|
if(false && empty($_REQUEST['inMainIframe']) && !inIframe()){
|
||||||
|
return $global['systemRootPath'] . 'plugin/Gallery/view/modeGalleryIframe.php';
|
||||||
|
}else{
|
||||||
|
return $global['systemRootPath'] . 'plugin/Gallery/view/modeGallery.php';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ _ob_start();
|
||||||
|
|
||||||
<body class="<?php echo $global['bodyClass']; ?>">
|
<body class="<?php echo $global['bodyClass']; ?>">
|
||||||
<?php include $global['systemRootPath'] . 'view/include/navbar.php'; ?>
|
<?php include $global['systemRootPath'] . 'view/include/navbar.php'; ?>
|
||||||
<div class="container-fluid gallery">
|
<div class="container-fluid gallery avideoLoadPage">
|
||||||
<?php
|
<?php
|
||||||
if(!empty($leaderBoardTop)){
|
if(!empty($leaderBoardTop)){
|
||||||
echo '<!-- leaderBoardTop start --><div class="row text-center" style="padding: 10px;">'.$leaderBoardTop.'</div><!-- leaderBoardTop end -->';
|
echo '<!-- leaderBoardTop start --><div class="row text-center" style="padding: 10px;">'.$leaderBoardTop.'</div><!-- leaderBoardTop end -->';
|
||||||
|
|
176
plugin/Gallery/view/modeGalleryIframe.php
Normal file
176
plugin/Gallery/view/modeGalleryIframe.php
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
<?php
|
||||||
|
//var_dump($_SERVER);exit;
|
||||||
|
$configFile = dirname(__FILE__) . '/../../../videos/configuration.php';
|
||||||
|
$doNotIncludeConfig = 1;
|
||||||
|
require_once $configFile;
|
||||||
|
|
||||||
|
$uuid = uniqid();
|
||||||
|
?><!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script>
|
||||||
|
function isASubIFrame() {
|
||||||
|
return document.location.ancestorOrigins.length > 0 && whoAmI() !== parent.whoAmI() && window.parent !== window.top;
|
||||||
|
}
|
||||||
|
if (isASubIFrame()) {
|
||||||
|
console.log('isASubIFrame', window.parent.document.location, document.location);
|
||||||
|
window.parent.document.location = document.location;
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<title>Loading...</title>
|
||||||
|
<link rel="shortcut icon" href="<?php echo $global['webSiteRootURL']; ?>videos/favicon.ico" sizes="16x16,24x24,32x32,48x48,144x144">
|
||||||
|
<link href="<?php echo $global['webSiteRootURL']; ?>node_modules/jquery-ui-dist/jquery-ui.min.css" rel="stylesheet" type="text/css"/>
|
||||||
|
<style>
|
||||||
|
html{
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, iframe {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
html, body, iframe, .ui-dialog .ui-dialog-content {
|
||||||
|
margin: 0px !important;
|
||||||
|
padding: 0px !important;
|
||||||
|
}
|
||||||
|
.ui-dialog{
|
||||||
|
box-shadow: 2px 2px 15px black;
|
||||||
|
}
|
||||||
|
iframe{
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
/*border: 4px solid green;
|
||||||
|
margin: 5px;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe
|
||||||
|
frameborder="0"
|
||||||
|
marginheight="0"
|
||||||
|
marginwidth="0"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
scrolling="auto"
|
||||||
|
src="<?php echo $global['webSiteRootURL']; ?>?inMainIframe=1" id="mainIframe"></iframe>
|
||||||
|
<script src="<?php echo $global['webSiteRootURL']; ?>node_modules/jquery/dist/jquery.min.js"></script>
|
||||||
|
<script src="<?php echo $global['webSiteRootURL']; ?>node_modules/jquery-ui-dist/jquery-ui.min.js" type="text/javascript"></script>
|
||||||
|
<script src="<?php echo $global['webSiteRootURL']; ?>view/js/jquery-dialogextend/build/jquery.dialogextend.min.js" type="text/javascript"></script>
|
||||||
|
<script src="<?php echo $global['webSiteRootURL']; ?>node_modules/sweetalert/dist/sweetalert.min.js" type="text/javascript"></script>
|
||||||
|
<script src="<?php echo $global['webSiteRootURL']; ?>node_modules/js-cookie/dist/js.cookie.js" type="text/javascript"></script>
|
||||||
|
<script src="<?php echo $global['webSiteRootURL']; ?>node_modules/jquery-toast-plugin/dist/jquery.toast.min.js" type="text/javascript"></script>
|
||||||
|
<script src="<?php echo $global['webSiteRootURL']; ?>view/js/script.js" type="text/javascript"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var uuid = '<?php echo $uuid; ?>';
|
||||||
|
|
||||||
|
function whoAmI() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function iframeURLChange(iframe, callback) {
|
||||||
|
var unloadHandler = function () {
|
||||||
|
// Timeout needed because the URL changes immediately after
|
||||||
|
// the `unload` event is dispatched.
|
||||||
|
setTimeout(function () {
|
||||||
|
console.log('iframe 1', iframe);
|
||||||
|
callback(iframe.contentWindow.location.href, iframe.contentDocument.title);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
function attachUnload() {
|
||||||
|
// Remove the unloadHandler in case it was already attached.
|
||||||
|
// Otherwise, the change will be dispatched twice.
|
||||||
|
iframe.contentWindow.removeEventListener("unload", unloadHandler);
|
||||||
|
iframe.contentWindow.addEventListener("unload", unloadHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe.addEventListener("load", attachUnload);
|
||||||
|
attachUnload();
|
||||||
|
}
|
||||||
|
|
||||||
|
iframeURLChange(document.getElementById("mainIframe"), function (src, title) {
|
||||||
|
console.log("URL changed 1:", src, title);
|
||||||
|
document.title = title;
|
||||||
|
window.history.pushState("", "", src.replace('?inMainIframe=1', ''));
|
||||||
|
});
|
||||||
|
|
||||||
|
var windowCount = 0;
|
||||||
|
function openWindow(url, iframeAllowAttributes, title) {
|
||||||
|
var id = 'window' + windowCount;
|
||||||
|
windowCount++;
|
||||||
|
var html = '<div id="' + id + '" title="' + title + '">';
|
||||||
|
html += '<iframe id="avideoWindowIframe' + id + '" frameBorder="0" class="animate__animated animate__bounceInDown" src="' + url + '" ' + iframeAllowAttributes + ' ></iframe>';
|
||||||
|
html += '</div>';
|
||||||
|
$('body').append(html);
|
||||||
|
$('#' + id).dialog({
|
||||||
|
draggable: true,
|
||||||
|
autoOpen: true,
|
||||||
|
modal: false,
|
||||||
|
responsive: true,
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
minHeight: 300,
|
||||||
|
minWidth: 300,
|
||||||
|
close: function (event, ui) {
|
||||||
|
$(this).remove();
|
||||||
|
}
|
||||||
|
}).dialogExtend({
|
||||||
|
"closable": true,
|
||||||
|
"maximizable": true,
|
||||||
|
"minimizable": true,
|
||||||
|
"collapsable": true,
|
||||||
|
"dblclick": "collapse",
|
||||||
|
"titlebar": "transparent",
|
||||||
|
"minimizeLocation": "right",
|
||||||
|
"icons": {
|
||||||
|
"close": "ui-icon-circle-close",
|
||||||
|
"maximize": "ui-icon-circle-plus",
|
||||||
|
"minimize": "ui-icon-circle-minus",
|
||||||
|
"collapse": "ui-icon-triangle-1-s",
|
||||||
|
"restore": "ui-icon-bullet"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function iframeURLChange2(iframe, selector, callback) {
|
||||||
|
var unloadHandler = function () {
|
||||||
|
// Timeout needed because the URL changes immediately after
|
||||||
|
// the `unload` event is dispatched.
|
||||||
|
setTimeout(function () {
|
||||||
|
//console.log('iframe 1', iframe);
|
||||||
|
if(iframe.contentDocument){
|
||||||
|
callback(selector, iframe.contentDocument.title);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
function attachUnload() {
|
||||||
|
// Remove the unloadHandler in case it was already attached.
|
||||||
|
// Otherwise, the change will be dispatched twice.
|
||||||
|
iframe.contentWindow.removeEventListener("unload", unloadHandler);
|
||||||
|
iframe.contentWindow.addEventListener("unload", unloadHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe.addEventListener("load", attachUnload);
|
||||||
|
attachUnload();
|
||||||
|
}
|
||||||
|
|
||||||
|
iframeURLChange2(document.getElementById('avideoWindowIframe' + id), '#' + id, function (selector, title) {
|
||||||
|
$(selector).dialog('option', 'title', title);
|
||||||
|
});
|
||||||
|
//addButtons($("." + id));
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
//$("#window").draggable({handle: ".panel-heading", containment: "body"});
|
||||||
|
//$("#window").resizable();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -2,11 +2,11 @@
|
||||||
require_once __DIR__.'/../../../videos/configuration.php';
|
require_once __DIR__.'/../../../videos/configuration.php';
|
||||||
$video = Video::getVideoLight($videos_id);
|
$video = Video::getVideoLight($videos_id);
|
||||||
?>
|
?>
|
||||||
<div class="dropdown btn-dark text-center" >
|
<div class="dropdown text-center" >
|
||||||
<button class="btn btn-dark btn-xs dropdown-toggle" type="button" data-toggle="dropdown" title=<?php printJSString('More Options', true); ?>>
|
<button class="btn btn-dark btn-xs dropdown-toggle" type="button" data-toggle="dropdown" title=<?php printJSString('More Options', true); ?>>
|
||||||
<i class="fas fa-ellipsis-v"></i>
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu btn-dark ">
|
||||||
<?php
|
<?php
|
||||||
if ((!empty($video['description'])) && !empty($obj->Description)) {
|
if ((!empty($video['description'])) && !empty($obj->Description)) {
|
||||||
$desc = nl2br(trim($video['description']));
|
$desc = nl2br(trim($video['description']));
|
||||||
|
@ -15,7 +15,7 @@ $video = Video::getVideoLight($videos_id);
|
||||||
$titleAlert = str_replace(array('"', "'"), array('``', "`"), $video['title']);
|
$titleAlert = str_replace(array('"', "'"), array('``', "`"), $video['title']);
|
||||||
?>
|
?>
|
||||||
<li>
|
<li>
|
||||||
<button type="button" class="btn-link" onclick='avideoAlert("<?php echo $titleAlert; ?>", "<div style=\"max-height: 300px; overflow-y: scroll;overflow-x: hidden;\" id=\"videoDescriptionAlertContent<?php echo $duid; ?>\" ></div>", "");$("#videoDescriptionAlertContent<?php echo $duid; ?>").html($("#videoDescription<?php echo $duid; ?>").html());return false;' data-toggle="tooltip" title="<?php echo __("Description"); ?>"><i class="far fa-file-alt"></i> <span class="hidden-md hidden-sm hidden-xs"><?php echo __("Description"); ?></span></a>
|
<button type="button" class="btn btn-dark" onclick='avideoAlert("<?php echo $titleAlert; ?>", "<div style=\"max-height: 300px; overflow-y: scroll;overflow-x: hidden;\" id=\"videoDescriptionAlertContent<?php echo $duid; ?>\" ></div>", "");$("#videoDescriptionAlertContent<?php echo $duid; ?>").html($("#videoDescription<?php echo $duid; ?>").html());return false;' data-toggle="tooltip" title="<?php echo __("Description"); ?>"><i class="far fa-file-alt"></i> <span class="hidden-md hidden-sm hidden-xs"><?php echo __("Description"); ?></span></a>
|
||||||
<div id="videoDescription<?php echo $duid; ?>" style="display: none;"><?php echo $desc; ?></div>
|
<div id="videoDescription<?php echo $duid; ?>" style="display: none;"><?php echo $desc; ?></div>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -25,7 +25,7 @@ $video = Video::getVideoLight($videos_id);
|
||||||
?>
|
?>
|
||||||
<?php if (!empty($video['trailer1'])) { ?>
|
<?php if (!empty($video['trailer1'])) { ?>
|
||||||
<li>
|
<li>
|
||||||
<button type="button" class="btn-link" onclick="showTrailer('<?php echo parseVideos($video['trailer1'], 1); ?>'); return false;" class="cursorPointer" >
|
<button type="button" class="btn btn-dark" onclick="showTrailer('<?php echo parseVideos($video['trailer1'], 1); ?>'); return false;" class="cursorPointer" >
|
||||||
<i class="fa fa-video"></i> <?php echo __("Trailer"); ?>
|
<i class="fa fa-video"></i> <?php echo __("Trailer"); ?>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -34,12 +34,12 @@ $video = Video::getVideoLight($videos_id);
|
||||||
?>
|
?>
|
||||||
<?php if (Video::canEdit($video['id'])) { ?>
|
<?php if (Video::canEdit($video['id'])) { ?>
|
||||||
<li>
|
<li>
|
||||||
<button type="button" class="btn-link" onclick="avideoModalIframe(webSiteRootURL + 'view/managerVideosLight.php?avideoIframe=1&videos_id=<?php echo $video['id']; ?>');return false;" data-toggle="tooltip" title="<?php echo __("Edit Video"); ?>">
|
<button type="button" class="btn btn-dark" onclick="avideoModalIframe(webSiteRootURL + 'view/managerVideosLight.php?avideoIframe=1&videos_id=<?php echo $video['id']; ?>');return false;" data-toggle="tooltip" title="<?php echo __("Edit Video"); ?>">
|
||||||
<i class="fa fa-edit"></i> <span class="hidden-md hidden-sm hidden-xs"><?php echo __("Edit Video"); ?></span>
|
<i class="fa fa-edit"></i> <span class="hidden-md hidden-sm hidden-xs"><?php echo __("Edit Video"); ?></span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<?php
|
<?php
|
||||||
$suggestedBTN = Layout::getSuggestedButton($video['id'], 'btn-link');
|
$suggestedBTN = Layout::getSuggestedButton($video['id'], 'btn btn-dark');
|
||||||
if (!empty($suggestedBTN)) {
|
if (!empty($suggestedBTN)) {
|
||||||
echo "<li>{$suggestedBTN}</li>";
|
echo "<li>{$suggestedBTN}</li>";
|
||||||
}
|
}
|
||||||
|
|
135
vendor/amphp/byte-stream/.github/workflows/ci.yml
vendored
Normal file
135
vendor/amphp/byte-stream/.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
name: Continuous Integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
push: null
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
unit_tests:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- operating-system: 'ubuntu-latest'
|
||||||
|
php-version: '7.1'
|
||||||
|
|
||||||
|
- operating-system: 'ubuntu-latest'
|
||||||
|
php-version: '7.2'
|
||||||
|
|
||||||
|
- operating-system: 'ubuntu-latest'
|
||||||
|
php-version: '7.3'
|
||||||
|
|
||||||
|
- operating-system: 'ubuntu-latest'
|
||||||
|
php-version: '7.4'
|
||||||
|
|
||||||
|
- operating-system: 'ubuntu-latest'
|
||||||
|
php-version: '8.0'
|
||||||
|
composer-flags: '--ignore-platform-req=php'
|
||||||
|
|
||||||
|
- operating-system: 'windows-latest'
|
||||||
|
php-version: '8.0'
|
||||||
|
composer-flags: '--ignore-platform-req=php'
|
||||||
|
|
||||||
|
- operating-system: 'macos-latest'
|
||||||
|
php-version: '8.0'
|
||||||
|
composer-flags: '--ignore-platform-req=php'
|
||||||
|
|
||||||
|
name: PHP ${{ matrix.php-version }} on ${{ matrix.operating-system }}
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.operating-system }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
|
||||||
|
- name: Use LF line ends
|
||||||
|
run: |
|
||||||
|
git config --global core.autocrlf false
|
||||||
|
git config --global core.eol lf
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get Composer cache directory
|
||||||
|
id: composer-cache
|
||||||
|
run: echo "::set-output name=dir::$(composer config cache-dir)"
|
||||||
|
|
||||||
|
- name: Cache dependencies
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }}-${{ matrix.composer-flags }}
|
||||||
|
restore-keys: |
|
||||||
|
composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }}-
|
||||||
|
composer-${{ runner.os }}-${{ matrix.php-version }}-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
uses: nick-invision/retry@v2
|
||||||
|
with:
|
||||||
|
timeout_minutes: 5
|
||||||
|
max_attempts: 5
|
||||||
|
retry_wait_seconds: 30
|
||||||
|
command: |
|
||||||
|
php_version=$(php -v)
|
||||||
|
composer update --optimize-autoloader --no-interaction --no-progress ${{ matrix.composer-flags }}
|
||||||
|
composer info -D
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: vendor/bin/phpunit --verbose
|
||||||
|
|
||||||
|
coding_standards:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- operating-system: 'ubuntu-latest'
|
||||||
|
php-version: '8.0'
|
||||||
|
composer-flags: '--ignore-platform-req=php'
|
||||||
|
|
||||||
|
name: Coding standards
|
||||||
|
runs-on: ${{ matrix.operating-system }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
|
||||||
|
- name: Use LF line ends
|
||||||
|
run: |
|
||||||
|
git config --global core.autocrlf false
|
||||||
|
git config --global core.eol lf
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get Composer cache directory
|
||||||
|
id: composer-cache
|
||||||
|
run: echo "::set-output name=dir::$(composer config cache-dir)"
|
||||||
|
|
||||||
|
- name: Cache dependencies
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }}-${{ matrix.composer-flags }}
|
||||||
|
restore-keys: |
|
||||||
|
composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }}-
|
||||||
|
composer-${{ runner.os }}-${{ matrix.php-version }}-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
uses: nick-invision/retry@v2
|
||||||
|
with:
|
||||||
|
timeout_minutes: 5
|
||||||
|
max_attempts: 5
|
||||||
|
retry_wait_seconds: 30
|
||||||
|
command: |
|
||||||
|
php_version=$(php -v)
|
||||||
|
composer update --optimize-autoloader --no-interaction --no-progress ${{ matrix.composer-flags }}
|
||||||
|
composer info -D
|
||||||
|
|
||||||
|
- name: Run style fixer
|
||||||
|
env:
|
||||||
|
PHP_CS_FIXER_IGNORE_ENV: 1
|
||||||
|
run: vendor/bin/php-cs-fixer --diff --dry-run -v fix
|
22
vendor/amphp/byte-stream/LICENSE
vendored
Normal file
22
vendor/amphp/byte-stream/LICENSE
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016-2021 amphp
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
58
vendor/amphp/byte-stream/composer.json
vendored
Normal file
58
vendor/amphp/byte-stream/composer.json
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"name": "amphp/byte-stream",
|
||||||
|
"homepage": "http://amphp.org/byte-stream",
|
||||||
|
"description": "A stream abstraction to make working with non-blocking I/O simple.",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/amphp/byte-stream/issues",
|
||||||
|
"irc": "irc://irc.freenode.org/amphp"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"stream",
|
||||||
|
"async",
|
||||||
|
"non-blocking",
|
||||||
|
"amp",
|
||||||
|
"amphp",
|
||||||
|
"io"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Aaron Piotrowski",
|
||||||
|
"email": "aaron@trowski.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Niklas Keller",
|
||||||
|
"email": "me@kelunik.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1",
|
||||||
|
"amphp/amp": "^2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"amphp/phpunit-util": "^1.4",
|
||||||
|
"phpunit/phpunit": "^6 || ^7 || ^8",
|
||||||
|
"friendsofphp/php-cs-fixer": "^2.3",
|
||||||
|
"amphp/php-cs-fixer-config": "dev-master",
|
||||||
|
"psalm/phar": "^3.11.4",
|
||||||
|
"jetbrains/phpstorm-stubs": "^2019.3"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Amp\\ByteStream\\": "lib"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib/functions.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Amp\\ByteStream\\Test\\": "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.x-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
vendor/amphp/byte-stream/lib/Base64/Base64DecodingInputStream.php
vendored
Normal file
65
vendor/amphp/byte-stream/lib/Base64/Base64DecodingInputStream.php
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream\Base64;
|
||||||
|
|
||||||
|
use Amp\ByteStream\InputStream;
|
||||||
|
use Amp\ByteStream\StreamException;
|
||||||
|
use Amp\Promise;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
final class Base64DecodingInputStream implements InputStream
|
||||||
|
{
|
||||||
|
/** @var InputStream|null */
|
||||||
|
private $source;
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private $buffer = '';
|
||||||
|
|
||||||
|
public function __construct(InputStream $source)
|
||||||
|
{
|
||||||
|
$this->source = $source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function read(): Promise
|
||||||
|
{
|
||||||
|
return call(function () {
|
||||||
|
if ($this->source === null) {
|
||||||
|
throw new StreamException('Failed to read stream chunk due to invalid base64 data');
|
||||||
|
}
|
||||||
|
|
||||||
|
$chunk = yield $this->source->read();
|
||||||
|
if ($chunk === null) {
|
||||||
|
if ($this->buffer === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chunk = \base64_decode($this->buffer, true);
|
||||||
|
if ($chunk === false) {
|
||||||
|
$this->source = null;
|
||||||
|
$this->buffer = null;
|
||||||
|
|
||||||
|
throw new StreamException('Failed to read stream chunk due to invalid base64 data');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->buffer = null;
|
||||||
|
|
||||||
|
return $chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->buffer .= $chunk;
|
||||||
|
|
||||||
|
$length = \strlen($this->buffer);
|
||||||
|
$chunk = \base64_decode(\substr($this->buffer, 0, $length - $length % 4), true);
|
||||||
|
if ($chunk === false) {
|
||||||
|
$this->source = null;
|
||||||
|
$this->buffer = null;
|
||||||
|
|
||||||
|
throw new StreamException('Failed to read stream chunk due to invalid base64 data');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->buffer = \substr($this->buffer, $length - $length % 4);
|
||||||
|
|
||||||
|
return $chunk;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
55
vendor/amphp/byte-stream/lib/Base64/Base64DecodingOutputStream.php
vendored
Normal file
55
vendor/amphp/byte-stream/lib/Base64/Base64DecodingOutputStream.php
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream\Base64;
|
||||||
|
|
||||||
|
use Amp\ByteStream\OutputStream;
|
||||||
|
use Amp\ByteStream\StreamException;
|
||||||
|
use Amp\Failure;
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
final class Base64DecodingOutputStream implements OutputStream
|
||||||
|
{
|
||||||
|
/** @var OutputStream */
|
||||||
|
private $destination;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $buffer = '';
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $offset = 0;
|
||||||
|
|
||||||
|
public function __construct(OutputStream $destination)
|
||||||
|
{
|
||||||
|
$this->destination = $destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function write(string $data): Promise
|
||||||
|
{
|
||||||
|
$this->buffer .= $data;
|
||||||
|
|
||||||
|
$length = \strlen($this->buffer);
|
||||||
|
$chunk = \base64_decode(\substr($this->buffer, 0, $length - $length % 4), true);
|
||||||
|
if ($chunk === false) {
|
||||||
|
return new Failure(new StreamException('Invalid base64 near offset ' . $this->offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->offset += $length - $length % 4;
|
||||||
|
$this->buffer = \substr($this->buffer, $length - $length % 4);
|
||||||
|
|
||||||
|
return $this->destination->write($chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function end(string $finalData = ""): Promise
|
||||||
|
{
|
||||||
|
$this->offset += \strlen($this->buffer);
|
||||||
|
|
||||||
|
$chunk = \base64_decode($this->buffer . $finalData, true);
|
||||||
|
if ($chunk === false) {
|
||||||
|
return new Failure(new StreamException('Invalid base64 near offset ' . $this->offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->buffer = '';
|
||||||
|
|
||||||
|
return $this->destination->end($chunk);
|
||||||
|
}
|
||||||
|
}
|
46
vendor/amphp/byte-stream/lib/Base64/Base64EncodingInputStream.php
vendored
Normal file
46
vendor/amphp/byte-stream/lib/Base64/Base64EncodingInputStream.php
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream\Base64;
|
||||||
|
|
||||||
|
use Amp\ByteStream\InputStream;
|
||||||
|
use Amp\Promise;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
final class Base64EncodingInputStream implements InputStream
|
||||||
|
{
|
||||||
|
/** @var InputStream */
|
||||||
|
private $source;
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private $buffer = '';
|
||||||
|
|
||||||
|
public function __construct(InputStream $source)
|
||||||
|
{
|
||||||
|
$this->source = $source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function read(): Promise
|
||||||
|
{
|
||||||
|
return call(function () {
|
||||||
|
$chunk = yield $this->source->read();
|
||||||
|
if ($chunk === null) {
|
||||||
|
if ($this->buffer === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chunk = \base64_encode($this->buffer);
|
||||||
|
$this->buffer = null;
|
||||||
|
|
||||||
|
return $chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->buffer .= $chunk;
|
||||||
|
|
||||||
|
$length = \strlen($this->buffer);
|
||||||
|
$chunk = \base64_encode(\substr($this->buffer, 0, $length - $length % 3));
|
||||||
|
$this->buffer = \substr($this->buffer, $length - $length % 3);
|
||||||
|
|
||||||
|
return $chunk;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
39
vendor/amphp/byte-stream/lib/Base64/Base64EncodingOutputStream.php
vendored
Normal file
39
vendor/amphp/byte-stream/lib/Base64/Base64EncodingOutputStream.php
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream\Base64;
|
||||||
|
|
||||||
|
use Amp\ByteStream\OutputStream;
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
final class Base64EncodingOutputStream implements OutputStream
|
||||||
|
{
|
||||||
|
/** @var OutputStream */
|
||||||
|
private $destination;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $buffer = '';
|
||||||
|
|
||||||
|
public function __construct(OutputStream $destination)
|
||||||
|
{
|
||||||
|
$this->destination = $destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function write(string $data): Promise
|
||||||
|
{
|
||||||
|
$this->buffer .= $data;
|
||||||
|
|
||||||
|
$length = \strlen($this->buffer);
|
||||||
|
$chunk = \base64_encode(\substr($this->buffer, 0, $length - $length % 3));
|
||||||
|
$this->buffer = \substr($this->buffer, $length - $length % 3);
|
||||||
|
|
||||||
|
return $this->destination->write($chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function end(string $finalData = ""): Promise
|
||||||
|
{
|
||||||
|
$chunk = \base64_encode($this->buffer . $finalData);
|
||||||
|
$this->buffer = '';
|
||||||
|
|
||||||
|
return $this->destination->end($chunk);
|
||||||
|
}
|
||||||
|
}
|
7
vendor/amphp/byte-stream/lib/ClosedException.php
vendored
Normal file
7
vendor/amphp/byte-stream/lib/ClosedException.php
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
final class ClosedException extends StreamException
|
||||||
|
{
|
||||||
|
}
|
39
vendor/amphp/byte-stream/lib/InMemoryStream.php
vendored
Normal file
39
vendor/amphp/byte-stream/lib/InMemoryStream.php
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input stream with a single already known data chunk.
|
||||||
|
*/
|
||||||
|
final class InMemoryStream implements InputStream
|
||||||
|
{
|
||||||
|
private $contents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $contents Data chunk or `null` for no data chunk.
|
||||||
|
*/
|
||||||
|
public function __construct(string $contents = null)
|
||||||
|
{
|
||||||
|
$this->contents = $contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads data from the stream.
|
||||||
|
*
|
||||||
|
* @return Promise<string|null> Resolves with the full contents or `null` if the stream has closed / already been consumed.
|
||||||
|
*/
|
||||||
|
public function read(): Promise
|
||||||
|
{
|
||||||
|
if ($this->contents === null) {
|
||||||
|
return new Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
$promise = new Success($this->contents);
|
||||||
|
$this->contents = null;
|
||||||
|
|
||||||
|
return $promise;
|
||||||
|
}
|
||||||
|
}
|
38
vendor/amphp/byte-stream/lib/InputStream.php
vendored
Normal file
38
vendor/amphp/byte-stream/lib/InputStream.php
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An `InputStream` allows reading byte streams in chunks.
|
||||||
|
*
|
||||||
|
* **Example**
|
||||||
|
*
|
||||||
|
* ```php
|
||||||
|
* function readAll(InputStream $in): Promise {
|
||||||
|
* return Amp\call(function () use ($in) {
|
||||||
|
* $buffer = "";
|
||||||
|
*
|
||||||
|
* while (($chunk = yield $in->read()) !== null) {
|
||||||
|
* $buffer .= $chunk;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* return $buffer;
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
interface InputStream
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Reads data from the stream.
|
||||||
|
*
|
||||||
|
* @return Promise Resolves with a string when new data is available or `null` if the stream has closed.
|
||||||
|
*
|
||||||
|
* @psalm-return Promise<string|null>
|
||||||
|
*
|
||||||
|
* @throws PendingReadError Thrown if another read operation is still pending.
|
||||||
|
*/
|
||||||
|
public function read(): Promise;
|
||||||
|
}
|
52
vendor/amphp/byte-stream/lib/InputStreamChain.php
vendored
Normal file
52
vendor/amphp/byte-stream/lib/InputStreamChain.php
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Success;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
final class InputStreamChain implements InputStream
|
||||||
|
{
|
||||||
|
/** @var InputStream[] */
|
||||||
|
private $streams;
|
||||||
|
/** @var bool */
|
||||||
|
private $reading = false;
|
||||||
|
|
||||||
|
public function __construct(InputStream ...$streams)
|
||||||
|
{
|
||||||
|
$this->streams = $streams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
public function read(): Promise
|
||||||
|
{
|
||||||
|
if ($this->reading) {
|
||||||
|
throw new PendingReadError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->streams) {
|
||||||
|
return new Success(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(function () {
|
||||||
|
$this->reading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
while ($this->streams) {
|
||||||
|
$chunk = yield $this->streams[0]->read();
|
||||||
|
if ($chunk === null) {
|
||||||
|
\array_shift($this->streams);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
$this->reading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
70
vendor/amphp/byte-stream/lib/IteratorStream.php
vendored
Normal file
70
vendor/amphp/byte-stream/lib/IteratorStream.php
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Deferred;
|
||||||
|
use Amp\Failure;
|
||||||
|
use Amp\Iterator;
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
final class IteratorStream implements InputStream
|
||||||
|
{
|
||||||
|
/** @var Iterator<string> */
|
||||||
|
private $iterator;
|
||||||
|
/** @var \Throwable|null */
|
||||||
|
private $exception;
|
||||||
|
/** @var bool */
|
||||||
|
private $pending = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psam-param Iterator<string> $iterator
|
||||||
|
*/
|
||||||
|
public function __construct(Iterator $iterator)
|
||||||
|
{
|
||||||
|
$this->iterator = $iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function read(): Promise
|
||||||
|
{
|
||||||
|
if ($this->exception) {
|
||||||
|
return new Failure($this->exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->pending) {
|
||||||
|
throw new PendingReadError;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pending = true;
|
||||||
|
/** @var Deferred<string|null> $deferred */
|
||||||
|
$deferred = new Deferred;
|
||||||
|
|
||||||
|
$this->iterator->advance()->onResolve(function ($error, $hasNextElement) use ($deferred) {
|
||||||
|
$this->pending = false;
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
$this->exception = $error;
|
||||||
|
$deferred->fail($error);
|
||||||
|
} elseif ($hasNextElement) {
|
||||||
|
$chunk = $this->iterator->getCurrent();
|
||||||
|
|
||||||
|
if (!\is_string($chunk)) {
|
||||||
|
$this->exception = new StreamException(\sprintf(
|
||||||
|
"Unexpected iterator value of type '%s', expected string",
|
||||||
|
\is_object($chunk) ? \get_class($chunk) : \gettype($chunk)
|
||||||
|
));
|
||||||
|
|
||||||
|
$deferred->fail($this->exception);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deferred->resolve($chunk);
|
||||||
|
} else {
|
||||||
|
$deferred->resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $deferred->promise();
|
||||||
|
}
|
||||||
|
}
|
71
vendor/amphp/byte-stream/lib/LineReader.php
vendored
Normal file
71
vendor/amphp/byte-stream/lib/LineReader.php
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
final class LineReader
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $delimiter;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $lineMode;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $buffer = "";
|
||||||
|
|
||||||
|
/** @var InputStream */
|
||||||
|
private $source;
|
||||||
|
|
||||||
|
public function __construct(InputStream $inputStream, string $delimiter = null)
|
||||||
|
{
|
||||||
|
$this->source = $inputStream;
|
||||||
|
$this->delimiter = $delimiter === null ? "\n" : $delimiter;
|
||||||
|
$this->lineMode = $delimiter === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Promise<string|null>
|
||||||
|
*/
|
||||||
|
public function readLine(): Promise
|
||||||
|
{
|
||||||
|
return call(function () {
|
||||||
|
if (false !== \strpos($this->buffer, $this->delimiter)) {
|
||||||
|
list($line, $this->buffer) = \explode($this->delimiter, $this->buffer, 2);
|
||||||
|
return $this->lineMode ? \rtrim($line, "\r") : $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (null !== $chunk = yield $this->source->read()) {
|
||||||
|
$this->buffer .= $chunk;
|
||||||
|
|
||||||
|
if (false !== \strpos($this->buffer, $this->delimiter)) {
|
||||||
|
list($line, $this->buffer) = \explode($this->delimiter, $this->buffer, 2);
|
||||||
|
return $this->lineMode ? \rtrim($line, "\r") : $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->buffer === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = $this->buffer;
|
||||||
|
$this->buffer = "";
|
||||||
|
return $this->lineMode ? \rtrim($line, "\r") : $line;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBuffer(): string
|
||||||
|
{
|
||||||
|
return $this->buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function clearBuffer()
|
||||||
|
{
|
||||||
|
$this->buffer = "";
|
||||||
|
}
|
||||||
|
}
|
176
vendor/amphp/byte-stream/lib/Message.php
vendored
Normal file
176
vendor/amphp/byte-stream/lib/Message.php
vendored
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Coroutine;
|
||||||
|
use Amp\Deferred;
|
||||||
|
use Amp\Failure;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a buffered message from an InputStream. The message can be consumed in chunks using the read() API or it may
|
||||||
|
* be buffered and accessed in its entirety by waiting for the promise to resolve.
|
||||||
|
*
|
||||||
|
* Other implementations may extend this class to add custom properties such as a `isBinary()` flag for WebSocket
|
||||||
|
* messages.
|
||||||
|
*
|
||||||
|
* Buffering Example:
|
||||||
|
*
|
||||||
|
* $stream = new Message($inputStream);
|
||||||
|
* $content = yield $stream;
|
||||||
|
*
|
||||||
|
* Streaming Example:
|
||||||
|
*
|
||||||
|
* $stream = new Message($inputStream);
|
||||||
|
*
|
||||||
|
* while (($chunk = yield $stream->read()) !== null) {
|
||||||
|
* // Immediately use $chunk, reducing memory consumption since the entire message is never buffered.
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @deprecated Use Amp\ByteStream\Payload instead.
|
||||||
|
*/
|
||||||
|
class Message implements InputStream, Promise
|
||||||
|
{
|
||||||
|
/** @var InputStream */
|
||||||
|
private $source;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $buffer = "";
|
||||||
|
|
||||||
|
/** @var Deferred|null */
|
||||||
|
private $pendingRead;
|
||||||
|
|
||||||
|
/** @var Coroutine|null */
|
||||||
|
private $coroutine;
|
||||||
|
|
||||||
|
/** @var bool True if onResolve() has been called. */
|
||||||
|
private $buffering = false;
|
||||||
|
|
||||||
|
/** @var Deferred|null */
|
||||||
|
private $backpressure;
|
||||||
|
|
||||||
|
/** @var bool True if the iterator has completed. */
|
||||||
|
private $complete = false;
|
||||||
|
|
||||||
|
/** @var \Throwable|null Used to fail future reads on failure. */
|
||||||
|
private $error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputStream $source An iterator that only emits strings.
|
||||||
|
*/
|
||||||
|
public function __construct(InputStream $source)
|
||||||
|
{
|
||||||
|
$this->source = $source;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function consume(): \Generator
|
||||||
|
{
|
||||||
|
while (($chunk = yield $this->source->read()) !== null) {
|
||||||
|
$buffer = $this->buffer .= $chunk;
|
||||||
|
|
||||||
|
if ($buffer === "") {
|
||||||
|
continue; // Do not succeed reads with empty string.
|
||||||
|
} elseif ($this->pendingRead) {
|
||||||
|
$deferred = $this->pendingRead;
|
||||||
|
$this->pendingRead = null;
|
||||||
|
$this->buffer = "";
|
||||||
|
$deferred->resolve($buffer);
|
||||||
|
$buffer = ""; // Destroy last emitted chunk to free memory.
|
||||||
|
} elseif (!$this->buffering) {
|
||||||
|
$buffer = ""; // Destroy last emitted chunk to free memory.
|
||||||
|
$this->backpressure = new Deferred;
|
||||||
|
yield $this->backpressure->promise();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->complete = true;
|
||||||
|
|
||||||
|
if ($this->pendingRead) {
|
||||||
|
$deferred = $this->pendingRead;
|
||||||
|
$this->pendingRead = null;
|
||||||
|
$deferred->resolve($this->buffer !== "" ? $this->buffer : null);
|
||||||
|
$this->buffer = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
final public function read(): Promise
|
||||||
|
{
|
||||||
|
if ($this->pendingRead) {
|
||||||
|
throw new PendingReadError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->coroutine === null) {
|
||||||
|
$this->coroutine = new Coroutine($this->consume());
|
||||||
|
$this->coroutine->onResolve(function ($error) {
|
||||||
|
if ($error) {
|
||||||
|
$this->error = $error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->pendingRead) {
|
||||||
|
$deferred = $this->pendingRead;
|
||||||
|
$this->pendingRead = null;
|
||||||
|
$deferred->fail($error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->error) {
|
||||||
|
return new Failure($this->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->buffer !== "") {
|
||||||
|
$buffer = $this->buffer;
|
||||||
|
$this->buffer = "";
|
||||||
|
|
||||||
|
if ($this->backpressure) {
|
||||||
|
$backpressure = $this->backpressure;
|
||||||
|
$this->backpressure = null;
|
||||||
|
$backpressure->resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Success($buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->complete) {
|
||||||
|
return new Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pendingRead = new Deferred;
|
||||||
|
return $this->pendingRead->promise();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
final public function onResolve(callable $onResolved)
|
||||||
|
{
|
||||||
|
$this->buffering = true;
|
||||||
|
|
||||||
|
if ($this->coroutine === null) {
|
||||||
|
$this->coroutine = new Coroutine($this->consume());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->backpressure) {
|
||||||
|
$backpressure = $this->backpressure;
|
||||||
|
$this->backpressure = null;
|
||||||
|
$backpressure->resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->coroutine->onResolve($onResolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes the source input stream.
|
||||||
|
*
|
||||||
|
* This might be required to resolve a promise with an InputStream, because promises in Amp can't be resolved with
|
||||||
|
* other promises.
|
||||||
|
*
|
||||||
|
* @return InputStream
|
||||||
|
*/
|
||||||
|
final public function getInputStream(): InputStream
|
||||||
|
{
|
||||||
|
return $this->source;
|
||||||
|
}
|
||||||
|
}
|
55
vendor/amphp/byte-stream/lib/OutputBuffer.php
vendored
Normal file
55
vendor/amphp/byte-stream/lib/OutputBuffer.php
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Deferred;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Success;
|
||||||
|
|
||||||
|
class OutputBuffer implements OutputStream, Promise
|
||||||
|
{
|
||||||
|
/** @var Deferred */
|
||||||
|
private $deferred;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $contents = '';
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $closed = false;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->deferred = new Deferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function write(string $data): Promise
|
||||||
|
{
|
||||||
|
if ($this->closed) {
|
||||||
|
throw new ClosedException("The stream has already been closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->contents .= $data;
|
||||||
|
|
||||||
|
return new Success(\strlen($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function end(string $finalData = ""): Promise
|
||||||
|
{
|
||||||
|
if ($this->closed) {
|
||||||
|
throw new ClosedException("The stream has already been closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->contents .= $finalData;
|
||||||
|
$this->closed = true;
|
||||||
|
|
||||||
|
$this->deferred->resolve($this->contents);
|
||||||
|
$this->contents = "";
|
||||||
|
|
||||||
|
return new Success(\strlen($finalData));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onResolve(callable $onResolved)
|
||||||
|
{
|
||||||
|
$this->deferred->promise()->onResolve($onResolved);
|
||||||
|
}
|
||||||
|
}
|
37
vendor/amphp/byte-stream/lib/OutputStream.php
vendored
Normal file
37
vendor/amphp/byte-stream/lib/OutputStream.php
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An `OutputStream` allows writing data in chunks. Writers can wait on the returned promises to feel the backpressure.
|
||||||
|
*/
|
||||||
|
interface OutputStream
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Writes data to the stream.
|
||||||
|
*
|
||||||
|
* @param string $data Bytes to write.
|
||||||
|
*
|
||||||
|
* @return Promise Succeeds once the data has been successfully written to the stream.
|
||||||
|
*
|
||||||
|
* @throws ClosedException If the stream has already been closed.
|
||||||
|
* @throws StreamException If writing to the stream fails.
|
||||||
|
*/
|
||||||
|
public function write(string $data): Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the stream as no longer writable. Optionally writes a final data chunk before. Note that this is not the
|
||||||
|
* same as forcefully closing the stream. This method waits for all pending writes to complete before closing the
|
||||||
|
* stream. Socket streams implementing this interface should only close the writable side of the stream.
|
||||||
|
*
|
||||||
|
* @param string $finalData Bytes to write.
|
||||||
|
*
|
||||||
|
* @return Promise Succeeds once the data has been successfully written to the stream.
|
||||||
|
*
|
||||||
|
* @throws ClosedException If the stream has already been closed.
|
||||||
|
* @throws StreamException If writing to the stream fails.
|
||||||
|
*/
|
||||||
|
public function end(string $finalData = ""): Promise;
|
||||||
|
}
|
92
vendor/amphp/byte-stream/lib/Payload.php
vendored
Normal file
92
vendor/amphp/byte-stream/lib/Payload.php
vendored
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Coroutine;
|
||||||
|
use Amp\Promise;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a buffered message from an InputStream. The message can be consumed in chunks using the read() API or it may
|
||||||
|
* be buffered and accessed in its entirety by calling buffer(). Once buffering is requested through buffer(), the
|
||||||
|
* stream cannot be read in chunks. On destruct any remaining data is read from the InputStream given to this class.
|
||||||
|
*/
|
||||||
|
class Payload implements InputStream
|
||||||
|
{
|
||||||
|
/** @var InputStream */
|
||||||
|
private $stream;
|
||||||
|
|
||||||
|
/** @var \Amp\Promise|null */
|
||||||
|
private $promise;
|
||||||
|
|
||||||
|
/** @var \Amp\Promise|null */
|
||||||
|
private $lastRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Amp\ByteStream\InputStream $stream
|
||||||
|
*/
|
||||||
|
public function __construct(InputStream $stream)
|
||||||
|
{
|
||||||
|
$this->stream = $stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if (!$this->promise) {
|
||||||
|
Promise\rethrow(new Coroutine($this->consume()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function consume(): \Generator
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->lastRead && null === yield $this->lastRead) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (null !== yield $this->stream->read()) {
|
||||||
|
// Discard unread bytes from message.
|
||||||
|
}
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
// If exception is thrown here the connection closed anyway.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*
|
||||||
|
* @throws \Error If a buffered message was requested by calling buffer().
|
||||||
|
*/
|
||||||
|
final public function read(): Promise
|
||||||
|
{
|
||||||
|
if ($this->promise) {
|
||||||
|
throw new \Error("Cannot stream message data once a buffered message has been requested");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->lastRead = $this->stream->read();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffers the entire message and resolves the returned promise then.
|
||||||
|
*
|
||||||
|
* @return Promise<string> Resolves with the entire message contents.
|
||||||
|
*/
|
||||||
|
final public function buffer(): Promise
|
||||||
|
{
|
||||||
|
if ($this->promise) {
|
||||||
|
return $this->promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->promise = call(function () {
|
||||||
|
$buffer = '';
|
||||||
|
if ($this->lastRead && null === yield $this->lastRead) {
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (null !== $chunk = yield $this->stream->read()) {
|
||||||
|
$buffer .= $chunk;
|
||||||
|
}
|
||||||
|
return $buffer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
17
vendor/amphp/byte-stream/lib/PendingReadError.php
vendored
Normal file
17
vendor/amphp/byte-stream/lib/PendingReadError.php
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown in case a second read operation is attempted while another read operation is still pending.
|
||||||
|
*/
|
||||||
|
final class PendingReadError extends \Error
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
string $message = "The previous read operation must complete before read can be called again",
|
||||||
|
int $code = 0,
|
||||||
|
\Throwable $previous = null
|
||||||
|
) {
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
262
vendor/amphp/byte-stream/lib/ResourceInputStream.php
vendored
Normal file
262
vendor/amphp/byte-stream/lib/ResourceInputStream.php
vendored
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Deferred;
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input stream abstraction for PHP's stream resources.
|
||||||
|
*/
|
||||||
|
final class ResourceInputStream implements InputStream
|
||||||
|
{
|
||||||
|
const DEFAULT_CHUNK_SIZE = 8192;
|
||||||
|
|
||||||
|
/** @var resource|null */
|
||||||
|
private $resource;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $watcher;
|
||||||
|
|
||||||
|
/** @var Deferred|null */
|
||||||
|
private $deferred;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $readable = true;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $chunkSize;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $useSingleRead;
|
||||||
|
|
||||||
|
/** @var callable */
|
||||||
|
private $immediateCallable;
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private $immediateWatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $stream Stream resource.
|
||||||
|
* @param int $chunkSize Chunk size per read operation.
|
||||||
|
*
|
||||||
|
* @throws \Error If an invalid stream or parameter has been passed.
|
||||||
|
*/
|
||||||
|
public function __construct($stream, int $chunkSize = self::DEFAULT_CHUNK_SIZE)
|
||||||
|
{
|
||||||
|
if (!\is_resource($stream) || \get_resource_type($stream) !== 'stream') {
|
||||||
|
throw new \Error("Expected a valid stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
$meta = \stream_get_meta_data($stream);
|
||||||
|
$useSingleRead = $meta["stream_type"] === "udp_socket" || $meta["stream_type"] === "STDIO";
|
||||||
|
$this->useSingleRead = $useSingleRead;
|
||||||
|
|
||||||
|
if (\strpos($meta["mode"], "r") === false && \strpos($meta["mode"], "+") === false) {
|
||||||
|
throw new \Error("Expected a readable stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
\stream_set_blocking($stream, false);
|
||||||
|
\stream_set_read_buffer($stream, 0);
|
||||||
|
|
||||||
|
$this->resource = &$stream;
|
||||||
|
$this->chunkSize = &$chunkSize;
|
||||||
|
|
||||||
|
$deferred = &$this->deferred;
|
||||||
|
$readable = &$this->readable;
|
||||||
|
|
||||||
|
$this->watcher = Loop::onReadable($this->resource, static function ($watcher) use (
|
||||||
|
&$deferred,
|
||||||
|
&$readable,
|
||||||
|
&$stream,
|
||||||
|
&$chunkSize,
|
||||||
|
$useSingleRead
|
||||||
|
) {
|
||||||
|
if ($useSingleRead) {
|
||||||
|
$data = @\fread($stream, $chunkSize);
|
||||||
|
} else {
|
||||||
|
$data = @\stream_get_contents($stream, $chunkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
\assert($data !== false, "Trying to read from a previously fclose()'d resource. Do NOT manually fclose() resources the loop still has a reference to.");
|
||||||
|
|
||||||
|
// Error suppression, because pthreads does crazy things with resources,
|
||||||
|
// which might be closed during two operations.
|
||||||
|
// See https://github.com/amphp/byte-stream/issues/32
|
||||||
|
if ($data === '' && @\feof($stream)) {
|
||||||
|
$readable = false;
|
||||||
|
$stream = null;
|
||||||
|
$data = null; // Stream closed, resolve read with null.
|
||||||
|
Loop::cancel($watcher);
|
||||||
|
} else {
|
||||||
|
Loop::disable($watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
$temp = $deferred;
|
||||||
|
$deferred = null;
|
||||||
|
|
||||||
|
\assert($temp instanceof Deferred);
|
||||||
|
$temp->resolve($data);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->immediateCallable = static function ($watcherId, $data) use (&$deferred) {
|
||||||
|
$temp = $deferred;
|
||||||
|
$deferred = null;
|
||||||
|
|
||||||
|
\assert($temp instanceof Deferred);
|
||||||
|
$temp->resolve($data);
|
||||||
|
};
|
||||||
|
|
||||||
|
Loop::disable($this->watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function read(): Promise
|
||||||
|
{
|
||||||
|
if ($this->deferred !== null) {
|
||||||
|
throw new PendingReadError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->readable) {
|
||||||
|
return new Success; // Resolve with null on closed stream.
|
||||||
|
}
|
||||||
|
|
||||||
|
\assert($this->resource !== null);
|
||||||
|
|
||||||
|
// Attempt a direct read, because Windows suffers from slow I/O on STDIN otherwise.
|
||||||
|
if ($this->useSingleRead) {
|
||||||
|
$data = @\fread($this->resource, $this->chunkSize);
|
||||||
|
} else {
|
||||||
|
$data = @\stream_get_contents($this->resource, $this->chunkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
\assert($data !== false, "Trying to read from a previously fclose()'d resource. Do NOT manually fclose() resources the loop still has a reference to.");
|
||||||
|
|
||||||
|
if ($data === '') {
|
||||||
|
// Error suppression, because pthreads does crazy things with resources,
|
||||||
|
// which might be closed during two operations.
|
||||||
|
// See https://github.com/amphp/byte-stream/issues/32
|
||||||
|
if (@\feof($this->resource)) {
|
||||||
|
$this->readable = false;
|
||||||
|
$this->resource = null;
|
||||||
|
Loop::cancel($this->watcher);
|
||||||
|
|
||||||
|
return new Success; // Stream closed, resolve read with null.
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->deferred = new Deferred;
|
||||||
|
Loop::enable($this->watcher);
|
||||||
|
|
||||||
|
return $this->deferred->promise();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent an immediate read → write loop from blocking everything
|
||||||
|
// See e.g. examples/benchmark-throughput.php
|
||||||
|
$this->deferred = new Deferred;
|
||||||
|
$this->immediateWatcher = Loop::defer($this->immediateCallable, $data);
|
||||||
|
|
||||||
|
return $this->deferred->promise();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the stream forcefully. Multiple `close()` calls are ignored.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
if (\is_resource($this->resource)) {
|
||||||
|
// Error suppression, as resource might already be closed
|
||||||
|
$meta = @\stream_get_meta_data($this->resource);
|
||||||
|
|
||||||
|
if ($meta && \strpos($meta["mode"], "+") !== false) {
|
||||||
|
@\stream_socket_shutdown($this->resource, \STREAM_SHUT_RD);
|
||||||
|
} else {
|
||||||
|
/** @psalm-suppress InvalidPropertyAssignmentValue */
|
||||||
|
@\fclose($this->resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->free();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nulls reference to resource, marks stream unreadable, and succeeds any pending read with null.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function free()
|
||||||
|
{
|
||||||
|
$this->readable = false;
|
||||||
|
$this->resource = null;
|
||||||
|
|
||||||
|
if ($this->deferred !== null) {
|
||||||
|
$deferred = $this->deferred;
|
||||||
|
$this->deferred = null;
|
||||||
|
$deferred->resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
Loop::cancel($this->watcher);
|
||||||
|
|
||||||
|
if ($this->immediateWatcher !== null) {
|
||||||
|
Loop::cancel($this->immediateWatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource|null The stream resource or null if the stream has closed.
|
||||||
|
*/
|
||||||
|
public function getResource()
|
||||||
|
{
|
||||||
|
return $this->resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setChunkSize(int $chunkSize)
|
||||||
|
{
|
||||||
|
$this->chunkSize = $chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* References the read watcher, so the loop keeps running in case there's an active read.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @see Loop::reference()
|
||||||
|
*/
|
||||||
|
public function reference()
|
||||||
|
{
|
||||||
|
if (!$this->resource) {
|
||||||
|
throw new \Error("Resource has already been freed");
|
||||||
|
}
|
||||||
|
|
||||||
|
Loop::reference($this->watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unreferences the read watcher, so the loop doesn't keep running even if there are active reads.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @see Loop::unreference()
|
||||||
|
*/
|
||||||
|
public function unreference()
|
||||||
|
{
|
||||||
|
if (!$this->resource) {
|
||||||
|
throw new \Error("Resource has already been freed");
|
||||||
|
}
|
||||||
|
|
||||||
|
Loop::unreference($this->watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->resource !== null) {
|
||||||
|
$this->free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
321
vendor/amphp/byte-stream/lib/ResourceOutputStream.php
vendored
Normal file
321
vendor/amphp/byte-stream/lib/ResourceOutputStream.php
vendored
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Deferred;
|
||||||
|
use Amp\Failure;
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output stream abstraction for PHP's stream resources.
|
||||||
|
*/
|
||||||
|
final class ResourceOutputStream implements OutputStream
|
||||||
|
{
|
||||||
|
const MAX_CONSECUTIVE_EMPTY_WRITES = 3;
|
||||||
|
const LARGE_CHUNK_SIZE = 128 * 1024;
|
||||||
|
|
||||||
|
/** @var resource|null */
|
||||||
|
private $resource;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $watcher;
|
||||||
|
|
||||||
|
/** @var \SplQueue<array> */
|
||||||
|
private $writes;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $writable = true;
|
||||||
|
|
||||||
|
/** @var int|null */
|
||||||
|
private $chunkSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $stream Stream resource.
|
||||||
|
* @param int|null $chunkSize Chunk size per `fwrite()` operation.
|
||||||
|
*/
|
||||||
|
public function __construct($stream, int $chunkSize = null)
|
||||||
|
{
|
||||||
|
if (!\is_resource($stream) || \get_resource_type($stream) !== 'stream') {
|
||||||
|
throw new \Error("Expected a valid stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
$meta = \stream_get_meta_data($stream);
|
||||||
|
|
||||||
|
if (\strpos($meta["mode"], "r") !== false && \strpos($meta["mode"], "+") === false) {
|
||||||
|
throw new \Error("Expected a writable stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
\stream_set_blocking($stream, false);
|
||||||
|
\stream_set_write_buffer($stream, 0);
|
||||||
|
|
||||||
|
$this->resource = $stream;
|
||||||
|
$this->chunkSize = &$chunkSize;
|
||||||
|
|
||||||
|
$writes = $this->writes = new \SplQueue;
|
||||||
|
$writable = &$this->writable;
|
||||||
|
$resource = &$this->resource;
|
||||||
|
|
||||||
|
$this->watcher = Loop::onWritable($stream, static function ($watcher, $stream) use ($writes, &$chunkSize, &$writable, &$resource) {
|
||||||
|
static $emptyWrites = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (!$writes->isEmpty()) {
|
||||||
|
/** @var Deferred $deferred */
|
||||||
|
list($data, $previous, $deferred) = $writes->shift();
|
||||||
|
$length = \strlen($data);
|
||||||
|
|
||||||
|
if ($length === 0) {
|
||||||
|
$deferred->resolve(0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\is_resource($stream) || (($metaData = @\stream_get_meta_data($stream)) && $metaData['eof'])) {
|
||||||
|
throw new ClosedException("The stream was closed by the peer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error reporting suppressed since fwrite() emits E_WARNING if the pipe is broken or the buffer is full.
|
||||||
|
// Use conditional, because PHP doesn't like getting null passed
|
||||||
|
if ($chunkSize) {
|
||||||
|
$written = @\fwrite($stream, $data, $chunkSize);
|
||||||
|
} else {
|
||||||
|
$written = @\fwrite($stream, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
\assert(
|
||||||
|
$written !== false || \PHP_VERSION_ID >= 70400, // PHP 7.4+ returns false on EPIPE.
|
||||||
|
"Trying to write on a previously fclose()'d resource. Do NOT manually fclose() resources the still referenced in the loop."
|
||||||
|
);
|
||||||
|
|
||||||
|
// PHP 7.4.0 and 7.4.1 may return false on EAGAIN.
|
||||||
|
if ($written === false && \PHP_VERSION_ID >= 70402) {
|
||||||
|
$message = "Failed to write to stream";
|
||||||
|
if ($error = \error_get_last()) {
|
||||||
|
$message .= \sprintf("; %s", $error["message"]);
|
||||||
|
}
|
||||||
|
throw new StreamException($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broken pipes between processes on macOS/FreeBSD do not detect EOF properly.
|
||||||
|
if ($written === 0 || $written === false) {
|
||||||
|
if ($emptyWrites++ > self::MAX_CONSECUTIVE_EMPTY_WRITES) {
|
||||||
|
$message = "Failed to write to stream after multiple attempts";
|
||||||
|
if ($error = \error_get_last()) {
|
||||||
|
$message .= \sprintf("; %s", $error["message"]);
|
||||||
|
}
|
||||||
|
throw new StreamException($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
$writes->unshift([$data, $previous, $deferred]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$emptyWrites = 0;
|
||||||
|
|
||||||
|
if ($length > $written) {
|
||||||
|
$data = \substr($data, $written);
|
||||||
|
$writes->unshift([$data, $written + $previous, $deferred]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deferred->resolve($written + $previous);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$resource = null;
|
||||||
|
$writable = false;
|
||||||
|
|
||||||
|
/** @psalm-suppress PossiblyUndefinedVariable */
|
||||||
|
$deferred->fail($exception);
|
||||||
|
while (!$writes->isEmpty()) {
|
||||||
|
list(, , $deferred) = $writes->shift();
|
||||||
|
$deferred->fail($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
Loop::cancel($watcher);
|
||||||
|
} finally {
|
||||||
|
if ($writes->isEmpty()) {
|
||||||
|
Loop::disable($watcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Loop::disable($this->watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes data to the stream.
|
||||||
|
*
|
||||||
|
* @param string $data Bytes to write.
|
||||||
|
*
|
||||||
|
* @return Promise Succeeds once the data has been successfully written to the stream.
|
||||||
|
*
|
||||||
|
* @throws ClosedException If the stream has already been closed.
|
||||||
|
*/
|
||||||
|
public function write(string $data): Promise
|
||||||
|
{
|
||||||
|
return $this->send($data, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the stream after all pending writes have been completed. Optionally writes a final data chunk before.
|
||||||
|
*
|
||||||
|
* @param string $finalData Bytes to write.
|
||||||
|
*
|
||||||
|
* @return Promise Succeeds once the data has been successfully written to the stream.
|
||||||
|
*
|
||||||
|
* @throws ClosedException If the stream has already been closed.
|
||||||
|
*/
|
||||||
|
public function end(string $finalData = ""): Promise
|
||||||
|
{
|
||||||
|
return $this->send($finalData, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function send(string $data, bool $end = false): Promise
|
||||||
|
{
|
||||||
|
if (!$this->writable) {
|
||||||
|
return new Failure(new ClosedException("The stream is not writable"));
|
||||||
|
}
|
||||||
|
|
||||||
|
$length = \strlen($data);
|
||||||
|
$written = 0;
|
||||||
|
|
||||||
|
if ($end) {
|
||||||
|
$this->writable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->writes->isEmpty()) {
|
||||||
|
if ($length === 0) {
|
||||||
|
if ($end) {
|
||||||
|
$this->close();
|
||||||
|
}
|
||||||
|
return new Success(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\is_resource($this->resource) || (($metaData = @\stream_get_meta_data($this->resource)) && $metaData['eof'])) {
|
||||||
|
return new Failure(new ClosedException("The stream was closed by the peer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error reporting suppressed since fwrite() emits E_WARNING if the pipe is broken or the buffer is full.
|
||||||
|
// Use conditional, because PHP doesn't like getting null passed.
|
||||||
|
if ($this->chunkSize) {
|
||||||
|
$written = @\fwrite($this->resource, $data, $this->chunkSize);
|
||||||
|
} else {
|
||||||
|
$written = @\fwrite($this->resource, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
\assert(
|
||||||
|
$written !== false || \PHP_VERSION_ID >= 70400, // PHP 7.4+ returns false on EPIPE.
|
||||||
|
"Trying to write on a previously fclose()'d resource. Do NOT manually fclose() resources the still referenced in the loop."
|
||||||
|
);
|
||||||
|
|
||||||
|
// PHP 7.4.0 and 7.4.1 may return false on EAGAIN.
|
||||||
|
if ($written === false && \PHP_VERSION_ID >= 70402) {
|
||||||
|
$message = "Failed to write to stream";
|
||||||
|
if ($error = \error_get_last()) {
|
||||||
|
$message .= \sprintf("; %s", $error["message"]);
|
||||||
|
}
|
||||||
|
return new Failure(new StreamException($message));
|
||||||
|
}
|
||||||
|
|
||||||
|
$written = (int) $written; // Cast potential false to 0.
|
||||||
|
|
||||||
|
if ($length === $written) {
|
||||||
|
if ($end) {
|
||||||
|
$this->close();
|
||||||
|
}
|
||||||
|
return new Success($written);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = \substr($data, $written);
|
||||||
|
}
|
||||||
|
|
||||||
|
$deferred = new Deferred;
|
||||||
|
|
||||||
|
if ($length - $written > self::LARGE_CHUNK_SIZE) {
|
||||||
|
$chunks = \str_split($data, self::LARGE_CHUNK_SIZE);
|
||||||
|
$data = \array_pop($chunks);
|
||||||
|
foreach ($chunks as $chunk) {
|
||||||
|
$this->writes->push([$chunk, $written, new Deferred]);
|
||||||
|
$written += self::LARGE_CHUNK_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writes->push([$data, $written, $deferred]);
|
||||||
|
Loop::enable($this->watcher);
|
||||||
|
$promise = $deferred->promise();
|
||||||
|
|
||||||
|
if ($end) {
|
||||||
|
$promise->onResolve([$this, "close"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the stream forcefully. Multiple `close()` calls are ignored.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
if (\is_resource($this->resource)) {
|
||||||
|
// Error suppression, as resource might already be closed
|
||||||
|
$meta = @\stream_get_meta_data($this->resource);
|
||||||
|
|
||||||
|
if ($meta && \strpos($meta["mode"], "+") !== false) {
|
||||||
|
@\stream_socket_shutdown($this->resource, \STREAM_SHUT_WR);
|
||||||
|
} else {
|
||||||
|
/** @psalm-suppress InvalidPropertyAssignmentValue psalm reports this as closed-resource */
|
||||||
|
@\fclose($this->resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->free();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nulls reference to resource, marks stream unwritable, and fails any pending write.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function free()
|
||||||
|
{
|
||||||
|
$this->resource = null;
|
||||||
|
$this->writable = false;
|
||||||
|
|
||||||
|
if (!$this->writes->isEmpty()) {
|
||||||
|
$exception = new ClosedException("The socket was closed before writing completed");
|
||||||
|
do {
|
||||||
|
/** @var Deferred $deferred */
|
||||||
|
list(, , $deferred) = $this->writes->shift();
|
||||||
|
$deferred->fail($exception);
|
||||||
|
} while (!$this->writes->isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
Loop::cancel($this->watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource|null Stream resource or null if end() has been called or the stream closed.
|
||||||
|
*/
|
||||||
|
public function getResource()
|
||||||
|
{
|
||||||
|
return $this->resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setChunkSize(int $chunkSize)
|
||||||
|
{
|
||||||
|
$this->chunkSize = $chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->resource !== null) {
|
||||||
|
$this->free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
vendor/amphp/byte-stream/lib/StreamException.php
vendored
Normal file
7
vendor/amphp/byte-stream/lib/StreamException.php
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
class StreamException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
112
vendor/amphp/byte-stream/lib/ZlibInputStream.php
vendored
Normal file
112
vendor/amphp/byte-stream/lib/ZlibInputStream.php
vendored
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows decompression of input streams using Zlib.
|
||||||
|
*/
|
||||||
|
final class ZlibInputStream implements InputStream
|
||||||
|
{
|
||||||
|
/** @var InputStream|null */
|
||||||
|
private $source;
|
||||||
|
/** @var int */
|
||||||
|
private $encoding;
|
||||||
|
/** @var array */
|
||||||
|
private $options;
|
||||||
|
/** @var resource|null */
|
||||||
|
private $resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputStream $source Input stream to read compressed data from.
|
||||||
|
* @param int $encoding Compression algorithm used, see `inflate_init()`.
|
||||||
|
* @param array $options Algorithm options, see `inflate_init()`.
|
||||||
|
*
|
||||||
|
* @throws StreamException
|
||||||
|
* @throws \Error
|
||||||
|
*
|
||||||
|
* @see http://php.net/manual/en/function.inflate-init.php
|
||||||
|
*/
|
||||||
|
public function __construct(InputStream $source, int $encoding, array $options = [])
|
||||||
|
{
|
||||||
|
$this->source = $source;
|
||||||
|
$this->encoding = $encoding;
|
||||||
|
$this->options = $options;
|
||||||
|
$this->resource = @\inflate_init($encoding, $options);
|
||||||
|
|
||||||
|
if ($this->resource === false) {
|
||||||
|
throw new StreamException("Failed initializing deflate context");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function read(): Promise
|
||||||
|
{
|
||||||
|
return call(function () {
|
||||||
|
if ($this->resource === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
\assert($this->source !== null);
|
||||||
|
|
||||||
|
$data = yield $this->source->read();
|
||||||
|
|
||||||
|
// Needs a double guard, as stream might have been closed while reading
|
||||||
|
/** @psalm-suppress ParadoxicalCondition */
|
||||||
|
if ($this->resource === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data === null) {
|
||||||
|
$decompressed = @\inflate_add($this->resource, "", \ZLIB_FINISH);
|
||||||
|
|
||||||
|
if ($decompressed === false) {
|
||||||
|
throw new StreamException("Failed adding data to deflate context");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->close();
|
||||||
|
|
||||||
|
return $decompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
$decompressed = @\inflate_add($this->resource, $data, \ZLIB_SYNC_FLUSH);
|
||||||
|
|
||||||
|
if ($decompressed === false) {
|
||||||
|
throw new StreamException("Failed adding data to deflate context");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $decompressed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function close()
|
||||||
|
{
|
||||||
|
$this->resource = null;
|
||||||
|
$this->source = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the used compression encoding.
|
||||||
|
*
|
||||||
|
* @return int Encoding specified on construction time.
|
||||||
|
*/
|
||||||
|
public function getEncoding(): int
|
||||||
|
{
|
||||||
|
return $this->encoding;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets the used compression options.
|
||||||
|
*
|
||||||
|
* @return array Options array passed on construction time.
|
||||||
|
*/
|
||||||
|
public function getOptions(): array
|
||||||
|
{
|
||||||
|
return $this->options;
|
||||||
|
}
|
||||||
|
}
|
119
vendor/amphp/byte-stream/lib/ZlibOutputStream.php
vendored
Normal file
119
vendor/amphp/byte-stream/lib/ZlibOutputStream.php
vendored
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows compression of output streams using Zlib.
|
||||||
|
*/
|
||||||
|
final class ZlibOutputStream implements OutputStream
|
||||||
|
{
|
||||||
|
/** @var OutputStream|null */
|
||||||
|
private $destination;
|
||||||
|
/** @var int */
|
||||||
|
private $encoding;
|
||||||
|
/** @var array */
|
||||||
|
private $options;
|
||||||
|
/** @var resource|null */
|
||||||
|
private $resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param OutputStream $destination Output stream to write the compressed data to.
|
||||||
|
* @param int $encoding Compression encoding to use, see `deflate_init()`.
|
||||||
|
* @param array $options Compression options to use, see `deflate_init()`.
|
||||||
|
*
|
||||||
|
* @throws StreamException If an invalid encoding or invalid options have been passed.
|
||||||
|
*
|
||||||
|
* @see http://php.net/manual/en/function.deflate-init.php
|
||||||
|
*/
|
||||||
|
public function __construct(OutputStream $destination, int $encoding, array $options = [])
|
||||||
|
{
|
||||||
|
$this->destination = $destination;
|
||||||
|
$this->encoding = $encoding;
|
||||||
|
$this->options = $options;
|
||||||
|
$this->resource = @\deflate_init($encoding, $options);
|
||||||
|
|
||||||
|
if ($this->resource === false) {
|
||||||
|
throw new StreamException("Failed initializing deflate context");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function write(string $data): Promise
|
||||||
|
{
|
||||||
|
if ($this->resource === null) {
|
||||||
|
throw new ClosedException("The stream has already been closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
\assert($this->destination !== null);
|
||||||
|
|
||||||
|
$compressed = \deflate_add($this->resource, $data, \ZLIB_SYNC_FLUSH);
|
||||||
|
|
||||||
|
if ($compressed === false) {
|
||||||
|
throw new StreamException("Failed adding data to deflate context");
|
||||||
|
}
|
||||||
|
|
||||||
|
$promise = $this->destination->write($compressed);
|
||||||
|
$promise->onResolve(function ($error) {
|
||||||
|
if ($error) {
|
||||||
|
$this->close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function end(string $finalData = ""): Promise
|
||||||
|
{
|
||||||
|
if ($this->resource === null) {
|
||||||
|
throw new ClosedException("The stream has already been closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
\assert($this->destination !== null);
|
||||||
|
|
||||||
|
$compressed = \deflate_add($this->resource, $finalData, \ZLIB_FINISH);
|
||||||
|
|
||||||
|
if ($compressed === false) {
|
||||||
|
throw new StreamException("Failed adding data to deflate context");
|
||||||
|
}
|
||||||
|
|
||||||
|
$promise = $this->destination->end($compressed);
|
||||||
|
$promise->onResolve(function () {
|
||||||
|
$this->close();
|
||||||
|
});
|
||||||
|
|
||||||
|
return $promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function close()
|
||||||
|
{
|
||||||
|
$this->resource = null;
|
||||||
|
$this->destination = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the used compression encoding.
|
||||||
|
*
|
||||||
|
* @return int Encoding specified on construction time.
|
||||||
|
*/
|
||||||
|
public function getEncoding(): int
|
||||||
|
{
|
||||||
|
return $this->encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the used compression options.
|
||||||
|
*
|
||||||
|
* @return array Options array passed on construction time.
|
||||||
|
*/
|
||||||
|
public function getOptions(): array
|
||||||
|
{
|
||||||
|
return $this->options;
|
||||||
|
}
|
||||||
|
}
|
188
vendor/amphp/byte-stream/lib/functions.php
vendored
Normal file
188
vendor/amphp/byte-stream/lib/functions.php
vendored
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\ByteStream;
|
||||||
|
|
||||||
|
use Amp\Iterator;
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Producer;
|
||||||
|
use Amp\Promise;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
if (\strlen('…') !== 3) {
|
||||||
|
throw new \Error(
|
||||||
|
'The mbstring.func_overload ini setting is enabled. It must be disabled to use the stream package.'
|
||||||
|
);
|
||||||
|
} // @codeCoverageIgnoreEnd
|
||||||
|
|
||||||
|
if (!\defined('STDOUT')) {
|
||||||
|
\define('STDOUT', \fopen('php://stdout', 'w'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\defined('STDERR')) {
|
||||||
|
\define('STDERR', \fopen('php://stderr', 'w'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Amp\ByteStream\InputStream $source
|
||||||
|
* @param \Amp\ByteStream\OutputStream $destination
|
||||||
|
*
|
||||||
|
* @return \Amp\Promise
|
||||||
|
*/
|
||||||
|
function pipe(InputStream $source, OutputStream $destination): Promise
|
||||||
|
{
|
||||||
|
return call(function () use ($source, $destination): \Generator {
|
||||||
|
$written = 0;
|
||||||
|
|
||||||
|
while (($chunk = yield $source->read()) !== null) {
|
||||||
|
$written += \strlen($chunk);
|
||||||
|
$writePromise = $destination->write($chunk);
|
||||||
|
$chunk = null; // free memory
|
||||||
|
yield $writePromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $written;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Amp\ByteStream\InputStream $source
|
||||||
|
*
|
||||||
|
* @return \Amp\Promise
|
||||||
|
*/
|
||||||
|
function buffer(InputStream $source): Promise
|
||||||
|
{
|
||||||
|
return call(function () use ($source): \Generator {
|
||||||
|
$buffer = "";
|
||||||
|
|
||||||
|
while (($chunk = yield $source->read()) !== null) {
|
||||||
|
$buffer .= $chunk;
|
||||||
|
$chunk = null; // free memory
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The php://input input buffer stream for the process associated with the currently active event loop.
|
||||||
|
*
|
||||||
|
* @return ResourceInputStream
|
||||||
|
*/
|
||||||
|
function getInputBufferStream(): ResourceInputStream
|
||||||
|
{
|
||||||
|
static $key = InputStream::class . '\\input';
|
||||||
|
|
||||||
|
$stream = Loop::getState($key);
|
||||||
|
|
||||||
|
if (!$stream) {
|
||||||
|
$stream = new ResourceInputStream(\fopen('php://input', 'rb'));
|
||||||
|
Loop::setState($key, $stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The php://output output buffer stream for the process associated with the currently active event loop.
|
||||||
|
*
|
||||||
|
* @return ResourceOutputStream
|
||||||
|
*/
|
||||||
|
function getOutputBufferStream(): ResourceOutputStream
|
||||||
|
{
|
||||||
|
static $key = OutputStream::class . '\\output';
|
||||||
|
|
||||||
|
$stream = Loop::getState($key);
|
||||||
|
|
||||||
|
if (!$stream) {
|
||||||
|
$stream = new ResourceOutputStream(\fopen('php://output', 'wb'));
|
||||||
|
Loop::setState($key, $stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The STDIN stream for the process associated with the currently active event loop.
|
||||||
|
*
|
||||||
|
* @return ResourceInputStream
|
||||||
|
*/
|
||||||
|
function getStdin(): ResourceInputStream
|
||||||
|
{
|
||||||
|
static $key = InputStream::class . '\\stdin';
|
||||||
|
|
||||||
|
$stream = Loop::getState($key);
|
||||||
|
|
||||||
|
if (!$stream) {
|
||||||
|
$stream = new ResourceInputStream(\STDIN);
|
||||||
|
Loop::setState($key, $stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The STDOUT stream for the process associated with the currently active event loop.
|
||||||
|
*
|
||||||
|
* @return ResourceOutputStream
|
||||||
|
*/
|
||||||
|
function getStdout(): ResourceOutputStream
|
||||||
|
{
|
||||||
|
static $key = OutputStream::class . '\\stdout';
|
||||||
|
|
||||||
|
$stream = Loop::getState($key);
|
||||||
|
|
||||||
|
if (!$stream) {
|
||||||
|
$stream = new ResourceOutputStream(\STDOUT);
|
||||||
|
Loop::setState($key, $stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The STDERR stream for the process associated with the currently active event loop.
|
||||||
|
*
|
||||||
|
* @return ResourceOutputStream
|
||||||
|
*/
|
||||||
|
function getStderr(): ResourceOutputStream
|
||||||
|
{
|
||||||
|
static $key = OutputStream::class . '\\stderr';
|
||||||
|
|
||||||
|
$stream = Loop::getState($key);
|
||||||
|
|
||||||
|
if (!$stream) {
|
||||||
|
$stream = new ResourceOutputStream(\STDERR);
|
||||||
|
Loop::setState($key, $stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLineDelimitedJson(InputStream $stream, bool $assoc = false, int $depth = 512, int $options = 0): Iterator
|
||||||
|
{
|
||||||
|
return new Producer(static function (callable $emit) use ($stream, $assoc, $depth, $options) {
|
||||||
|
$reader = new LineReader($stream);
|
||||||
|
|
||||||
|
while (null !== $line = yield $reader->readLine()) {
|
||||||
|
$line = \trim($line);
|
||||||
|
|
||||||
|
if ($line === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||||
|
$data = \json_decode($line, $assoc, $depth, $options);
|
||||||
|
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||||
|
$error = \json_last_error();
|
||||||
|
|
||||||
|
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||||
|
if ($error !== \JSON_ERROR_NONE) {
|
||||||
|
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||||
|
throw new StreamException('Failed to parse JSON: ' . \json_last_error_msg(), $error);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield $emit($data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
53
vendor/amphp/byte-stream/psalm.xml
vendored
Normal file
53
vendor/amphp/byte-stream/psalm.xml
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<psalm
|
||||||
|
totallyTyped="false"
|
||||||
|
errorLevel="2"
|
||||||
|
phpVersion="7.0"
|
||||||
|
resolveFromConfigFile="true"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="https://getpsalm.org/schema/config"
|
||||||
|
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||||
|
>
|
||||||
|
<projectFiles>
|
||||||
|
<directory name="examples"/>
|
||||||
|
<directory name="lib"/>
|
||||||
|
<ignoreFiles>
|
||||||
|
<directory name="vendor"/>
|
||||||
|
</ignoreFiles>
|
||||||
|
</projectFiles>
|
||||||
|
|
||||||
|
<issueHandlers>
|
||||||
|
<StringIncrement>
|
||||||
|
<errorLevel type="suppress">
|
||||||
|
<directory name="examples"/>
|
||||||
|
<directory name="lib"/>
|
||||||
|
</errorLevel>
|
||||||
|
</StringIncrement>
|
||||||
|
|
||||||
|
<RedundantConditionGivenDocblockType>
|
||||||
|
<errorLevel type="suppress">
|
||||||
|
<directory name="lib"/>
|
||||||
|
</errorLevel>
|
||||||
|
</RedundantConditionGivenDocblockType>
|
||||||
|
|
||||||
|
<DocblockTypeContradiction>
|
||||||
|
<errorLevel type="suppress">
|
||||||
|
<directory name="lib"/>
|
||||||
|
</errorLevel>
|
||||||
|
</DocblockTypeContradiction>
|
||||||
|
|
||||||
|
<MissingClosureParamType>
|
||||||
|
<errorLevel type="suppress">
|
||||||
|
<directory name="examples"/>
|
||||||
|
<directory name="lib"/>
|
||||||
|
</errorLevel>
|
||||||
|
</MissingClosureParamType>
|
||||||
|
|
||||||
|
<MissingClosureReturnType>
|
||||||
|
<errorLevel type="suppress">
|
||||||
|
<directory name="examples"/>
|
||||||
|
<directory name="lib"/>
|
||||||
|
</errorLevel>
|
||||||
|
</MissingClosureReturnType>
|
||||||
|
</issueHandlers>
|
||||||
|
</psalm>
|
86
vendor/amphp/parallel/.github/workflows/ci.yml
vendored
Normal file
86
vendor/amphp/parallel/.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
name: Continuous Integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- operating-system: 'ubuntu-latest'
|
||||||
|
php-version: '7.1'
|
||||||
|
|
||||||
|
- operating-system: 'ubuntu-latest'
|
||||||
|
php-version: '7.2'
|
||||||
|
|
||||||
|
- operating-system: 'ubuntu-latest'
|
||||||
|
php-version: '7.3'
|
||||||
|
|
||||||
|
- operating-system: 'ubuntu-latest'
|
||||||
|
php-version: '7.4'
|
||||||
|
|
||||||
|
- operating-system: 'ubuntu-latest'
|
||||||
|
php-version: '8.0'
|
||||||
|
composer-flags: '--ignore-platform-req=php'
|
||||||
|
|
||||||
|
- operating-system: 'windows-latest'
|
||||||
|
php-version: '7.4'
|
||||||
|
job-description: 'on Windows'
|
||||||
|
|
||||||
|
- operating-system: 'macos-latest'
|
||||||
|
php-version: '7.4'
|
||||||
|
job-description: 'on macOS'
|
||||||
|
|
||||||
|
name: PHP ${{ matrix.php-version }} ${{ matrix.job-description }}
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.operating-system }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set git to use LF
|
||||||
|
run: |
|
||||||
|
git config --global core.autocrlf false
|
||||||
|
git config --global core.eol lf
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
extensions: parallel
|
||||||
|
|
||||||
|
- name: Get Composer cache directory
|
||||||
|
id: composer-cache
|
||||||
|
run: echo "::set-output name=dir::$(composer config cache-dir)"
|
||||||
|
|
||||||
|
- name: Cache dependencies
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }}-${{ matrix.composer-flags }}
|
||||||
|
restore-keys: |
|
||||||
|
composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.*') }}-
|
||||||
|
composer-${{ runner.os }}-${{ matrix.php-version }}-
|
||||||
|
composer-${{ runner.os }}-
|
||||||
|
composer-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
uses: nick-invision/retry@v2
|
||||||
|
with:
|
||||||
|
timeout_minutes: 5
|
||||||
|
max_attempts: 5
|
||||||
|
retry_wait_seconds: 30
|
||||||
|
command: |
|
||||||
|
composer update --optimize-autoloader --no-interaction --no-progress ${{ matrix.composer-flags }}
|
||||||
|
composer info -D
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: vendor/bin/phpunit ${{ matrix.phpunit-flags }}
|
||||||
|
|
||||||
|
- name: Run style fixer
|
||||||
|
run: vendor/bin/php-cs-fixer --diff --dry-run -v fix
|
||||||
|
env:
|
||||||
|
PHP_CS_FIXER_IGNORE_ENV: 1
|
3
vendor/amphp/parallel/.gitmodules
vendored
Normal file
3
vendor/amphp/parallel/.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "docs/.shared"]
|
||||||
|
path = docs/.shared
|
||||||
|
url = https://github.com/amphp/amphp.github.io
|
13
vendor/amphp/parallel/.php_cs.dist
vendored
Normal file
13
vendor/amphp/parallel/.php_cs.dist
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$config = new Amp\CodeStyle\Config();
|
||||||
|
$config->getFinder()
|
||||||
|
->in(__DIR__ . '/examples')
|
||||||
|
->in(__DIR__ . '/lib')
|
||||||
|
->in(__DIR__ . '/test');
|
||||||
|
|
||||||
|
$cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__;
|
||||||
|
|
||||||
|
$config->setCacheFile($cacheDir . '/.php_cs.cache');
|
||||||
|
|
||||||
|
return $config;
|
4
vendor/amphp/parallel/.valgrindrc
vendored
Normal file
4
vendor/amphp/parallel/.valgrindrc
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
--error-limit=no
|
||||||
|
--trace-children=yes
|
||||||
|
--track-fds=yes
|
||||||
|
--undef-value-errors=no
|
21
vendor/amphp/parallel/LICENSE
vendored
Normal file
21
vendor/amphp/parallel/LICENSE
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-2021 amphp
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
45
vendor/amphp/parallel/Makefile
vendored
Normal file
45
vendor/amphp/parallel/Makefile
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
PHP_BIN := php
|
||||||
|
COMPOSER_BIN := composer
|
||||||
|
|
||||||
|
COVERAGE = coverage
|
||||||
|
SRCS = lib test
|
||||||
|
|
||||||
|
find_php_files = $(shell find $(1) -type f -name "*.php")
|
||||||
|
src = $(foreach d,$(SRCS),$(call find_php_files,$(d)))
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: setup phpunit code-style
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean: clean-coverage clean-vendor
|
||||||
|
|
||||||
|
.PHONY: clean-coverage
|
||||||
|
clean-coverage:
|
||||||
|
test ! -e coverage || rm -r coverage
|
||||||
|
|
||||||
|
.PHONY: clean-vendor
|
||||||
|
clean-vendor:
|
||||||
|
test ! -e vendor || rm -r vendor
|
||||||
|
|
||||||
|
.PHONY: setup
|
||||||
|
setup: vendor/autoload.php
|
||||||
|
|
||||||
|
.PHONY: deps-update
|
||||||
|
deps-update:
|
||||||
|
$(COMPOSER_BIN) update
|
||||||
|
|
||||||
|
.PHONY: phpunit
|
||||||
|
phpunit: setup
|
||||||
|
$(PHP_BIN) vendor/bin/phpunit
|
||||||
|
|
||||||
|
.PHONY: code-style
|
||||||
|
code-style: setup
|
||||||
|
PHP_CS_FIXER_IGNORE_ENV=1 $(PHP_BIN) vendor/bin/php-cs-fixer --diff -v fix
|
||||||
|
|
||||||
|
composer.lock: composer.json
|
||||||
|
$(COMPOSER_BIN) install
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
vendor/autoload.php: composer.lock
|
||||||
|
$(COMPOSER_BIN) install
|
||||||
|
touch $@
|
86
vendor/amphp/parallel/README.md
vendored
Normal file
86
vendor/amphp/parallel/README.md
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://amphp.org/parallel"><img src="https://raw.githubusercontent.com/amphp/logo/master/repos/parallel.png?v=12-07-2017" alt="parallel"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://travis-ci.org/amphp/parallel"><img src="https://img.shields.io/travis/amphp/parallel/master.svg?style=flat-square" alt="Build Status"/></a>
|
||||||
|
<a href="https://coveralls.io/github/amphp/parallel?branch=master"><img src="https://img.shields.io/coveralls/amphp/parallel/master.svg?style=flat-square" alt="Code Coverage"/></a>
|
||||||
|
<a href="https://github.com/amphp/parallel/releases"><img src="https://img.shields.io/github/release/amphp/parallel.svg?style=flat-square" alt="Release"/></a>
|
||||||
|
<a href="https://github.com/amphp/parallel/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="License"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
`amphp/parallel` provides *true parallel processing* for PHP using multiple processes or native threads, *without blocking and no extensions required*.
|
||||||
|
|
||||||
|
To be as flexible as possible, this library comes with a collection of non-blocking concurrency tools that can be used independently as needed, as well as an "opinionated" worker API that allows you to assign units of work to a pool of worker threads or processes.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This package can be installed as a [Composer](https://getcomposer.org/) dependency.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require amphp/parallel
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The basic usage of this library is to submit blocking tasks to be executed by a worker pool in order to avoid blocking the main event loop.
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Amp\Parallel\Worker;
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
$urls = [
|
||||||
|
'https://secure.php.net',
|
||||||
|
'https://amphp.org',
|
||||||
|
'https://github.com',
|
||||||
|
];
|
||||||
|
|
||||||
|
$promises = [];
|
||||||
|
foreach ($urls as $url) {
|
||||||
|
$promises[$url] = Worker\enqueueCallable('file_get_contents', $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
$responses = Promise\wait(Promise\all($promises));
|
||||||
|
|
||||||
|
foreach ($responses as $url => $response) {
|
||||||
|
\printf("Read %d bytes from %s\n", \strlen($response), $url);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[`file_get_contents`](https://secure.php.net/file_get_contents) is just used as an example for a blocking function here.
|
||||||
|
If you just want to fetch multiple HTTP resources concurrently, it's better to use [`amphp/http-client`](https://amphp.org/http-client/), our non-blocking HTTP client.
|
||||||
|
|
||||||
|
The functions you call must be predefined or autoloadable by Composer so they also exist in the worker processes.
|
||||||
|
Instead of simple callables, you can also enqueue `Task` instances with `Amp\Parallel\Worker\enqueue()`.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Documentation can be found on [amphp.org/parallel](https://amphp.org/parallel/) as well as in the [`./docs`](./docs) directory.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
`amphp/parallel` follows the [semver](http://semver.org/) semantic versioning specification like all other `amphp` packages.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
If you discover any security related issues, please email [`me@kelunik.com`](mailto:me@kelunik.com) instead of using the issue tracker.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The MIT License (MIT). Please see [`LICENSE`](./LICENSE) for more information.
|
||||||
|
|
||||||
|
## Development and Contributing
|
||||||
|
|
||||||
|
Want to hack on the source? A [Vagrant](http://vagrantup.com) box is provided with the repository to give a common development environment for running concurrent threads and processes, and comes with a bunch of handy tools and scripts for testing and experimentation.
|
||||||
|
|
||||||
|
Starting up and logging into the virtual machine is as simple as
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vagrant up && vagrant ssh
|
||||||
|
```
|
||||||
|
|
||||||
|
Once inside the VM, you can install PHP extensions with [Pickle](https://github.com/FriendsOfPHP/pickle), switch versions with `newphp VERSION`, and test for memory leaks with [Valgrind](http://valgrind.org).
|
17
vendor/amphp/parallel/Vagrantfile
vendored
Normal file
17
vendor/amphp/parallel/Vagrantfile
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
Vagrant.configure(2) do |config|
|
||||||
|
config.vm.box = "rasmus/php7dev"
|
||||||
|
|
||||||
|
config.vm.provision "shell", inline: <<-SHELL
|
||||||
|
newphp 7 zts
|
||||||
|
|
||||||
|
# Install pthreads from master
|
||||||
|
git clone https://github.com/krakjoe/pthreads
|
||||||
|
cd pthreads
|
||||||
|
git checkout master
|
||||||
|
phpize
|
||||||
|
./configure
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
echo 'extension=pthreads.so' >> `php -i | grep php-cli.ini | awk '{print $5}'`
|
||||||
|
SHELL
|
||||||
|
end
|
41
vendor/amphp/parallel/appveyor.yml
vendored
Normal file
41
vendor/amphp/parallel/appveyor.yml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
build: false
|
||||||
|
shallow_clone: false
|
||||||
|
|
||||||
|
platform:
|
||||||
|
- x86
|
||||||
|
- x64
|
||||||
|
|
||||||
|
clone_folder: c:\projects\amphp
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- c:\tools\php73 -> appveyor.yml
|
||||||
|
|
||||||
|
init:
|
||||||
|
- SET PATH=C:\Program Files\OpenSSL;c:\tools\php73;%PATH%
|
||||||
|
- SET COMPOSER_NO_INTERACTION=1
|
||||||
|
- SET PHP=1
|
||||||
|
- SET ANSICON=121x90 (121x90)
|
||||||
|
|
||||||
|
install:
|
||||||
|
- IF EXIST c:\tools\php73 (SET PHP=0)
|
||||||
|
- IF %PHP%==1 sc config wuauserv start= auto
|
||||||
|
- IF %PHP%==1 net start wuauserv
|
||||||
|
- IF %PHP%==1 cinst -y OpenSSL.Light
|
||||||
|
- IF %PHP%==1 cinst -y php
|
||||||
|
- cd c:\tools\php73
|
||||||
|
- IF %PHP%==1 copy php.ini-production php.ini /Y
|
||||||
|
- IF %PHP%==1 echo date.timezone="UTC" >> php.ini
|
||||||
|
- IF %PHP%==1 echo extension_dir=ext >> php.ini
|
||||||
|
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
|
||||||
|
- IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini
|
||||||
|
- IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini
|
||||||
|
- cd c:\projects\amphp
|
||||||
|
- appveyor DownloadFile https://getcomposer.org/composer.phar
|
||||||
|
- php composer.phar install --prefer-dist --no-progress
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- cd c:\projects\amphp
|
||||||
|
- phpdbg -qrr vendor/phpunit/phpunit/phpunit --colors=always --coverage-text --coverage-clover build/logs/clover.xml
|
||||||
|
# Disable for now, because it can't be combined and files can't be shown on coveralls.io
|
||||||
|
# https://github.com/php-coveralls/php-coveralls/issues/234
|
||||||
|
# - vendor/bin/coveralls -v
|
62
vendor/amphp/parallel/composer.json
vendored
Normal file
62
vendor/amphp/parallel/composer.json
vendored
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"name": "amphp/parallel",
|
||||||
|
"description": "Parallel processing component for Amp.",
|
||||||
|
"keywords": [
|
||||||
|
"asynchronous",
|
||||||
|
"async",
|
||||||
|
"concurrent",
|
||||||
|
"multi-threading",
|
||||||
|
"multi-processing"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/amphp/parallel",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Aaron Piotrowski",
|
||||||
|
"email": "aaron@trowski.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Stephen Coakley",
|
||||||
|
"email": "me@stephencoakley.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1",
|
||||||
|
"amphp/amp": "^2",
|
||||||
|
"amphp/byte-stream": "^1.6.1",
|
||||||
|
"amphp/parser": "^1",
|
||||||
|
"amphp/process": "^1",
|
||||||
|
"amphp/serialization": "^1",
|
||||||
|
"amphp/sync": "^1.0.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^8 || ^7",
|
||||||
|
"amphp/phpunit-util": "^1.1",
|
||||||
|
"amphp/php-cs-fixer-config": "dev-master"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Amp\\Parallel\\": "lib"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib/Context/functions.php",
|
||||||
|
"lib/Sync/functions.php",
|
||||||
|
"lib/Worker/functions.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Amp\\Parallel\\Example\\": "examples",
|
||||||
|
"Amp\\Parallel\\Test\\": "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"check": [
|
||||||
|
"@cs",
|
||||||
|
"@test"
|
||||||
|
],
|
||||||
|
"cs": "php-cs-fixer fix -v --diff --dry-run",
|
||||||
|
"cs-fix": "php-cs-fixer fix -v --diff",
|
||||||
|
"test": "@php -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit --coverage-text"
|
||||||
|
}
|
||||||
|
}
|
5
vendor/amphp/parallel/docs/Gemfile
vendored
Normal file
5
vendor/amphp/parallel/docs/Gemfile
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
source "https://rubygems.org"
|
||||||
|
gem "github-pages"
|
||||||
|
gem "kramdown"
|
||||||
|
gem "jekyll-github-metadata"
|
||||||
|
gem "jekyll-relative-links"
|
29
vendor/amphp/parallel/docs/_config.yml
vendored
Normal file
29
vendor/amphp/parallel/docs/_config.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
kramdown:
|
||||||
|
input: GFM
|
||||||
|
toc_levels: 2..3
|
||||||
|
|
||||||
|
baseurl: "/parallel"
|
||||||
|
layouts_dir: ".shared/layout"
|
||||||
|
includes_dir: ".shared/includes"
|
||||||
|
|
||||||
|
exclude: ["Gemfile", "Gemfile.lock", "README.md", "vendor"]
|
||||||
|
safe: true
|
||||||
|
|
||||||
|
repository: amphp/parallel
|
||||||
|
gems:
|
||||||
|
- "jekyll-github-metadata"
|
||||||
|
- "jekyll-relative-links"
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
- scope:
|
||||||
|
path: ""
|
||||||
|
type: "pages"
|
||||||
|
values:
|
||||||
|
layout: "docs"
|
||||||
|
|
||||||
|
shared_asset_path: "/parallel/asset"
|
||||||
|
|
||||||
|
navigation:
|
||||||
|
- processes
|
||||||
|
- workers
|
||||||
|
- worker-pool
|
49
vendor/amphp/parallel/docs/index.md
vendored
Normal file
49
vendor/amphp/parallel/docs/index.md
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
---
|
||||||
|
title: Parallel processing for PHP
|
||||||
|
permalink: /
|
||||||
|
---
|
||||||
|
This package provides *true parallel processing* for PHP using multiple processes or native threads, *without blocking and no extensions required*.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This package can be installed as a [Composer](https://getcomposer.org/) dependency.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require amphp/parallel
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The basic usage of this library is to submit blocking tasks to be executed by a worker pool in order to avoid blocking the main event loop.
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Amp\Parallel\Worker;
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
$urls = [
|
||||||
|
'https://secure.php.net',
|
||||||
|
'https://amphp.org',
|
||||||
|
'https://github.com',
|
||||||
|
];
|
||||||
|
|
||||||
|
$promises = [];
|
||||||
|
foreach ($urls as $url) {
|
||||||
|
$promises[$url] = Worker\enqueueCallable('file_get_contents', $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
$responses = Promise\wait(Promise\all($promises));
|
||||||
|
|
||||||
|
foreach ($responses as $url => $response) {
|
||||||
|
\printf("Read %d bytes from %s\n", \strlen($response), $url);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[`file_get_contents`](https://secure.php.net/file_get_contents) is just used as an example for a blocking function here.
|
||||||
|
If you just want to fetch multiple HTTP resources concurrently, it's better to use [`amphp/http-client`](https://amphp.org/http-client/), our non-blocking HTTP client.
|
||||||
|
|
||||||
|
The functions you call must be predefined or autoloadable by Composer so they also exist in the worker processes.
|
||||||
|
Instead of simple callables, you can also [enqueue `Task` instances](./workers#task) with `Amp\Parallel\Worker\enqueue()`.
|
55
vendor/amphp/parallel/docs/processes.md
vendored
Normal file
55
vendor/amphp/parallel/docs/processes.md
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
---
|
||||||
|
title: Processes and Threads
|
||||||
|
permalink: /processes
|
||||||
|
---
|
||||||
|
The `Process` and `Parallel` classes simplify writing and running PHP in parallel. A script written to be run in parallel must return a callable that will be run in a child process (or a thread if [`ext-parallel`](https://github.com/krakjoe/parallel) is installed). The callable receives a single argument – an instance of `Channel` that can be used to send data between the parent and child processes. Any serializable data can be sent across this channel. The `Context` object, which extends the `Channel` interface, is the other end of the communication channel.
|
||||||
|
|
||||||
|
In the example below, a child process or thread is used to call a blocking function (`file_get_contents()` is only an example of a blocking function, use [`http-client`](https://amphp.org/http-client) for non-blocking HTTP requests). The result of that function is then sent back to the parent using the `Channel` object. The return value of the child process callable is available using the `Context::join()` method.
|
||||||
|
|
||||||
|
## Child process or thread
|
||||||
|
|
||||||
|
```php
|
||||||
|
# child.php
|
||||||
|
|
||||||
|
use Amp\Parallel\Sync\Channel;
|
||||||
|
|
||||||
|
return function (Channel $channel): \Generator {
|
||||||
|
$url = yield $channel->receive();
|
||||||
|
|
||||||
|
$data = file_get_contents($url); // Example blocking function
|
||||||
|
|
||||||
|
yield $channel->send($data);
|
||||||
|
|
||||||
|
return 'Any serializable data';
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parent Process
|
||||||
|
|
||||||
|
```php
|
||||||
|
# parent.php
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Parallel\Context;
|
||||||
|
|
||||||
|
Loop::run(function () {
|
||||||
|
// Creates a context using Process, or if ext-parallel is installed, Parallel.
|
||||||
|
$context = Context\create(__DIR__ . '/child.php');
|
||||||
|
|
||||||
|
$pid = yield $context->start();
|
||||||
|
|
||||||
|
$url = 'https://google.com';
|
||||||
|
|
||||||
|
yield $context->send($url);
|
||||||
|
|
||||||
|
$requestData = yield $context->receive();
|
||||||
|
|
||||||
|
printf("Received %d bytes from %s\n", \strlen($requestData), $url);
|
||||||
|
|
||||||
|
$returnValue = yield $context->join();
|
||||||
|
|
||||||
|
printf("Child processes exited with '%s'\n", $returnValue);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Child processes are also great for CPU-intensive operations such as image manipulation or for running daemons that perform periodic tasks based on input from the parent.
|
67
vendor/amphp/parallel/docs/worker-pool.md
vendored
Normal file
67
vendor/amphp/parallel/docs/worker-pool.md
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
---
|
||||||
|
title: Worker Pool
|
||||||
|
permalink: /worker-pool
|
||||||
|
---
|
||||||
|
The easiest way to use workers is through a worker pool. `Pool` implements `Worker`, so worker pools can be used to enqueue
|
||||||
|
tasks in the same way as a worker, but rather than using a single worker process or thread, the pool uses multiple workers
|
||||||
|
to execute tasks. This allows multiple tasks to be executed simultaneously.
|
||||||
|
|
||||||
|
## `Pool`
|
||||||
|
|
||||||
|
The `Pool` interface extends [`Worker`](./workers#worker), adding methods to get information about the pool or pull a single `Worker` instance
|
||||||
|
out of the pool. A pool uses multiple `Worker` instances to execute enqueued [tasks](./workers#task).
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for worker pools.
|
||||||
|
*/
|
||||||
|
interface Pool extends Worker
|
||||||
|
{
|
||||||
|
/** @var int The default maximum pool size. */
|
||||||
|
const DEFAULT_MAX_SIZE = 32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a worker from the pool. The worker is marked as busy and will only be reused if the pool runs out of
|
||||||
|
* idle workers. The worker will be automatically marked as idle once no references to the returned worker remain.
|
||||||
|
*
|
||||||
|
* @return Worker
|
||||||
|
*
|
||||||
|
* @throws \Amp\Parallel\Context\StatusError If the queue is not running.
|
||||||
|
*/
|
||||||
|
public function getWorker(): Worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number of workers currently running in the pool.
|
||||||
|
*
|
||||||
|
* @return int The number of workers.
|
||||||
|
*/
|
||||||
|
public function getWorkerCount(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number of workers that are currently idle.
|
||||||
|
*
|
||||||
|
* @return int The number of idle workers.
|
||||||
|
*/
|
||||||
|
public function getIdleWorkerCount(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the maximum number of workers the pool may spawn to handle concurrent tasks.
|
||||||
|
*
|
||||||
|
* @return int The maximum number of workers.
|
||||||
|
*/
|
||||||
|
public function getMaxSize(): int;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If a set of tasks should be run within a single worker, use the `Pool::getWorker()` method to pull a single worker from the pool.
|
||||||
|
The worker is automatically returned to the pool when the instance returned is destroyed.
|
||||||
|
|
||||||
|
### Global worker pool
|
||||||
|
|
||||||
|
A global worker pool is available and can be set using the function `Amp\Parallel\Worker\pool(?Pool $pool = null)`.
|
||||||
|
Passing an instance of `Pool` will set the global pool to the given instance. Invoking the function without an instance will return
|
||||||
|
the current global instance.
|
133
vendor/amphp/parallel/docs/workers.md
vendored
Normal file
133
vendor/amphp/parallel/docs/workers.md
vendored
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
---
|
||||||
|
title: Workers
|
||||||
|
permalink: /workers
|
||||||
|
---
|
||||||
|
|
||||||
|
## `Worker`
|
||||||
|
|
||||||
|
`Worker` provides a simple interface for executing PHP code in parallel in a separate PHP process or thread.
|
||||||
|
Classes implementing [`Task`](#task) are used to define the code to be run in parallel.
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for a parallel worker thread that runs a queue of tasks.
|
||||||
|
*/
|
||||||
|
interface Worker
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks if the worker is running.
|
||||||
|
*
|
||||||
|
* @return bool True if the worker is running, otherwise false.
|
||||||
|
*/
|
||||||
|
public function isRunning(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the worker is currently idle.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isIdle(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueues a task to be executed by the worker.
|
||||||
|
*
|
||||||
|
* @param Task $task The task to enqueue.
|
||||||
|
*
|
||||||
|
* @return \Amp\Promise<mixed> Resolves with the return value of Task::run().
|
||||||
|
*/
|
||||||
|
public function enqueue(Task $task): Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Amp\Promise<int> Exit code.
|
||||||
|
*/
|
||||||
|
public function shutdown(): Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately kills the context.
|
||||||
|
*/
|
||||||
|
public function kill();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `Task`
|
||||||
|
|
||||||
|
The `Task` interface has a single `run()` method that gets invoked in the worker to dispatch the work that needs to be done.
|
||||||
|
The `run()` method can be written using blocking code since the code is executed in a separate process or thread. The method
|
||||||
|
may also be asynchronous, returning a `Promise` or `Generator` that is run as a coroutine.
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A runnable unit of execution.
|
||||||
|
*/
|
||||||
|
interface Task
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Runs the task inside the caller's context.
|
||||||
|
*
|
||||||
|
* Does not have to be a coroutine, can also be a regular function returning a value.
|
||||||
|
*
|
||||||
|
* @param Environment
|
||||||
|
*
|
||||||
|
* @return mixed|\Amp\Promise|\Generator
|
||||||
|
*/
|
||||||
|
public function run(Environment $environment);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Task instances are `serialize`'d in the main process and `unserialize`'d in the worker.
|
||||||
|
That means that all data that is passed between the main process and a worker needs to be serializable.
|
||||||
|
|
||||||
|
## `Environment`
|
||||||
|
|
||||||
|
The passed `Environment` allows to persist data between multiple tasks executed by the same worker, e.g. database connections or file handles, without resorting to globals for that.
|
||||||
|
Additionally `Environment` allows setting a TTL for entries, so can be used as a cache.
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
interface Environment extends \ArrayAccess
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function exists(string $key): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return mixed|null Returns null if the key does not exist.
|
||||||
|
*/
|
||||||
|
public function get(string $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value Using null for the value deletes the key.
|
||||||
|
* @param int $ttl Number of seconds until data is automatically deleted. Use null for unlimited TTL.
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value, int $ttl = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
*/
|
||||||
|
public function delete(string $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all values.
|
||||||
|
*/
|
||||||
|
public function clear();
|
||||||
|
}
|
||||||
|
```
|
34
vendor/amphp/parallel/lib/Context/Context.php
vendored
Normal file
34
vendor/amphp/parallel/lib/Context/Context.php
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context;
|
||||||
|
|
||||||
|
use Amp\Parallel\Sync\Channel;
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
interface Context extends Channel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isRunning(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the execution context.
|
||||||
|
*
|
||||||
|
* @return Promise<null> Resolved once the context has started.
|
||||||
|
*/
|
||||||
|
public function start(): Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately kills the context.
|
||||||
|
*/
|
||||||
|
public function kill();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Amp\Promise<mixed> Resolves with the returned from the context.
|
||||||
|
*
|
||||||
|
* @throws \Amp\Parallel\Context\ContextException If the context dies unexpectedly.
|
||||||
|
* @throws \Amp\Parallel\Sync\PanicError If the context throws an uncaught exception.
|
||||||
|
*/
|
||||||
|
public function join(): Promise;
|
||||||
|
}
|
7
vendor/amphp/parallel/lib/Context/ContextException.php
vendored
Normal file
7
vendor/amphp/parallel/lib/Context/ContextException.php
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context;
|
||||||
|
|
||||||
|
class ContextException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
28
vendor/amphp/parallel/lib/Context/ContextFactory.php
vendored
Normal file
28
vendor/amphp/parallel/lib/Context/ContextFactory.php
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
interface ContextFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a new execution context.
|
||||||
|
*
|
||||||
|
* @param string|string[] $script Path to PHP script or array with first element as path and following elements options
|
||||||
|
* to the PHP script (e.g.: ['bin/worker', 'Option1Value', 'Option2Value'].
|
||||||
|
*
|
||||||
|
* @return Context
|
||||||
|
*/
|
||||||
|
public function create($script): Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and starts a new execution context.
|
||||||
|
*
|
||||||
|
* @param string|string[] $script Path to PHP script or array with first element as path and following elements options
|
||||||
|
* to the PHP script (e.g.: ['bin/worker', 'Option1Value', 'Option2Value'].
|
||||||
|
*
|
||||||
|
* @return Promise<Context>
|
||||||
|
*/
|
||||||
|
public function run($script): Promise;
|
||||||
|
}
|
36
vendor/amphp/parallel/lib/Context/DefaultContextFactory.php
vendored
Normal file
36
vendor/amphp/parallel/lib/Context/DefaultContextFactory.php
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
class DefaultContextFactory implements ContextFactory
|
||||||
|
{
|
||||||
|
public function create($script): Context
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a thread if ext-parallel is installed, otherwise creates a child process.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
if (Parallel::isSupported()) {
|
||||||
|
return new Parallel($script);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Process($script);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and starts a thread if ext-parallel is installed, otherwise creates a child process.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function run($script): Promise
|
||||||
|
{
|
||||||
|
if (Parallel::isSupported()) {
|
||||||
|
return Parallel::run($script);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Process::run($script);
|
||||||
|
}
|
||||||
|
}
|
65
vendor/amphp/parallel/lib/Context/Internal/ParallelHub.php
vendored
Normal file
65
vendor/amphp/parallel/lib/Context/Internal/ParallelHub.php
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context\Internal;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Parallel\Sync\ChannelledSocket;
|
||||||
|
use parallel\Events;
|
||||||
|
use parallel\Future;
|
||||||
|
|
||||||
|
class ParallelHub extends ProcessHub
|
||||||
|
{
|
||||||
|
const EXIT_CHECK_FREQUENCY = 250;
|
||||||
|
|
||||||
|
/** @var ChannelledSocket[] */
|
||||||
|
private $channels;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $watcher;
|
||||||
|
|
||||||
|
/** @var Events */
|
||||||
|
private $events;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$events = $this->events = new Events;
|
||||||
|
$this->events->setBlocking(false);
|
||||||
|
|
||||||
|
$channels = &$this->channels;
|
||||||
|
$this->watcher = Loop::repeat(self::EXIT_CHECK_FREQUENCY, static function () use (&$channels, $events): void {
|
||||||
|
while ($event = $events->poll()) {
|
||||||
|
$id = (int) $event->source;
|
||||||
|
\assert(isset($channels[$id]), 'Channel for context ID not found');
|
||||||
|
$channel = $channels[$id];
|
||||||
|
unset($channels[$id]);
|
||||||
|
$channel->close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Loop::disable($this->watcher);
|
||||||
|
Loop::unreference($this->watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(int $id, ChannelledSocket $channel, Future $future): void
|
||||||
|
{
|
||||||
|
$this->channels[$id] = $channel;
|
||||||
|
$this->events->addFuture((string) $id, $future);
|
||||||
|
|
||||||
|
Loop::enable($this->watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(int $id): void
|
||||||
|
{
|
||||||
|
if (!isset($this->channels[$id])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this->channels[$id]);
|
||||||
|
$this->events->remove((string) $id);
|
||||||
|
|
||||||
|
if (empty($this->channels)) {
|
||||||
|
Loop::disable($this->watcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
151
vendor/amphp/parallel/lib/Context/Internal/ProcessHub.php
vendored
Normal file
151
vendor/amphp/parallel/lib/Context/Internal/ProcessHub.php
vendored
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context\Internal;
|
||||||
|
|
||||||
|
use Amp\Deferred;
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Parallel\Context\ContextException;
|
||||||
|
use Amp\Parallel\Sync\ChannelledSocket;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\TimeoutException;
|
||||||
|
use function Amp\asyncCall;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
class ProcessHub
|
||||||
|
{
|
||||||
|
const PROCESS_START_TIMEOUT = 5000;
|
||||||
|
const KEY_RECEIVE_TIMEOUT = 1000;
|
||||||
|
|
||||||
|
/** @var resource|null */
|
||||||
|
private $server;
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private $uri;
|
||||||
|
|
||||||
|
/** @var int[] */
|
||||||
|
private $keys;
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private $watcher;
|
||||||
|
|
||||||
|
/** @var Deferred[] */
|
||||||
|
private $acceptor = [];
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private $toUnlink;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$isWindows = \strncasecmp(\PHP_OS, "WIN", 3) === 0;
|
||||||
|
|
||||||
|
if ($isWindows) {
|
||||||
|
$this->uri = "tcp://127.0.0.1:0";
|
||||||
|
} else {
|
||||||
|
$suffix = \bin2hex(\random_bytes(10));
|
||||||
|
$path = \sys_get_temp_dir() . "/amp-parallel-ipc-" . $suffix . ".sock";
|
||||||
|
$this->uri = "unix://" . $path;
|
||||||
|
$this->toUnlink = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = \stream_context_create([
|
||||||
|
'socket' => ['backlog' => 128],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->server = \stream_socket_server(
|
||||||
|
$this->uri,
|
||||||
|
$errno,
|
||||||
|
$errstr,
|
||||||
|
\STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
|
||||||
|
$context
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$this->server) {
|
||||||
|
throw new \RuntimeException(\sprintf("Could not create IPC server: (Errno: %d) %s", $errno, $errstr));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isWindows) {
|
||||||
|
$name = \stream_socket_get_name($this->server, false);
|
||||||
|
$port = \substr($name, \strrpos($name, ":") + 1);
|
||||||
|
$this->uri = "tcp://127.0.0.1:" . $port;
|
||||||
|
}
|
||||||
|
|
||||||
|
$keys = &$this->keys;
|
||||||
|
$acceptor = &$this->acceptor;
|
||||||
|
$this->watcher = Loop::onReadable(
|
||||||
|
$this->server,
|
||||||
|
static function (string $watcher, $server) use (&$keys, &$acceptor): void {
|
||||||
|
// Error reporting suppressed since stream_socket_accept() emits E_WARNING on client accept failure.
|
||||||
|
while ($client = @\stream_socket_accept($server, 0)) { // Timeout of 0 to be non-blocking.
|
||||||
|
asyncCall(static function () use ($client, &$keys, &$acceptor) {
|
||||||
|
$channel = new ChannelledSocket($client, $client);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$received = yield Promise\timeout($channel->receive(), self::KEY_RECEIVE_TIMEOUT);
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$channel->close();
|
||||||
|
return; // Ignore possible foreign connection attempt.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\is_string($received) || !isset($keys[$received])) {
|
||||||
|
$channel->close();
|
||||||
|
return; // Ignore possible foreign connection attempt.
|
||||||
|
}
|
||||||
|
|
||||||
|
$pid = $keys[$received];
|
||||||
|
|
||||||
|
$deferred = $acceptor[$pid];
|
||||||
|
unset($acceptor[$pid], $keys[$received]);
|
||||||
|
$deferred->resolve($channel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Loop::disable($this->watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
Loop::cancel($this->watcher);
|
||||||
|
\fclose($this->server);
|
||||||
|
if ($this->toUnlink !== null) {
|
||||||
|
@\unlink($this->toUnlink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUri(): string
|
||||||
|
{
|
||||||
|
return $this->uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateKey(int $pid, int $length): string
|
||||||
|
{
|
||||||
|
$key = \random_bytes($length);
|
||||||
|
$this->keys[$key] = $pid;
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function accept(int $pid): Promise
|
||||||
|
{
|
||||||
|
return call(function () use ($pid): \Generator {
|
||||||
|
$this->acceptor[$pid] = new Deferred;
|
||||||
|
|
||||||
|
Loop::enable($this->watcher);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$channel = yield Promise\timeout($this->acceptor[$pid]->promise(), self::PROCESS_START_TIMEOUT);
|
||||||
|
} catch (TimeoutException $exception) {
|
||||||
|
$key = \array_search($pid, $this->keys, true);
|
||||||
|
\assert(\is_string($key), "Key for {$pid} not found");
|
||||||
|
unset($this->acceptor[$pid], $this->keys[$key]);
|
||||||
|
throw new ContextException("Starting the process timed out", 0, $exception);
|
||||||
|
} finally {
|
||||||
|
if (empty($this->acceptor)) {
|
||||||
|
Loop::disable($this->watcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $channel;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
155
vendor/amphp/parallel/lib/Context/Internal/Thread.php
vendored
Normal file
155
vendor/amphp/parallel/lib/Context/Internal/Thread.php
vendored
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context\Internal;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Parallel\Sync\Channel;
|
||||||
|
use Amp\Parallel\Sync\ChannelException;
|
||||||
|
use Amp\Parallel\Sync\ChannelledSocket;
|
||||||
|
use Amp\Parallel\Sync\ExitFailure;
|
||||||
|
use Amp\Parallel\Sync\ExitSuccess;
|
||||||
|
use Amp\Parallel\Sync\SerializationException;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An internal thread that executes a given function concurrently.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Thread extends \Thread
|
||||||
|
{
|
||||||
|
const KILL_CHECK_FREQUENCY = 250;
|
||||||
|
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/** @var callable The function to execute in the thread. */
|
||||||
|
private $function;
|
||||||
|
|
||||||
|
/** @var mixed[] Arguments to pass to the function. */
|
||||||
|
private $args;
|
||||||
|
|
||||||
|
/** @var resource */
|
||||||
|
private $socket;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $killed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new thread object.
|
||||||
|
*
|
||||||
|
* @param int $id Thread ID.
|
||||||
|
* @param resource $socket IPC communication socket.
|
||||||
|
* @param callable $function The function to execute in the thread.
|
||||||
|
* @param mixed[] $args Arguments to pass to the function.
|
||||||
|
*/
|
||||||
|
public function __construct(int $id, $socket, callable $function, array $args = [])
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
$this->function = $function;
|
||||||
|
$this->args = $args;
|
||||||
|
$this->socket = $socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the thread code and the initialized function.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore Only executed in thread.
|
||||||
|
*/
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
\define("AMP_CONTEXT", "thread");
|
||||||
|
\define("AMP_CONTEXT_ID", $this->id);
|
||||||
|
|
||||||
|
/* First thing we need to do is re-initialize the class autoloader. If
|
||||||
|
* we don't do this first, any object of a class that was loaded after
|
||||||
|
* the thread started will just be garbage data and unserializable
|
||||||
|
* values (like resources) will be lost. This happens even with
|
||||||
|
* thread-safe objects.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Protect scope by using an unbound closure (protects static access as well).
|
||||||
|
(static function (): void {
|
||||||
|
$paths = [
|
||||||
|
\dirname(__DIR__, 3) . \DIRECTORY_SEPARATOR . "vendor" . \DIRECTORY_SEPARATOR . "autoload.php",
|
||||||
|
\dirname(__DIR__, 5) . \DIRECTORY_SEPARATOR . "autoload.php",
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
if (\file_exists($path)) {
|
||||||
|
$autoloadPath = $path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($autoloadPath)) {
|
||||||
|
throw new \Error("Could not locate autoload.php");
|
||||||
|
}
|
||||||
|
|
||||||
|
require $autoloadPath;
|
||||||
|
})->bindTo(null, null)();
|
||||||
|
|
||||||
|
// At this point, the thread environment has been prepared so begin using the thread.
|
||||||
|
|
||||||
|
if ($this->killed) {
|
||||||
|
return; // Thread killed while requiring autoloader, simply exit.
|
||||||
|
}
|
||||||
|
|
||||||
|
Loop::run(function (): \Generator {
|
||||||
|
$watcher = Loop::repeat(self::KILL_CHECK_FREQUENCY, function (): void {
|
||||||
|
if ($this->killed) {
|
||||||
|
Loop::stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Loop::unreference($watcher);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$channel = new ChannelledSocket($this->socket, $this->socket);
|
||||||
|
yield from $this->execute($channel);
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
return; // Parent context exited or destroyed thread, no need to continue.
|
||||||
|
} finally {
|
||||||
|
Loop::cancel($watcher);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a local variable to true so the running event loop can check for a kill signal.
|
||||||
|
*/
|
||||||
|
public function kill()
|
||||||
|
{
|
||||||
|
return $this->killed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Amp\Parallel\Sync\Channel $channel
|
||||||
|
*
|
||||||
|
* @return \Generator
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore Only executed in thread.
|
||||||
|
*/
|
||||||
|
private function execute(Channel $channel): \Generator
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$result = new ExitSuccess(yield call($this->function, $channel, ...$this->args));
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$result = new ExitFailure($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->killed) {
|
||||||
|
return; // Parent is not listening for a result.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to return the result.
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
yield $channel->send($result);
|
||||||
|
} catch (SerializationException $exception) {
|
||||||
|
// Serializing the result failed. Send the reason why.
|
||||||
|
yield $channel->send(new ExitFailure($exception));
|
||||||
|
}
|
||||||
|
} catch (ChannelException $exception) {
|
||||||
|
// The result was not sendable! The parent context must have died or killed the context.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
vendor/amphp/parallel/lib/Context/Internal/process-runner.php
vendored
Normal file
123
vendor/amphp/parallel/lib/Context/Internal/process-runner.php
vendored
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context\Internal;
|
||||||
|
|
||||||
|
use Amp\Parallel\Context\Process;
|
||||||
|
use Amp\Parallel\Sync;
|
||||||
|
use Amp\Promise;
|
||||||
|
use function Amp\call;
|
||||||
|
use function Amp\getCurrentTime;
|
||||||
|
|
||||||
|
\define("AMP_CONTEXT", "process");
|
||||||
|
\define("AMP_CONTEXT_ID", \getmypid());
|
||||||
|
|
||||||
|
// Doesn't exist in phpdbg...
|
||||||
|
if (\function_exists("cli_set_process_title")) {
|
||||||
|
@\cli_set_process_title("amp-process");
|
||||||
|
}
|
||||||
|
|
||||||
|
(function (): void {
|
||||||
|
$paths = [
|
||||||
|
\dirname(__DIR__, 5) . "/autoload.php",
|
||||||
|
\dirname(__DIR__, 3) . "/vendor/autoload.php",
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
if (\file_exists($path)) {
|
||||||
|
$autoloadPath = $path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($autoloadPath)) {
|
||||||
|
\trigger_error("Could not locate autoload.php in any of the following files: " . \implode(", ", $paths), E_USER_ERROR);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
require $autoloadPath;
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function () use ($argc, $argv): void {
|
||||||
|
// Remove this scripts path from process arguments.
|
||||||
|
--$argc;
|
||||||
|
\array_shift($argv);
|
||||||
|
|
||||||
|
if (!isset($argv[0])) {
|
||||||
|
\trigger_error("No socket path provided", E_USER_ERROR);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove socket path from process arguments.
|
||||||
|
--$argc;
|
||||||
|
$uri = \array_shift($argv);
|
||||||
|
|
||||||
|
$key = "";
|
||||||
|
|
||||||
|
// Read random key from STDIN and send back to parent over IPC socket to authenticate.
|
||||||
|
do {
|
||||||
|
if (($chunk = \fread(\STDIN, Process::KEY_LENGTH)) === false || \feof(\STDIN)) {
|
||||||
|
\trigger_error("Could not read key from parent", E_USER_ERROR);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$key .= $chunk;
|
||||||
|
} while (\strlen($key) < Process::KEY_LENGTH);
|
||||||
|
|
||||||
|
$connectStart = getCurrentTime();
|
||||||
|
|
||||||
|
while (!$socket = \stream_socket_client($uri, $errno, $errstr, 5, \STREAM_CLIENT_CONNECT)) {
|
||||||
|
if (getCurrentTime() < $connectStart + 5000) { // try for 5 seconds, after that the parent times out anyway
|
||||||
|
\trigger_error("Could not connect to IPC socket", \E_USER_ERROR);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
\usleep(50 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
$channel = new Sync\ChannelledSocket($socket, $socket);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Promise\wait($channel->send($key));
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
\trigger_error("Could not send key to parent", E_USER_ERROR);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isset($argv[0])) {
|
||||||
|
throw new \Error("No script path given");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\is_file($argv[0])) {
|
||||||
|
throw new \Error(\sprintf("No script found at '%s' (be sure to provide the full path to the script)", $argv[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Protect current scope by requiring script within another function.
|
||||||
|
$callable = (function () use ($argc, $argv): callable { // Using $argc so it is available to the required script.
|
||||||
|
return require $argv[0];
|
||||||
|
})();
|
||||||
|
} catch (\TypeError $exception) {
|
||||||
|
throw new \Error(\sprintf("Script '%s' did not return a callable function", $argv[0]), 0, $exception);
|
||||||
|
} catch (\ParseError $exception) {
|
||||||
|
throw new \Error(\sprintf("Script '%s' contains a parse error: " . $exception->getMessage(), $argv[0]), 0, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = new Sync\ExitSuccess(Promise\wait(call($callable, $channel)));
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$result = new Sync\ExitFailure($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Promise\wait(call(function () use ($channel, $result): \Generator {
|
||||||
|
try {
|
||||||
|
yield $channel->send($result);
|
||||||
|
} catch (Sync\SerializationException $exception) {
|
||||||
|
// Serializing the result failed. Send the reason why.
|
||||||
|
yield $channel->send(new Sync\ExitFailure($exception));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
\trigger_error("Could not send result to parent; be sure to shutdown the child before ending the parent", E_USER_ERROR);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
})();
|
418
vendor/amphp/parallel/lib/Context/Parallel.php
vendored
Normal file
418
vendor/amphp/parallel/lib/Context/Parallel.php
vendored
Normal file
|
@ -0,0 +1,418 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Parallel\Sync\ChannelException;
|
||||||
|
use Amp\Parallel\Sync\ChannelledSocket;
|
||||||
|
use Amp\Parallel\Sync\ExitFailure;
|
||||||
|
use Amp\Parallel\Sync\ExitResult;
|
||||||
|
use Amp\Parallel\Sync\ExitSuccess;
|
||||||
|
use Amp\Parallel\Sync\SerializationException;
|
||||||
|
use Amp\Parallel\Sync\SynchronizationError;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\TimeoutException;
|
||||||
|
use parallel\Runtime;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements an execution context using native threads provided by the parallel extension.
|
||||||
|
*/
|
||||||
|
final class Parallel implements Context
|
||||||
|
{
|
||||||
|
const EXIT_CHECK_FREQUENCY = 250;
|
||||||
|
const KEY_LENGTH = 32;
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private static $autoloadPath;
|
||||||
|
|
||||||
|
/** @var int Next thread ID. */
|
||||||
|
private static $nextId = 1;
|
||||||
|
|
||||||
|
/** @var Internal\ProcessHub */
|
||||||
|
private $hub;
|
||||||
|
|
||||||
|
/** @var int|null */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/** @var Runtime|null */
|
||||||
|
private $runtime;
|
||||||
|
|
||||||
|
/** @var ChannelledSocket|null A channel for communicating with the parallel thread. */
|
||||||
|
private $channel;
|
||||||
|
|
||||||
|
/** @var string Script path. */
|
||||||
|
private $script;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
private $args = [];
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $oid = 0;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $killed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if threading is enabled.
|
||||||
|
*
|
||||||
|
* @return bool True if threading is enabled, otherwise false.
|
||||||
|
*/
|
||||||
|
public static function isSupported(): bool
|
||||||
|
{
|
||||||
|
return \extension_loaded('parallel');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and starts a new thread.
|
||||||
|
*
|
||||||
|
* @param string|array $script Path to PHP script or array with first element as path and following elements options
|
||||||
|
* to the PHP script (e.g.: ['bin/worker', 'Option1Value', 'Option2Value'].
|
||||||
|
*
|
||||||
|
* @return Promise<Thread> The thread object that was spawned.
|
||||||
|
*/
|
||||||
|
public static function run($script): Promise
|
||||||
|
{
|
||||||
|
$thread = new self($script);
|
||||||
|
return call(function () use ($thread): \Generator {
|
||||||
|
yield $thread->start();
|
||||||
|
return $thread;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|array $script Path to PHP script or array with first element as path and following elements options
|
||||||
|
* to the PHP script (e.g.: ['bin/worker', 'Option1Value', 'Option2Value'].
|
||||||
|
*
|
||||||
|
* @throws \Error Thrown if the pthreads extension is not available.
|
||||||
|
*/
|
||||||
|
public function __construct($script)
|
||||||
|
{
|
||||||
|
if (!self::isSupported()) {
|
||||||
|
throw new \Error("The parallel extension is required to create parallel threads.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->hub = Loop::getState(self::class);
|
||||||
|
if (!$this->hub instanceof Internal\ParallelHub) {
|
||||||
|
$this->hub = new Internal\ParallelHub;
|
||||||
|
Loop::setState(self::class, $this->hub);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_array($script)) {
|
||||||
|
$this->script = (string) \array_shift($script);
|
||||||
|
$this->args = \array_values(\array_map("strval", $script));
|
||||||
|
} else {
|
||||||
|
$this->script = (string) $script;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::$autoloadPath === null) {
|
||||||
|
$paths = [
|
||||||
|
\dirname(__DIR__, 2) . \DIRECTORY_SEPARATOR . "vendor" . \DIRECTORY_SEPARATOR . "autoload.php",
|
||||||
|
\dirname(__DIR__, 4) . \DIRECTORY_SEPARATOR . "autoload.php",
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
if (\file_exists($path)) {
|
||||||
|
self::$autoloadPath = $path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::$autoloadPath === null) {
|
||||||
|
throw new \Error("Could not locate autoload.php");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the thread to the condition before starting. The new thread can be started and run independently of the
|
||||||
|
* first thread.
|
||||||
|
*/
|
||||||
|
public function __clone()
|
||||||
|
{
|
||||||
|
$this->runtime = null;
|
||||||
|
$this->channel = null;
|
||||||
|
$this->id = null;
|
||||||
|
$this->oid = 0;
|
||||||
|
$this->killed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kills the thread if it is still running.
|
||||||
|
*
|
||||||
|
* @throws \Amp\Parallel\Context\ContextException
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if (\getmypid() === $this->oid) {
|
||||||
|
$this->kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the context is running.
|
||||||
|
*
|
||||||
|
* @return bool True if the context is running, otherwise false.
|
||||||
|
*/
|
||||||
|
public function isRunning(): bool
|
||||||
|
{
|
||||||
|
return $this->channel !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawns the thread and begins the thread's execution.
|
||||||
|
*
|
||||||
|
* @return Promise<int> Resolved once the thread has started.
|
||||||
|
*
|
||||||
|
* @throws \Amp\Parallel\Context\StatusError If the thread has already been started.
|
||||||
|
* @throws \Amp\Parallel\Context\ContextException If starting the thread was unsuccessful.
|
||||||
|
*/
|
||||||
|
public function start(): Promise
|
||||||
|
{
|
||||||
|
if ($this->oid !== 0) {
|
||||||
|
throw new StatusError('The thread has already been started.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->oid = \getmypid();
|
||||||
|
|
||||||
|
$this->runtime = new Runtime(self::$autoloadPath);
|
||||||
|
|
||||||
|
$this->id = self::$nextId++;
|
||||||
|
|
||||||
|
$future = $this->runtime->run(static function (int $id, string $uri, string $key, string $path, array $argv): int {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
// Only executed in thread.
|
||||||
|
\define("AMP_CONTEXT", "parallel");
|
||||||
|
\define("AMP_CONTEXT_ID", $id);
|
||||||
|
|
||||||
|
if (!$socket = \stream_socket_client($uri, $errno, $errstr, 5, \STREAM_CLIENT_CONNECT)) {
|
||||||
|
\trigger_error("Could not connect to IPC socket", E_USER_ERROR);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$channel = new ChannelledSocket($socket, $socket);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Promise\wait($channel->send($key));
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
\trigger_error("Could not send key to parent", E_USER_ERROR);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Loop::unreference(Loop::repeat(self::EXIT_CHECK_FREQUENCY, function (): void {
|
||||||
|
// Timer to give the chance for the PHP VM to be interrupted by Runtime::kill(), since system calls such as
|
||||||
|
// select() will not be interrupted.
|
||||||
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!\is_file($path)) {
|
||||||
|
throw new \Error(\sprintf("No script found at '%s' (be sure to provide the full path to the script)", $path));
|
||||||
|
}
|
||||||
|
|
||||||
|
$argc = \array_unshift($argv, $path);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Protect current scope by requiring script within another function.
|
||||||
|
$callable = (function () use ($argc, $argv): callable { // Using $argc so it is available to the required script.
|
||||||
|
return require $argv[0];
|
||||||
|
})->bindTo(null, null)();
|
||||||
|
} catch (\TypeError $exception) {
|
||||||
|
throw new \Error(\sprintf("Script '%s' did not return a callable function", $path), 0, $exception);
|
||||||
|
} catch (\ParseError $exception) {
|
||||||
|
throw new \Error(\sprintf("Script '%s' contains a parse error", $path), 0, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = new ExitSuccess(Promise\wait(call($callable, $channel)));
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$result = new ExitFailure($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise\wait(call(function () use ($channel, $result): \Generator {
|
||||||
|
try {
|
||||||
|
yield $channel->send($result);
|
||||||
|
} catch (SerializationException $exception) {
|
||||||
|
// Serializing the result failed. Send the reason why.
|
||||||
|
yield $channel->send(new ExitFailure($exception));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
\trigger_error("Could not send result to parent; be sure to shutdown the child before ending the parent", E_USER_ERROR);
|
||||||
|
return 1;
|
||||||
|
} finally {
|
||||||
|
$channel->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}, [
|
||||||
|
$this->id,
|
||||||
|
$this->hub->getUri(),
|
||||||
|
$this->hub->generateKey($this->id, self::KEY_LENGTH),
|
||||||
|
$this->script,
|
||||||
|
$this->args
|
||||||
|
]);
|
||||||
|
|
||||||
|
return call(function () use ($future): \Generator {
|
||||||
|
try {
|
||||||
|
$this->channel = yield $this->hub->accept($this->id);
|
||||||
|
$this->hub->add($this->id, $this->channel, $future);
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$this->kill();
|
||||||
|
throw new ContextException("Starting the parallel runtime failed", 0, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->killed) {
|
||||||
|
$this->kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately kills the context.
|
||||||
|
*/
|
||||||
|
public function kill(): void
|
||||||
|
{
|
||||||
|
$this->killed = true;
|
||||||
|
|
||||||
|
if ($this->runtime !== null) {
|
||||||
|
try {
|
||||||
|
$this->runtime->kill();
|
||||||
|
} finally {
|
||||||
|
$this->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes channel and socket if still open.
|
||||||
|
*/
|
||||||
|
private function close(): void
|
||||||
|
{
|
||||||
|
$this->runtime = null;
|
||||||
|
|
||||||
|
if ($this->channel !== null) {
|
||||||
|
$this->channel->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->channel = null;
|
||||||
|
|
||||||
|
$this->hub->remove($this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a promise that resolves when the context ends and joins with the
|
||||||
|
* parent context.
|
||||||
|
*
|
||||||
|
* @return \Amp\Promise<mixed>
|
||||||
|
*
|
||||||
|
* @throws StatusError Thrown if the context has not been started.
|
||||||
|
* @throws SynchronizationError Thrown if an exit status object is not received.
|
||||||
|
* @throws ContextException If the context stops responding.
|
||||||
|
*/
|
||||||
|
public function join(): Promise
|
||||||
|
{
|
||||||
|
if ($this->channel === null) {
|
||||||
|
throw new StatusError('The thread has not been started or has already finished.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(function (): \Generator {
|
||||||
|
try {
|
||||||
|
$response = yield $this->channel->receive();
|
||||||
|
$this->close();
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$this->kill();
|
||||||
|
throw new ContextException("Failed to receive result from thread", 0, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response instanceof ExitResult) {
|
||||||
|
$this->kill();
|
||||||
|
throw new SynchronizationError('Did not receive an exit result from thread.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response->getResult();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function receive(): Promise
|
||||||
|
{
|
||||||
|
if ($this->channel === null) {
|
||||||
|
throw new StatusError('The thread has not been started.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(function (): \Generator {
|
||||||
|
try {
|
||||||
|
$data = yield $this->channel->receive();
|
||||||
|
} catch (ChannelException $e) {
|
||||||
|
throw new ContextException("The thread stopped responding, potentially due to a fatal error or calling exit", 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data instanceof ExitResult) {
|
||||||
|
$data = $data->getResult();
|
||||||
|
throw new SynchronizationError(\sprintf(
|
||||||
|
'Thread unexpectedly exited with result of type: %s',
|
||||||
|
\is_object($data) ? \get_class($data) : \gettype($data)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function send($data): Promise
|
||||||
|
{
|
||||||
|
if ($this->channel === null) {
|
||||||
|
throw new StatusError('The thread has not been started or has already finished.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data instanceof ExitResult) {
|
||||||
|
throw new \Error('Cannot send exit result objects.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(function () use ($data): \Generator {
|
||||||
|
try {
|
||||||
|
return yield $this->channel->send($data);
|
||||||
|
} catch (ChannelException $e) {
|
||||||
|
if ($this->channel === null) {
|
||||||
|
throw new ContextException("The thread stopped responding, potentially due to a fatal error or calling exit", 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = yield Promise\timeout($this->join(), 100);
|
||||||
|
} catch (ContextException | ChannelException | TimeoutException $ex) {
|
||||||
|
$this->kill();
|
||||||
|
throw new ContextException("The thread stopped responding, potentially due to a fatal error or calling exit", 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SynchronizationError(\sprintf(
|
||||||
|
'Thread unexpectedly exited with result of type: %s',
|
||||||
|
\is_object($data) ? \get_class($data) : \gettype($data)
|
||||||
|
), 0, $e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the thread. This ID will be unique to this process.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @throws \Amp\Process\StatusError
|
||||||
|
*/
|
||||||
|
public function getId(): int
|
||||||
|
{
|
||||||
|
if ($this->id === null) {
|
||||||
|
throw new StatusError('The thread has not been started');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
}
|
401
vendor/amphp/parallel/lib/Context/Process.php
vendored
Normal file
401
vendor/amphp/parallel/lib/Context/Process.php
vendored
Normal file
|
@ -0,0 +1,401 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Parallel\Sync\ChannelException;
|
||||||
|
use Amp\Parallel\Sync\ChannelledSocket;
|
||||||
|
use Amp\Parallel\Sync\ExitResult;
|
||||||
|
use Amp\Parallel\Sync\SynchronizationError;
|
||||||
|
use Amp\Process\Process as BaseProcess;
|
||||||
|
use Amp\Process\ProcessInputStream;
|
||||||
|
use Amp\Process\ProcessOutputStream;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\TimeoutException;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
final class Process implements Context
|
||||||
|
{
|
||||||
|
const SCRIPT_PATH = __DIR__ . "/Internal/process-runner.php";
|
||||||
|
const KEY_LENGTH = 32;
|
||||||
|
|
||||||
|
/** @var string|null External version of SCRIPT_PATH if inside a PHAR. */
|
||||||
|
private static $pharScriptPath;
|
||||||
|
|
||||||
|
/** @var string|null PHAR path with a '.phar' extension. */
|
||||||
|
private static $pharCopy;
|
||||||
|
|
||||||
|
/** @var string|null Cached path to located PHP binary. */
|
||||||
|
private static $binaryPath;
|
||||||
|
|
||||||
|
/** @var Internal\ProcessHub */
|
||||||
|
private $hub;
|
||||||
|
|
||||||
|
/** @var BaseProcess */
|
||||||
|
private $process;
|
||||||
|
|
||||||
|
/** @var ChannelledSocket */
|
||||||
|
private $channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and starts the process at the given path using the optional PHP binary path.
|
||||||
|
*
|
||||||
|
* @param string|array $script Path to PHP script or array with first element as path and following elements options
|
||||||
|
* to the PHP script (e.g.: ['bin/worker', 'Option1Value', 'Option2Value'].
|
||||||
|
* @param string|null $cwd Working directory.
|
||||||
|
* @param mixed[] $env Array of environment variables.
|
||||||
|
* @param string $binary Path to PHP binary. Null will attempt to automatically locate the binary.
|
||||||
|
*
|
||||||
|
* @return Promise<Process>
|
||||||
|
*/
|
||||||
|
public static function run($script, string $cwd = null, array $env = [], string $binary = null): Promise
|
||||||
|
{
|
||||||
|
$process = new self($script, $cwd, $env, $binary);
|
||||||
|
return call(function () use ($process): \Generator {
|
||||||
|
yield $process->start();
|
||||||
|
return $process;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|array $script Path to PHP script or array with first element as path and following elements options
|
||||||
|
* to the PHP script (e.g.: ['bin/worker', 'Option1Value', 'Option2Value'].
|
||||||
|
* @param string|null $cwd Working directory.
|
||||||
|
* @param mixed[] $env Array of environment variables.
|
||||||
|
* @param string $binary Path to PHP binary. Null will attempt to automatically locate the binary.
|
||||||
|
*
|
||||||
|
* @throws \Error If the PHP binary path given cannot be found or is not executable.
|
||||||
|
*/
|
||||||
|
public function __construct($script, string $cwd = null, array $env = [], string $binary = null)
|
||||||
|
{
|
||||||
|
$this->hub = Loop::getState(self::class);
|
||||||
|
if (!$this->hub instanceof Internal\ProcessHub) {
|
||||||
|
$this->hub = new Internal\ProcessHub;
|
||||||
|
Loop::setState(self::class, $this->hub);
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = [
|
||||||
|
"html_errors" => "0",
|
||||||
|
"display_errors" => "0",
|
||||||
|
"log_errors" => "1",
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($binary === null) {
|
||||||
|
if (\PHP_SAPI === "cli") {
|
||||||
|
$binary = \PHP_BINARY;
|
||||||
|
} else {
|
||||||
|
$binary = self::$binaryPath ?? self::locateBinary();
|
||||||
|
}
|
||||||
|
} elseif (!\is_executable($binary)) {
|
||||||
|
throw new \Error(\sprintf("The PHP binary path '%s' was not found or is not executable", $binary));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write process runner to external file if inside a PHAR,
|
||||||
|
// because PHP can't open files inside a PHAR directly except for the stub.
|
||||||
|
if (\strpos(self::SCRIPT_PATH, "phar://") === 0) {
|
||||||
|
if (self::$pharScriptPath) {
|
||||||
|
$scriptPath = self::$pharScriptPath;
|
||||||
|
} else {
|
||||||
|
$path = \dirname(self::SCRIPT_PATH);
|
||||||
|
|
||||||
|
if (\substr(\Phar::running(false), -5) !== ".phar") {
|
||||||
|
self::$pharCopy = \sys_get_temp_dir() . "/phar-" . \bin2hex(\random_bytes(10)) . ".phar";
|
||||||
|
\copy(\Phar::running(false), self::$pharCopy);
|
||||||
|
|
||||||
|
\register_shutdown_function(static function (): void {
|
||||||
|
@\unlink(self::$pharCopy);
|
||||||
|
});
|
||||||
|
|
||||||
|
$path = "phar://" . self::$pharCopy . "/" . \substr($path, \strlen(\Phar::running(true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = \file_get_contents(self::SCRIPT_PATH);
|
||||||
|
$contents = \str_replace("__DIR__", \var_export($path, true), $contents);
|
||||||
|
$suffix = \bin2hex(\random_bytes(10));
|
||||||
|
self::$pharScriptPath = $scriptPath = \sys_get_temp_dir() . "/amp-process-runner-" . $suffix . ".php";
|
||||||
|
\file_put_contents($scriptPath, $contents);
|
||||||
|
|
||||||
|
\register_shutdown_function(static function (): void {
|
||||||
|
@\unlink(self::$pharScriptPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monkey-patch the script path in the same way, only supported if the command is given as array.
|
||||||
|
if (isset(self::$pharCopy) && \is_array($script) && isset($script[0])) {
|
||||||
|
$script[0] = "phar://" . self::$pharCopy . \substr($script[0], \strlen(\Phar::running(true)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$scriptPath = self::SCRIPT_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_array($script)) {
|
||||||
|
$script = \implode(" ", \array_map("escapeshellarg", $script));
|
||||||
|
} else {
|
||||||
|
$script = \escapeshellarg($script);
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = \implode(" ", [
|
||||||
|
\escapeshellarg($binary),
|
||||||
|
$this->formatOptions($options),
|
||||||
|
\escapeshellarg($scriptPath),
|
||||||
|
$this->hub->getUri(),
|
||||||
|
$script,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->process = new BaseProcess($command, $cwd, $env);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function locateBinary(): string
|
||||||
|
{
|
||||||
|
$executable = \strncasecmp(\PHP_OS, "WIN", 3) === 0 ? "php.exe" : "php";
|
||||||
|
|
||||||
|
$paths = \array_filter(\explode(\PATH_SEPARATOR, \getenv("PATH")));
|
||||||
|
$paths[] = \PHP_BINDIR;
|
||||||
|
$paths = \array_unique($paths);
|
||||||
|
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
$path .= \DIRECTORY_SEPARATOR . $executable;
|
||||||
|
if (\is_executable($path)) {
|
||||||
|
return self::$binaryPath = $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \Error("Could not locate PHP executable binary");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatOptions(array $options): string
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($options as $option => $value) {
|
||||||
|
$result[] = \sprintf("-d%s=%s", $option, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return \implode(" ", $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private method to prevent cloning.
|
||||||
|
*/
|
||||||
|
private function __clone()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function start(): Promise
|
||||||
|
{
|
||||||
|
return call(function (): \Generator {
|
||||||
|
try {
|
||||||
|
$pid = yield $this->process->start();
|
||||||
|
|
||||||
|
yield $this->process->getStdin()->write($this->hub->generateKey($pid, self::KEY_LENGTH));
|
||||||
|
|
||||||
|
$this->channel = yield $this->hub->accept($pid);
|
||||||
|
|
||||||
|
return $pid;
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
if ($this->isRunning()) {
|
||||||
|
$this->kill();
|
||||||
|
}
|
||||||
|
throw new ContextException("Starting the process failed", 0, $exception);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isRunning(): bool
|
||||||
|
{
|
||||||
|
return $this->process->isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function receive(): Promise
|
||||||
|
{
|
||||||
|
if ($this->channel === null) {
|
||||||
|
throw new StatusError("The process has not been started");
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(function (): \Generator {
|
||||||
|
try {
|
||||||
|
$data = yield $this->channel->receive();
|
||||||
|
} catch (ChannelException $e) {
|
||||||
|
throw new ContextException("The process stopped responding, potentially due to a fatal error or calling exit", 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data instanceof ExitResult) {
|
||||||
|
$data = $data->getResult();
|
||||||
|
throw new SynchronizationError(\sprintf(
|
||||||
|
'Process unexpectedly exited with result of type: %s',
|
||||||
|
\is_object($data) ? \get_class($data) : \gettype($data)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function send($data): Promise
|
||||||
|
{
|
||||||
|
if ($this->channel === null) {
|
||||||
|
throw new StatusError("The process has not been started");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data instanceof ExitResult) {
|
||||||
|
throw new \Error("Cannot send exit result objects");
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(function () use ($data): \Generator {
|
||||||
|
try {
|
||||||
|
return yield $this->channel->send($data);
|
||||||
|
} catch (ChannelException $e) {
|
||||||
|
if ($this->channel === null) {
|
||||||
|
throw new ContextException("The process stopped responding, potentially due to a fatal error or calling exit", 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = yield Promise\timeout($this->join(), 100);
|
||||||
|
} catch (ContextException | ChannelException | TimeoutException $ex) {
|
||||||
|
if ($this->isRunning()) {
|
||||||
|
$this->kill();
|
||||||
|
}
|
||||||
|
throw new ContextException("The process stopped responding, potentially due to a fatal error or calling exit", 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SynchronizationError(\sprintf(
|
||||||
|
'Process unexpectedly exited with result of type: %s',
|
||||||
|
\is_object($data) ? \get_class($data) : \gettype($data)
|
||||||
|
), 0, $e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function join(): Promise
|
||||||
|
{
|
||||||
|
if ($this->channel === null) {
|
||||||
|
throw new StatusError("The process has not been started");
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(function (): \Generator {
|
||||||
|
try {
|
||||||
|
$data = yield $this->channel->receive();
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
if ($this->isRunning()) {
|
||||||
|
$this->kill();
|
||||||
|
}
|
||||||
|
throw new ContextException("Failed to receive result from process", 0, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$data instanceof ExitResult) {
|
||||||
|
if ($this->isRunning()) {
|
||||||
|
$this->kill();
|
||||||
|
}
|
||||||
|
throw new SynchronizationError("Did not receive an exit result from process");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->channel->close();
|
||||||
|
|
||||||
|
$code = yield $this->process->join();
|
||||||
|
if ($code !== 0) {
|
||||||
|
throw new ContextException(\sprintf("Process exited with code %d", $code));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $data->getResult();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a signal to the process.
|
||||||
|
*
|
||||||
|
* @see \Amp\Process\Process::signal()
|
||||||
|
*
|
||||||
|
* @param int $signo
|
||||||
|
*
|
||||||
|
* @throws \Amp\Process\ProcessException
|
||||||
|
* @throws \Amp\Process\StatusError
|
||||||
|
*/
|
||||||
|
public function signal(int $signo): void
|
||||||
|
{
|
||||||
|
$this->process->signal($signo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the PID of the process.
|
||||||
|
*
|
||||||
|
* @see \Amp\Process\Process::getPid()
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @throws \Amp\Process\StatusError
|
||||||
|
*/
|
||||||
|
public function getPid(): int
|
||||||
|
{
|
||||||
|
return $this->process->getPid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the STDIN stream of the process.
|
||||||
|
*
|
||||||
|
* @see \Amp\Process\Process::getStdin()
|
||||||
|
*
|
||||||
|
* @return ProcessOutputStream
|
||||||
|
*
|
||||||
|
* @throws \Amp\Process\StatusError
|
||||||
|
*/
|
||||||
|
public function getStdin(): ProcessOutputStream
|
||||||
|
{
|
||||||
|
return $this->process->getStdin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the STDOUT stream of the process.
|
||||||
|
*
|
||||||
|
* @see \Amp\Process\Process::getStdout()
|
||||||
|
*
|
||||||
|
* @return ProcessInputStream
|
||||||
|
*
|
||||||
|
* @throws \Amp\Process\StatusError
|
||||||
|
*/
|
||||||
|
public function getStdout(): ProcessInputStream
|
||||||
|
{
|
||||||
|
return $this->process->getStdout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the STDOUT stream of the process.
|
||||||
|
*
|
||||||
|
* @see \Amp\Process\Process::getStderr()
|
||||||
|
*
|
||||||
|
* @return ProcessInputStream
|
||||||
|
*
|
||||||
|
* @throws \Amp\Process\StatusError
|
||||||
|
*/
|
||||||
|
public function getStderr(): ProcessInputStream
|
||||||
|
{
|
||||||
|
return $this->process->getStderr();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function kill(): void
|
||||||
|
{
|
||||||
|
$this->process->kill();
|
||||||
|
|
||||||
|
if ($this->channel !== null) {
|
||||||
|
$this->channel->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
vendor/amphp/parallel/lib/Context/StatusError.php
vendored
Normal file
7
vendor/amphp/parallel/lib/Context/StatusError.php
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context;
|
||||||
|
|
||||||
|
class StatusError extends \Error
|
||||||
|
{
|
||||||
|
}
|
330
vendor/amphp/parallel/lib/Context/Thread.php
vendored
Normal file
330
vendor/amphp/parallel/lib/Context/Thread.php
vendored
Normal file
|
@ -0,0 +1,330 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context;
|
||||||
|
|
||||||
|
use Amp\Failure;
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Parallel\Sync\ChannelledSocket;
|
||||||
|
use Amp\Parallel\Sync\ExitResult;
|
||||||
|
use Amp\Parallel\Sync\SynchronizationError;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Success;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements an execution context using native multi-threading.
|
||||||
|
*
|
||||||
|
* The thread context is not itself threaded. A local instance of the context is
|
||||||
|
* maintained both in the context that creates the thread and in the thread
|
||||||
|
* itself.
|
||||||
|
*
|
||||||
|
* @deprecated ext-pthreads development has been halted, see https://github.com/krakjoe/pthreads/issues/929
|
||||||
|
*/
|
||||||
|
final class Thread implements Context
|
||||||
|
{
|
||||||
|
const EXIT_CHECK_FREQUENCY = 250;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private static $nextId = 1;
|
||||||
|
|
||||||
|
/** @var Internal\Thread An internal thread instance. */
|
||||||
|
private $thread;
|
||||||
|
|
||||||
|
/** @var ChannelledSocket A channel for communicating with the thread. */
|
||||||
|
private $channel;
|
||||||
|
|
||||||
|
/** @var resource */
|
||||||
|
private $socket;
|
||||||
|
|
||||||
|
/** @var callable */
|
||||||
|
private $function;
|
||||||
|
|
||||||
|
/** @var mixed[] */
|
||||||
|
private $args;
|
||||||
|
|
||||||
|
/** @var int|null */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $oid = 0;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $watcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if threading is enabled.
|
||||||
|
*
|
||||||
|
* @return bool True if threading is enabled, otherwise false.
|
||||||
|
*/
|
||||||
|
public static function isSupported(): bool
|
||||||
|
{
|
||||||
|
return \extension_loaded('pthreads');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and starts a new thread.
|
||||||
|
*
|
||||||
|
* @param callable $function The callable to invoke in the thread. First argument is an instance of
|
||||||
|
* \Amp\Parallel\Sync\Channel.
|
||||||
|
* @param mixed ...$args Additional arguments to pass to the given callable.
|
||||||
|
*
|
||||||
|
* @return Promise<Thread> The thread object that was spawned.
|
||||||
|
*/
|
||||||
|
public static function run(callable $function, ...$args): Promise
|
||||||
|
{
|
||||||
|
$thread = new self($function, ...$args);
|
||||||
|
return call(function () use ($thread): \Generator {
|
||||||
|
yield $thread->start();
|
||||||
|
return $thread;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new thread.
|
||||||
|
*
|
||||||
|
* @param callable $function The callable to invoke in the thread. First argument is an instance of
|
||||||
|
* \Amp\Parallel\Sync\Channel.
|
||||||
|
* @param mixed ...$args Additional arguments to pass to the given callable.
|
||||||
|
*
|
||||||
|
* @throws \Error Thrown if the pthreads extension is not available.
|
||||||
|
*/
|
||||||
|
public function __construct(callable $function, ...$args)
|
||||||
|
{
|
||||||
|
if (!self::isSupported()) {
|
||||||
|
throw new \Error("The pthreads extension is required to create threads.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->function = $function;
|
||||||
|
$this->args = $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the thread to the condition before starting. The new thread can be started and run independently of the
|
||||||
|
* first thread.
|
||||||
|
*/
|
||||||
|
public function __clone()
|
||||||
|
{
|
||||||
|
$this->thread = null;
|
||||||
|
$this->socket = null;
|
||||||
|
$this->channel = null;
|
||||||
|
$this->oid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kills the thread if it is still running.
|
||||||
|
*
|
||||||
|
* @throws \Amp\Parallel\Context\ContextException
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if (\getmypid() === $this->oid) {
|
||||||
|
$this->kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the context is running.
|
||||||
|
*
|
||||||
|
* @return bool True if the context is running, otherwise false.
|
||||||
|
*/
|
||||||
|
public function isRunning(): bool
|
||||||
|
{
|
||||||
|
return $this->channel !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawns the thread and begins the thread's execution.
|
||||||
|
*
|
||||||
|
* @return Promise<int> Resolved once the thread has started.
|
||||||
|
*
|
||||||
|
* @throws \Amp\Parallel\Context\StatusError If the thread has already been started.
|
||||||
|
* @throws \Amp\Parallel\Context\ContextException If starting the thread was unsuccessful.
|
||||||
|
*/
|
||||||
|
public function start(): Promise
|
||||||
|
{
|
||||||
|
if ($this->oid !== 0) {
|
||||||
|
throw new StatusError('The thread has already been started.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->oid = \getmypid();
|
||||||
|
|
||||||
|
$sockets = @\stream_socket_pair(
|
||||||
|
\stripos(\PHP_OS, "win") === 0 ? STREAM_PF_INET : STREAM_PF_UNIX,
|
||||||
|
STREAM_SOCK_STREAM,
|
||||||
|
STREAM_IPPROTO_IP
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($sockets === false) {
|
||||||
|
$message = "Failed to create socket pair";
|
||||||
|
if ($error = \error_get_last()) {
|
||||||
|
$message .= \sprintf(" Errno: %d; %s", $error["type"], $error["message"]);
|
||||||
|
}
|
||||||
|
return new Failure(new ContextException($message));
|
||||||
|
}
|
||||||
|
|
||||||
|
list($channel, $this->socket) = $sockets;
|
||||||
|
|
||||||
|
$this->id = self::$nextId++;
|
||||||
|
|
||||||
|
$thread = $this->thread = new Internal\Thread($this->id, $this->socket, $this->function, $this->args);
|
||||||
|
|
||||||
|
if (!$this->thread->start(\PTHREADS_INHERIT_INI)) {
|
||||||
|
return new Failure(new ContextException('Failed to start the thread.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$channel = $this->channel = new ChannelledSocket($channel, $channel);
|
||||||
|
|
||||||
|
$this->watcher = Loop::repeat(self::EXIT_CHECK_FREQUENCY, static function ($watcher) use ($thread, $channel): void {
|
||||||
|
if (!$thread->isRunning()) {
|
||||||
|
// Delay closing to avoid race condition between thread exiting and data becoming available.
|
||||||
|
Loop::delay(self::EXIT_CHECK_FREQUENCY, [$channel, "close"]);
|
||||||
|
Loop::cancel($watcher);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Loop::disable($this->watcher);
|
||||||
|
|
||||||
|
return new Success($this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately kills the context.
|
||||||
|
*
|
||||||
|
* @throws ContextException If killing the thread was unsuccessful.
|
||||||
|
*/
|
||||||
|
public function kill(): void
|
||||||
|
{
|
||||||
|
if ($this->thread !== null) {
|
||||||
|
try {
|
||||||
|
if ($this->thread->isRunning() && !$this->thread->kill()) {
|
||||||
|
throw new ContextException('Could not kill thread.');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$this->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes channel and socket if still open.
|
||||||
|
*/
|
||||||
|
private function close(): void
|
||||||
|
{
|
||||||
|
if ($this->channel !== null) {
|
||||||
|
$this->channel->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->channel = null;
|
||||||
|
Loop::cancel($this->watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a promise that resolves when the context ends and joins with the
|
||||||
|
* parent context.
|
||||||
|
*
|
||||||
|
* @return \Amp\Promise<mixed>
|
||||||
|
*
|
||||||
|
* @throws StatusError Thrown if the context has not been started.
|
||||||
|
* @throws SynchronizationError Thrown if an exit status object is not received.
|
||||||
|
* @throws ContextException If the context stops responding.
|
||||||
|
*/
|
||||||
|
public function join(): Promise
|
||||||
|
{
|
||||||
|
if ($this->channel == null || $this->thread === null) {
|
||||||
|
throw new StatusError('The thread has not been started or has already finished.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(function (): \Generator {
|
||||||
|
Loop::enable($this->watcher);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = yield $this->channel->receive();
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$this->kill();
|
||||||
|
throw new ContextException("Failed to receive result from thread", 0, $exception);
|
||||||
|
} finally {
|
||||||
|
Loop::disable($this->watcher);
|
||||||
|
$this->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response instanceof ExitResult) {
|
||||||
|
$this->kill();
|
||||||
|
throw new SynchronizationError('Did not receive an exit result from thread.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response->getResult();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function receive(): Promise
|
||||||
|
{
|
||||||
|
if ($this->channel === null) {
|
||||||
|
throw new StatusError('The process has not been started.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(function (): \Generator {
|
||||||
|
Loop::enable($this->watcher);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = yield $this->channel->receive();
|
||||||
|
} finally {
|
||||||
|
Loop::disable($this->watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data instanceof ExitResult) {
|
||||||
|
$data = $data->getResult();
|
||||||
|
throw new SynchronizationError(\sprintf(
|
||||||
|
'Thread process unexpectedly exited with result of type: %s',
|
||||||
|
\is_object($data) ? \get_class($data) : \gettype($data)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function send($data): Promise
|
||||||
|
{
|
||||||
|
if ($this->channel === null) {
|
||||||
|
throw new StatusError('The thread has not been started or has already finished.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data instanceof ExitResult) {
|
||||||
|
throw new \Error('Cannot send exit result objects.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return call(function () use ($data): \Generator {
|
||||||
|
Loop::enable($this->watcher);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = yield $this->channel->send($data);
|
||||||
|
} finally {
|
||||||
|
Loop::disable($this->watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the thread. This ID will be unique to this process.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @throws \Amp\Process\StatusError
|
||||||
|
*/
|
||||||
|
public function getId(): int
|
||||||
|
{
|
||||||
|
if ($this->id === null) {
|
||||||
|
throw new StatusError('The thread has not been started');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
}
|
54
vendor/amphp/parallel/lib/Context/functions.php
vendored
Normal file
54
vendor/amphp/parallel/lib/Context/functions.php
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Context;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
const LOOP_FACTORY_IDENTIFIER = ContextFactory::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|string[] $script Path to PHP script or array with first element as path and following elements options
|
||||||
|
* to the PHP script (e.g.: ['bin/worker', 'Option1Value', 'Option2Value'].
|
||||||
|
*
|
||||||
|
* @return Context
|
||||||
|
*/
|
||||||
|
function create($script): Context
|
||||||
|
{
|
||||||
|
return factory()->create($script);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and starts a process based on installed extensions (a thread if ext-parallel is installed, otherwise a child
|
||||||
|
* process).
|
||||||
|
*
|
||||||
|
* @param string|string[] $script Path to PHP script or array with first element as path and following elements options
|
||||||
|
* to the PHP script (e.g.: ['bin/worker', 'Option1Value', 'Option2Value'].
|
||||||
|
*
|
||||||
|
* @return Promise<Context>
|
||||||
|
*/
|
||||||
|
function run($script): Promise
|
||||||
|
{
|
||||||
|
return factory()->run($script);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or sets the global context factory.
|
||||||
|
*
|
||||||
|
* @param ContextFactory|null $factory
|
||||||
|
*
|
||||||
|
* @return ContextFactory
|
||||||
|
*/
|
||||||
|
function factory(?ContextFactory $factory = null): ContextFactory
|
||||||
|
{
|
||||||
|
if ($factory === null) {
|
||||||
|
$factory = Loop::getState(LOOP_FACTORY_IDENTIFIER);
|
||||||
|
if ($factory) {
|
||||||
|
return $factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
$factory = new DefaultContextFactory;
|
||||||
|
}
|
||||||
|
Loop::setState(LOOP_FACTORY_IDENTIFIER, $factory);
|
||||||
|
return $factory;
|
||||||
|
}
|
36
vendor/amphp/parallel/lib/Sync/Channel.php
vendored
Normal file
36
vendor/amphp/parallel/lib/Sync/Channel.php
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for sending messages between execution contexts.
|
||||||
|
*/
|
||||||
|
interface Channel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return \Amp\Promise<mixed>
|
||||||
|
*
|
||||||
|
* @throws \Amp\Parallel\Context\StatusError Thrown if the context has not been started.
|
||||||
|
* @throws \Amp\Parallel\Sync\SynchronizationError If the context has not been started or the context
|
||||||
|
* unexpectedly ends.
|
||||||
|
* @throws \Amp\Parallel\Sync\ChannelException If receiving from the channel fails.
|
||||||
|
* @throws \Amp\Parallel\Sync\SerializationException If unserializing the data fails.
|
||||||
|
*/
|
||||||
|
public function receive(): Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $data
|
||||||
|
*
|
||||||
|
* @return \Amp\Promise<int> Resolves with the number of bytes sent on the channel.
|
||||||
|
*
|
||||||
|
* @throws \Amp\Parallel\Context\StatusError Thrown if the context has not been started.
|
||||||
|
* @throws \Amp\Parallel\Sync\SynchronizationError If the context has not been started or the context
|
||||||
|
* unexpectedly ends.
|
||||||
|
* @throws \Amp\Parallel\Sync\ChannelException If sending on the channel fails.
|
||||||
|
* @throws \Error If an ExitResult object is given.
|
||||||
|
* @throws \Amp\Parallel\Sync\SerializationException If serializing the data fails.
|
||||||
|
*/
|
||||||
|
public function send($data): Promise;
|
||||||
|
}
|
7
vendor/amphp/parallel/lib/Sync/ChannelException.php
vendored
Normal file
7
vendor/amphp/parallel/lib/Sync/ChannelException.php
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
class ChannelException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
65
vendor/amphp/parallel/lib/Sync/ChannelParser.php
vendored
Normal file
65
vendor/amphp/parallel/lib/Sync/ChannelParser.php
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
use Amp\Parser\Parser;
|
||||||
|
use Amp\Serialization\NativeSerializer;
|
||||||
|
use Amp\Serialization\Serializer;
|
||||||
|
use function Amp\Serialization\encodeUnprintableChars;
|
||||||
|
|
||||||
|
final class ChannelParser extends Parser
|
||||||
|
{
|
||||||
|
const HEADER_LENGTH = 5;
|
||||||
|
|
||||||
|
/** @var Serializer */
|
||||||
|
private $serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable(mixed $data) Callback invoked when data is parsed.
|
||||||
|
* @param Serializer|null $serializer
|
||||||
|
*/
|
||||||
|
public function __construct(callable $callback, ?Serializer $serializer = null)
|
||||||
|
{
|
||||||
|
$this->serializer = $serializer ?? new NativeSerializer;
|
||||||
|
parent::__construct(self::parser($callback, $this->serializer));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $data Data to encode to send over a channel.
|
||||||
|
*
|
||||||
|
* @return string Encoded data that can be parsed by this class.
|
||||||
|
*
|
||||||
|
* @throws SerializationException
|
||||||
|
*/
|
||||||
|
public function encode($data): string
|
||||||
|
{
|
||||||
|
$data = $this->serializer->serialize($data);
|
||||||
|
return \pack("CL", 0, \strlen($data)) . $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $push
|
||||||
|
* @param Serializer $serializer
|
||||||
|
*
|
||||||
|
* @return \Generator
|
||||||
|
*
|
||||||
|
* @throws ChannelException
|
||||||
|
* @throws SerializationException
|
||||||
|
*/
|
||||||
|
private static function parser(callable $push, Serializer $serializer): \Generator
|
||||||
|
{
|
||||||
|
while (true) {
|
||||||
|
$header = yield self::HEADER_LENGTH;
|
||||||
|
$data = \unpack("Cprefix/Llength", $header);
|
||||||
|
|
||||||
|
if ($data["prefix"] !== 0) {
|
||||||
|
$data = $header . yield;
|
||||||
|
throw new ChannelException("Invalid packet received: " . encodeUnprintableChars($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = yield $data["length"];
|
||||||
|
|
||||||
|
$push($serializer->unserialize($data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
vendor/amphp/parallel/lib/Sync/ChannelledSocket.php
vendored
Normal file
71
vendor/amphp/parallel/lib/Sync/ChannelledSocket.php
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
use Amp\ByteStream\ResourceInputStream;
|
||||||
|
use Amp\ByteStream\ResourceOutputStream;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Serialization\Serializer;
|
||||||
|
|
||||||
|
final class ChannelledSocket implements Channel
|
||||||
|
{
|
||||||
|
/** @var ChannelledStream */
|
||||||
|
private $channel;
|
||||||
|
|
||||||
|
/** @var ResourceInputStream */
|
||||||
|
private $read;
|
||||||
|
|
||||||
|
/** @var ResourceOutputStream */
|
||||||
|
private $write;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $read Readable stream resource.
|
||||||
|
* @param resource $write Writable stream resource.
|
||||||
|
* @param Serializer|null $serializer
|
||||||
|
*
|
||||||
|
* @throws \Error If a stream resource is not given for $resource.
|
||||||
|
*/
|
||||||
|
public function __construct($read, $write, ?Serializer $serializer = null)
|
||||||
|
{
|
||||||
|
$this->channel = new ChannelledStream(
|
||||||
|
$this->read = new ResourceInputStream($read),
|
||||||
|
$this->write = new ResourceOutputStream($write),
|
||||||
|
$serializer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function receive(): Promise
|
||||||
|
{
|
||||||
|
return $this->channel->receive();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function send($data): Promise
|
||||||
|
{
|
||||||
|
return $this->channel->send($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unreference(): void
|
||||||
|
{
|
||||||
|
$this->read->unreference();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reference(): void
|
||||||
|
{
|
||||||
|
$this->read->reference();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the read and write resource streams.
|
||||||
|
*/
|
||||||
|
public function close(): void
|
||||||
|
{
|
||||||
|
$this->read->close();
|
||||||
|
$this->write->close();
|
||||||
|
}
|
||||||
|
}
|
83
vendor/amphp/parallel/lib/Sync/ChannelledStream.php
vendored
Normal file
83
vendor/amphp/parallel/lib/Sync/ChannelledStream.php
vendored
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
use Amp\ByteStream\InputStream;
|
||||||
|
use Amp\ByteStream\OutputStream;
|
||||||
|
use Amp\ByteStream\StreamException;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Serialization\Serializer;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An asynchronous channel for sending data between threads and processes.
|
||||||
|
*
|
||||||
|
* Supports full duplex read and write.
|
||||||
|
*/
|
||||||
|
final class ChannelledStream implements Channel
|
||||||
|
{
|
||||||
|
/** @var InputStream */
|
||||||
|
private $read;
|
||||||
|
|
||||||
|
/** @var OutputStream */
|
||||||
|
private $write;
|
||||||
|
|
||||||
|
/** @var \SplQueue */
|
||||||
|
private $received;
|
||||||
|
|
||||||
|
/** @var ChannelParser */
|
||||||
|
private $parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new channel from the given stream objects. Note that $read and $write can be the same object.
|
||||||
|
*
|
||||||
|
* @param InputStream $read
|
||||||
|
* @param OutputStream $write
|
||||||
|
* @param Serializer|null $serializer
|
||||||
|
*/
|
||||||
|
public function __construct(InputStream $read, OutputStream $write, ?Serializer $serializer = null)
|
||||||
|
{
|
||||||
|
$this->read = $read;
|
||||||
|
$this->write = $write;
|
||||||
|
$this->received = new \SplQueue;
|
||||||
|
$this->parser = new ChannelParser([$this->received, 'push'], $serializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function send($data): Promise
|
||||||
|
{
|
||||||
|
return call(function () use ($data): \Generator {
|
||||||
|
try {
|
||||||
|
return yield $this->write->write($this->parser->encode($data));
|
||||||
|
} catch (StreamException $exception) {
|
||||||
|
throw new ChannelException("Sending on the channel failed. Did the context die?", 0, $exception);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function receive(): Promise
|
||||||
|
{
|
||||||
|
return call(function (): \Generator {
|
||||||
|
while ($this->received->isEmpty()) {
|
||||||
|
try {
|
||||||
|
$chunk = yield $this->read->read();
|
||||||
|
} catch (StreamException $exception) {
|
||||||
|
throw new ChannelException("Reading from the channel failed. Did the context die?", 0, $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($chunk === null) {
|
||||||
|
throw new ChannelException("The channel closed unexpectedly. Did the context die?");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->parser->push($chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->received->shift();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
83
vendor/amphp/parallel/lib/Sync/ContextPanicError.php
vendored
Normal file
83
vendor/amphp/parallel/lib/Sync/ContextPanicError.php
vendored
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
final class ContextPanicError extends PanicError
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $originalMessage;
|
||||||
|
|
||||||
|
/** @var int|string */
|
||||||
|
private $originalCode;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
private $originalTrace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $className Original exception class name.
|
||||||
|
* @param string $message Original exception message.
|
||||||
|
* @param int|string $code Original exception code.
|
||||||
|
* @param array $trace Backtrace generated by {@see formatFlattenedBacktrace()}.
|
||||||
|
* @param self|null $previous Instance representing any previous exception thrown in the child process or thread.
|
||||||
|
*/
|
||||||
|
public function __construct(string $className, string $message, $code, array $trace, ?self $previous = null)
|
||||||
|
{
|
||||||
|
$format = 'Uncaught %s in child process or thread with message "%s" and code "%s"; use %s::getOriginalTrace() '
|
||||||
|
. 'for the stack trace in the child process or thread';
|
||||||
|
|
||||||
|
parent::__construct(
|
||||||
|
$className,
|
||||||
|
\sprintf($format, $className, $message, $code, self::class),
|
||||||
|
formatFlattenedBacktrace($trace),
|
||||||
|
$previous
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->originalMessage = $message;
|
||||||
|
$this->originalCode = $code;
|
||||||
|
$this->originalTrace = $trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Original exception class name.
|
||||||
|
*/
|
||||||
|
public function getOriginalClassName(): string
|
||||||
|
{
|
||||||
|
return $this->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Original exception message.
|
||||||
|
*/
|
||||||
|
public function getOriginalMessage(): string
|
||||||
|
{
|
||||||
|
return $this->originalMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|string Original exception code.
|
||||||
|
*/
|
||||||
|
public function getOriginalCode()
|
||||||
|
{
|
||||||
|
return $this->originalCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Original exception stack trace.
|
||||||
|
*
|
||||||
|
* @return array Same as {@see Throwable::getTrace()}, except all function arguments are formatted as strings.
|
||||||
|
*/
|
||||||
|
public function getOriginalTrace(): array
|
||||||
|
{
|
||||||
|
return $this->originalTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Original backtrace flattened to a human-readable string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOriginalTraceAsString(): string
|
||||||
|
{
|
||||||
|
return $this->getPanicTrace();
|
||||||
|
}
|
||||||
|
}
|
48
vendor/amphp/parallel/lib/Sync/ExitFailure.php
vendored
Normal file
48
vendor/amphp/parallel/lib/Sync/ExitFailure.php
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
final class ExitFailure implements ExitResult
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $type;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $message;
|
||||||
|
|
||||||
|
/** @var int|string */
|
||||||
|
private $code;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
private $trace;
|
||||||
|
|
||||||
|
/** @var self|null */
|
||||||
|
private $previous;
|
||||||
|
|
||||||
|
public function __construct(\Throwable $exception)
|
||||||
|
{
|
||||||
|
$this->type = \get_class($exception);
|
||||||
|
$this->message = $exception->getMessage();
|
||||||
|
$this->code = $exception->getCode();
|
||||||
|
$this->trace = flattenThrowableBacktrace($exception);
|
||||||
|
|
||||||
|
if ($previous = $exception->getPrevious()) {
|
||||||
|
$this->previous = new self($previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getResult()
|
||||||
|
{
|
||||||
|
throw $this->createException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createException(): ContextPanicError
|
||||||
|
{
|
||||||
|
$previous = $this->previous ? $this->previous->createException() : null;
|
||||||
|
|
||||||
|
return new ContextPanicError($this->type, $this->message, $this->code, $this->trace, $previous);
|
||||||
|
}
|
||||||
|
}
|
13
vendor/amphp/parallel/lib/Sync/ExitResult.php
vendored
Normal file
13
vendor/amphp/parallel/lib/Sync/ExitResult.php
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
interface ExitResult
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return mixed Return value of the callable given to the execution context.
|
||||||
|
*
|
||||||
|
* @throws \Amp\Parallel\Sync\PanicError If the context exited with an uncaught exception.
|
||||||
|
*/
|
||||||
|
public function getResult();
|
||||||
|
}
|
22
vendor/amphp/parallel/lib/Sync/ExitSuccess.php
vendored
Normal file
22
vendor/amphp/parallel/lib/Sync/ExitSuccess.php
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
final class ExitSuccess implements ExitResult
|
||||||
|
{
|
||||||
|
/** @var mixed */
|
||||||
|
private $result;
|
||||||
|
|
||||||
|
public function __construct($result)
|
||||||
|
{
|
||||||
|
$this->result = $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getResult()
|
||||||
|
{
|
||||||
|
return $this->result;
|
||||||
|
}
|
||||||
|
}
|
33
vendor/amphp/parallel/lib/Sync/Internal/ParcelStorage.php
vendored
Normal file
33
vendor/amphp/parallel/lib/Sync/Internal/ParcelStorage.php
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync\Internal;
|
||||||
|
|
||||||
|
final class ParcelStorage extends \Threaded
|
||||||
|
{
|
||||||
|
/** @var mixed */
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function __construct($value)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get()
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function set($value): void
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
}
|
56
vendor/amphp/parallel/lib/Sync/PanicError.php
vendored
Normal file
56
vendor/amphp/parallel/lib/Sync/PanicError.php
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated ContextPanicError will be thrown from uncaught exceptions in child processes and threads instead of
|
||||||
|
* this class.
|
||||||
|
*/
|
||||||
|
class PanicError extends \Error
|
||||||
|
{
|
||||||
|
/** @var string Class name of uncaught exception. */
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
/** @var string Stack trace of the panic. */
|
||||||
|
private $trace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new panic error.
|
||||||
|
*
|
||||||
|
* @param string $name The uncaught exception class.
|
||||||
|
* @param string $message The panic message.
|
||||||
|
* @param string $trace The panic stack trace.
|
||||||
|
* @param \Throwable|null $previous Previous exception.
|
||||||
|
*/
|
||||||
|
public function __construct(string $name, string $message = '', string $trace = '', ?\Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct($message, 0, $previous);
|
||||||
|
|
||||||
|
$this->name = $name;
|
||||||
|
$this->trace = $trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use ContextPanicError::getOriginalClassName() instead.
|
||||||
|
*
|
||||||
|
* Returns the class name of the uncaught exception.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use ContextPanicError::getOriginalTraceAsString() instead.
|
||||||
|
*
|
||||||
|
* Gets the stack trace at the point the panic occurred.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPanicTrace(): string
|
||||||
|
{
|
||||||
|
return $this->trace;
|
||||||
|
}
|
||||||
|
}
|
38
vendor/amphp/parallel/lib/Sync/Parcel.php
vendored
Normal file
38
vendor/amphp/parallel/lib/Sync/Parcel.php
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parcel object for sharing data across execution contexts.
|
||||||
|
*
|
||||||
|
* A parcel is an object that stores a value in a safe way that can be shared
|
||||||
|
* between different threads or processes. Different handles to the same parcel
|
||||||
|
* will access the same data, and a parcel handle itself is serializable and
|
||||||
|
* can be transported to other execution contexts.
|
||||||
|
*
|
||||||
|
* Wrapping and unwrapping values in the parcel are not atomic. To prevent race
|
||||||
|
* conditions and guarantee safety, you should use the provided synchronization
|
||||||
|
* methods to acquire a lock for exclusive access to the parcel first before
|
||||||
|
* accessing the contained value.
|
||||||
|
*/
|
||||||
|
interface Parcel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Asynchronously invokes a callback while maintaining an exclusive lock on the parcel. The current value of the
|
||||||
|
* parcel is provided as the first argument to the callback function.
|
||||||
|
*
|
||||||
|
* @param callable $callback The synchronized callback to invoke. The parcel value is given as the single argument
|
||||||
|
* to the callback function. The callback may be a regular function or a coroutine.
|
||||||
|
*
|
||||||
|
* @return \Amp\Promise<mixed> Resolves with the return value of $callback or fails if $callback
|
||||||
|
* throws an exception.
|
||||||
|
*/
|
||||||
|
public function synchronized(callable $callback): Promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Amp\Promise<mixed> A promise for the value inside the parcel.
|
||||||
|
*/
|
||||||
|
public function unwrap(): Promise;
|
||||||
|
}
|
7
vendor/amphp/parallel/lib/Sync/ParcelException.php
vendored
Normal file
7
vendor/amphp/parallel/lib/Sync/ParcelException.php
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
class ParcelException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
7
vendor/amphp/parallel/lib/Sync/SharedMemoryException.php
vendored
Normal file
7
vendor/amphp/parallel/lib/Sync/SharedMemoryException.php
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
class SharedMemoryException extends ParcelException
|
||||||
|
{
|
||||||
|
}
|
456
vendor/amphp/parallel/lib/Sync/SharedMemoryParcel.php
vendored
Normal file
456
vendor/amphp/parallel/lib/Sync/SharedMemoryParcel.php
vendored
Normal file
|
@ -0,0 +1,456 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Serialization\NativeSerializer;
|
||||||
|
use Amp\Serialization\Serializer;
|
||||||
|
use Amp\Sync\Lock;
|
||||||
|
use Amp\Sync\PosixSemaphore;
|
||||||
|
use Amp\Sync\SyncException;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container object for sharing a value across contexts.
|
||||||
|
*
|
||||||
|
* A shared object is a container that stores an object inside shared memory.
|
||||||
|
* The object can be accessed and mutated by any thread or process. The shared
|
||||||
|
* object handle itself is serializable and can be sent to any thread or process
|
||||||
|
* to give access to the value that is shared in the container.
|
||||||
|
*
|
||||||
|
* Because each shared object uses its own shared memory segment, it is much
|
||||||
|
* more efficient to store a larger object containing many values inside a
|
||||||
|
* single shared container than to use many small shared containers.
|
||||||
|
*
|
||||||
|
* Note that accessing a shared object is not atomic. Access to a shared object
|
||||||
|
* should be protected with a mutex to preserve data integrity.
|
||||||
|
*
|
||||||
|
* When used with forking, the object must be created prior to forking for both
|
||||||
|
* processes to access the synchronized object.
|
||||||
|
*
|
||||||
|
* @see http://php.net/manual/en/book.shmop.php The shared memory extension.
|
||||||
|
* @see http://man7.org/linux/man-pages/man2/shmctl.2.html How shared memory works on Linux.
|
||||||
|
* @see https://msdn.microsoft.com/en-us/library/ms810613.aspx How shared memory works on Windows.
|
||||||
|
*/
|
||||||
|
final class SharedMemoryParcel implements Parcel
|
||||||
|
{
|
||||||
|
/** @var int The byte offset to the start of the object data in memory. */
|
||||||
|
const MEM_DATA_OFFSET = 7;
|
||||||
|
|
||||||
|
// A list of valid states the object can be in.
|
||||||
|
const STATE_UNALLOCATED = 0;
|
||||||
|
const STATE_ALLOCATED = 1;
|
||||||
|
const STATE_MOVED = 2;
|
||||||
|
const STATE_FREED = 3;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/** @var int The shared memory segment key. */
|
||||||
|
private $key;
|
||||||
|
|
||||||
|
/** @var PosixSemaphore A semaphore for synchronizing on the parcel. */
|
||||||
|
private $semaphore;
|
||||||
|
|
||||||
|
/** @var resource|null An open handle to the shared memory segment. */
|
||||||
|
private $handle;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $initializer = 0;
|
||||||
|
|
||||||
|
/** @var Serializer */
|
||||||
|
private $serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $id
|
||||||
|
* @param mixed $value
|
||||||
|
* @param int $size The initial size in bytes of the shared memory segment. It will automatically be expanded as
|
||||||
|
* necessary.
|
||||||
|
* @param int $permissions Permissions to access the semaphore. Use file permission format specified as 0xxx.
|
||||||
|
* @param Serializer|null $serializer
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*
|
||||||
|
* @throws SharedMemoryException
|
||||||
|
* @throws SyncException
|
||||||
|
* @throws \Error If the size or permissions are invalid.
|
||||||
|
*/
|
||||||
|
public static function create(
|
||||||
|
string $id,
|
||||||
|
$value,
|
||||||
|
int $size = 8192,
|
||||||
|
int $permissions = 0600,
|
||||||
|
?Serializer $serializer = null
|
||||||
|
): self {
|
||||||
|
$parcel = new self($id, $serializer);
|
||||||
|
$parcel->init($value, $size, $permissions);
|
||||||
|
return $parcel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $id
|
||||||
|
* @param Serializer|null $serializer
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*
|
||||||
|
* @throws SharedMemoryException
|
||||||
|
*/
|
||||||
|
public static function use(string $id, ?Serializer $serializer = null): self
|
||||||
|
{
|
||||||
|
$parcel = new self($id, $serializer);
|
||||||
|
$parcel->open();
|
||||||
|
return $parcel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $id
|
||||||
|
* @param Serializer|null $serializer
|
||||||
|
*/
|
||||||
|
private function __construct(string $id, ?Serializer $serializer = null)
|
||||||
|
{
|
||||||
|
if (!\extension_loaded("shmop")) {
|
||||||
|
throw new \Error(__CLASS__ . " requires the shmop extension");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->id = $id;
|
||||||
|
$this->key = self::makeKey($this->id);
|
||||||
|
$this->serializer = $serializer ?? new NativeSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $value
|
||||||
|
* @param int $size
|
||||||
|
* @param int $permissions
|
||||||
|
*
|
||||||
|
* @throws SharedMemoryException
|
||||||
|
* @throws SyncException
|
||||||
|
* @throws \Error If the size or permissions are invalid.
|
||||||
|
*/
|
||||||
|
private function init($value, int $size = 8192, int $permissions = 0600): void
|
||||||
|
{
|
||||||
|
if ($size <= 0) {
|
||||||
|
throw new \Error('The memory size must be greater than 0');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($permissions <= 0 || $permissions > 0777) {
|
||||||
|
throw new \Error('Invalid permissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->semaphore = PosixSemaphore::create($this->id, 1);
|
||||||
|
$this->initializer = \getmypid();
|
||||||
|
|
||||||
|
$this->memOpen($this->key, 'n', $permissions, $size + self::MEM_DATA_OFFSET);
|
||||||
|
$this->setHeader(self::STATE_ALLOCATED, 0, $permissions);
|
||||||
|
$this->wrap($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function open(): void
|
||||||
|
{
|
||||||
|
$this->semaphore = PosixSemaphore::use($this->id);
|
||||||
|
$this->memOpen($this->key, 'w', 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the object has been freed.
|
||||||
|
*
|
||||||
|
* Note that this does not check if the object has been destroyed; it only
|
||||||
|
* checks if this handle has freed its reference to the object.
|
||||||
|
*
|
||||||
|
* @return bool True if the object is freed, otherwise false.
|
||||||
|
*/
|
||||||
|
private function isFreed(): bool
|
||||||
|
{
|
||||||
|
// If we are no longer connected to the memory segment, check if it has
|
||||||
|
// been invalidated.
|
||||||
|
if ($this->handle !== null) {
|
||||||
|
$this->handleMovedMemory();
|
||||||
|
$header = $this->getHeader();
|
||||||
|
return $header['state'] === static::STATE_FREED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function unwrap(): Promise
|
||||||
|
{
|
||||||
|
return call(function () {
|
||||||
|
$lock = yield $this->semaphore->acquire();
|
||||||
|
\assert($lock instanceof Lock);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $this->getValue();
|
||||||
|
} finally {
|
||||||
|
$lock->release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*
|
||||||
|
* @throws SharedMemoryException
|
||||||
|
* @throws SerializationException
|
||||||
|
*/
|
||||||
|
private function getValue()
|
||||||
|
{
|
||||||
|
if ($this->isFreed()) {
|
||||||
|
throw new SharedMemoryException('The object has already been freed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = $this->getHeader();
|
||||||
|
|
||||||
|
// Make sure the header is in a valid state and format.
|
||||||
|
if ($header['state'] !== self::STATE_ALLOCATED || $header['size'] <= 0) {
|
||||||
|
throw new SharedMemoryException('Shared object memory is corrupt');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the actual value data from memory and unserialize it.
|
||||||
|
$data = $this->memGet(self::MEM_DATA_OFFSET, $header['size']);
|
||||||
|
return $this->serializer->unserialize($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the value requires more memory to store than currently allocated, a
|
||||||
|
* new shared memory segment will be allocated with a larger size to store
|
||||||
|
* the value in. The previous memory segment will be cleaned up and marked
|
||||||
|
* for deletion. Other processes and threads will be notified of the new
|
||||||
|
* memory segment on the next read attempt. Once all running processes and
|
||||||
|
* threads disconnect from the old segment, it will be freed by the OS.
|
||||||
|
*/
|
||||||
|
private function wrap($value): void
|
||||||
|
{
|
||||||
|
if ($this->isFreed()) {
|
||||||
|
throw new SharedMemoryException('The object has already been freed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$serialized = $this->serializer->serialize($value);
|
||||||
|
$size = \strlen($serialized);
|
||||||
|
$header = $this->getHeader();
|
||||||
|
|
||||||
|
/* If we run out of space, we need to allocate a new shared memory
|
||||||
|
segment that is larger than the current one. To coordinate with other
|
||||||
|
processes, we will leave a message in the old segment that the segment
|
||||||
|
has moved and along with the new key. The old segment will be discarded
|
||||||
|
automatically after all other processes notice the change and close
|
||||||
|
the old handle.
|
||||||
|
*/
|
||||||
|
if (\shmop_size($this->handle) < $size + self::MEM_DATA_OFFSET) {
|
||||||
|
$this->key = $this->key < 0xffffffff ? $this->key + 1 : \random_int(0x10, 0xfffffffe);
|
||||||
|
$this->setHeader(self::STATE_MOVED, $this->key, 0);
|
||||||
|
|
||||||
|
$this->memDelete();
|
||||||
|
\shmop_close($this->handle);
|
||||||
|
|
||||||
|
$this->memOpen($this->key, 'n', $header['permissions'], $size * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrite the header and the serialized value to memory.
|
||||||
|
$this->setHeader(self::STATE_ALLOCATED, $size, $header['permissions']);
|
||||||
|
$this->memSet(self::MEM_DATA_OFFSET, $serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function synchronized(callable $callback): Promise
|
||||||
|
{
|
||||||
|
return call(function () use ($callback): \Generator {
|
||||||
|
$lock = yield $this->semaphore->acquire();
|
||||||
|
\assert($lock instanceof Lock);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = yield call($callback, $this->getValue());
|
||||||
|
|
||||||
|
if ($result !== null) {
|
||||||
|
$this->wrap($result);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$lock->release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees the shared object from memory.
|
||||||
|
*
|
||||||
|
* The memory containing the shared value will be invalidated. When all
|
||||||
|
* process disconnect from the object, the shared memory block will be
|
||||||
|
* destroyed by the OS.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->initializer === 0 || $this->initializer !== \getmypid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isFreed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the memory block by setting its state to FREED.
|
||||||
|
$this->setHeader(static::STATE_FREED, 0, 0);
|
||||||
|
|
||||||
|
// Request the block to be deleted, then close our local handle.
|
||||||
|
$this->memDelete();
|
||||||
|
\shmop_close($this->handle);
|
||||||
|
$this->handle = null;
|
||||||
|
|
||||||
|
$this->semaphore = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private method to prevent cloning.
|
||||||
|
*/
|
||||||
|
private function __clone()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent serialization.
|
||||||
|
*/
|
||||||
|
public function __sleep()
|
||||||
|
{
|
||||||
|
throw new \Error('A shared memory parcel cannot be serialized!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current memory segment handle, handling any moves made on the
|
||||||
|
* data.
|
||||||
|
*
|
||||||
|
* @throws SharedMemoryException
|
||||||
|
*/
|
||||||
|
private function handleMovedMemory(): void
|
||||||
|
{
|
||||||
|
// Read from the memory block and handle moved blocks until we find the
|
||||||
|
// correct block.
|
||||||
|
while (true) {
|
||||||
|
$header = $this->getHeader();
|
||||||
|
|
||||||
|
// If the state is STATE_MOVED, the memory is stale and has been moved
|
||||||
|
// to a new location. Move handle and try to read again.
|
||||||
|
if ($header['state'] !== self::STATE_MOVED) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
\shmop_close($this->handle);
|
||||||
|
$this->key = $header['size'];
|
||||||
|
$this->memOpen($this->key, 'w', 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and returns the data header at the current memory segment.
|
||||||
|
*
|
||||||
|
* @return array An associative array of header data.
|
||||||
|
*
|
||||||
|
* @throws SharedMemoryException
|
||||||
|
*/
|
||||||
|
private function getHeader(): array
|
||||||
|
{
|
||||||
|
$data = $this->memGet(0, self::MEM_DATA_OFFSET);
|
||||||
|
return \unpack('Cstate/Lsize/Spermissions', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the header data for the current memory segment.
|
||||||
|
*
|
||||||
|
* @param int $state An object state.
|
||||||
|
* @param int $size The size of the stored data, or other value.
|
||||||
|
* @param int $permissions The permissions mask on the memory segment.
|
||||||
|
*
|
||||||
|
* @throws SharedMemoryException
|
||||||
|
*/
|
||||||
|
private function setHeader(int $state, int $size, int $permissions): void
|
||||||
|
{
|
||||||
|
$header = \pack('CLS', $state, $size, $permissions);
|
||||||
|
$this->memSet(0, $header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a shared memory handle.
|
||||||
|
*
|
||||||
|
* @param int $key The shared memory key.
|
||||||
|
* @param string $mode The mode to open the shared memory in.
|
||||||
|
* @param int $permissions Process permissions on the shared memory.
|
||||||
|
* @param int $size The size to crate the shared memory in bytes.
|
||||||
|
*
|
||||||
|
* @throws SharedMemoryException
|
||||||
|
*/
|
||||||
|
private function memOpen(int $key, string $mode, int $permissions, int $size): void
|
||||||
|
{
|
||||||
|
$handle = @\shmop_open($key, $mode, $permissions, $size);
|
||||||
|
if ($handle === false) {
|
||||||
|
$error = \error_get_last();
|
||||||
|
throw new SharedMemoryException(
|
||||||
|
'Failed to create shared memory block: ' . ($error['message'] ?? 'unknown error')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$this->handle = $handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads binary data from shared memory.
|
||||||
|
*
|
||||||
|
* @param int $offset The offset to read from.
|
||||||
|
* @param int $size The number of bytes to read.
|
||||||
|
*
|
||||||
|
* @return string The binary data at the given offset.
|
||||||
|
*
|
||||||
|
* @throws SharedMemoryException
|
||||||
|
*/
|
||||||
|
private function memGet(int $offset, int $size): string
|
||||||
|
{
|
||||||
|
$data = \shmop_read($this->handle, $offset, $size);
|
||||||
|
if ($data === false) {
|
||||||
|
$error = \error_get_last();
|
||||||
|
throw new SharedMemoryException(
|
||||||
|
'Failed to read from shared memory block: ' . ($error['message'] ?? 'unknown error')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes binary data to shared memory.
|
||||||
|
*
|
||||||
|
* @param int $offset The offset to write to.
|
||||||
|
* @param string $data The binary data to write.
|
||||||
|
*
|
||||||
|
* @throws SharedMemoryException
|
||||||
|
*/
|
||||||
|
private function memSet(int $offset, string $data): void
|
||||||
|
{
|
||||||
|
if (!\shmop_write($this->handle, $data, $offset)) {
|
||||||
|
$error = \error_get_last();
|
||||||
|
throw new SharedMemoryException(
|
||||||
|
'Failed to write to shared memory block: ' . ($error['message'] ?? 'unknown error')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the shared memory segment to be deleted.
|
||||||
|
*
|
||||||
|
* @throws SharedMemoryException
|
||||||
|
*/
|
||||||
|
private function memDelete(): void
|
||||||
|
{
|
||||||
|
if (!\shmop_delete($this->handle)) {
|
||||||
|
$error = \error_get_last();
|
||||||
|
throw new SharedMemoryException(
|
||||||
|
'Failed to discard shared memory block' . ($error['message'] ?? 'unknown error')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function makeKey(string $id): int
|
||||||
|
{
|
||||||
|
return \abs(\unpack("l", \md5($id, true))[1]);
|
||||||
|
}
|
||||||
|
}
|
7
vendor/amphp/parallel/lib/Sync/SynchronizationError.php
vendored
Normal file
7
vendor/amphp/parallel/lib/Sync/SynchronizationError.php
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
class SynchronizationError extends \Error
|
||||||
|
{
|
||||||
|
}
|
64
vendor/amphp/parallel/lib/Sync/ThreadedParcel.php
vendored
Normal file
64
vendor/amphp/parallel/lib/Sync/ThreadedParcel.php
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Success;
|
||||||
|
use Amp\Sync\ThreadedMutex;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A thread-safe container that shares a value between multiple threads.
|
||||||
|
*
|
||||||
|
* @deprecated ext-pthreads development has been halted, see https://github.com/krakjoe/pthreads/issues/929
|
||||||
|
*/
|
||||||
|
final class ThreadedParcel implements Parcel
|
||||||
|
{
|
||||||
|
/** @var ThreadedMutex */
|
||||||
|
private $mutex;
|
||||||
|
|
||||||
|
/** @var \Threaded */
|
||||||
|
private $storage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new shared object container.
|
||||||
|
*
|
||||||
|
* @param mixed $value The value to store in the container.
|
||||||
|
*/
|
||||||
|
public function __construct($value)
|
||||||
|
{
|
||||||
|
$this->mutex = new ThreadedMutex;
|
||||||
|
$this->storage = new Internal\ParcelStorage($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function unwrap(): Promise
|
||||||
|
{
|
||||||
|
return new Success($this->storage->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Amp\Promise
|
||||||
|
*/
|
||||||
|
public function synchronized(callable $callback): Promise
|
||||||
|
{
|
||||||
|
return call(function () use ($callback): \Generator {
|
||||||
|
/** @var \Amp\Sync\Lock $lock */
|
||||||
|
$lock = yield $this->mutex->acquire();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = yield call($callback, $this->storage->get());
|
||||||
|
|
||||||
|
if ($result !== null) {
|
||||||
|
$this->storage->set($result);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
$lock->release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
97
vendor/amphp/parallel/lib/Sync/functions.php
vendored
Normal file
97
vendor/amphp/parallel/lib/Sync/functions.php
vendored
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Sync;
|
||||||
|
|
||||||
|
use Amp\Serialization\SerializationException as SerializerException;
|
||||||
|
|
||||||
|
// Alias must be defined in an always-loaded file as catch blocks do not trigger the autoloader.
|
||||||
|
\class_alias(SerializerException::class, SerializationException::class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Throwable $exception
|
||||||
|
*
|
||||||
|
* @return array Serializable exception backtrace, with all function arguments flattened to strings.
|
||||||
|
*/
|
||||||
|
function flattenThrowableBacktrace(\Throwable $exception): array
|
||||||
|
{
|
||||||
|
$trace = $exception->getTrace();
|
||||||
|
|
||||||
|
foreach ($trace as &$call) {
|
||||||
|
unset($call['object']);
|
||||||
|
$call['args'] = \array_map(__NAMESPACE__ . '\\flattenArgument', $call['args'] ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $trace Backtrace produced by {@see formatFlattenedBacktrace()}.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function formatFlattenedBacktrace(array $trace): string
|
||||||
|
{
|
||||||
|
$output = [];
|
||||||
|
|
||||||
|
foreach ($trace as $index => $call) {
|
||||||
|
if (isset($call['class'])) {
|
||||||
|
$name = $call['class'] . $call['type'] . $call['function'];
|
||||||
|
} else {
|
||||||
|
$name = $call['function'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$output[] = \sprintf(
|
||||||
|
'#%d %s(%d): %s(%s)',
|
||||||
|
$index,
|
||||||
|
$call['file'] ?? '[internal function]',
|
||||||
|
$call['line'] ?? 0,
|
||||||
|
$name,
|
||||||
|
\implode(', ', $call['args'] ?? ['...'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return \implode("\n", $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $value
|
||||||
|
*
|
||||||
|
* @return string Serializable string representation of $value for backtraces.
|
||||||
|
*/
|
||||||
|
function flattenArgument($value): string
|
||||||
|
{
|
||||||
|
if ($value instanceof \Closure) {
|
||||||
|
$closureReflection = new \ReflectionFunction($value);
|
||||||
|
return \sprintf(
|
||||||
|
'Closure(%s:%s)',
|
||||||
|
$closureReflection->getFileName(),
|
||||||
|
$closureReflection->getStartLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_object($value)) {
|
||||||
|
return \sprintf('Object(%s)', \get_class($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_array($value)) {
|
||||||
|
return 'Array([' . \implode(', ', \array_map(__FUNCTION__, $value)) . '])';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_resource($value)) {
|
||||||
|
return \sprintf('Resource(%s)', \get_resource_type($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_string($value)) {
|
||||||
|
return '"' . $value . '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_null($value)) {
|
||||||
|
return 'null';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_bool($value)) {
|
||||||
|
return $value ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $value;
|
||||||
|
}
|
204
vendor/amphp/parallel/lib/Worker/BasicEnvironment.php
vendored
Normal file
204
vendor/amphp/parallel/lib/Worker/BasicEnvironment.php
vendored
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
use Amp\Loop;
|
||||||
|
use Amp\Struct;
|
||||||
|
|
||||||
|
final class BasicEnvironment implements Environment
|
||||||
|
{
|
||||||
|
/** @var array */
|
||||||
|
private $data = [];
|
||||||
|
|
||||||
|
/** @var \SplPriorityQueue */
|
||||||
|
private $queue;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $timer;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->queue = $queue = new \SplPriorityQueue;
|
||||||
|
$data = &$this->data;
|
||||||
|
|
||||||
|
$this->timer = Loop::repeat(1000, static function (string $watcherId) use ($queue, &$data): void {
|
||||||
|
$time = \time();
|
||||||
|
while (!$queue->isEmpty()) {
|
||||||
|
list($key, $expiration) = $queue->top();
|
||||||
|
|
||||||
|
if (!isset($data[$key])) {
|
||||||
|
// Item removed.
|
||||||
|
$queue->extract();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$struct = $data[$key];
|
||||||
|
|
||||||
|
if ($struct->expire === 0) {
|
||||||
|
// Item was set again without a TTL.
|
||||||
|
$queue->extract();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($struct->expire !== $expiration) {
|
||||||
|
// Expiration changed or TTL updated.
|
||||||
|
$queue->extract();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($time < $struct->expire) {
|
||||||
|
// Item at top has not expired, break out of loop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($data[$key]);
|
||||||
|
|
||||||
|
$queue->extract();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($queue->isEmpty()) {
|
||||||
|
Loop::disable($watcherId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Loop::disable($this->timer);
|
||||||
|
Loop::unreference($this->timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function exists(string $key): bool
|
||||||
|
{
|
||||||
|
return isset($this->data[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return mixed|null Returns null if the key does not exist.
|
||||||
|
*/
|
||||||
|
public function get(string $key)
|
||||||
|
{
|
||||||
|
if (!isset($this->data[$key])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$struct = $this->data[$key];
|
||||||
|
|
||||||
|
if ($struct->ttl !== null) {
|
||||||
|
$expire = \time() + $struct->ttl;
|
||||||
|
if ($struct->expire < $expire) {
|
||||||
|
$struct->expire = $expire;
|
||||||
|
$this->queue->insert([$key, $struct->expire], -$struct->expire);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $struct->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value Using null for the value deletes the key.
|
||||||
|
* @param int $ttl Number of seconds until data is automatically deleted. Use null for unlimited TTL.
|
||||||
|
*
|
||||||
|
* @throws \Error If the time-to-live is not a positive integer.
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value, int $ttl = null): void
|
||||||
|
{
|
||||||
|
if ($value === null) {
|
||||||
|
$this->delete($key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ttl !== null && $ttl <= 0) {
|
||||||
|
throw new \Error("The time-to-live must be a positive integer or null");
|
||||||
|
}
|
||||||
|
|
||||||
|
$struct = new class {
|
||||||
|
use Struct;
|
||||||
|
public $data;
|
||||||
|
public $expire = 0;
|
||||||
|
public $ttl;
|
||||||
|
};
|
||||||
|
|
||||||
|
$struct->data = $value;
|
||||||
|
|
||||||
|
if ($ttl !== null) {
|
||||||
|
$struct->ttl = $ttl;
|
||||||
|
$struct->expire = \time() + $ttl;
|
||||||
|
$this->queue->insert([$key, $struct->expire], -$struct->expire);
|
||||||
|
|
||||||
|
Loop::enable($this->timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->data[$key] = $struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
*/
|
||||||
|
public function delete(string $key): void
|
||||||
|
{
|
||||||
|
unset($this->data[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias of exists().
|
||||||
|
*
|
||||||
|
* @param $key
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function offsetExists($key): bool
|
||||||
|
{
|
||||||
|
return $this->exists($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias of get().
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function offsetGet($key)
|
||||||
|
{
|
||||||
|
return $this->get($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias of set() with $ttl = null.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function offsetSet($key, $value): void
|
||||||
|
{
|
||||||
|
$this->set($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias of delete().
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
*/
|
||||||
|
public function offsetUnset($key): void
|
||||||
|
{
|
||||||
|
$this->delete($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all values.
|
||||||
|
*/
|
||||||
|
public function clear(): void
|
||||||
|
{
|
||||||
|
$this->data = [];
|
||||||
|
|
||||||
|
Loop::disable($this->timer);
|
||||||
|
$this->queue = new \SplPriorityQueue;
|
||||||
|
}
|
||||||
|
}
|
71
vendor/amphp/parallel/lib/Worker/BootstrapWorkerFactory.php
vendored
Normal file
71
vendor/amphp/parallel/lib/Worker/BootstrapWorkerFactory.php
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
use Amp\Parallel\Context\Parallel;
|
||||||
|
use Amp\Parallel\Context\Thread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker factory that includes a custom bootstrap file after initializing the worker.
|
||||||
|
*/
|
||||||
|
final class BootstrapWorkerFactory implements WorkerFactory
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $bootstrapPath;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $className;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $bootstrapFilePath Path to custom bootstrap file.
|
||||||
|
* @param string $envClassName Name of class implementing \Amp\Parallel\Worker\Environment to instigate in each
|
||||||
|
* worker. Defaults to \Amp\Parallel\Worker\BasicEnvironment.
|
||||||
|
*
|
||||||
|
* @throws \Error If the given class name does not exist or does not implement {@see Environment}.
|
||||||
|
*/
|
||||||
|
public function __construct(string $bootstrapFilePath, string $envClassName = BasicEnvironment::class)
|
||||||
|
{
|
||||||
|
if (!\file_exists($bootstrapFilePath)) {
|
||||||
|
throw new \Error(\sprintf("No file found at autoload path given '%s'", $bootstrapFilePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\class_exists($envClassName)) {
|
||||||
|
throw new \Error(\sprintf("Invalid environment class name '%s'", $envClassName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\is_subclass_of($envClassName, Environment::class)) {
|
||||||
|
throw new \Error(\sprintf(
|
||||||
|
"The class '%s' does not implement '%s'",
|
||||||
|
$envClassName,
|
||||||
|
Environment::class
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->bootstrapPath = $bootstrapFilePath;
|
||||||
|
$this->className = $envClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* The type of worker created depends on the extensions available. If multi-threading is enabled, a WorkerThread
|
||||||
|
* will be created. If threads are not available a WorkerProcess will be created.
|
||||||
|
*/
|
||||||
|
public function create(): Worker
|
||||||
|
{
|
||||||
|
if (Parallel::isSupported()) {
|
||||||
|
return new WorkerParallel($this->className, $this->bootstrapPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Thread::isSupported()) {
|
||||||
|
return new WorkerThread($this->className, $this->bootstrapPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WorkerProcess(
|
||||||
|
$this->className,
|
||||||
|
[],
|
||||||
|
\getenv("AMP_PHP_BINARY") ?: (\defined("AMP_PHP_BINARY") ? \AMP_PHP_BINARY : null),
|
||||||
|
$this->bootstrapPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
47
vendor/amphp/parallel/lib/Worker/CallableTask.php
vendored
Normal file
47
vendor/amphp/parallel/lib/Worker/CallableTask.php
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task implementation dispatching a simple callable.
|
||||||
|
*/
|
||||||
|
final class CallableTask implements Task
|
||||||
|
{
|
||||||
|
/** @var callable */
|
||||||
|
private $callable;
|
||||||
|
|
||||||
|
/** @var mixed[] */
|
||||||
|
private $args;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $callable Callable will be serialized.
|
||||||
|
* @param mixed $args Arguments to pass to the function. Must be serializable.
|
||||||
|
*/
|
||||||
|
public function __construct(callable $callable, array $args)
|
||||||
|
{
|
||||||
|
$this->callable = $callable;
|
||||||
|
$this->args = $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(Environment $environment)
|
||||||
|
{
|
||||||
|
if ($this->callable instanceof \__PHP_Incomplete_Class) {
|
||||||
|
throw new \Error('When using a class instance as a callable, the class must be autoloadable');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_array($this->callable) && ($this->callable[0] ?? null) instanceof \__PHP_Incomplete_Class) {
|
||||||
|
throw new \Error('When using a class instance method as a callable, the class must be autoloadable');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\is_callable($this->callable)) {
|
||||||
|
$message = 'User-defined functions must be autoloadable (that is, defined in a file autoloaded by composer)';
|
||||||
|
if (\is_string($this->callable)) {
|
||||||
|
$message .= \sprintf("; unable to load function '%s'", $this->callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \Error($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($this->callable)(...$this->args);
|
||||||
|
}
|
||||||
|
}
|
267
vendor/amphp/parallel/lib/Worker/DefaultPool.php
vendored
Normal file
267
vendor/amphp/parallel/lib/Worker/DefaultPool.php
vendored
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
use Amp\Parallel\Context\StatusError;
|
||||||
|
use Amp\Promise;
|
||||||
|
use function Amp\asyncCall;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a pool of workers that can be used to execute multiple tasks asynchronously.
|
||||||
|
*
|
||||||
|
* A worker pool is a collection of worker threads that can perform multiple
|
||||||
|
* tasks simultaneously. The load on each worker is balanced such that tasks
|
||||||
|
* are completed as soon as possible and workers are used efficiently.
|
||||||
|
*/
|
||||||
|
final class DefaultPool implements Pool
|
||||||
|
{
|
||||||
|
/** @var bool Indicates if the pool is currently running. */
|
||||||
|
private $running = true;
|
||||||
|
|
||||||
|
/** @var int The maximum number of workers the pool should spawn. */
|
||||||
|
private $maxSize;
|
||||||
|
|
||||||
|
/** @var WorkerFactory A worker factory to be used to create new workers. */
|
||||||
|
private $factory;
|
||||||
|
|
||||||
|
/** @var \SplObjectStorage A collection of all workers in the pool. */
|
||||||
|
private $workers;
|
||||||
|
|
||||||
|
/** @var \SplQueue A collection of idle workers. */
|
||||||
|
private $idleWorkers;
|
||||||
|
|
||||||
|
/** @var \SplQueue A queue of workers that have been assigned to tasks. */
|
||||||
|
private $busyQueue;
|
||||||
|
|
||||||
|
/** @var \Closure */
|
||||||
|
private $push;
|
||||||
|
|
||||||
|
/** @var Promise|null */
|
||||||
|
private $exitStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new worker pool.
|
||||||
|
*
|
||||||
|
* @param int $maxSize The maximum number of workers the pool should spawn.
|
||||||
|
* Defaults to `Pool::DEFAULT_MAX_SIZE`.
|
||||||
|
* @param WorkerFactory|null $factory A worker factory to be used to create
|
||||||
|
* new workers.
|
||||||
|
*
|
||||||
|
* @throws \Error
|
||||||
|
*/
|
||||||
|
public function __construct(int $maxSize = self::DEFAULT_MAX_SIZE, WorkerFactory $factory = null)
|
||||||
|
{
|
||||||
|
if ($maxSize < 0) {
|
||||||
|
throw new \Error("Maximum size must be a non-negative integer");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->maxSize = $maxSize;
|
||||||
|
|
||||||
|
// Use the global factory if none is given.
|
||||||
|
$this->factory = $factory ?: factory();
|
||||||
|
|
||||||
|
$this->workers = new \SplObjectStorage;
|
||||||
|
$this->idleWorkers = new \SplQueue;
|
||||||
|
$this->busyQueue = new \SplQueue;
|
||||||
|
|
||||||
|
$workers = $this->workers;
|
||||||
|
$idleWorkers = $this->idleWorkers;
|
||||||
|
$busyQueue = $this->busyQueue;
|
||||||
|
|
||||||
|
$this->push = static function (Worker $worker) use ($workers, $idleWorkers, $busyQueue): void {
|
||||||
|
if (!$workers->contains($worker) || ($workers[$worker] -= 1) > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Worker is completely idle, remove from busy queue and add to idle queue.
|
||||||
|
foreach ($busyQueue as $key => $busy) {
|
||||||
|
if ($busy === $worker) {
|
||||||
|
unset($busyQueue[$key]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$idleWorkers->push($worker);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->isRunning()) {
|
||||||
|
$this->kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the pool is running.
|
||||||
|
*
|
||||||
|
* @return bool True if the pool is running, otherwise false.
|
||||||
|
*/
|
||||||
|
public function isRunning(): bool
|
||||||
|
{
|
||||||
|
return $this->running;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the pool has any idle workers.
|
||||||
|
*
|
||||||
|
* @return bool True if the pool has at least one idle worker, otherwise false.
|
||||||
|
*/
|
||||||
|
public function isIdle(): bool
|
||||||
|
{
|
||||||
|
return $this->idleWorkers->count() > 0 || $this->workers->count() === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getMaxSize(): int
|
||||||
|
{
|
||||||
|
return $this->maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getWorkerCount(): int
|
||||||
|
{
|
||||||
|
return $this->workers->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getIdleWorkerCount(): int
|
||||||
|
{
|
||||||
|
return $this->idleWorkers->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueues a {@see Task} to be executed by the worker pool.
|
||||||
|
*
|
||||||
|
* @param Task $task The task to enqueue.
|
||||||
|
*
|
||||||
|
* @return Promise<mixed> The return value of Task::run().
|
||||||
|
*
|
||||||
|
* @throws StatusError If the pool has been shutdown.
|
||||||
|
* @throws TaskFailureThrowable If the task throws an exception.
|
||||||
|
*/
|
||||||
|
public function enqueue(Task $task): Promise
|
||||||
|
{
|
||||||
|
$worker = $this->pull();
|
||||||
|
|
||||||
|
$promise = $worker->enqueue($task);
|
||||||
|
$promise->onResolve(function () use ($worker): void {
|
||||||
|
($this->push)($worker);
|
||||||
|
});
|
||||||
|
return $promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuts down the pool and all workers in it.
|
||||||
|
*
|
||||||
|
* @return Promise<int[]> Array of exit status from all workers.
|
||||||
|
*
|
||||||
|
* @throws StatusError If the pool has not been started.
|
||||||
|
*/
|
||||||
|
public function shutdown(): Promise
|
||||||
|
{
|
||||||
|
if ($this->exitStatus) {
|
||||||
|
return $this->exitStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->running = false;
|
||||||
|
|
||||||
|
$shutdowns = [];
|
||||||
|
foreach ($this->workers as $worker) {
|
||||||
|
if ($worker->isRunning()) {
|
||||||
|
$shutdowns[] = $worker->shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->exitStatus = Promise\all($shutdowns);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kills all workers in the pool and halts the worker pool.
|
||||||
|
*/
|
||||||
|
public function kill(): void
|
||||||
|
{
|
||||||
|
$this->running = false;
|
||||||
|
|
||||||
|
foreach ($this->workers as $worker) {
|
||||||
|
\assert($worker instanceof Worker);
|
||||||
|
if ($worker->isRunning()) {
|
||||||
|
$worker->kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getWorker(): Worker
|
||||||
|
{
|
||||||
|
return new Internal\PooledWorker($this->pull(), $this->push);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pulls a worker from the pool.
|
||||||
|
*
|
||||||
|
* @return Worker
|
||||||
|
* @throws StatusError
|
||||||
|
*/
|
||||||
|
private function pull(): Worker
|
||||||
|
{
|
||||||
|
if (!$this->isRunning()) {
|
||||||
|
throw new StatusError("The pool was shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if ($this->idleWorkers->isEmpty()) {
|
||||||
|
if ($this->getWorkerCount() >= $this->maxSize) {
|
||||||
|
// All possible workers busy, so shift from head (will be pushed back onto tail below).
|
||||||
|
$worker = $this->busyQueue->shift();
|
||||||
|
} else {
|
||||||
|
// Max worker count has not been reached, so create another worker.
|
||||||
|
$worker = $this->factory->create();
|
||||||
|
if (!$worker->isRunning()) {
|
||||||
|
throw new WorkerException('Worker factory did not create a viable worker');
|
||||||
|
}
|
||||||
|
$this->workers->attach($worker, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Shift a worker off the idle queue.
|
||||||
|
$worker = $this->idleWorkers->shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
\assert($worker instanceof Worker);
|
||||||
|
|
||||||
|
if ($worker->isRunning()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Worker crashed; trigger error and remove it from the pool.
|
||||||
|
|
||||||
|
asyncCall(function () use ($worker): \Generator {
|
||||||
|
try {
|
||||||
|
$code = yield $worker->shutdown();
|
||||||
|
\trigger_error('Worker in pool exited unexpectedly with code ' . $code, \E_USER_WARNING);
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
\trigger_error(
|
||||||
|
'Worker in pool crashed with exception on shutdown: ' . $exception->getMessage(),
|
||||||
|
\E_USER_WARNING
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->workers->detach($worker);
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
$this->busyQueue->push($worker);
|
||||||
|
$this->workers[$worker] += 1;
|
||||||
|
|
||||||
|
return $worker;
|
||||||
|
}
|
||||||
|
}
|
61
vendor/amphp/parallel/lib/Worker/DefaultWorkerFactory.php
vendored
Normal file
61
vendor/amphp/parallel/lib/Worker/DefaultWorkerFactory.php
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
use Amp\Parallel\Context\Parallel;
|
||||||
|
use Amp\Parallel\Context\Thread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The built-in worker factory type.
|
||||||
|
*/
|
||||||
|
final class DefaultWorkerFactory implements WorkerFactory
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $className;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $envClassName Name of class implementing \Amp\Parallel\Worker\Environment to instigate in each
|
||||||
|
* worker. Defaults to \Amp\Parallel\Worker\BasicEnvironment.
|
||||||
|
*
|
||||||
|
* @throws \Error If the given class name does not exist or does not implement {@see Environment}.
|
||||||
|
*/
|
||||||
|
public function __construct(string $envClassName = BasicEnvironment::class)
|
||||||
|
{
|
||||||
|
if (!\class_exists($envClassName)) {
|
||||||
|
throw new \Error(\sprintf("Invalid environment class name '%s'", $envClassName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\is_subclass_of($envClassName, Environment::class)) {
|
||||||
|
throw new \Error(\sprintf(
|
||||||
|
"The class '%s' does not implement '%s'",
|
||||||
|
$envClassName,
|
||||||
|
Environment::class
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->className = $envClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* The type of worker created depends on the extensions available. If multi-threading is enabled, a WorkerThread
|
||||||
|
* will be created. If threads are not available a WorkerProcess will be created.
|
||||||
|
*/
|
||||||
|
public function create(): Worker
|
||||||
|
{
|
||||||
|
if (Parallel::isSupported()) {
|
||||||
|
return new WorkerParallel($this->className);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Thread::isSupported()) {
|
||||||
|
return new WorkerThread($this->className);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WorkerProcess(
|
||||||
|
$this->className,
|
||||||
|
[],
|
||||||
|
\getenv("AMP_PHP_BINARY") ?: (\defined("AMP_PHP_BINARY") ? \AMP_PHP_BINARY : null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
37
vendor/amphp/parallel/lib/Worker/Environment.php
vendored
Normal file
37
vendor/amphp/parallel/lib/Worker/Environment.php
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
interface Environment extends \ArrayAccess
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function exists(string $key): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return mixed|null Returns null if the key does not exist.
|
||||||
|
*/
|
||||||
|
public function get(string $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value Using null for the value deletes the key.
|
||||||
|
* @param int $ttl Number of seconds until data is automatically deleted. Use null for unlimited TTL.
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value, int $ttl = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
*/
|
||||||
|
public function delete(string $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all values.
|
||||||
|
*/
|
||||||
|
public function clear();
|
||||||
|
}
|
38
vendor/amphp/parallel/lib/Worker/Internal/Job.php
vendored
Normal file
38
vendor/amphp/parallel/lib/Worker/Internal/Job.php
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker\Internal;
|
||||||
|
|
||||||
|
use Amp\Parallel\Worker\Task;
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
final class Job
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/** @var Task */
|
||||||
|
private $task;
|
||||||
|
|
||||||
|
public function __construct(Task $task)
|
||||||
|
{
|
||||||
|
static $id = 'a';
|
||||||
|
|
||||||
|
$this->task = $task;
|
||||||
|
$this->id = $id++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTask(): Task
|
||||||
|
{
|
||||||
|
// Classes that cannot be autoloaded will be unserialized as an instance of __PHP_Incomplete_Class.
|
||||||
|
if ($this->task instanceof \__PHP_Incomplete_Class) {
|
||||||
|
throw new \Error(\sprintf("Classes implementing %s must be autoloadable by the Composer autoloader", Task::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->task;
|
||||||
|
}
|
||||||
|
}
|
75
vendor/amphp/parallel/lib/Worker/Internal/PooledWorker.php
vendored
Normal file
75
vendor/amphp/parallel/lib/Worker/Internal/PooledWorker.php
vendored
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker\Internal;
|
||||||
|
|
||||||
|
use Amp\Parallel\Worker\Task;
|
||||||
|
use Amp\Parallel\Worker\Worker;
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
final class PooledWorker implements Worker
|
||||||
|
{
|
||||||
|
/** @var callable */
|
||||||
|
private $push;
|
||||||
|
|
||||||
|
/** @var Worker */
|
||||||
|
private $worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Worker $worker
|
||||||
|
* @param callable $push Callable to push the worker back into the queue.
|
||||||
|
*/
|
||||||
|
public function __construct(Worker $worker, callable $push)
|
||||||
|
{
|
||||||
|
$this->worker = $worker;
|
||||||
|
$this->push = $push;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically pushes the worker back into the queue.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
($this->push)($this->worker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isRunning(): bool
|
||||||
|
{
|
||||||
|
return $this->worker->isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isIdle(): bool
|
||||||
|
{
|
||||||
|
return $this->worker->isIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function enqueue(Task $task): Promise
|
||||||
|
{
|
||||||
|
return $this->worker->enqueue($task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function shutdown(): Promise
|
||||||
|
{
|
||||||
|
return $this->worker->shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function kill(): void
|
||||||
|
{
|
||||||
|
$this->worker->kill();
|
||||||
|
}
|
||||||
|
}
|
65
vendor/amphp/parallel/lib/Worker/Internal/TaskFailure.php
vendored
Normal file
65
vendor/amphp/parallel/lib/Worker/Internal/TaskFailure.php
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker\Internal;
|
||||||
|
|
||||||
|
use Amp\Failure;
|
||||||
|
use Amp\Parallel\Sync;
|
||||||
|
use Amp\Parallel\Worker\TaskFailureError;
|
||||||
|
use Amp\Parallel\Worker\TaskFailureException;
|
||||||
|
use Amp\Parallel\Worker\TaskFailureThrowable;
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
final class TaskFailure extends TaskResult
|
||||||
|
{
|
||||||
|
const PARENT_EXCEPTION = 0;
|
||||||
|
const PARENT_ERROR = 1;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $type;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $parent;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $message;
|
||||||
|
|
||||||
|
/** @var int|string */
|
||||||
|
private $code;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
private $trace;
|
||||||
|
|
||||||
|
/** @var self|null */
|
||||||
|
private $previous;
|
||||||
|
|
||||||
|
public function __construct(string $id, \Throwable $exception)
|
||||||
|
{
|
||||||
|
parent::__construct($id);
|
||||||
|
$this->type = \get_class($exception);
|
||||||
|
$this->parent = $exception instanceof \Error ? self::PARENT_ERROR : self::PARENT_EXCEPTION;
|
||||||
|
$this->message = $exception->getMessage();
|
||||||
|
$this->code = $exception->getCode();
|
||||||
|
$this->trace = Sync\flattenThrowableBacktrace($exception);
|
||||||
|
|
||||||
|
if ($previous = $exception->getPrevious()) {
|
||||||
|
$this->previous = new self($id, $previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function promise(): Promise
|
||||||
|
{
|
||||||
|
return new Failure($this->createException());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createException(): TaskFailureThrowable
|
||||||
|
{
|
||||||
|
$previous = $this->previous ? $this->previous->createException() : null;
|
||||||
|
|
||||||
|
if ($this->parent === self::PARENT_ERROR) {
|
||||||
|
return new TaskFailureError($this->type, $this->message, $this->code, $this->trace, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TaskFailureException($this->type, $this->message, $this->code, $this->trace, $previous);
|
||||||
|
}
|
||||||
|
}
|
33
vendor/amphp/parallel/lib/Worker/Internal/TaskResult.php
vendored
Normal file
33
vendor/amphp/parallel/lib/Worker/Internal/TaskResult.php
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker\Internal;
|
||||||
|
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
abstract class TaskResult
|
||||||
|
{
|
||||||
|
/** @var string Task identifier. */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $id Task identifier.
|
||||||
|
*/
|
||||||
|
public function __construct(string $id)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Task identifier.
|
||||||
|
*/
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Promise<mixed> Resolved with the task result or failure reason.
|
||||||
|
*/
|
||||||
|
abstract public function promise(): Promise;
|
||||||
|
}
|
33
vendor/amphp/parallel/lib/Worker/Internal/TaskSuccess.php
vendored
Normal file
33
vendor/amphp/parallel/lib/Worker/Internal/TaskSuccess.php
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker\Internal;
|
||||||
|
|
||||||
|
use Amp\Failure;
|
||||||
|
use Amp\Parallel\Worker\Task;
|
||||||
|
use Amp\Promise;
|
||||||
|
use Amp\Success;
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
final class TaskSuccess extends TaskResult
|
||||||
|
{
|
||||||
|
/** @var mixed Result of task. */
|
||||||
|
private $result;
|
||||||
|
|
||||||
|
public function __construct(string $id, $result)
|
||||||
|
{
|
||||||
|
parent::__construct($id);
|
||||||
|
$this->result = $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function promise(): Promise
|
||||||
|
{
|
||||||
|
if ($this->result instanceof \__PHP_Incomplete_Class) {
|
||||||
|
return new Failure(new \Error(\sprintf(
|
||||||
|
"Class instances returned from %s::run() must be autoloadable by the Composer autoloader",
|
||||||
|
Task::class
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Success($this->result);
|
||||||
|
}
|
||||||
|
}
|
65
vendor/amphp/parallel/lib/Worker/Internal/WorkerProcess.php
vendored
Normal file
65
vendor/amphp/parallel/lib/Worker/Internal/WorkerProcess.php
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker\Internal;
|
||||||
|
|
||||||
|
use Amp\ByteStream;
|
||||||
|
use Amp\Parallel\Context\Context;
|
||||||
|
use Amp\Parallel\Context\Process;
|
||||||
|
use Amp\Promise;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
class WorkerProcess implements Context
|
||||||
|
{
|
||||||
|
/** @var Process */
|
||||||
|
private $process;
|
||||||
|
|
||||||
|
public function __construct($script, array $env = [], string $binary = null)
|
||||||
|
{
|
||||||
|
$this->process = new Process($script, null, $env, $binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function receive(): Promise
|
||||||
|
{
|
||||||
|
return $this->process->receive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send($data): Promise
|
||||||
|
{
|
||||||
|
return $this->process->send($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isRunning(): bool
|
||||||
|
{
|
||||||
|
return $this->process->isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function start(): Promise
|
||||||
|
{
|
||||||
|
return call(function () {
|
||||||
|
$result = yield $this->process->start();
|
||||||
|
|
||||||
|
$stdout = $this->process->getStdout();
|
||||||
|
$stdout->unreference();
|
||||||
|
|
||||||
|
$stderr = $this->process->getStderr();
|
||||||
|
$stderr->unreference();
|
||||||
|
|
||||||
|
ByteStream\pipe($stdout, ByteStream\getStdout());
|
||||||
|
ByteStream\pipe($stderr, ByteStream\getStderr());
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function kill(): void
|
||||||
|
{
|
||||||
|
if ($this->process->isRunning()) {
|
||||||
|
$this->process->kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function join(): Promise
|
||||||
|
{
|
||||||
|
return $this->process->join();
|
||||||
|
}
|
||||||
|
}
|
48
vendor/amphp/parallel/lib/Worker/Internal/worker-process.php
vendored
Normal file
48
vendor/amphp/parallel/lib/Worker/Internal/worker-process.php
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker\Internal;
|
||||||
|
|
||||||
|
use Amp\Parallel\Sync;
|
||||||
|
use Amp\Parallel\Worker;
|
||||||
|
use Amp\Promise;
|
||||||
|
|
||||||
|
return function (Sync\Channel $channel) use ($argc, $argv): Promise {
|
||||||
|
if (!\defined("AMP_WORKER")) {
|
||||||
|
\define("AMP_WORKER", \AMP_CONTEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($argv[2])) {
|
||||||
|
if (!\is_file($argv[2])) {
|
||||||
|
throw new \Error(\sprintf("No file found at bootstrap file path given '%s'", $argv[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include file within closure to protect scope.
|
||||||
|
(function () use ($argc, $argv): void {
|
||||||
|
require $argv[2];
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($argv[1])) {
|
||||||
|
throw new \Error("No environment class name provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
$className = $argv[1];
|
||||||
|
|
||||||
|
if (!\class_exists($className)) {
|
||||||
|
throw new \Error(\sprintf("Invalid environment class name '%s'", $className));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\is_subclass_of($className, Worker\Environment::class)) {
|
||||||
|
throw new \Error(\sprintf(
|
||||||
|
"The class '%s' does not implement '%s'",
|
||||||
|
$className,
|
||||||
|
Worker\Environment::class
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$environment = new $className;
|
||||||
|
|
||||||
|
$runner = new Worker\TaskRunner($channel, $environment);
|
||||||
|
|
||||||
|
return $runner->run();
|
||||||
|
};
|
43
vendor/amphp/parallel/lib/Worker/Pool.php
vendored
Normal file
43
vendor/amphp/parallel/lib/Worker/Pool.php
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for worker pools.
|
||||||
|
*/
|
||||||
|
interface Pool extends Worker
|
||||||
|
{
|
||||||
|
/** @var int The default maximum pool size. */
|
||||||
|
const DEFAULT_MAX_SIZE = 32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a worker from the pool. The worker is marked as busy and will only be reused if the pool runs out of
|
||||||
|
* idle workers. The worker will be automatically marked as idle once no references to the returned worker remain.
|
||||||
|
*
|
||||||
|
* @return \Amp\Parallel\Worker\Worker
|
||||||
|
*
|
||||||
|
* @throws \Amp\Parallel\Context\StatusError If the queue is not running.
|
||||||
|
*/
|
||||||
|
public function getWorker(): Worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number of workers currently running in the pool.
|
||||||
|
*
|
||||||
|
* @return int The number of workers.
|
||||||
|
*/
|
||||||
|
public function getWorkerCount(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number of workers that are currently idle.
|
||||||
|
*
|
||||||
|
* @return int The number of idle workers.
|
||||||
|
*/
|
||||||
|
public function getIdleWorkerCount(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the maximum number of workers the pool may spawn to handle concurrent tasks.
|
||||||
|
*
|
||||||
|
* @return int The maximum number of workers.
|
||||||
|
*/
|
||||||
|
public function getMaxSize(): int;
|
||||||
|
}
|
20
vendor/amphp/parallel/lib/Worker/Task.php
vendored
Normal file
20
vendor/amphp/parallel/lib/Worker/Task.php
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A runnable unit of execution.
|
||||||
|
*/
|
||||||
|
interface Task
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Runs the task inside the caller's context.
|
||||||
|
*
|
||||||
|
* Does not have to be a coroutine, can also be a regular function returning a value.
|
||||||
|
*
|
||||||
|
* @param \Amp\Parallel\Worker\Environment
|
||||||
|
*
|
||||||
|
* @return mixed|\Amp\Promise|\Generator
|
||||||
|
*/
|
||||||
|
public function run(Environment $environment);
|
||||||
|
}
|
53
vendor/amphp/parallel/lib/Worker/TaskError.php
vendored
Normal file
53
vendor/amphp/parallel/lib/Worker/TaskError.php
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated TaskFailureError will be thrown from failed Tasks instead of this class.
|
||||||
|
*/
|
||||||
|
class TaskError extends \Error
|
||||||
|
{
|
||||||
|
/** @var string Class name of error thrown from task. */
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
/** @var string Stack trace of the error thrown from task. */
|
||||||
|
private $trace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name The exception class name.
|
||||||
|
* @param string $message The panic message.
|
||||||
|
* @param string $trace The panic stack trace.
|
||||||
|
* @param \Throwable|null $previous Previous exception.
|
||||||
|
*/
|
||||||
|
public function __construct(string $name, string $message = '', string $trace = '', ?\Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct($message, 0, $previous);
|
||||||
|
|
||||||
|
$this->name = $name;
|
||||||
|
$this->trace = $trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use TaskFailureThrowable::getOriginalClassName() instead.
|
||||||
|
*
|
||||||
|
* Returns the class name of the error thrown from the task.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use TaskFailureThrowable::getOriginalTraceAsString() instead.
|
||||||
|
*
|
||||||
|
* Gets the stack trace at the point the error was thrown in the task.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getWorkerTrace(): string
|
||||||
|
{
|
||||||
|
return $this->trace;
|
||||||
|
}
|
||||||
|
}
|
53
vendor/amphp/parallel/lib/Worker/TaskException.php
vendored
Normal file
53
vendor/amphp/parallel/lib/Worker/TaskException.php
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated TaskFailureException will be thrown from failed Tasks instead of this class.
|
||||||
|
*/
|
||||||
|
class TaskException extends \Exception
|
||||||
|
{
|
||||||
|
/** @var string Class name of exception thrown from task. */
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
/** @var string Stack trace of the exception thrown from task. */
|
||||||
|
private $trace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name The exception class name.
|
||||||
|
* @param string $message The panic message.
|
||||||
|
* @param string $trace The panic stack trace.
|
||||||
|
* @param \Throwable|null $previous Previous exception.
|
||||||
|
*/
|
||||||
|
public function __construct(string $name, string $message = '', string $trace = '', ?\Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct($message, 0, $previous);
|
||||||
|
|
||||||
|
$this->name = $name;
|
||||||
|
$this->trace = $trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use TaskFailureThrowable::getOriginalClassName() instead.
|
||||||
|
*
|
||||||
|
* Returns the class name of the exception thrown from the task.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use TaskFailureThrowable::getOriginalTraceAsString() instead.
|
||||||
|
*
|
||||||
|
* Gets the stack trace at the point the exception was thrown in the task.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getWorkerTrace(): string
|
||||||
|
{
|
||||||
|
return $this->trace;
|
||||||
|
}
|
||||||
|
}
|
86
vendor/amphp/parallel/lib/Worker/TaskFailureError.php
vendored
Normal file
86
vendor/amphp/parallel/lib/Worker/TaskFailureError.php
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
use function Amp\Parallel\Sync\formatFlattenedBacktrace;
|
||||||
|
|
||||||
|
final class TaskFailureError extends TaskError implements TaskFailureThrowable
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $originalMessage;
|
||||||
|
|
||||||
|
/** @var int|string */
|
||||||
|
private $originalCode;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
private $originalTrace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $className Original exception class name.
|
||||||
|
* @param string $message Original exception message.
|
||||||
|
* @param int|string $code Original exception code.
|
||||||
|
* @param array $trace Backtrace generated by
|
||||||
|
* {@see \Amp\Parallel\Sync\flattenThrowableBacktrace()}.
|
||||||
|
* @param TaskFailureThrowable|null $previous Instance representing any previous exception thrown in the Task.
|
||||||
|
*/
|
||||||
|
public function __construct(string $className, string $message, $code, array $trace, ?TaskFailureThrowable $previous = null)
|
||||||
|
{
|
||||||
|
$format = 'Uncaught %s in worker with message "%s" and code "%s"; use %s::getOriginalTrace() '
|
||||||
|
. 'for the stack trace in the worker';
|
||||||
|
|
||||||
|
parent::__construct(
|
||||||
|
$className,
|
||||||
|
\sprintf($format, $className, $message, $code, self::class),
|
||||||
|
formatFlattenedBacktrace($trace),
|
||||||
|
$previous
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->originalMessage = $message;
|
||||||
|
$this->originalCode = $code;
|
||||||
|
$this->originalTrace = $trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Original exception class name.
|
||||||
|
*/
|
||||||
|
public function getOriginalClassName(): string
|
||||||
|
{
|
||||||
|
return $this->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Original exception message.
|
||||||
|
*/
|
||||||
|
public function getOriginalMessage(): string
|
||||||
|
{
|
||||||
|
return $this->originalMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|string Original exception code.
|
||||||
|
*/
|
||||||
|
public function getOriginalCode()
|
||||||
|
{
|
||||||
|
return $this->originalCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the original exception stack trace.
|
||||||
|
*
|
||||||
|
* @return array Same as {@see Throwable::getTrace()}, except all function arguments are formatted as strings.
|
||||||
|
*/
|
||||||
|
public function getOriginalTrace(): array
|
||||||
|
{
|
||||||
|
return $this->originalTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Original backtrace flattened to a human-readable string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOriginalTraceAsString(): string
|
||||||
|
{
|
||||||
|
return $this->getWorkerTrace();
|
||||||
|
}
|
||||||
|
}
|
86
vendor/amphp/parallel/lib/Worker/TaskFailureException.php
vendored
Normal file
86
vendor/amphp/parallel/lib/Worker/TaskFailureException.php
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
use function Amp\Parallel\Sync\formatFlattenedBacktrace;
|
||||||
|
|
||||||
|
final class TaskFailureException extends TaskException implements TaskFailureThrowable
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $originalMessage;
|
||||||
|
|
||||||
|
/** @var int|string */
|
||||||
|
private $originalCode;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
private $originalTrace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $className Original exception class name.
|
||||||
|
* @param string $message Original exception message.
|
||||||
|
* @param int|string $code Original exception code.
|
||||||
|
* @param array $trace Backtrace generated by
|
||||||
|
* {@see \Amp\Parallel\Sync\flattenThrowableBacktrace()}.
|
||||||
|
* @param TaskFailureThrowable|null $previous Instance representing any previous exception thrown in the Task.
|
||||||
|
*/
|
||||||
|
public function __construct(string $className, string $message, $code, array $trace, ?TaskFailureThrowable $previous = null)
|
||||||
|
{
|
||||||
|
$format = 'Uncaught %s in worker with message "%s" and code "%s"; use %s::getOriginalTrace() '
|
||||||
|
. 'for the stack trace in the worker';
|
||||||
|
|
||||||
|
parent::__construct(
|
||||||
|
$className,
|
||||||
|
\sprintf($format, $className, $message, $code, self::class),
|
||||||
|
formatFlattenedBacktrace($trace),
|
||||||
|
$previous
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->originalMessage = $message;
|
||||||
|
$this->originalCode = $code;
|
||||||
|
$this->originalTrace = $trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Original exception class name.
|
||||||
|
*/
|
||||||
|
public function getOriginalClassName(): string
|
||||||
|
{
|
||||||
|
return $this->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Original exception message.
|
||||||
|
*/
|
||||||
|
public function getOriginalMessage(): string
|
||||||
|
{
|
||||||
|
return $this->originalMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|string Original exception code.
|
||||||
|
*/
|
||||||
|
public function getOriginalCode()
|
||||||
|
{
|
||||||
|
return $this->originalCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the original exception stack trace.
|
||||||
|
*
|
||||||
|
* @return array Same as {@see Throwable::getTrace()}, except all function arguments are formatted as strings.
|
||||||
|
*/
|
||||||
|
public function getOriginalTrace(): array
|
||||||
|
{
|
||||||
|
return $this->originalTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Original backtrace flattened to a human-readable string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOriginalTraceAsString(): string
|
||||||
|
{
|
||||||
|
return $this->getWorkerTrace();
|
||||||
|
}
|
||||||
|
}
|
38
vendor/amphp/parallel/lib/Worker/TaskFailureThrowable.php
vendored
Normal file
38
vendor/amphp/parallel/lib/Worker/TaskFailureThrowable.php
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common interface for exceptions thrown when Task::run() throws an exception when being executed in a worker.
|
||||||
|
*/
|
||||||
|
interface TaskFailureThrowable extends \Throwable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string Original exception class name.
|
||||||
|
*/
|
||||||
|
public function getOriginalClassName(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Original exception message.
|
||||||
|
*/
|
||||||
|
public function getOriginalMessage(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|string Original exception code.
|
||||||
|
*/
|
||||||
|
public function getOriginalCode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the original exception stack trace.
|
||||||
|
*
|
||||||
|
* @return array Same as {@see Throwable::getTrace()}, except all function arguments are formatted as strings.
|
||||||
|
*/
|
||||||
|
public function getOriginalTrace(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Original backtrace flattened to a human-readable string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOriginalTraceAsString(): string;
|
||||||
|
}
|
68
vendor/amphp/parallel/lib/Worker/TaskRunner.php
vendored
Normal file
68
vendor/amphp/parallel/lib/Worker/TaskRunner.php
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Amp\Parallel\Worker;
|
||||||
|
|
||||||
|
use Amp\Coroutine;
|
||||||
|
use Amp\Parallel\Sync\Channel;
|
||||||
|
use Amp\Parallel\Sync\SerializationException;
|
||||||
|
use Amp\Promise;
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
|
final class TaskRunner
|
||||||
|
{
|
||||||
|
/** @var Channel */
|
||||||
|
private $channel;
|
||||||
|
|
||||||
|
/** @var Environment */
|
||||||
|
private $environment;
|
||||||
|
|
||||||
|
public function __construct(Channel $channel, Environment $environment)
|
||||||
|
{
|
||||||
|
$this->channel = $channel;
|
||||||
|
$this->environment = $environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the task runner, receiving tasks from the parent and sending the result of those tasks.
|
||||||
|
*
|
||||||
|
* @return \Amp\Promise
|
||||||
|
*/
|
||||||
|
public function run(): Promise
|
||||||
|
{
|
||||||
|
return new Coroutine($this->execute());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @coroutine
|
||||||
|
*
|
||||||
|
* @return \Generator
|
||||||
|
*/
|
||||||
|
private function execute(): \Generator
|
||||||
|
{
|
||||||
|
$job = yield $this->channel->receive();
|
||||||
|
|
||||||
|
while ($job instanceof Internal\Job) {
|
||||||
|
try {
|
||||||
|
$result = yield call([$job->getTask(), "run"], $this->environment);
|
||||||
|
$result = new Internal\TaskSuccess($job->getId(), $result);
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
$result = new Internal\TaskFailure($job->getId(), $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
$job = null; // Free memory from last job.
|
||||||
|
|
||||||
|
try {
|
||||||
|
yield $this->channel->send($result);
|
||||||
|
} catch (SerializationException $exception) {
|
||||||
|
// Could not serialize task result.
|
||||||
|
yield $this->channel->send(new Internal\TaskFailure($result->getId(), $exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = null; // Free memory from last result.
|
||||||
|
|
||||||
|
$job = yield $this->channel->receive();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $job;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue