diff --git a/.gitignore b/.gitignore index cbfa2d6..53cec81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/bin/ /vendor/ composer.lock /.idea/ diff --git a/README.md b/README.md index db5af5e..ca8958f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,74 @@ -# php-async +# joopschilder/php-async Asynchronous PHP callable processing with return values via SysV shared memory.
Requires the `php-sysvshm` extension.
-Works with PHP from version 5.3 upwards due to `shm_attach(...)` returning a resource instead of an int. +Works with PHP >= 5.3 due to `shm_attach(...)` returning a resource instead of an int.
+
+Note: This project is merely an experiment. It is, however, available on packagist. +If you think your project lacks witchcraft combined with black magic, just add this package to your `composer.json`: +```json +{ + "require": { + "joopschilder/php-async": "dev-master" + } +} +``` + + +## Examples + +### Promises +You can actually return anything that is serializable in PHP: objects, arrays, strings, you name it. +```php +resolve(); +$pid = $promise->getValue(); +printf("Me (%d) and %d have worked very hard!\n", getmypid(), $pid); +``` +The shutdown handler and destructors should take care of the rest. + + + +### Asynchronous curl requests +... though you should probably look into curl multi handles for this: curl_multi_init() on PHP.net. + +```php +isChild = true; - $instance->attachShm(); + Runtime::setChild(); + $instance->_attachToShm(); try { - $response = call_user_func_array($function, $parameters); - if (is_resource($instance->shm)) - shm_put_var($instance->shm, $key, $response ?? Promise::RESPONSE_NONE); + $response = call_user_func($function, ...$parameters); + shm_put_var($instance->shm, $key, $response ?? Promise::RESPONSE_NONE); exit(0); } catch (\Throwable $throwable) { - if (is_resource($instance->shm)) - shm_put_var($instance->shm, $key, Promise::RESPONSE_ERROR); + shm_put_var($instance->shm, $key, Promise::RESPONSE_ERROR); exit(1); } @@ -89,7 +94,7 @@ class Asynchronous /** * */ - public static function cleanup() + public static function reap() { /* * Iterate over all child process PIDs and check @@ -106,7 +111,7 @@ class Asynchronous /** * */ - public static function awaitChildren() + public static function waitForChildren() { self::getInstance()->_awaitChildren(); } @@ -142,9 +147,11 @@ class Asynchronous * Use the filename as an identifier to create the * System V IPC key. */ - $this->shmKey = ftok(__FILE__, 't'); + if ($this->shmKey == null) + $this->shmKey = ftok(__FILE__, 't'); + Promise::__setShmKey($this->shmKey); - $this->attachShm(); + $this->_attachToShm(); } /** @@ -179,12 +186,13 @@ class Asynchronous return $this; } + /** * @return $this */ - private function attachShm() + private function _attachToShm() { - $this->shm = shm_attach($this->shmKey); + $this->shm = shm_attach($this->shmKey, self::BLOCK_SIZE_BYTES); return $this; } @@ -224,7 +232,9 @@ class Asynchronous * 9.999.999 is reached (Windows limit for * shm keys). */ - self::$key = (++self::$key > 9999999) ? 0 : self::$key; + self::$key++; + if (self::$key > 99999999) + self::$key = 0; return $promiseKey; } @@ -241,7 +251,7 @@ class Asynchronous * The shutdown handler */ register_shutdown_function(function () use (&$instance) { - if ($instance->isChild) + if (Runtime::isChild()) return; $instance->_awaitChildren(); @@ -249,12 +259,16 @@ class Asynchronous }); } + /** + * + */ public function __destruct() { - /* - * To be sure - add destructor - */ - self::removeShmBlock(); + if (Runtime::isChild()) + return; + + $instance = self::getInstance(); + $instance->_removeShmBlock(); } } \ No newline at end of file diff --git a/src/Promise.php b/src/Promise.php index 0f4937e..56125ca 100644 --- a/src/Promise.php +++ b/src/Promise.php @@ -27,7 +27,6 @@ class Promise /** @var mixed|null */ private $value; - /** * @param int $shmKey */ @@ -53,11 +52,14 @@ class Promise } /** - * @return bool + * @return $this */ - public function shmValid() + private function attempt() { - return is_resource($this->shm); + if (shm_has_var($this->shm, $this->key)) + $this->value = shm_get_var($this->shm, $this->key); + + return $this; } /** @@ -65,21 +67,18 @@ class Promise */ public function isResolved() { - if ($this->shmValid()) - return shm_has_var($this->shm, $this->key); + $this->attempt(); - return true; + return !is_null($this->value); } /** * @return bool */ - public function isEmpty() + public function isVoid() { - $value = $this->getValue(); - - return $value === self::RESPONSE_NONE || $value === null; + return $this->getValue() === self::RESPONSE_NONE; } @@ -97,12 +96,7 @@ class Promise */ public function getValue() { - if ($this->shmValid()) - return $this->isResolved() ? $this->resolve()->value : null; - - $this->value = self::RESPONSE_ERROR; - - return $this->value; + return $this->isResolved() ? $this->value : null; } @@ -113,14 +107,11 @@ class Promise { /* * Actually block execution until a value is written to - * the expected location of this Promise. + * the expected memory location of this Promise. */ while (!$this->isResolved()) usleep(1000); - if (is_null($this->value) && $this->shmValid()) - $this->value = shm_get_var($this->shm, $this->key); - return $this; } @@ -137,11 +128,13 @@ class Promise * garbage collector has noticed that there are no more * references to this Promise instance. */ - if ($this->shmValid()) { - if (shm_has_var($this->shm, $this->key)) - shm_remove_var($this->shm, $this->key); + if (Runtime::isChild()) + return; + + if (shm_has_var($this->shm, $this->key)) + shm_remove_var($this->shm, $this->key); + + shm_detach($this->shm); - shm_detach($this->shm); - } } } \ No newline at end of file diff --git a/src/Runtime.php b/src/Runtime.php new file mode 100644 index 0000000..130a81d --- /dev/null +++ b/src/Runtime.php @@ -0,0 +1,55 @@ +