Skip to content

Commit

Permalink
Refactor for better naming and error checking
Browse files Browse the repository at this point in the history
  • Loading branch information
terremoth committed Dec 8, 2024
1 parent 5ded013 commit db22d1c
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 62 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ composer.phar
.idea
.vscode
.phpunit.cache

demo.txt
demo-file.tx
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ It uses a combination of:
- Symfony/Process lib
- and PHP's native Shmop extension

**Warning: it does not works on MSYS/MINGW terminals!**. It will work fine on both Windows (cmd and powershell) and Linux.

See [demos/demo.php](demos/demo.php) for examples.

## Installation
Expand All @@ -33,7 +35,7 @@ composer require terremoth/php-async

require_once 'vendor/autoload.php';

use Terremoth\Async\File;
use Terremoth\Async\PhpFile;
use Terremoth\Async\Process;

$process = new Process();
Expand All @@ -49,7 +51,7 @@ $process->send(function () {
});

$args = ['--verbose', '-n', '123'];
$asyncFile = new File('existing-php-file.php', $args); // make sure to pass the correct file with its path
$asyncFile = new PhpFile('existing-php-file.php', $args); // make sure to pass the correct file with its path
$asyncFile->run();

```
Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"php": "^8.2",
"symfony/process": "^7.2",
"laravel/serializable-closure": "^2.0",
"ext-shmop": "*"
"ext-shmop": "*",
"ext-fileinfo": "*",
"ext-zlib": "*"
},
"keywords": [
"async",
Expand Down
6 changes: 3 additions & 3 deletions demos/demo.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
require_once 'vendor/autoload.php';

use Terremoth\Async\Process;
use Terremoth\Async\File;
use Terremoth\Async\PhpFile;

$process = new Process();
echo date('c') . ' :: Sending process. You should not wait any longer to see next message: ' . PHP_EOL;
try {
$process->send(function () {

$var1 = 4;
$var2 = 2 + 2;
$var2 = ((2 + 2) / $var1) + ((2 * 2) - 1);

sleep(5);

Expand All @@ -28,7 +28,7 @@
echo date('c') . ' :: Now let\'s process a file that takes a long time...' . PHP_EOL;

try {
$file = new File(__DIR__ . DIRECTORY_SEPARATOR . 'time-wasting-file.php');
$file = new PhpFile(__DIR__ . DIRECTORY_SEPARATOR . 'time-wasting-file.php');
$file->run();
echo date('c') . ' :: Ended...' . PHP_EOL;
} catch (Exception $e) {
Expand Down
34 changes: 0 additions & 34 deletions src/Terremoth/Async/File.php

This file was deleted.

44 changes: 44 additions & 0 deletions src/Terremoth/Async/PhpFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Terremoth\Async;

use Exception;
use InvalidArgumentException;
use Symfony\Component\Process\Process as SymfonyProcess;

readonly class PhpFile
{
/**
* @param string $file
* @throws Exception
* @param array<array-key, null>|list{int} $args
*/
public function __construct(private string $file, private array $args = [])
{
if (!is_readable($this->file)) {
throw new InvalidArgumentException('Error: file ' . $this->file
. ' does not exists or is not readable!');
}

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $this->file);
finfo_close($finfo);

if (!in_array($mimeType, ['text/x-php', 'application/x-php', 'application/php', 'application/x-httpd-php'])) {
throw new Exception('Error: file ' . $this->file . ' is not a PHP file!');
}
}

public function run(): void
{
if (PHP_OS_FAMILY === 'Windows') {
$template = ['start', "", '/B', PHP_BINARY, $this->file, ...$this->args];
$process = new SymfonyProcess($template);
$process->start();
return;
}

$args = implode(' ', $this->args);
exec(PHP_BINARY . ' ' . $this->file . ' ' . $args . ' > /dev/null 2>&1 &');
}
}
33 changes: 13 additions & 20 deletions src/Terremoth/Async/Process.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
use Closure;
use Exception;
use Laravel\SerializableClosure\SerializableClosure;
use Symfony\Component\Process\Process as SymfonyProcess;

class Process
{
private const MAX_INT = 2147483647;

public function __construct(private int $key = 0)
public function __construct(private int $shmopKey = 0)
{
if (!$this->key) {
$this->key = mt_rand(0, self::MAX_INT); // communication key
if (!$this->shmopKey) {
$this->shmopKey = mt_rand(0, self::MAX_INT); // communication key
}
}

Expand All @@ -23,30 +22,24 @@ public function __construct(private int $key = 0)
*/
public function send(Closure $asyncFunction): void
{
$separator = DIRECTORY_SEPARATOR;
$dirSlash = DIRECTORY_SEPARATOR;
$serialized = serialize(new SerializableClosure($asyncFunction));
$serializedLength = strlen($serialized);
$shmopInstance = shmop_open($this->key, 'c', 0660, $serializedLength);
$compressedLength = mb_strlen($serialized);
$shmopInstance = shmop_open($this->shmopKey, 'c', 0660, $compressedLength);

if (!$shmopInstance) {
throw new Exception('Could not create shmop instance with key: ' . $this->key);
throw new Exception('Could not create shmop instance with key: ' . $this->shmopKey);
}

$bytesWritten = shmop_write($shmopInstance, $serialized, 0);

if ($bytesWritten != $serializedLength) {
throw new Exception('Could not write the entire data to shared memory with length: ' .
$serializedLength . '. Bytes written: ' . $bytesWritten);
if ($bytesWritten < $compressedLength) {
throw new Exception('Error: Could not write the entire data to shared memory with length: ' .
$compressedLength . '. Bytes written: ' . $bytesWritten . PHP_EOL);
}

if (PHP_OS_FAMILY === 'Windows') {
$arg = ['start', '""', '/B', PHP_BINARY, __DIR__ . $separator . 'background_processor.php', $this->key];
$process = new SymfonyProcess($arg);
$process->start();
return;
}

exec(PHP_BINARY . ' ' . __DIR__ . $separator . 'background_processor.php ' . $this->key .
' > /dev/null 2>&1 &');
$fileWithPath = __DIR__ . $dirSlash . 'background_processor.php';
$file = new PhpFile($fileWithPath, [$this->shmopKey]);
$file->run();
}
}
23 changes: 21 additions & 2 deletions src/Terremoth/Async/background_processor.php
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
<?php

ini_set('display_errors', 'on');
ini_set('display_startup_errors', 1);
ini_set('error_log', 'php-async-errors-' . date('YmdH') . '.log');
error_reporting(E_ALL);

require_once 'vendor/autoload.php';
require_once 'script_functions.php';

use Laravel\SerializableClosure\SerializableClosure;

if (!isset($argv[1])) {
fwrite(STDERR, 'Error: Key not provided');
error('Shmop Key not provided');
exit(1);
}

$key = (int)$argv[1];

$shmopInstance = shmop_open($key, 'a', 0, 0);

if (!$shmopInstance) {
error('Could not open Shmop');
exit(1);
}

$length = shmop_size($shmopInstance);
$data = shmop_read($shmopInstance, 0, $length);

if ($length === 0) {
error('Shmop length cannot be zero!');
exit(1);
}

$dataCompressed = shmop_read($shmopInstance, 0, $length);
$data = stringFromMemoryBlock($dataCompressed);

/**
* @var SerializableClosure $serializedClosure
Expand Down
17 changes: 17 additions & 0 deletions src/Terremoth/Async/script_functions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

function error(string $error): void
{
$error = 'Error: ' . $error;
fwrite(STDERR, $error);
error_log($error);
}

function stringFromMemoryBlock(string $value): string
{
$position = strpos($value, "\0");
if ($position === false) {
return $value;
}
return substr($value, 0, $position);
}

0 comments on commit db22d1c

Please sign in to comment.