Initial commit
This commit is contained in:
commit
86ff40274b
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/vendor/
|
||||||
|
/composer.lock
|
27
README.md
Normal file
27
README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
noccylabs/ipc
|
||||||
|
=============
|
||||||
|
|
||||||
|
This is a one-size-fits-all IPC library to facilitate communication between
|
||||||
|
threads and processes. It contains the following features:
|
||||||
|
|
||||||
|
**Core:**
|
||||||
|
|
||||||
|
- [ ] Semaphore
|
||||||
|
- [ ] Mutex
|
||||||
|
- [x] Queue
|
||||||
|
- [x] SharedMemory key-value store
|
||||||
|
- [ ] SharedMemory blocks
|
||||||
|
- [x] Signals
|
||||||
|
- [x] Locks
|
||||||
|
|
||||||
|
**High-Level:**
|
||||||
|
|
||||||
|
- [ ] EventBridge
|
||||||
|
- [ ] EventDispatcher
|
||||||
|
- [ ] InterOp/Marshalling
|
||||||
|
- [x] Asynchronous timers
|
||||||
|
|
||||||
|
**Transports:**
|
||||||
|
|
||||||
|
- [x] Stream channels
|
||||||
|
|
27
composer.json
Normal file
27
composer.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "noccylabs/ipc",
|
||||||
|
"description": "A complete set of IPC facilities",
|
||||||
|
"type": "library",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christopher Vagnetoft",
|
||||||
|
"email": "cvagnetoft@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {},
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "composer",
|
||||||
|
"url": "https://packages.noccylabs.info/composer/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/signals.stub.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"NoccyLabs\\Ipc\\": "src/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
examples/timers.php
Normal file
15
examples/timers.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__."/../vendor/autoload.php";
|
||||||
|
|
||||||
|
use NoccyLabs\Ipc\Interop\Async\Timer;
|
||||||
|
|
||||||
|
$timer1 = new Timer(function () { echo "Hello "; });
|
||||||
|
$timer2 = new Timer(function () { echo "World! "; });
|
||||||
|
|
||||||
|
for ($n = 0; $n < 1000; $n++) {
|
||||||
|
if ($n == 500) {
|
||||||
|
unset($timer2);
|
||||||
|
}
|
||||||
|
usleep(10000);
|
||||||
|
}
|
20
phpunit.xml
Normal file
20
phpunit.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/5.4/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
backupGlobals="false"
|
||||||
|
beStrictAboutCoversAnnotation="true"
|
||||||
|
beStrictAboutOutputDuringTests="true"
|
||||||
|
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||||
|
beStrictAboutTodoAnnotatedTests="true"
|
||||||
|
verbose="true">
|
||||||
|
<testsuite>
|
||||||
|
<directory suffix="Test.php">tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||||
|
<directory suffix=".php">src</directory>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
80
src/Interop/Async/Timer.php
Normal file
80
src/Interop/Async/Timer.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Interop\Async;
|
||||||
|
|
||||||
|
use NoccyLabs\Ipc\Signal\SignalHandler;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple second-granularity timer for asynchronous events
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Timer
|
||||||
|
{
|
||||||
|
private static $timers = null;
|
||||||
|
|
||||||
|
private $callback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param callable $callback
|
||||||
|
*/
|
||||||
|
public function __construct(callable $callback)
|
||||||
|
{
|
||||||
|
$this->callback = $callback;
|
||||||
|
$this->seconds = $seconds;
|
||||||
|
self::registerTimer($this->callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
self::clearTimer($this->callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a timer callback function
|
||||||
|
*
|
||||||
|
* @param callable $timer
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function registerTimer(callable $timer)
|
||||||
|
{
|
||||||
|
if (self::$timers === null) {
|
||||||
|
$handler = new SignalHandler(SIGALRM, [ __CLASS__."::onSignal" ]);
|
||||||
|
pcntl_alarm(1);
|
||||||
|
}
|
||||||
|
self::$timers[] = $timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a timer callback function
|
||||||
|
*
|
||||||
|
* @param callable $timer
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function clearTimer(callable $timer)
|
||||||
|
{
|
||||||
|
self::$timers = array_filter(self::$timers, function ($t) use ($timer) {
|
||||||
|
return $t !== $timer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle signals when the alarm fires
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function onSignal()
|
||||||
|
{
|
||||||
|
foreach (self::$timers as $timer) {
|
||||||
|
call_user_func($timer);
|
||||||
|
}
|
||||||
|
pcntl_alarm(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
13
src/Interop/Channel/ChannelInterface.php
Normal file
13
src/Interop/Channel/ChannelInterface.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Interop\Channel;
|
||||||
|
|
||||||
|
|
||||||
|
interface ChannelInterface
|
||||||
|
{
|
||||||
|
public function isOpen():bool;
|
||||||
|
|
||||||
|
public function receive();
|
||||||
|
|
||||||
|
public function send($data);
|
||||||
|
}
|
54
src/Interop/Channel/StreamChannel.php
Normal file
54
src/Interop/Channel/StreamChannel.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Interop\Channel;
|
||||||
|
|
||||||
|
|
||||||
|
class StreamChannel implements ChannelInterface
|
||||||
|
{
|
||||||
|
protected $stream;
|
||||||
|
|
||||||
|
public function __construct($stream)
|
||||||
|
{
|
||||||
|
if (!is_resource($stream)) {
|
||||||
|
if (!is_string($stream)) {
|
||||||
|
throw new \LogicException("Invalid stream");
|
||||||
|
}
|
||||||
|
$stream = stream_socket_client($stream, $errno, $errstr);
|
||||||
|
if (!$stream) {
|
||||||
|
throw new \RuntimeException(sprintf("%d %s", $errno, $errstr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stream = $stream;
|
||||||
|
|
||||||
|
$this->isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send($data)
|
||||||
|
{
|
||||||
|
fwrite($this->stream, json_encode($data)."\0");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function receive()
|
||||||
|
{
|
||||||
|
$buf = fread($this->stream, 8192);
|
||||||
|
|
||||||
|
return json_decode(rtrim($buf, "\0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isOpen(): bool
|
||||||
|
{
|
||||||
|
return is_resource($this->stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createPair()
|
||||||
|
{
|
||||||
|
$fd = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
|
||||||
|
|
||||||
|
return [
|
||||||
|
new StreamChannel($fd[0]),
|
||||||
|
new StreamChannel($fd[1])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
84
src/Key/FileKey.php
Normal file
84
src/Key/FileKey.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Key;
|
||||||
|
|
||||||
|
class FileKey implements KeyInterface
|
||||||
|
{
|
||||||
|
private $proj;
|
||||||
|
|
||||||
|
private $pathname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param string $pathname
|
||||||
|
* @param string $proj
|
||||||
|
* @param boolean $create
|
||||||
|
*/
|
||||||
|
public function __construct(string $pathname, $proj="\0", bool $create=false)
|
||||||
|
{
|
||||||
|
if (!file_exists($pathname)) {
|
||||||
|
if (!$create) {
|
||||||
|
throw new \RuntimeException("Path does not exist: {$pathname}");
|
||||||
|
}
|
||||||
|
if (!is_dir(dirname($pathname))) {
|
||||||
|
throw new \RuntimeException("The directory ".dirname($pathname)." does not exist");
|
||||||
|
}
|
||||||
|
touch($pathname);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pathname = $pathname;
|
||||||
|
$this->proj = substr($proj,0,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the pathname used to generate the key
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPathname():string
|
||||||
|
{
|
||||||
|
return $this->pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the project identifier. Not guaranteed to be printable
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getProjectIdentifier():string
|
||||||
|
{
|
||||||
|
return $this->proj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the key value
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getKey():int
|
||||||
|
{
|
||||||
|
return ftok($this->pathname, $this->proj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clone the FileKey, increasing the project identifier to create a new key for the
|
||||||
|
* same file
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __clone()
|
||||||
|
{
|
||||||
|
$this->proj = chr(ord($this->proj) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create printable representation of the key
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return $this->getKey();
|
||||||
|
}
|
||||||
|
}
|
9
src/Key/KeyInterface.php
Normal file
9
src/Key/KeyInterface.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Key;
|
||||||
|
|
||||||
|
interface KeyInterface
|
||||||
|
{
|
||||||
|
public function getKey():int;
|
||||||
|
public function __toString();
|
||||||
|
}
|
88
src/Lock/FileLock.php
Normal file
88
src/Lock/FileLock.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Lock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple filesystem-based lock
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class FileLock
|
||||||
|
{
|
||||||
|
const DEFAULT_TIMEOUT = 5;
|
||||||
|
|
||||||
|
protected $pathname;
|
||||||
|
|
||||||
|
protected $resource;
|
||||||
|
|
||||||
|
protected $locked = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param string $pathname
|
||||||
|
*/
|
||||||
|
public function __construct(string $pathname)
|
||||||
|
{
|
||||||
|
if (!file_exists($pathname)) {
|
||||||
|
throw new \RuntimeException(sprintf(
|
||||||
|
"The file %s does not exist",
|
||||||
|
$pathname
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pathname = $pathname;
|
||||||
|
$this->resource = fopen($pathname, "r");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->release();
|
||||||
|
fclose($this->resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acquire an exclusive lock
|
||||||
|
*
|
||||||
|
* @param float $timeout
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function acquire(float $timeout = self::DEFAULT_TIMEOUT):bool
|
||||||
|
{
|
||||||
|
if ($this->locked) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expire = microtime(true) + $timeout;
|
||||||
|
while (!flock($this->resource, LOCK_EX|LOCK_NB)) {
|
||||||
|
if (microtime(true)>$expire) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->locked = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release an acquired lock
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function release()
|
||||||
|
{
|
||||||
|
flock($this->resource, LOCK_UN);
|
||||||
|
$this->locked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the lock is acquired
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function isLocked():bool
|
||||||
|
{
|
||||||
|
return $this->locked;
|
||||||
|
}
|
||||||
|
}
|
87
src/Msg/Queue.php
Normal file
87
src/Msg/Queue.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Msg;
|
||||||
|
|
||||||
|
use NoccyLabs\Ipc\Key\KeyInterface;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message queue wrapper
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Queue
|
||||||
|
{
|
||||||
|
protected $resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param KeyInterface $key
|
||||||
|
* @param integer $perms
|
||||||
|
*/
|
||||||
|
public function __construct(KeyInterface $key, int $perms = 0666)
|
||||||
|
{
|
||||||
|
$this->resource = msg_get_queue($key->getKey(), $perms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send to the queue
|
||||||
|
*
|
||||||
|
* @param integer $msgtype
|
||||||
|
* @param mixed $message
|
||||||
|
* @param boolean $blocking
|
||||||
|
* @throws RuntimeException on error
|
||||||
|
*/
|
||||||
|
public function send(int $msgtype, $message, bool $blocking = true)
|
||||||
|
{
|
||||||
|
$ret = msg_send($this->resource, $msgtype, $message, true, $blocking, $errno);
|
||||||
|
|
||||||
|
if ($ret === false) {
|
||||||
|
throw new \RuntimeException(sprintf(
|
||||||
|
"msg_send error %d",
|
||||||
|
$errno
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive from the queue
|
||||||
|
*
|
||||||
|
* @param integer $desiredmsgtype
|
||||||
|
* @param integer $msgtype
|
||||||
|
* @param integer $maxsize
|
||||||
|
* @param boolean $blocking
|
||||||
|
* @return mixed
|
||||||
|
* @throws RuntimeException on error
|
||||||
|
*/
|
||||||
|
public function receive(int $desiredmsgtype, &$msgtype, int $maxsize=4096, bool $blocking = true)
|
||||||
|
{
|
||||||
|
$ret = msg_receive(
|
||||||
|
$this->resource,
|
||||||
|
$desiredmsgtype,
|
||||||
|
$msgtype,
|
||||||
|
$maxsize,
|
||||||
|
$message,
|
||||||
|
true,
|
||||||
|
$blocking?MSG_IPC_NOWAIT:0,
|
||||||
|
$errno
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($ret === false) {
|
||||||
|
throw new \RuntimeException(sprintf(
|
||||||
|
"msg_receive error %d",
|
||||||
|
$errno
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
}
|
15
src/Sem/Mutex.php
Normal file
15
src/Sem/Mutex.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Sem;
|
||||||
|
|
||||||
|
use NoccyLabs\Ipc\Key\KeyInterface;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Mutex extends Semaphore
|
||||||
|
{
|
||||||
|
public function __construct(KeyInterface $key)
|
||||||
|
{
|
||||||
|
parent::__construct($key, 1);
|
||||||
|
}
|
||||||
|
}
|
25
src/Sem/Semaphore.php
Normal file
25
src/Sem/Semaphore.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Sem;
|
||||||
|
|
||||||
|
use NoccyLabs\Ipc\Key\KeyInterface;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Semaphore
|
||||||
|
{
|
||||||
|
public function __construct(KeyInterface $key, int $max)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function acquire(float $timeout)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function release()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
133
src/Shm/SharedData.php
Normal file
133
src/Shm/SharedData.php
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Shm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared data storage with a key-value interface
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class SharedData extends SharedMemory
|
||||||
|
{
|
||||||
|
private const IDX_LOCK = 0;
|
||||||
|
private const IDX_MAP = 1;
|
||||||
|
private const IDX_DATA = 2;
|
||||||
|
|
||||||
|
private $checks = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acquire a lock
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function lock()
|
||||||
|
{
|
||||||
|
$expire = microtime(true) + 1;
|
||||||
|
while ($this[self::IDX_LOCK] !== null) {
|
||||||
|
usleep(1000);
|
||||||
|
// Forcefully continue after 1 second
|
||||||
|
if (microtime(true)>$expire) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this[self::IDX_LOCK] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release the lock
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function unlock()
|
||||||
|
{
|
||||||
|
$this[self::IDX_LOCK] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of a key
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get(string $key)
|
||||||
|
{
|
||||||
|
$map = (array)$this[self::IDX_MAP];
|
||||||
|
|
||||||
|
// If the key doesn't exist, return null
|
||||||
|
if (!array_key_exists($key, $map)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = $map[$key];
|
||||||
|
$keyvar = $this[self::IDX_DATA + $index];
|
||||||
|
|
||||||
|
// If for some reason we don't get anything back, return null
|
||||||
|
if (!$keyvar) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the checksum for later and return the value
|
||||||
|
$this->checks[$key] = $keyvar[0];
|
||||||
|
return $keyvar[1];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a key to a specific value
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value
|
||||||
|
* @param boolean $check
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value, $check=false):bool
|
||||||
|
{
|
||||||
|
// Acquire a lock and fetch the map
|
||||||
|
$this->lock();
|
||||||
|
$map = (array)$this[self::IDX_MAP];
|
||||||
|
|
||||||
|
// If the key doesn't exist in the map, we need to set it up
|
||||||
|
if (!array_key_exists($key, $map)) {
|
||||||
|
$free = 0;
|
||||||
|
// Find first free index
|
||||||
|
foreach ($map as $map_key=>$index) {
|
||||||
|
if ($index != $free) { break; }
|
||||||
|
$free++;
|
||||||
|
}
|
||||||
|
// Store the index in the map, and sort it by index.
|
||||||
|
$map[$key] = $free;
|
||||||
|
asort($map);
|
||||||
|
$this[self::IDX_MAP] = $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up the key and fetch the data
|
||||||
|
$index = $map[$key];
|
||||||
|
$keyvar = $this[self::IDX_DATA + $index];
|
||||||
|
|
||||||
|
// Check the data to make sure the data hasn't changed since last read
|
||||||
|
if ($check && $keyvar && array_key_exists($key, $this->checks)) {
|
||||||
|
if ($keyvar[0] != $this->checks[$key]) {
|
||||||
|
$this->unlock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the data
|
||||||
|
$this[self::IDX_DATA + $index] = [ md5($value), $value ];
|
||||||
|
$this->unlock();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the map contains the key. This method doesn't acquire a lock
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function contains(string $key):bool
|
||||||
|
{
|
||||||
|
$map = (array)$this[self::IDX_MAP];
|
||||||
|
|
||||||
|
return array_key_exists($key, $map);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
94
src/Shm/SharedMemory.php
Normal file
94
src/Shm/SharedMemory.php
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Shm;
|
||||||
|
|
||||||
|
use NoccyLabs\Ipc\Key\KeyInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared memory segment
|
||||||
|
*/
|
||||||
|
class SharedMemory implements \ArrayAccess
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param KeyInterface $key
|
||||||
|
* @param integer $memsize
|
||||||
|
* @param integer $perm
|
||||||
|
*/
|
||||||
|
public function __construct(KeyInterface $key, int $memsize = 64000, int $perm = 0666)
|
||||||
|
{
|
||||||
|
if (!($shm_resource = shm_attach($key->getKey(), $memsize, $perm))) {
|
||||||
|
throw new \RuntimeException("Unable to attach shm resource {$key}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->resource = $shm_resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
shm_detach($this->resource);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the shm segment
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function destroy()
|
||||||
|
{
|
||||||
|
shm_remove($this->resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @param int $offset
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function offsetExists($offset)
|
||||||
|
{
|
||||||
|
return shm_has_var($this->resource, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @param int $offset
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function offsetGet($offset)
|
||||||
|
{
|
||||||
|
return shm_has_var($this->resource, $offset)?shm_get_var($this->resource, $offset):null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @param int $offset
|
||||||
|
* @param mixed $value
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function offsetSet($offset, $value)
|
||||||
|
{
|
||||||
|
shm_put_var($this->resource, $offset, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @param int $offset
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function offsetUnset($offset)
|
||||||
|
{
|
||||||
|
shm_remove_var($this->resource, $offset);
|
||||||
|
}
|
||||||
|
}
|
99
src/Shm/SharedMemoryBlock.php
Normal file
99
src/Shm/SharedMemoryBlock.php
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Shm;
|
||||||
|
|
||||||
|
use NoccyLabs\Ipc\Key\KeyInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block of shared memory that can be read and write like a string buffer
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class SharedMemoryBlock
|
||||||
|
{
|
||||||
|
|
||||||
|
const FLAG_ACCESS = "a";
|
||||||
|
const FLAG_CREATE = "c";
|
||||||
|
const FLAG_WRITE = "w";
|
||||||
|
const FLAG_NEW = "n";
|
||||||
|
|
||||||
|
protected $resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param KeyInterface $key
|
||||||
|
* @param string $flags
|
||||||
|
* @param integer $memsize
|
||||||
|
* @param integer $perm
|
||||||
|
*/
|
||||||
|
public function __construct(KeyInterface $key, string $flags, int $memsize, int $perm = 0666)
|
||||||
|
{
|
||||||
|
if (!($shm_resource = shmop_open($key->getKey(), $memsize, $perm))) {
|
||||||
|
throw new \RuntimeException("Unable to attach shm resource {$key}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->resource = $shm_resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if (!$this->resource) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shmop_close($this->resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the memory block
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function destroy()
|
||||||
|
{
|
||||||
|
shmop_delete($this->resource);
|
||||||
|
shmop_close($this->resource);
|
||||||
|
$this->resource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read bytes from the shared block
|
||||||
|
*
|
||||||
|
* @param integer $length
|
||||||
|
* @param integer $offset
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function read($length=0, $offset=0)
|
||||||
|
{
|
||||||
|
if ($length == 0) {
|
||||||
|
$length = shmop_size($this->resource) - $offset;
|
||||||
|
}
|
||||||
|
return shmop_read($this->resource, $offset, $length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write bytes to the shared block
|
||||||
|
*
|
||||||
|
* @param mixed $data
|
||||||
|
* @param integer $offset
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function write($data, $offset=0):int
|
||||||
|
{
|
||||||
|
return shmop_write($this->resource, $data, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of the memory block
|
||||||
|
*
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function getSize():int
|
||||||
|
{
|
||||||
|
return shmop_size($this->resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
src/Signal/Signal.php
Normal file
24
src/Signal/Signal.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Signal;
|
||||||
|
|
||||||
|
|
||||||
|
class Signal
|
||||||
|
{
|
||||||
|
private $signo;
|
||||||
|
|
||||||
|
public function __construct(int $signo)
|
||||||
|
{
|
||||||
|
$this->signo = $signo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHandler(callable $handler):void
|
||||||
|
{
|
||||||
|
pcntl_signal($this->signo, $handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch($pid):bool
|
||||||
|
{
|
||||||
|
return posix_kill($pid, $this->signo);
|
||||||
|
}
|
||||||
|
}
|
37
src/Signal/SignalHandler.php
Normal file
37
src/Signal/SignalHandler.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Signal;
|
||||||
|
|
||||||
|
|
||||||
|
class SignalHandler
|
||||||
|
{
|
||||||
|
private $signo;
|
||||||
|
|
||||||
|
private $callbacks;
|
||||||
|
|
||||||
|
public function __construct(int $signo, array $callbacks=[])
|
||||||
|
{
|
||||||
|
$this->signo = $signo;
|
||||||
|
$this->callbacks = $callbacks;
|
||||||
|
pcntl_signal($this->signo, [ $this, "onSignal" ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addHandler(callable $handler):void
|
||||||
|
{
|
||||||
|
$this->callbacks[] = $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onSignal($signo)
|
||||||
|
{
|
||||||
|
foreach ($this->callbacks as $callback) {
|
||||||
|
if (call_user_func($callback) === true)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
src/Signal/SignalTrap.php
Normal file
32
src/Signal/SignalTrap.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Signal;
|
||||||
|
|
||||||
|
|
||||||
|
class SignalTrap
|
||||||
|
{
|
||||||
|
protected $signal;
|
||||||
|
|
||||||
|
protected $trapped = false;
|
||||||
|
|
||||||
|
public function __construct(int $signo)
|
||||||
|
{
|
||||||
|
$this->signal = new Signal($signo);
|
||||||
|
$this->signal->setHandler([ $this, "onSignal" ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onSignal()
|
||||||
|
{
|
||||||
|
$this->trapped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isTrapped($reset=true):bool
|
||||||
|
{
|
||||||
|
if ($this->trapped) {
|
||||||
|
$reset && ($this->trapped = false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
41
src/signals.stub.php
Normal file
41
src/signals.stub.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if (function_exists('pcntl_async_signals')) {
|
||||||
|
define("SIGNALS_ASYNC", true);
|
||||||
|
pcntl_async_signals(true);
|
||||||
|
} else {
|
||||||
|
define("SIGNALS_ASYNC", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signal shims
|
||||||
|
defined("SIGHUP") || define("SIGHUP", 1); // Hangup (POSIX)
|
||||||
|
defined("SIGINT") || define("SIGINT", 2); // Terminal interrupt (ANSI)
|
||||||
|
defined("SIGQUIT") || define("SIGQUIT", 3); // Terminal quit (POSIX)
|
||||||
|
defined("SIGILL") || define("SIGILL", 4); // Illegal instruction (ANSI)
|
||||||
|
defined("SIGTRAP") || define("SIGTRAP", 5); // Trace trap (POSIX)
|
||||||
|
defined("SIGIOT") || define("SIGIOT", 6); // IOT Trap (4.2 BSD)
|
||||||
|
defined("SIGBUS") || define("SIGBUS", 7); // BUS error (4.2 BSD)
|
||||||
|
defined("SIGFPE") || define("SIGFPE", 8); // Floating point exception (ANSI)
|
||||||
|
defined("SIGKILL") || define("SIGKILL", 9); // Kill(can't be caught or ignored) (POSIX)
|
||||||
|
defined("SIGUSR1") || define("SIGUSR1", 10); // User defined signal 1 (POSIX)
|
||||||
|
defined("SIGSEGV") || define("SIGSEGV", 11); // Invalid memory segment access (ANSI)
|
||||||
|
defined("SIGUSR2") || define("SIGUSR2", 12); // User defined signal 2 (POSIX)
|
||||||
|
defined("SIGPIPE") || define("SIGPIPE", 13); // Write on a pipe with no reader, Broken pipe (POSIX)
|
||||||
|
defined("SIGALRM") || define("SIGALRM", 14); // Alarm clock (POSIX)
|
||||||
|
defined("SIGTERM") || define("SIGTERM", 15); // Termination (ANSI)
|
||||||
|
defined("SIGSTKFLT") || define("SIGSTKFLT", 16); // Stack fault
|
||||||
|
defined("SIGCHLD") || define("SIGCHLD", 17); // Child process has stopped or exited, changed (POSIX)
|
||||||
|
defined("SIGCONT") || define("SIGCONT", 18); // Continue executing, if stopped (POSIX)
|
||||||
|
defined("SIGSTOP") || define("SIGSTOP", 19); // Stop executing(can't be caught or ignored) (POSIX)
|
||||||
|
defined("SIGTSTP") || define("SIGTSTP", 20); // Terminal stop signal (POSIX)
|
||||||
|
defined("SIGTTIN") || define("SIGTTIN", 21); // Background process trying to read, from TTY (POSIX)
|
||||||
|
defined("SIGTTOU") || define("SIGTTOU", 22); // Background process trying to write, to TTY (POSIX)
|
||||||
|
defined("SIGURG") || define("SIGURG", 23); // Urgent condition on socket (4.2 BSD)
|
||||||
|
defined("SIGXCPU") || define("SIGXCPU", 24); // CPU limit exceeded (4.2 BSD)
|
||||||
|
defined("SIGXFSZ") || define("SIGXFSZ", 25); // File size limit exceeded (4.2 BSD)
|
||||||
|
defined("SIGVTALRM") || define("SIGVTALRM", 26); // Virtual alarm clock (4.2 BSD)
|
||||||
|
defined("SIGPROF") || define("SIGPROF", 27); // Profiling alarm clock (4.2 BSD)
|
||||||
|
defined("SIGWINCH") || define("SIGWINCH", 28); // Window size change (4.3 BSD, Sun)
|
||||||
|
defined("SIGIO") || define("SIGIO", 29); // I/O now possible (4.2 BSD)
|
||||||
|
defined("SIGPWR") || define("SIGPWR", 30); // Power failure restart (System V)
|
||||||
|
|
37
tests/Interop/Channel/StreamChannelTest.php
Normal file
37
tests/Interop/Channel/StreamChannelTest.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Interop\Channel;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class StreamChannelTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function testCreatingFromStreams()
|
||||||
|
{
|
||||||
|
$fd = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
|
||||||
|
|
||||||
|
$sc1 = new StreamChannel($fd[0]);
|
||||||
|
$sc2 = new StreamChannel($fd[1]);
|
||||||
|
|
||||||
|
$frame = "Hello World";
|
||||||
|
|
||||||
|
$sc1->send($frame);
|
||||||
|
$rcvd = $sc2->receive();
|
||||||
|
|
||||||
|
$this->assertEquals($frame, $rcvd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreatingPair()
|
||||||
|
{
|
||||||
|
[ $sc1, $sc2 ] = StreamChannel::createPair();
|
||||||
|
|
||||||
|
$frame = "Hello World";
|
||||||
|
|
||||||
|
$sc1->send($frame);
|
||||||
|
$rcvd = $sc2->receive();
|
||||||
|
|
||||||
|
$this->assertEquals($frame, $rcvd);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
39
tests/Key/FileKeyTest.php
Normal file
39
tests/Key/FileKeyTest.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Key;
|
||||||
|
|
||||||
|
class FileKeyTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
public function testKeyGenerationFromExistingFile()
|
||||||
|
{
|
||||||
|
$keyfile = new FileKey(__FILE__);
|
||||||
|
|
||||||
|
$key = $keyfile->getKey();
|
||||||
|
|
||||||
|
$this->assertGreaterThan(0, $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testKeyGenerationFromTemporaryFile()
|
||||||
|
{
|
||||||
|
$tempfile = "/tmp/key.tmp";
|
||||||
|
file_exists($tempfile) && unlink($tempfile);
|
||||||
|
|
||||||
|
$keyfile = new FileKey($tempfile, "a", true);
|
||||||
|
|
||||||
|
$key = $keyfile->getKey();
|
||||||
|
|
||||||
|
$this->assertGreaterThan(0, $key);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCloningShouldIncreaseProject()
|
||||||
|
{
|
||||||
|
$keyfile = new FileKey(__FILE__, "a");
|
||||||
|
|
||||||
|
$this->assertEquals("a", $keyfile->getProjectIdentifier());
|
||||||
|
|
||||||
|
$keyfile2 = clone $keyfile;
|
||||||
|
|
||||||
|
$this->assertEquals("b", $keyfile2->getProjectIdentifier());
|
||||||
|
}
|
||||||
|
}
|
18
tests/Lock/FileLockTest.php
Normal file
18
tests/Lock/FileLockTest.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Lock;
|
||||||
|
|
||||||
|
|
||||||
|
class FileLockTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function testTheLockingShouldBeExclusive()
|
||||||
|
{
|
||||||
|
$lock1 = new FileLock(__FILE__);
|
||||||
|
$lock2 = new FileLock(__FILE__);
|
||||||
|
|
||||||
|
$this->assertEquals(true, $lock1->acquire(0));
|
||||||
|
$this->assertEquals(false, $lock2->acquire(0));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
70
tests/Msg/QueueTest.php
Normal file
70
tests/Msg/QueueTest.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Msg;
|
||||||
|
|
||||||
|
use NoccyLabs\Ipc\Key\FileKey;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class QueueTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
public function testSendingAndReceiving()
|
||||||
|
{
|
||||||
|
$key = new FileKey(__FILE__);
|
||||||
|
|
||||||
|
$queue = new Queue($key);
|
||||||
|
|
||||||
|
$queue->send(1, "Hello World");
|
||||||
|
|
||||||
|
$ret = $queue->receive(1, $type);
|
||||||
|
$this->assertEquals("Hello World", $ret);
|
||||||
|
$this->assertEquals(1, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSendingAndReceivingWithTypes()
|
||||||
|
{
|
||||||
|
$key = new FileKey(__FILE__);
|
||||||
|
|
||||||
|
$queue = new Queue($key);
|
||||||
|
|
||||||
|
$queue->send(1, "Hello World");
|
||||||
|
$queue->send(2, "Hello World");
|
||||||
|
$queue->send(3, "Hello World");
|
||||||
|
|
||||||
|
$ret = $queue->receive(1, $type);
|
||||||
|
$this->assertEquals(1, $type);
|
||||||
|
|
||||||
|
$ret = $queue->receive(2, $type);
|
||||||
|
$this->assertEquals(2, $type);
|
||||||
|
|
||||||
|
$ret = $queue->receive(3, $type);
|
||||||
|
$this->assertEquals(3, $type);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReceivingFromFront()
|
||||||
|
{
|
||||||
|
$key = new FileKey(__FILE__);
|
||||||
|
|
||||||
|
$queue = new Queue($key);
|
||||||
|
|
||||||
|
$queue->send(1, "Hello World");
|
||||||
|
$queue->send(2, "Hello World");
|
||||||
|
$queue->send(3, "Hello World");
|
||||||
|
|
||||||
|
$ret = $queue->receive(0, $type);
|
||||||
|
$this->assertEquals(1, $type);
|
||||||
|
|
||||||
|
$ret = $queue->receive(0, $type);
|
||||||
|
$this->assertEquals(2, $type);
|
||||||
|
|
||||||
|
$ret = $queue->receive(0, $type);
|
||||||
|
$this->assertEquals(3, $type);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
31
tests/Shm/SharedDataTest.php
Normal file
31
tests/Shm/SharedDataTest.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Shm;
|
||||||
|
|
||||||
|
use NoccyLabs\Ipc\Key\FileKey;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SharedDataTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
public function testOpeningSharedData()
|
||||||
|
{
|
||||||
|
$key = new FileKey(__DIR__);
|
||||||
|
$shm = new SharedData($key);
|
||||||
|
|
||||||
|
$this->assertNull($shm->get("foo"));
|
||||||
|
|
||||||
|
$this->assertTrue($shm->set("foo", "hello"));
|
||||||
|
$this->assertEquals("hello", $shm->get("foo"));
|
||||||
|
|
||||||
|
$this->assertNull($shm->get("bar"));
|
||||||
|
|
||||||
|
$this->assertTrue($shm->set("bar", "world"));
|
||||||
|
$this->assertEquals("hello", $shm->get("foo"));
|
||||||
|
$this->assertEquals("world", $shm->get("bar"));
|
||||||
|
|
||||||
|
$shm->destroy();
|
||||||
|
}
|
||||||
|
}
|
85
tests/Signal/SignalHandlerTest.php
Normal file
85
tests/Signal/SignalHandlerTest.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Signal;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SignalHandlerTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function testSignalHandlerWithSingleHandlerInConstructor()
|
||||||
|
{
|
||||||
|
$callbacks = [
|
||||||
|
function () use (&$handler1) { $handler1 = true; }
|
||||||
|
];
|
||||||
|
|
||||||
|
$handler = new SignalHandler(SIGUSR1, $callbacks);
|
||||||
|
|
||||||
|
posix_kill(posix_getpid(), SIGUSR1);
|
||||||
|
|
||||||
|
usleep(1000);
|
||||||
|
|
||||||
|
$this->assertEquals(true, $handler1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSignalHandlerWithMultipleHandlersInConstructor()
|
||||||
|
{
|
||||||
|
$callbacks = [
|
||||||
|
function () use (&$handler1) { $handler1 = true; },
|
||||||
|
function () use (&$handler2) { $handler2 = true; },
|
||||||
|
function () use (&$handler3) { $handler3 = true; }
|
||||||
|
];
|
||||||
|
|
||||||
|
$handler = new SignalHandler(SIGUSR1, $callbacks);
|
||||||
|
|
||||||
|
posix_kill(posix_getpid(), SIGUSR1);
|
||||||
|
|
||||||
|
usleep(1000);
|
||||||
|
|
||||||
|
$this->assertEquals(true, $handler1);
|
||||||
|
$this->assertEquals(true, $handler2);
|
||||||
|
$this->assertEquals(true, $handler3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSignalHandlerWithAddedHandlers()
|
||||||
|
{
|
||||||
|
$callbacks = [
|
||||||
|
function () use (&$handler1) { $handler1 = true; },
|
||||||
|
function () use (&$handler2) { $handler2 = true; }
|
||||||
|
];
|
||||||
|
|
||||||
|
$handler = new SignalHandler(SIGUSR1, $callbacks);
|
||||||
|
|
||||||
|
$handler->addHandler(function () use (&$handler3) { $handler3 = true; });
|
||||||
|
|
||||||
|
posix_kill(posix_getpid(), SIGUSR1);
|
||||||
|
|
||||||
|
usleep(1000);
|
||||||
|
|
||||||
|
$this->assertEquals(true, $handler1);
|
||||||
|
$this->assertEquals(true, $handler2);
|
||||||
|
$this->assertEquals(true, $handler3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSignalHandlerWithBlockingHandler()
|
||||||
|
{
|
||||||
|
$callbacks = [
|
||||||
|
function () use (&$handler1) { $handler1 = true; return true; },
|
||||||
|
function () use (&$handler2) { $handler2 = true; },
|
||||||
|
function () use (&$handler3) { $handler3 = true; }
|
||||||
|
];
|
||||||
|
|
||||||
|
$handler = new SignalHandler(SIGUSR1, $callbacks);
|
||||||
|
|
||||||
|
posix_kill(posix_getpid(), SIGUSR1);
|
||||||
|
|
||||||
|
usleep(1000);
|
||||||
|
|
||||||
|
$this->assertEquals(true, $handler1);
|
||||||
|
$this->assertEquals(false, $handler2);
|
||||||
|
$this->assertEquals(false, $handler3);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
40
tests/Signal/SignalTest.php
Normal file
40
tests/Signal/SignalTest.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Signal;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SignalTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function testSimpleSignalHandler()
|
||||||
|
{
|
||||||
|
$callback = function () use (&$handler1) { $handler1 = true; };
|
||||||
|
|
||||||
|
$handler = new Signal(SIGUSR1);
|
||||||
|
$handler->setHandler($callback);
|
||||||
|
|
||||||
|
posix_kill(posix_getpid(), SIGUSR1);
|
||||||
|
|
||||||
|
usleep(1000);
|
||||||
|
|
||||||
|
$this->assertEquals(true, $handler1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSimpleSignalDispatcher()
|
||||||
|
{
|
||||||
|
$callback = function () use (&$handler1) { $handler1 = true; };
|
||||||
|
|
||||||
|
$handler = new Signal(SIGUSR1);
|
||||||
|
$handler->setHandler($callback);
|
||||||
|
|
||||||
|
$handler->dispatch(posix_getpid());
|
||||||
|
|
||||||
|
usleep(1000);
|
||||||
|
|
||||||
|
$this->assertEquals(true, $handler1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
42
tests/Signal/SignalTrapTest.php
Normal file
42
tests/Signal/SignalTrapTest.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Ipc\Signal;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SignalTrapTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function testSignalTrap()
|
||||||
|
{
|
||||||
|
$trap = new SignalTrap(SIGUSR1);
|
||||||
|
|
||||||
|
$this->assertEquals(false, $trap->isTrapped());
|
||||||
|
|
||||||
|
posix_kill(posix_getpid(), SIGUSR1);
|
||||||
|
|
||||||
|
usleep(1000);
|
||||||
|
|
||||||
|
$this->assertEquals(true, $trap->isTrapped());
|
||||||
|
|
||||||
|
$this->assertEquals(false, $trap->isTrapped());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSignalTrapWithoutResetting()
|
||||||
|
{
|
||||||
|
$trap = new SignalTrap(SIGUSR1);
|
||||||
|
|
||||||
|
$this->assertEquals(false, $trap->isTrapped());
|
||||||
|
|
||||||
|
posix_kill(posix_getpid(), SIGUSR1);
|
||||||
|
|
||||||
|
usleep(1000);
|
||||||
|
|
||||||
|
$this->assertEquals(true, $trap->isTrapped(false));
|
||||||
|
|
||||||
|
$this->assertEquals(true, $trap->isTrapped());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user