├── .editorconfig ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CurrentProcess.php ├── Exception ├── InvalidArgumentException.php ├── LogicException.php └── RuntimeException.php ├── Pipe ├── AbstractFifo.php ├── FifoInterface.php ├── ReadableFifo.php └── WritableFifo.php ├── Process.php ├── ProcessInterface.php ├── README.md ├── SystemV ├── IpcKeyUtils.php ├── MessageQueue.php ├── Semaphore.php └── SharedMemory.php ├── Tests ├── Pipe │ ├── ReadFifo.php │ ├── ReadableFifoTest.php │ ├── WritableFifoTest.php │ └── WriteFifo.php ├── ProcessTest.php ├── SystemV │ ├── MessageQueueTest.php │ ├── SemaphoreTest.php │ └── SharedMemoryTest.php └── Utils.php ├── composer.json └── phpunit.xml.dist /.editorconfig: -------------------------------------------------------------------------------- 1 | ; top-most EditorConfig file 2 | root = true 3 | 4 | ; Unix-style newlines 5 | [*] 6 | end_of_line = LF 7 | 8 | [*.php] 9 | indent_style = space 10 | indent_size = 4 -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.operating-system }} 15 | strategy: 16 | max-parallel: 15 17 | matrix: 18 | operating-system: [ubuntu-latest] 19 | php-versions: ['8.1', '8.2', '8.3'] 20 | name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | 26 | - name: Install PHP 27 | uses: shivammathur/setup-php@v2 28 | with: 29 | php-version: ${{ matrix.php-versions }} 30 | 31 | - name: Check PHP Version 32 | run: php -v 33 | 34 | - name: Validate composer.json and composer.lock 35 | run: composer validate --strict 36 | 37 | - name: Cache Composer packages 38 | id: composer-cache 39 | uses: actions/cache@v3 40 | with: 41 | path: vendor 42 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 43 | restore-keys: | 44 | ${{ runner.os }}-php- 45 | 46 | - name: Install dependencies 47 | run: composer install --prefer-dist --no-progress 48 | 49 | # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" 50 | # Docs: https://getcomposer.org/doc/articles/scripts.md 51 | 52 | - name: Run test suite 53 | run: ./vendor/bin/phpunit --coverage-clover coverage.xml 54 | 55 | - name: Upload coverage to Codecov 56 | uses: codecov/codecov-action@v1 57 | with: 58 | token: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .settings/ 3 | .project 4 | .buildpath 5 | composer.lock 6 | .idea 7 | tmp/ 8 | .phpunit.result.cache -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - "Tests/*" 4 | - "vendor/*" 5 | 6 | tools: 7 | external_code_coverage: 8 | timeout: 600 9 | 10 | php_sim: true 11 | 12 | php_changetracking: true 13 | 14 | php_cs_fixer: 15 | enabled: true 16 | config: 17 | level: all 18 | filter: 19 | excluded_paths: 20 | - "Tests/*" 21 | - "vendor/*" 22 | 23 | php_mess_detector: 24 | enabled: true 25 | filter: 26 | excluded_paths: 27 | - "Tests/*" 28 | - "vendor/*" 29 | 30 | php_pdepend: 31 | enabled: true 32 | filter: 33 | excluded_paths: 34 | - "Tests/*" 35 | - "vendor/*" 36 | 37 | php_analyzer: 38 | enabled: true 39 | filter: 40 | excluded_paths: 41 | - "Tests/*" 42 | - "vendor/*" 43 | 44 | 45 | php_cpd: 46 | enabled: true 47 | excluded_dirs: 48 | - "Tests/*" 49 | - "vendor/*" 50 | 51 | php_loc: 52 | enabled: true 53 | excluded_dirs: 54 | - "Tests/*" 55 | - "vendor/*" -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 8.1 5 | - 8.2 6 | - 8.3 7 | 8 | before_script: 9 | - composer install 10 | 11 | script: ./vendor/bin/phpunit --coverage-clover=coverage.xml 12 | 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) 15 | 16 | notifications: 17 | email: false -------------------------------------------------------------------------------- /CurrentProcess.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process; 14 | 15 | final class CurrentProcess 16 | { 17 | /** 18 | * @var int|null 19 | */ 20 | protected ?int $pid; 21 | 22 | public function __construct() 23 | { 24 | pcntl_async_signals(true); 25 | } 26 | 27 | /** 28 | * Returns the curren process id. 29 | * 30 | * @return int 31 | */ 32 | public function pid(): int 33 | { 34 | if (null === $this->pid) { 35 | $this->pid = posix_getpid(); 36 | } 37 | return $this->pid; 38 | } 39 | 40 | /** 41 | * Registers a callback for some signals. 42 | * 43 | * @param array|int $signals a signal or an array of signals 44 | * @param callable|int $handler 45 | * @param bool $restartSysCalls 46 | */ 47 | public function signal(array|int $signals, callable|int $handler, bool $restartSysCalls = true): void 48 | { 49 | foreach ((array)$signals as $signal) { 50 | pcntl_signal($signal, $handler, $restartSysCalls); 51 | } 52 | } 53 | 54 | /** 55 | * Gets the handler for a signal 56 | * @param int $signal 57 | * @return callable|int 58 | */ 59 | public function getSignalHandler(int $signal): callable|int 60 | { 61 | return pcntl_signal_get_handler($signal); 62 | } 63 | } -------------------------------------------------------------------------------- /Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process\Exception; 14 | 15 | class InvalidArgumentException extends \InvalidArgumentException 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Exception/LogicException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process\Exception; 14 | 15 | class LogicException extends \LogicException 16 | { 17 | } -------------------------------------------------------------------------------- /Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process\Exception; 14 | 15 | class RuntimeException extends \RuntimeException 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Pipe/AbstractFifo.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process\Pipe; 14 | 15 | use Slince\Process\Exception\InvalidArgumentException; 16 | use Slince\Process\Exception\RuntimeException; 17 | 18 | abstract class AbstractFifo implements FifoInterface 19 | { 20 | protected string $pathname; 21 | protected string $mode; 22 | protected int $permission; 23 | protected $stream; 24 | protected bool $blocking; 25 | 26 | public function __construct(string $pathname, bool $blocking, string $mode, int $permission = 0666) 27 | { 28 | if (($exists = file_exists($pathname)) && filetype($pathname) !== 'fifo') { 29 | throw new InvalidArgumentException("The file already exists, but is not a valid fifo file"); 30 | } 31 | if (!$exists && !posix_mkfifo($pathname, $permission)) { 32 | throw new RuntimeException("Cannot create the fifo file"); 33 | } 34 | $this->pathname = $pathname; 35 | $this->blocking = $blocking; 36 | $this->mode = $mode; 37 | $this->permission = $permission; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function isBlocking(): bool 44 | { 45 | return $this->blocking; 46 | } 47 | 48 | /** 49 | * Open the fifo. 50 | * 51 | * @return void 52 | */ 53 | public function open(): void 54 | { 55 | $this->stream = fopen($this->pathname, $this->mode); 56 | if (!$this->blocking) { 57 | if (false === stream_set_blocking($this->stream, false)) { 58 | throw new RuntimeException('Cannot set steam to non blocking'); 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function close(): void 67 | { 68 | is_resource($this->stream) && fclose($this->stream); 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function getStream() 75 | { 76 | if (null !== $this->stream) { 77 | return $this->stream; 78 | } 79 | $this->open(); 80 | return $this->stream; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Pipe/FifoInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process\Pipe; 14 | 15 | interface FifoInterface 16 | { 17 | /** 18 | * Returns whether the fifo is blocking 19 | * @return boolean 20 | */ 21 | public function isBlocking(): bool; 22 | 23 | /** 24 | * Gets the stream of the fifo. 25 | * @return resource 26 | */ 27 | public function getStream(); 28 | 29 | /** 30 | * Open ths fifo. 31 | * 32 | * @return void 33 | */ 34 | public function open(): void; 35 | 36 | /** 37 | * Close the fifo. 38 | * @return void 39 | */ 40 | public function close(): void; 41 | 42 | /** 43 | * Reads data from the fifo. 44 | * @param int $length The bytes that need to read. 45 | * @return string 46 | */ 47 | public function read(int $length = 1024): string; 48 | 49 | /** 50 | * Write data to the fifo. 51 | * @param string $message 52 | */ 53 | public function write(string $message): int; 54 | } 55 | -------------------------------------------------------------------------------- /Pipe/ReadableFifo.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process\Pipe; 14 | 15 | use Slince\Process\Exception\RuntimeException; 16 | 17 | class ReadableFifo extends AbstractFifo 18 | { 19 | public function __construct(string $pathname, bool $blocking = true, string $mode = 'r', int $permission = 0666) 20 | { 21 | parent::__construct($pathname, $blocking, $mode, $permission); 22 | } 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function read(int $length = 1024): string 28 | { 29 | $stream = $this->getStream(); 30 | return fread($stream, $length); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function write(string $message): int 37 | { 38 | throw new RuntimeException("Cannot write some data to an write-only fifo"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Pipe/WritableFifo.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process\Pipe; 14 | 15 | use Slince\Process\Exception\RuntimeException; 16 | 17 | class WritableFifo extends AbstractFifo 18 | { 19 | public function __construct(string $pathname, bool $blocking = true, string $mode = 'w', int $permission = 0666) 20 | { 21 | parent::__construct($pathname, $blocking, $mode, $permission); 22 | } 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function read(int $length = 1024): string 28 | { 29 | throw new RuntimeException("Cannot read data from an write-only fifo"); 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function write(string $message): int 36 | { 37 | $stream = $this->getStream(); 38 | if (false === ($bytes = fwrite($stream, $message, strlen($message)))) { 39 | throw new RuntimeException(sprintf('Cannot write message to the fifo "%s"', $this->pathname)); 40 | } 41 | return $bytes; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Process.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process; 14 | 15 | use Slince\Process\Exception\LogicException; 16 | use Slince\Process\Exception\RuntimeException; 17 | 18 | final class Process implements ProcessInterface 19 | { 20 | /** 21 | * @var string 22 | */ 23 | private string $status = self::STATUS_READY; 24 | 25 | /** 26 | * @var \Closure 27 | */ 28 | private \Closure $callback; 29 | private ?int $pid = null; 30 | private ?int $statusInfo = null; 31 | private ?int $exitCode; 32 | private ?string $exitCodeText; 33 | private ?int $stopSignal; 34 | private ?int $termSignal; 35 | 36 | private static ?CurrentProcess $currentProcess = null; 37 | 38 | public function __construct(callable $callback) 39 | { 40 | if (!function_exists('pcntl_fork')) { 41 | throw new RuntimeException('The Process class relies on ext-pcntl, which is not available on your PHP installation.'); 42 | } 43 | $this->callback = $callback; 44 | } 45 | 46 | /** 47 | * Returns the current process instance. 48 | * @return CurrentProcess 49 | */ 50 | public static function current(): CurrentProcess 51 | { 52 | if (null === self::$currentProcess) { 53 | self::$currentProcess = new CurrentProcess(); 54 | } 55 | return self::$currentProcess; 56 | } 57 | 58 | /** 59 | * Checks whether support pcntl. 60 | * @return bool 61 | */ 62 | public static function isSupported(): bool 63 | { 64 | return function_exists('pcntl_fork'); 65 | } 66 | 67 | 68 | /** 69 | * Checks whether support signal. 70 | * @return bool 71 | */ 72 | public static function isSupportPosixSignal(): bool 73 | { 74 | return function_exists('pcntl_signal'); 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function start(): void 81 | { 82 | if ($this->isRunning()) { 83 | throw new RuntimeException("The process is already running"); 84 | } 85 | $pid = \pcntl_fork(); 86 | if ($pid < 0) { 87 | throw new RuntimeException("Could not fork"); 88 | } elseif ($pid > 0) { //Records the pid of the child process 89 | $this->pid = $pid; 90 | $this->status = self::STATUS_RUNNING; 91 | } else { 92 | $this->pid = posix_getpid(); 93 | try { 94 | $exitCode = call_user_func($this->callback); 95 | } catch (\Exception $e) { 96 | $exitCode = 255; 97 | } 98 | exit(intval($exitCode)); 99 | } 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | public function wait(): void 106 | { 107 | $this->updateStatus(true); 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | public function run(): void 114 | { 115 | $this->start(); 116 | $this->wait(); 117 | } 118 | 119 | /** 120 | * {@inheritdoc} 121 | */ 122 | public function getPid(): ?int 123 | { 124 | return $this->pid; 125 | } 126 | 127 | /** 128 | * {@inheritdoc} 129 | */ 130 | public function signal(int $signal): void 131 | { 132 | if (!$this->isRunning() && !$this->isStopped()) { 133 | throw new RuntimeException("The process is not currently running or stopped"); 134 | } 135 | posix_kill($this->getPid(), $signal); 136 | } 137 | 138 | private function updateStatus(bool $blocking): void 139 | { 140 | if (!$this->isAliveStatus()) { 141 | return; 142 | } 143 | $options = $blocking ? 0 : WNOHANG | WUNTRACED; 144 | $result = pcntl_waitpid($this->getPid(), $this->statusInfo, $options); 145 | if ($result === -1) { 146 | throw new RuntimeException("Error waits on or returns the status of the process"); 147 | } elseif ($result === 0) { 148 | $this->status = self::STATUS_RUNNING; 149 | } else { 150 | if (pcntl_wifexited($this->statusInfo)) { 151 | $this->status = self::STATUS_EXITED; 152 | $this->exitCode = pcntl_wexitstatus($this->statusInfo); 153 | $this->exitCodeText = pcntl_strerror($this->exitCode); 154 | } 155 | if (pcntl_wifsignaled($this->statusInfo)) { 156 | $this->status = self::STATUS_TERMINATED; 157 | $this->termSignal = pcntl_wtermsig($this->statusInfo); 158 | } 159 | if (pcntl_wifstopped($this->statusInfo)) { 160 | $this->status = self::STATUS_STOPPED; 161 | $this->stopSignal = pcntl_wstopsig($this->statusInfo); 162 | } 163 | } 164 | } 165 | 166 | /** 167 | * Checks whether the process status is running. 168 | * @return bool 169 | */ 170 | private function isAliveStatus(): bool 171 | { 172 | return $this->status === self::STATUS_RUNNING || $this->status === self::STATUS_STOPPED; 173 | } 174 | 175 | /** 176 | * {@inheritdoc} 177 | */ 178 | public function stop(): void 179 | { 180 | $this->signal(SIGSTOP); 181 | } 182 | 183 | /** 184 | * {@inheritdoc} 185 | */ 186 | public function continue(): void 187 | { 188 | $this->signal(SIGCONT); 189 | } 190 | 191 | /** 192 | * {@inheritdoc} 193 | */ 194 | public function terminate(int $signal = SIGTERM): void 195 | { 196 | $this->signal($signal); 197 | } 198 | 199 | /** 200 | * {@inheritdoc} 201 | */ 202 | public function isRunning(): bool 203 | { 204 | $this->updateStatus(false); 205 | 206 | return self::STATUS_RUNNING === $this->status; 207 | } 208 | 209 | /** 210 | * {@inheritdoc} 211 | */ 212 | public function isStopped(): bool 213 | { 214 | $this->updateStatus(false); 215 | 216 | return self::STATUS_STOPPED === $this->status; 217 | } 218 | 219 | /** 220 | * {@inheritdoc} 221 | */ 222 | public function isTerminated(): bool 223 | { 224 | $this->updateStatus(false); 225 | 226 | return self::STATUS_TERMINATED === $this->status; 227 | } 228 | 229 | /** 230 | * {@inheritdoc} 231 | */ 232 | public function isExited(): bool 233 | { 234 | $this->updateStatus(false); 235 | 236 | return self::STATUS_EXITED === $this->status; 237 | } 238 | 239 | /** 240 | * {@inheritdoc} 241 | */ 242 | public function getStatus(): string 243 | { 244 | $this->updateStatus(false); 245 | 246 | return $this->status; 247 | } 248 | 249 | /** 250 | * Ensures the process is running or terminated, throws a LogicException if the process has a not started. 251 | * 252 | * @throws LogicException if the process has not run 253 | */ 254 | private function requireProcessIsRunning(string $functionName): void 255 | { 256 | if (!$this->isRunning()) { 257 | throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName)); 258 | } 259 | } 260 | 261 | /** 262 | * Ensures the process is terminated, throws a LogicException if the process has a status different than "stopped". 263 | * 264 | * @throws LogicException if the process is not yet terminated 265 | */ 266 | private function requireProcessIsStopped(string $functionName): void 267 | { 268 | if (!$this->isStopped()) { 269 | throw new LogicException(sprintf('Process must be stopped before calling "%s()".', $functionName)); 270 | } 271 | } 272 | 273 | /** 274 | * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated". 275 | * 276 | * @throws LogicException if the process is not yet terminated 277 | */ 278 | private function requireProcessIsTerminated(string $functionName): void 279 | { 280 | if (!$this->isTerminated()) { 281 | throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName)); 282 | } 283 | } 284 | 285 | /** 286 | * Ensures the process is terminated, throws a LogicException if the process has a status different than "exited". 287 | * 288 | * @throws LogicException if the process is not yet terminated 289 | */ 290 | private function requireProcessIsExited(string $functionName): void 291 | { 292 | if (!$this->isExited()) { 293 | throw new LogicException(sprintf('Process must be exited before calling "%s()".', $functionName)); 294 | } 295 | } 296 | 297 | /** 298 | * {@inheritdoc} 299 | */ 300 | public function getExitCode(): ?int 301 | { 302 | return $this->exitCode; 303 | } 304 | 305 | /** 306 | * {@inheritdoc} 307 | */ 308 | public function getExitCodeText(): ?string 309 | { 310 | return $this->exitCodeText; 311 | } 312 | 313 | /** 314 | * {@inheritdoc} 315 | */ 316 | public function getStopSignal(): ?int 317 | { 318 | return $this->stopSignal; 319 | } 320 | 321 | /** 322 | * {@inheritdoc} 323 | */ 324 | public function getTermSignal(): int 325 | { 326 | return $this->termSignal; 327 | } 328 | 329 | /** 330 | * {@inheritdoc} 331 | */ 332 | public function hasBeenContinued(): bool 333 | { 334 | $this->requireProcessIsRunning(__FUNCTION__); 335 | 336 | return pcntl_wifcontinued($this->statusInfo); 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /ProcessInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process; 14 | 15 | use Slince\Process\Exception\LogicException; 16 | use Slince\Process\Exception\RuntimeException; 17 | 18 | interface ProcessInterface 19 | { 20 | /** 21 | * process status,running 22 | * @var string 23 | */ 24 | const STATUS_READY = 'ready'; 25 | 26 | /** 27 | * process status,running 28 | * @var string 29 | */ 30 | const STATUS_RUNNING = 'running'; 31 | 32 | /** 33 | * process status,stopped 34 | * @var string 35 | */ 36 | const STATUS_STOPPED = 'stopped'; 37 | 38 | /** 39 | * process status,terminated 40 | * @var string 41 | */ 42 | const STATUS_TERMINATED = 'terminated'; 43 | 44 | /** 45 | * process status,exited 46 | * @var string 47 | */ 48 | const STATUS_EXITED = 'exited'; 49 | 50 | /** 51 | * Starts the process. 52 | */ 53 | public function start(): void; 54 | 55 | /** 56 | * Wait for the process exit. 57 | */ 58 | public function wait(); 59 | 60 | /** 61 | * Starts and wait the process. 62 | * @return void 63 | */ 64 | public function run(): void; 65 | 66 | /** 67 | * Stop the process. 68 | */ 69 | public function stop(): void; 70 | 71 | /** 72 | * Resume the process. 73 | */ 74 | public function continue(): void; 75 | 76 | /** 77 | * Terminate the process with an optional signal. 78 | * @param int $signal 79 | */ 80 | public function terminate(int $signal = SIGKILL); 81 | 82 | /** 83 | * Sends a POSIX signal to the process. 84 | * 85 | * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) 86 | * 87 | * @throws LogicException In case the process is not running 88 | * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed 89 | * @throws RuntimeException In case of failure 90 | */ 91 | public function signal(int $signal); 92 | 93 | /** 94 | * Gets the process id. 95 | * 96 | * @return int|null 97 | */ 98 | public function getPid(): ?int; 99 | 100 | /** 101 | * Checks whether the process is running. 102 | * 103 | * @return bool 104 | */ 105 | public function isRunning(): bool; 106 | 107 | /** 108 | * Checks if the process is stopped. 109 | * 110 | * @return bool true if process is stopped, false otherwise 111 | */ 112 | public function isStopped(): bool; 113 | 114 | /** 115 | * Checks if the process is terminated. 116 | * 117 | * @return bool true if process is terminated, false otherwise 118 | */ 119 | public function isTerminated(): bool; 120 | 121 | /** 122 | * Checks if the process is exited. 123 | * 124 | * @return bool true if process is exited, false otherwise 125 | */ 126 | public function isExited(): bool; 127 | 128 | /** 129 | * Gets the process status. 130 | * 131 | * The status is one of: ready, started, terminated. 132 | * 133 | * @return string The current process status 134 | */ 135 | public function getStatus(): string; 136 | 137 | /** 138 | * Returns the exit code returned by the process. 139 | * 140 | * @return int|null The exit status code 141 | */ 142 | public function getExitCode(): ?int; 143 | 144 | /** 145 | * Returns the exit code text returned by the process. 146 | * 147 | * @return string|null The exit status code text 148 | */ 149 | public function getExitCodeText(): ?string; 150 | 151 | /** 152 | * Returns the number of the signal that caused the child process to terminate its execution. 153 | * 154 | * It is only meaningful if hasBeenSignaled() returns true. 155 | * 156 | * @return int|null 157 | * 158 | * @throws RuntimeException In case --enable-sigchild is activated 159 | * @throws LogicException In case the process is not terminated 160 | */ 161 | public function getTermSignal(): ?int; 162 | 163 | /** 164 | * Returns the number of the signal that caused the child process to stop its execution. 165 | * 166 | * It is only meaningful if hasBeenStopped() returns true. 167 | * 168 | * @return int|null 169 | * 170 | * @throws LogicException In case the process is not terminated 171 | */ 172 | public function getStopSignal(): ?int; 173 | 174 | /** 175 | * Returns true if the child process has been continued. 176 | * 177 | * It always returns false on Windows. 178 | * 179 | * @return bool 180 | */ 181 | public function hasBeenContinued(): bool; 182 | } 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Process Library 2 | 3 | [![Build Status](https://img.shields.io/github/actions/workflow/status/slince/process/test.yml?style=flat-square)](https://github.com/slince/process/actions) 4 | [![Coverage Status](https://img.shields.io/codecov/c/github/slince/process.svg?style=flat-square)](https://codecov.io/github/slince/process) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/slince/process.svg?style=flat-square)](https://packagist.org/packages/slince/process) 6 | [![Latest Stable Version](https://img.shields.io/packagist/v/slince/process.svg?style=flat-square&label=stable)](https://packagist.org/packages/slince/process) 7 | [![Scrutinizer](https://img.shields.io/scrutinizer/g/slince/process.svg?style=flat-square)](https://scrutinizer-ci.com/g/slince/process/?branch=master) 8 | 9 | The library help to work with processes. It provides a more readable api and various modes for IPC via pipe(FIFO) and system v. 10 | 11 | # Installation 12 | 13 | Install via composer 14 | 15 | ```bash 16 | composer require slince/process 17 | ``` 18 | 19 | # Dependencies 20 | 21 | The library replies on the following php's extension. 22 | 23 | - ext-pcntl. Provides control processes (MUST) 24 | - ext-sysvshm. Porvides system v shared memory (OPTIONAL) 25 | - ext-sysvsem. Porvides system v semaphore (OPTIONAL) 26 | - ext-sysmsg. Porvides system v message queue (OPTIONAL) 27 | 28 | # Usage 29 | 30 | ## Basic usage 31 | 32 | ```php 33 | $process = new Slince\Process\Process(function(){ 34 | echo 'hello, my pid is ' . getmypid(); 35 | }); 36 | $process->start(); 37 | 38 | var_dump($process->isRunning()); // echo true 39 | var_dump($process->getPid()); // will output the pid of child process 40 | //do something other 41 | 42 | $process->wait(); //waiting for the process to exit 43 | ``` 44 | 45 | ## Sends signal to the process 46 | 47 | >Note: If your php version is less than 7.1, please add the statement `declare(ticks=1);` at the beginning of the file: 48 | 49 | ```php 50 | $process = new Slince\Process\Process(function(){ 51 | Slince\Process\Process::current()->signal([SIGUSR1, SIGUSR2], function(){ 52 | echo 'trigger signal'; 53 | }); 54 | echo 'hello, my pid is ' . getmypid(); 55 | }); 56 | $process->start(); 57 | $process->signal(SIGUSER1); 58 | //do something 59 | $process->wait(); 60 | ``` 61 | 62 | ## Shared memory 63 | 64 | ```php 65 | $memory = new Slince\Process\SystemV\SharedMemory(); 66 | $memory->set('foo', 'bar'); 67 | var_dump($memory->get('foo')); 68 | ``` 69 | The default size of shared memory is the sysvshm.init_mem in the php.ini, otherwise 10000 bytes. You can adjust this. 70 | 71 | ```php 72 | $memory = new Slince\Process\SystemV\SharedMemory(__FILE__, '5M'); //Adjusts to 5m 73 | ``` 74 | 75 | ## Semaphore 76 | ```php 77 | $semaphore = new Slince\Process\SystemV\Semaphore(); 78 | $semaphore->acquire(); //Acquires a lock 79 | // do something 80 | $semaphore->release() //Releases a lock 81 | ``` 82 | 83 | ## Message queue 84 | 85 | ```php 86 | $queue = new Slince\Process\SystemV\MessageQueue(); 87 | $queue->send('hello'); 88 | echo $queue->receive(); //Will output hello 89 | ``` 90 | 91 | ## Fifo 92 | 93 | ```php 94 | $writeFifo = new Slince\Process\Pipe\WritableFifo('/tmp/test.pipe'); 95 | $writeFifo->write('some message'); 96 | $readFifo = new Slince\Process\Pipe\ReadableFifo('/tmp/test.pipe'); 97 | echo $readFifo->read(); 98 | ``` 99 | 100 | ## License 101 | 102 | The MIT license. See [MIT](https://opensource.org/licenses/MIT) -------------------------------------------------------------------------------- /SystemV/IpcKeyUtils.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process\SystemV; 14 | 15 | use Slince\Process\Exception\RuntimeException; 16 | 17 | final class IpcKeyUtils 18 | { 19 | /** 20 | * Generates the ipc key from an existing file and a project identifier 21 | * @param string $pathname 22 | * @param string $projectId 23 | * @return int 24 | */ 25 | public static function generate(string $pathname, string $projectId = 'p'): int 26 | { 27 | if (!file_exists($pathname) && !touch($pathname)) { 28 | throw new RuntimeException(sprintf("Cannot create the files [%s]", $pathname)); 29 | } 30 | return ftok($pathname, $projectId); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SystemV/MessageQueue.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process\SystemV; 14 | 15 | use Slince\Process\Exception\RuntimeException; 16 | use SysvMessageQueue; 17 | 18 | final class MessageQueue 19 | { 20 | /** 21 | * The resource that can be used to access to the system v message queue 22 | * @var SysvMessageQueue 23 | */ 24 | protected SysvMessageQueue $mqId; 25 | 26 | public function __construct(string $pathname = __FILE__, int $mode = 0666) 27 | { 28 | $this->mqId = msg_get_queue(IpcKeyUtils::generate($pathname), $mode); 29 | } 30 | 31 | /** 32 | * Sets information for the queue 33 | * @param array $states 34 | * @return bool 35 | */ 36 | public function setStates(array $states): bool 37 | { 38 | return msg_set_queue($this->mqId, $states); 39 | } 40 | 41 | /** 42 | * Sets information for the queue by specifying the key and value 43 | * @param string $key 44 | * @param string|int $value 45 | * @return bool 46 | */ 47 | public function setState(string $key, mixed $value): bool 48 | { 49 | return $this->setStates([$key => $value]); 50 | } 51 | 52 | /** 53 | * Gets the information of the queue 54 | * 55 | * The return value is an array whose keys and values have the following meanings: 56 | * Array structure for msg_stat_queue 57 | * msg_perm.uid The uid of the owner of the queue. 58 | * msg_perm.gid The gid of the owner of the queue. 59 | * msg_perm.mode The file access mode of the queue. 60 | * msg_stime The time that the last message was sent to the queue. 61 | * msg_rtime The time that the last message was received from the queue. 62 | * msg_ctime The time that the queue was last changed. 63 | * msg_qnum The number of messages waiting to be read from the queue. 64 | * msg_qbytes The maximum number of bytes allowed in one message queue. On Linux, this value may be read and modified via /proc/sys/kernel/msgmnb. 65 | * msg_lspid The pid of the process that sent the last message to the queue. 66 | * msg_lrpid The pid of the process that received the last message from the queue. 67 | * 68 | * @return array 69 | */ 70 | public function getState(): array 71 | { 72 | return msg_stat_queue($this->mqId); 73 | } 74 | 75 | /** 76 | * Sends the message to the queue 77 | * @param string $message 78 | * @param int $messageType 79 | * @param bool $blocking 80 | * @param bool $unserialize 81 | */ 82 | public function send(string $message, int $messageType = 1, bool $blocking = true, bool $unserialize = false): void 83 | { 84 | if (!msg_send( 85 | $this->mqId, 86 | $messageType, 87 | $message, 88 | $unserialize, 89 | $blocking, 90 | $errorCode 91 | ) 92 | ) { 93 | throw new RuntimeException("Failed to send the message to the queue", $errorCode); 94 | } 95 | } 96 | 97 | /** 98 | * Gets the message from the queue 99 | * @param int $desiredMessageType 100 | * @param bool $blocking 101 | * @param int $maxSize The max size you want receive(Unit:bytes) 102 | * @param bool $unserialize 103 | * @return string|null 104 | */ 105 | public function receive(bool $blocking = true, int $desiredMessageType = 1, int $maxSize = 10240, bool $unserialize = false): ?string 106 | { 107 | $flags = $blocking ? 0 : MSG_IPC_NOWAIT; 108 | if (!msg_receive( 109 | $this->mqId, 110 | $desiredMessageType, 111 | $realMessageType, 112 | $maxSize, 113 | $message, 114 | $unserialize, 115 | $flags, 116 | $errorCode 117 | )) { 118 | if ($blocking) { 119 | throw new RuntimeException("Failed to receive message from the queue, error code: {$errorCode}", $errorCode); 120 | } 121 | } 122 | return $message ?: null; 123 | } 124 | 125 | /** 126 | * Remove the message queue 127 | * @return void 128 | */ 129 | public function destroy(): void 130 | { 131 | msg_remove_queue($this->mqId); 132 | } 133 | 134 | public function __destruct() 135 | { 136 | $this->destroy(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /SystemV/Semaphore.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process\SystemV; 14 | 15 | use Slince\Process\Exception\RuntimeException; 16 | 17 | final class Semaphore 18 | { 19 | /** 20 | * Whether the semaphore is locked 21 | * @var boolean 22 | */ 23 | protected bool $locked; 24 | 25 | /** 26 | * The resource that can be used to access the System V semaphore 27 | * @var resource 28 | */ 29 | protected $semId; 30 | 31 | public function __construct(string $pathname = __FILE__, $maxAcquireNum = 1, $permission = 0666) 32 | { 33 | if (!($this->semId = sem_get(IpcKeyUtils::generate($pathname), $maxAcquireNum, $permission))) { 34 | throw new RuntimeException("Cannot get semaphore identifier"); 35 | } 36 | } 37 | 38 | /** 39 | * Acquires the lock 40 | * @param bool $blocking 41 | * @return bool 42 | */ 43 | public function acquire(bool $blocking = true): bool 44 | { 45 | $result = sem_acquire($this->semId, !$blocking); 46 | if ($result) { 47 | $this->locked = true; 48 | } 49 | return $result; 50 | } 51 | 52 | /** 53 | * Release the lock 54 | * @return bool 55 | */ 56 | public function release(): bool 57 | { 58 | if ($this->locked && sem_release($this->semId)) { 59 | $this->locked = false; 60 | return true; 61 | } 62 | return false; 63 | } 64 | 65 | /** 66 | * Destroy semaphore 67 | * @return void 68 | */ 69 | public function destroy(): void 70 | { 71 | sem_remove($this->semId); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SystemV/SharedMemory.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | namespace Slince\Process\SystemV; 14 | 15 | use SysvSharedMemory; 16 | 17 | final class SharedMemory 18 | { 19 | /** 20 | * The resource that be generated after call "shm_attach" 21 | * @var SysvSharedMemory 22 | */ 23 | protected SysvSharedMemory $shmId; 24 | 25 | /** 26 | * The size of the shared memory 27 | * @var int 28 | */ 29 | protected int $size; 30 | 31 | public function __construct(string $pathname = __FILE__, ?string $size = null, int $permission = 0666) 32 | { 33 | if (!is_null($size)) { 34 | $this->size = SharedMemory::humanReadableToBytes($size); 35 | } else { 36 | $this->size = (int)ini_get('sysvshm.init_mem') ?: 10000; 37 | } 38 | $this->shmId = shm_attach(IpcKeyUtils::generate($pathname), $this->size, $permission); 39 | } 40 | 41 | /** 42 | * Gets a value from the shared memory 43 | * @param int $key 44 | * @return mixed 45 | */ 46 | public function get(int $key): mixed 47 | { 48 | return shm_get_var($this->shmId, $key); 49 | } 50 | 51 | /** 52 | * Persists data in the shared memory 53 | * @param int $key 54 | * @param mixed $value 55 | * @return bool 56 | */ 57 | public function set(int $key, mixed $value) 58 | { 59 | return shm_put_var($this->shmId, $key, $value); 60 | } 61 | 62 | /** 63 | * Delete an item from the shared memory by its key 64 | * @param int $key 65 | * @return bool 66 | */ 67 | public function delete(int $key): bool 68 | { 69 | return shm_remove_var($this->shmId, $key); 70 | } 71 | 72 | /** 73 | * Checks whether an item exists in the shared memory 74 | * @param int $key 75 | * @return bool 76 | */ 77 | public function has(int $key): bool 78 | { 79 | return shm_has_var($this->shmId, $key); 80 | } 81 | 82 | /** 83 | * Deletes all items 84 | */ 85 | public function clear(): bool 86 | { 87 | return shm_remove($this->shmId); 88 | } 89 | 90 | /** 91 | * Disconnects from shared memory 92 | */ 93 | public function close(): void 94 | { 95 | if ($this->shmId) { 96 | shm_detach($this->shmId); 97 | } 98 | } 99 | 100 | 101 | /** 102 | * Removes all items and disconnects from shared memory 103 | * @return void 104 | */ 105 | public function destroy(): void 106 | { 107 | if ($this->shmId) { 108 | shm_remove($this->shmId); 109 | shm_detach($this->shmId); 110 | } 111 | } 112 | 113 | /** 114 | * Convert human readable file size (e.g. "10K" or "3M") into bytes 115 | * @link https://github.com/brandonsavage/Upload/blob/master/src/Upload/File.php#L446 116 | * @param string $input 117 | * @return int 118 | */ 119 | public static function humanReadableToBytes(string $input): int 120 | { 121 | $number = (int)$input; 122 | $units = array( 123 | 'b' => 1, 124 | 'k' => 1024, 125 | 'm' => 1048576, 126 | 'g' => 1073741824 127 | ); 128 | $unit = strtolower(substr($input, -1)); 129 | if (isset($units[$unit])) { 130 | $number = $number * $units[$unit]; 131 | } 132 | return $number; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Tests/Pipe/ReadFifo.php: -------------------------------------------------------------------------------- 1 | assertEquals(5, $writeBytes); 23 | $fifo = new ReadableFifo('/tmp/test1.pipe', false); 24 | $this->assertEquals('hello', $fifo->read()); 25 | } 26 | 27 | public function testNonBlockingRead() 28 | { 29 | $process = new Process(function () { 30 | $fifo = new WritableFifo('/tmp/test1.pipe', true); 31 | $fifo->open(); 32 | sleep(2); 33 | $fifo->write("hello"); 34 | return 0; 35 | }); 36 | $process->start(); 37 | $fifo = new ReadableFifo('/tmp/test1.pipe', false); 38 | $this->assertEmpty($fifo->read()); 39 | $process->wait(); 40 | } 41 | 42 | public function testBlockingRead() 43 | { 44 | $process = new Process(function () { 45 | $fifo = new WritableFifo('/tmp/test1.pipe', true); 46 | $fifo->open(); 47 | sleep(2); 48 | $fifo->write("hello"); 49 | return 0; 50 | }); 51 | $process->start(); 52 | $fifo = new ReadableFifo('/tmp/test1.pipe', true); 53 | $this->assertEquals('hello', $fifo->read()); 54 | $process->wait(); 55 | } 56 | 57 | public function testWrite() 58 | { 59 | $fifo = new ReadableFifo('/tmp/test1.pipe'); 60 | $this->expectException(RuntimeException::class); 61 | $fifo->write('some message'); 62 | } 63 | 64 | public function testIsBlocking() 65 | { 66 | $fifo = new ReadableFifo('/tmp/test1.pipe'); 67 | $this->assertTrue($fifo->isBlocking()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/Pipe/WritableFifoTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($fifo->isBlocking()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/Pipe/WriteFifo.php: -------------------------------------------------------------------------------- 1 | assertFalse($process->isRunning()); 16 | $process->start(); 17 | $this->assertTrue($process->isRunning()); 18 | $process->wait(); 19 | } 20 | 21 | public function testWait() 22 | { 23 | $process = new Process(function () { 24 | sleep(1); 25 | }); 26 | $process->start(); 27 | $process->wait(); 28 | $this->assertFalse($process->isRunning()); 29 | } 30 | 31 | public function testGetPid() 32 | { 33 | $process = new Process(function () { 34 | sleep(1); 35 | }); 36 | $this->assertNull($process->getPid()); 37 | $process->start(); 38 | $this->assertGreaterThan(0, $process->getPid()); 39 | $process->wait(); 40 | } 41 | 42 | public function testGetExitCode() 43 | { 44 | $process = new Process(function () { 45 | usleep(100); 46 | }); 47 | $process->run(); 48 | $this->assertTrue($process->isExited()); 49 | $this->assertEquals(0, $process->getExitCode()); 50 | $process = new Process(function () { 51 | exit(255); 52 | }); 53 | $process->run(); 54 | $this->assertTrue($process->isExited()); 55 | $this->assertEquals(255, $process->getExitCode()); 56 | $this->assertNotEmpty($process->getExitCodeText()); 57 | } 58 | 59 | public function testIfStopped() 60 | { 61 | $process = new Process(function () { 62 | while (true) { 63 | echo 'hehe', PHP_EOL; 64 | } 65 | }); 66 | $process->start(); 67 | $process->stop(); 68 | 69 | $this->assertTrue($process->isStopped()); 70 | $this->assertEquals(SIGSTOP, $process->getStopSignal()); 71 | } 72 | 73 | public function testIfSignaled() 74 | { 75 | $process = new Process(function () { 76 | sleep(1); 77 | }); 78 | $process->start(); 79 | $process->terminate(); 80 | sleep(1); 81 | $this->assertTrue($process->isTerminated()); 82 | $this->assertEquals(SIGTERM, $process->getTermSignal()); 83 | } 84 | 85 | public function testGetStatus() 86 | { 87 | $process = new Process(function () { 88 | sleep(1); 89 | }); 90 | $this->assertEquals(ProcessInterface::STATUS_READY, $process->getStatus()); 91 | $process->start(); 92 | $this->assertEquals(ProcessInterface::STATUS_RUNNING, $process->getStatus()); 93 | $process->wait(); 94 | $this->assertEquals(ProcessInterface::STATUS_EXITED, $process->getStatus()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Tests/SystemV/MessageQueueTest.php: -------------------------------------------------------------------------------- 1 | send('hello', 1); 14 | } 15 | 16 | public function testReceive() 17 | { 18 | $process = new Process(function () { 19 | $queue = new MessageQueue(); 20 | $queue->send('hello'); 21 | }); 22 | $process->run(); 23 | $queue = new MessageQueue(); 24 | $this->assertEquals('hello', $queue->receive()); 25 | } 26 | 27 | public function testBlockingReceive() 28 | { 29 | $process = new Process(function () { 30 | sleep(2); 31 | $queue = new MessageQueue(); 32 | $queue->send('hello'); 33 | }); 34 | $process->start(); 35 | $queue = new MessageQueue(); 36 | $this->assertNull($queue->receive(false)); 37 | $this->assertEquals('hello', $queue->receive()); 38 | $process->wait(); 39 | } 40 | 41 | public function testGetState() 42 | { 43 | $queue = new MessageQueue(); 44 | $this->assertNotEmpty($queue->getState()); 45 | } 46 | 47 | public function testSetState() 48 | { 49 | $queue = new MessageQueue(); 50 | $mode = $queue->getState()['msg_perm.mode']; 51 | $queue->setState('msg_perm.mode', $mode + 1); 52 | $this->assertEquals($mode + 1, $queue->getState()['msg_perm.mode']); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/SystemV/SemaphoreTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($semaphore->acquire()); 14 | $this->assertFalse($semaphore->acquire(false)); 15 | } 16 | 17 | public function testRelease() 18 | { 19 | $semaphore = new Semaphore(); 20 | $semaphore->acquire(); 21 | $this->assertTrue($semaphore->release()); 22 | $this->assertFalse($semaphore->release()); 23 | } 24 | 25 | public function testMutex() 26 | { 27 | $process = new Process(function () { 28 | $semaphore = new Semaphore(); 29 | $semaphore->acquire(); 30 | sleep(2); 31 | $semaphore->release(); 32 | }); 33 | $process->start(); 34 | sleep(1); 35 | $semaphore = new Semaphore(); 36 | $this->assertFalse($semaphore->acquire(false)); 37 | $process->wait(); 38 | $this->assertTrue($semaphore->acquire(false)); 39 | $semaphore->release(); 40 | } 41 | 42 | public function testBlockingMutex() 43 | { 44 | $process = new Process(function () { 45 | $semaphore = new Semaphore(); 46 | $semaphore->acquire(); 47 | sleep(2); 48 | $semaphore->release(); 49 | }); 50 | $process->start(); 51 | sleep(1); 52 | $semaphore = new Semaphore(); 53 | $this->assertTrue($semaphore->acquire()); 54 | $semaphore->release(); 55 | $process->wait(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/SystemV/SharedMemoryTest.php: -------------------------------------------------------------------------------- 1 | destroy(); 12 | } 13 | 14 | public function testSet() 15 | { 16 | $memory = new SharedMemory(); 17 | $result = $memory->set(0, 'bar'); 18 | $this->assertTrue($result); 19 | $memory->destroy(); 20 | } 21 | 22 | public function testGet() 23 | { 24 | $memory = new SharedMemory(); 25 | $memory->set(0, 'bar'); 26 | $this->assertEquals('bar', $memory->get(0)); 27 | } 28 | 29 | public function testHas() 30 | { 31 | $memory = new SharedMemory(); 32 | var_dump($memory->get(0)); 33 | 34 | $this->assertFalse($memory->has(0)); 35 | $memory->set(0, 'bar'); 36 | $this->assertTrue($memory->has(0)); 37 | } 38 | 39 | public function testDelete() 40 | { 41 | $memory = new SharedMemory(); 42 | $memory->set(0, 'bar'); 43 | $this->assertTrue($memory->has(0)); 44 | $memory->delete(0); 45 | $this->assertFalse($memory->has(0)); 46 | } 47 | 48 | public function testClear() 49 | { 50 | $memory = new SharedMemory(); 51 | $memory->set(0, 'bar'); 52 | $memory->set(1, 'baz'); 53 | $this->assertTrue($memory->has(0)); 54 | $this->assertTrue($memory->has(1)); 55 | $result = $memory->clear(); 56 | $this->assertTrue($result); 57 | } 58 | 59 | public function testClose() 60 | { 61 | $memory = new SharedMemory(); 62 | $result = $memory->set(0, 'bar'); 63 | $this->assertTrue($result); 64 | $memory->close(); 65 | } 66 | 67 | public function testDestroy() 68 | { 69 | $memory = new SharedMemory(); 70 | $result = $memory->set(0, 'bar'); 71 | $this->assertTrue($result); 72 | $memory->destroy(); 73 | } 74 | 75 | public function testConvertToHumanReadableSize() 76 | { 77 | $this->assertEquals(8 * 1024, SharedMemory::humanReadableToBytes('8K')); 78 | $this->assertEquals(8 * 1024 * 1024, SharedMemory::humanReadableToBytes('8M')); 79 | $this->assertEquals(8 * 1024 * 1024 * 1024, SharedMemory::humanReadableToBytes('8G')); 80 | $this->assertEquals(8 * 1024 * 1024 * 1024, SharedMemory::humanReadableToBytes('8g')); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/Utils.php: -------------------------------------------------------------------------------- 1 | =8.1" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "^9.0|^10.0" 23 | }, 24 | "suggest": { 25 | "ext-pcntl": "*", 26 | "ext-posix": "*", 27 | "ext-sysvsem": "Required for system v semaphore", 28 | "ext-sysvshm": "Required for system v shared memory", 29 | "ext-sysvmsg": "Required for system v message queue" 30 | }, 31 | "minimum-stability": "stable" 32 | } 33 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | ./Tests/ 11 | 12 | 13 | 14 | 15 | 16 | ./ 17 | 18 | ./Tests 19 | ./vendor 20 | 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------