@@ -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