Skip to content

Commit 2d31c12

Browse files
committed
Implement Unix task timeout
1 parent 5683988 commit 2d31c12

File tree

1 file changed

+37
-9
lines changed

1 file changed

+37
-9
lines changed

src/AsyncTask.php

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,16 @@ public function run(): void
7777
{
7878
// todo startup configs
7979
// install a timeout detector
80-
// this handles Windoes timeouts, but also Unix timeouts where PHP max_execution_time is set less than the task time limit
81-
register_shutdown_function([$this, 'checkRuntimeTimeout']);
80+
// this single function checks all kinds of timeouts
81+
register_shutdown_function([$this, 'checkTaskTimeout']);
8282
if (OsInfo::isWindows()) {
8383
// windows can just use PHP's time limit
8484
set_time_limit($this->timeLimit);
85+
} else {
86+
// assume anything not Windows to be Unix
87+
// we already set it to kill this task after the timeout, so we just need to install a listener
88+
pcntl_async_signals(true);
89+
pcntl_signal(SIGTERM, [$this, 'pcntlGracefulExit']);
8590
}
8691

8792
// then, execute the task itself
@@ -221,22 +226,45 @@ public function withoutTimeLimit(): static
221226
return $this;
222227
}
223228

229+
private function pcntlGracefulExit(): never
230+
{
231+
// just exit is ok
232+
// exit asap so that our error checking inside shutdown functions can take palce outside of the usual max_execution_time limit
233+
exit();
234+
}
235+
224236
/**
225-
* Checks whether the task timed out via the PHP runtime error, and if so, triggers the timeout handler.
237+
* Checks whether the task timed out, and if so, triggers the timeout handler.
238+
*
239+
* This will check various kinds of timeouts.
226240
*
227241
* This handles Windows timeouts.
228242
* @return void
229243
*/
230-
protected function checkRuntimeTimeout(): void
244+
protected function checkTaskTimeout(): void
231245
{
246+
// we perform a series of checks to see if this task has timed out
247+
$hasTimedOut = false;
248+
249+
// external killing; could be normal Unix timeout SIG_TERM or manual Windows taskkill
250+
// Laravel Artisan very conveniently has a LARAVEL_START = microtime(true) to let us check time elapsed
251+
$timeElapsed = microtime(true) - constant("LARAVEL_START");
252+
if ($timeElapsed > $this->timeLimit) {
253+
// timeout!
254+
$hasTimedOut = true;
255+
}
256+
232257
// runtime timeout triggers a PHP fatal error
258+
// check this
233259
$lastError = error_get_last();
234-
if ($lastError === null) {
235-
// no error; skip
236-
return;
260+
if ($lastError !== null && str_contains($lastError['message'], "Maximum execution time")) {
261+
// has error, and is timeout!
262+
$hasTimedOut = true;
237263
}
238-
if (!str_contains($lastError['message'], "Maximum execution time")) {
239-
// some other unrelated errors; skip
264+
265+
// all checks concluded
266+
if (!$hasTimedOut) {
267+
// not timeout-related
240268
return;
241269
}
242270
// timeout!

0 commit comments

Comments
 (0)