Skip to content

Commit 2f35071

Browse files
committed
Merge branch 'master' into serial
2 parents 3253157 + c89946f commit 2f35071

13 files changed

+404
-47
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
# Change Log of `laravel-process-async`
2+
Note: you may refer to `README.md` for description of features.
23

34
## Dev (WIP)
5+
- Task IDs can be given to tasks (generated or not) (https://github.com/Vectorial1024/laravel-process-async/issues/5)
6+
7+
## 0.2.0 (2025-01-04)
48
- Task runners are now detached from the task giver (https://github.com/Vectorial1024/laravel-process-async/issues/7)
9+
- Task runners can now have time limits (https://github.com/Vectorial1024/laravel-process-async/issues/1)
510

611
## 0.1.0 (2024-12-02)
712
Initial release:

README.md

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![PHP Dependency Version][php-version-image]][packagist-url]
66
[![GitHub Repo Stars][github-stars-image]][github-repo-url]
77

8-
Utilize Laravel Processes to run PHP code asynchronously.
8+
Utilize Laravel Processes to run PHP code asynchronously, as if using Laravel Concurrency.
99

1010
## What really is this?
1111
[Laravel Processes](https://laravel.com/docs/10.x/processes) was first introduced in Laravel 10. This library wraps around `Process::start()` to let you execute code in the background to achieve async, albeit with some caveats:
@@ -45,7 +45,7 @@ If you are on Unix, check that you also have the following:
4545
## Change log
4646
Please see `CHANGELOG.md`.
4747

48-
## Example code
48+
## Example code and features
4949
Tasks can be defined as PHP closures, or (recommended) as an instance of a class that implements `AsyncTaskInterface`.
5050

5151
A very simple example task to write Hello World to a file:
@@ -54,10 +54,7 @@ A very simple example task to write Hello World to a file:
5454
// define the task...
5555
$target = "document.txt";
5656
$task = new AsyncTask(function () use ($target) {
57-
$fp = fopen($target, "w");
58-
fwrite($fp, "Hello World!!");
59-
fflush($fp);
60-
fclose($fp);
57+
file_put_contents($target, "Hello World!");
6158
});
6259

6360
// if you are using interfaces, then it is just like this:
@@ -87,6 +84,38 @@ $task->withTimeLimit(15)->start();
8784
$task->withoutTimeLimit()->start();
8885
```
8986

87+
Some tips:
88+
- Don't sleep too long! On Windows, timeout handlers cannot trigger while your task is sleeping.
89+
- Use short but frequent sleeps instead.
90+
- Avoid using `SIGINT`! On Unix, this signal is reserved for timeout detection.
91+
92+
### Task IDs
93+
You can assign task IDs to tasks before they are run, but you cannot change them after the tasks are started. This allows you to track the statuses of long-running tasks across web requests.
94+
95+
By default, if a task does not has its user-specified task ID when starting, a ULID will be generated as its task ID.
96+
97+
```php
98+
// create a task with a specified task ID...
99+
$task = new AsyncTask(function () {}, "customTaskID");
100+
101+
// will return a status object for immediate checking...
102+
$status = $task->start();
103+
104+
// in case the task ID was not given, what is the generated task ID?
105+
$taskID = $status->taskID;
106+
107+
// is that task still running?
108+
$status->isRunning();
109+
110+
// when task IDs are known, task status objects can be recreated on-the-fly
111+
$anotherStatus = new AsyncTaskStatus("customTaskID");
112+
```
113+
114+
Some tips:
115+
- Task IDs can be optional (i.e. `null`) but CANNOT be blank (i.e. `""`)!
116+
- If multiple tasks are started with the same task ID, then the task status object will only track the first task that was started
117+
- Known issue: on Windows, checking task statuses can be slow (about 0.5 - 1 seconds) due to underlying bottlenecks
118+
90119
## Testing
91120
PHPUnit via Composer script:
92121
```sh

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vectorial1024/laravel-process-async",
3-
"description": "Utilize Laravel Process to run PHP code asynchronously.",
3+
"description": "Utilize Laravel Process to run PHP code asynchronously, as if using Laravel Concurrency.",
44
"keywords": [
55
"laravel",
66
"background",

src/AsyncTask.php

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
use Closure;
88
use Illuminate\Process\InvokedProcess;
99
use Illuminate\Support\Facades\Process;
10+
use Illuminate\Support\Str;
11+
use InvalidArgumentException;
12+
use Laravel\SerializableClosure\SerializableClosure;
1013
use LogicException;
1114
use loophp\phposinfo\OsInfo;
1215
use RuntimeException;
@@ -24,6 +27,14 @@ class AsyncTask
2427
*/
2528
private Closure|AsyncTaskInterface $theTask;
2629

30+
/**
31+
* The user-specified ID of the current task. (Null means user did not specify any ID).
32+
*
33+
* If null, the task will generate an unsaved random ID when it is started.
34+
* @var string|null
35+
*/
36+
private string|null $taskID;
37+
2738
/**
2839
* The process that is actually running this task. Tasks that are not started will have null here.
2940
* @var InvokedProcess|null
@@ -92,11 +103,16 @@ class AsyncTask
92103
/**
93104
* Creates an AsyncTask instance.
94105
* @param Closure|AsyncTaskInterface $theTask The task to be executed in the background.
106+
* @param string|null $taskID (optional) The user-specified task ID of this AsyncTask. Should be unique.
95107
*/
96-
public function __construct(Closure|AsyncTaskInterface $theTask)
108+
public function __construct(Closure|AsyncTaskInterface $theTask, string|null $taskID = null)
97109
{
98110
// opis/closure allows direct storage of closure
99111
$this->theTask = $theTask;
112+
if ($taskID === "") {
113+
throw new InvalidArgumentException("AsyncTask ID cannot be empty.");
114+
}
115+
$this->taskID = $taskID;
100116
}
101117

102118
public function __serialize(): array
@@ -135,7 +151,9 @@ public function run(): void
135151
register_shutdown_function([$this, 'shutdownCheckTaskTimeout']);
136152
if (OsInfo::isWindows()) {
137153
// windows can just use PHP's time limit
138-
set_time_limit($this->timeLimit);
154+
if ($this->timeLimit > 0) {
155+
set_time_limit($this->timeLimit);
156+
}
139157
} else {
140158
// assume anything not Windows to be Unix
141159
// we already set it to kill this task after the timeout, so we just need to install a listener to catch the signal and exit gracefully
@@ -171,21 +189,26 @@ public function run(): void
171189

172190
/**
173191
* Starts this AsyncTask immediately in the background. A runner will then run this AsyncTask.
174-
* @return void
192+
* @return AsyncTaskStatus The status object for the started AsyncTask.
175193
*/
176-
public function start(): void
194+
public function start(): AsyncTaskStatus
177195
{
196+
// prepare the task details
197+
$taskID = $this->taskID ?? Str::ulid()->toString();
198+
$taskStatus = new AsyncTaskStatus($taskID);
199+
178200
// prepare the runner command
179201
$serializedTask = $this->toBase64Serial();
180-
$baseCommand = "php artisan async:run $serializedTask";
202+
$encodedTaskID = $taskStatus->getEncodedTaskID();
203+
$baseCommand = "php artisan async:run $serializedTask --id='$encodedTaskID'";
181204

182205
// then, specific actions depending on the runtime OS
183206
if (OsInfo::isWindows()) {
184207
// basically, in windows, it is too tedioous to check whether we are in cmd or ps,
185208
// but we require cmd (ps won't work here), so might as well force cmd like this
186209
// windows has real max time limit
187-
$this->runnerProcess = Process::quietly()->start("cmd /c start /b $baseCommand");
188-
return;
210+
$this->runnerProcess = Process::quietly()->start("cmd >nul 2>nul /c start /b $baseCommand");
211+
return $taskStatus;
189212
}
190213
// assume anything not windows to be unix
191214
// unix use nohup
@@ -209,6 +232,7 @@ public function start(): void
209232
$timeoutClause = static::$timeoutCmdName . " -s 2 {$this->timeLimit}";
210233
}
211234
$this->runnerProcess = Process::quietly()->start("nohup $timeoutClause $baseCommand >/dev/null 2>&1");
235+
return $taskStatus;
212236
}
213237

214238
/**

src/AsyncTaskRunnerCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class AsyncTaskRunnerCommand extends Command
1414
*
1515
* @var string
1616
*/
17-
protected $signature = 'async:run {task}';
17+
protected $signature = 'async:run {task} {--id=}';
1818

1919
/**
2020
* The console command description.

0 commit comments

Comments
 (0)