Initial commit

This commit is contained in:
Joop Schilder 2019-01-16 01:10:54 +01:00
commit a296fd61aa
5 changed files with 258 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/vendor/
/composer.lock
/.idea/
/.phpstorm/
/.vscode/

35
bin/app.php Executable file
View 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
View 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
View 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
View 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);
}
}