Initial commit
This commit is contained in:
commit
a296fd61aa
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/vendor/
|
||||||
|
/composer.lock
|
||||||
|
/.idea/
|
||||||
|
/.phpstorm/
|
||||||
|
/.vscode/
|
35
bin/app.php
Executable file
35
bin/app.php
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
|
||||||
|
// Create a function we want to run asynchronously
|
||||||
|
$process = function ($i) {
|
||||||
|
$delayMicroseconds = (10 - $i) * 1000000;
|
||||||
|
usleep($delayMicroseconds);
|
||||||
|
|
||||||
|
return getmypid();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Execute the functions asynchronously - each returning a Promise
|
||||||
|
$promises = [];
|
||||||
|
foreach (range(0, 10) as $i)
|
||||||
|
$promises[] = Asynchronous::run($process, $i);
|
||||||
|
|
||||||
|
|
||||||
|
// Wait for all promises to resolve
|
||||||
|
while (count($promises) > 0) {
|
||||||
|
foreach ($promises as $index => $promise) {
|
||||||
|
if ($promise->isResolved() && !$promise->isEmpty()) {
|
||||||
|
print("Response retrieved: " . $promise->getValue() . PHP_EOL);
|
||||||
|
unset($promises[$index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
|
19
composer.json
Normal file
19
composer.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "joop/php-async",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Joop Schilder",
|
||||||
|
"email": "j.n.m.schilder@st.hanze.nl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"": ["src"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-pcntl": "*",
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-sysvshm": "*"
|
||||||
|
}
|
||||||
|
}
|
127
src/Asynchronous.php
Normal file
127
src/Asynchronous.php
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Asynchronous
|
||||||
|
*/
|
||||||
|
class Asynchronous
|
||||||
|
{
|
||||||
|
|
||||||
|
private static $instance;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $isChild = false;
|
||||||
|
|
||||||
|
private static $key = 0;
|
||||||
|
|
||||||
|
/** @var int[] */
|
||||||
|
private $children = [];
|
||||||
|
|
||||||
|
/** @var resource */
|
||||||
|
private $shm;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $shmKey;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $tempFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous constructor.
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$this->tempFile = tempnam(__DIR__ . '/../temp', 'PHP');
|
||||||
|
$this->shmKey = ftok($this->tempFile, 'a');
|
||||||
|
Promise::_setShmKey($this->shmKey);
|
||||||
|
$this->attach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
private function attach()
|
||||||
|
{
|
||||||
|
$this->shm = shm_attach($this->shmKey);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Asynchronous
|
||||||
|
*/
|
||||||
|
private static function getInstance()
|
||||||
|
{
|
||||||
|
if (is_null(self::$instance))
|
||||||
|
self::$instance = new static();
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $function
|
||||||
|
* @param mixed ...$parameters
|
||||||
|
* @return Promise|null;
|
||||||
|
*/
|
||||||
|
public static function run(callable $function, ...$parameters)
|
||||||
|
{
|
||||||
|
$instance = self::getInstance();
|
||||||
|
$pid = pcntl_fork();
|
||||||
|
|
||||||
|
if ($pid === false)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
$key = self::generatePromiseKey();
|
||||||
|
|
||||||
|
if ($pid > 0) {
|
||||||
|
$instance->children[] = $pid;
|
||||||
|
|
||||||
|
return new Promise($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
$instance->isChild = true;
|
||||||
|
$instance->attach();
|
||||||
|
try {
|
||||||
|
$response = call_user_func($function, ...$parameters);
|
||||||
|
shm_put_var($instance->shm, $key, $response ?? Promise::RESPONSE_NONE);
|
||||||
|
exit(0);
|
||||||
|
} catch (Throwable $throwable) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private static function generatePromiseKey()
|
||||||
|
{
|
||||||
|
$promiseKey = self::$key;
|
||||||
|
self::$key++;
|
||||||
|
if (self::$key > 9999999)
|
||||||
|
self::$key = 0;
|
||||||
|
|
||||||
|
return $promiseKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->isChild)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (count($this->children) > 0) {
|
||||||
|
pcntl_wait($status);
|
||||||
|
array_shift($this->children);
|
||||||
|
}
|
||||||
|
shm_remove($this->shm);
|
||||||
|
shm_detach($this->shm);
|
||||||
|
unlink($this->tempFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
72
src/Promise.php
Normal file
72
src/Promise.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
class Promise
|
||||||
|
{
|
||||||
|
public const RESPONSE_NONE = '__PROMISE_RESPONSE_NONE__';
|
||||||
|
|
||||||
|
private $shm;
|
||||||
|
private $key;
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private static $shmKey;
|
||||||
|
|
||||||
|
public static function _setShmKey(int $shmKey)
|
||||||
|
{
|
||||||
|
self::$shmKey = $shmKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise constructor.
|
||||||
|
* @param int $key
|
||||||
|
*/
|
||||||
|
public function __construct(int $key)
|
||||||
|
{
|
||||||
|
$this->key = $key;
|
||||||
|
$this->value = null;
|
||||||
|
$this->shm = shm_attach(self::$shmKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isResolved()
|
||||||
|
{
|
||||||
|
return shm_has_var($this->shm, $this->key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isEmpty()
|
||||||
|
{
|
||||||
|
return $this->getValue() === self::RESPONSE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed|null
|
||||||
|
*/
|
||||||
|
public function getValue()
|
||||||
|
{
|
||||||
|
return $this->isResolved() ? $this->resolve()->value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function resolve()
|
||||||
|
{
|
||||||
|
while (!$this->isResolved())
|
||||||
|
usleep(1000); // 1ms
|
||||||
|
|
||||||
|
$this->value = shm_get_var($this->shm, $this->key);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if (is_resource($this->shm))
|
||||||
|
shm_detach($this->shm);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user