88use Laravel \SerializableClosure \SerializableClosure ;
99use LogicException ;
1010use loophp \phposinfo \OsInfo ;
11+ use RuntimeException ;
1112
1213/**
1314 * The common handler of an AsyncTask; this can be a closure (will be wrapped inside AsyncTask) or an interface instance.
@@ -32,6 +33,24 @@ class AsyncTask
3233 */
3334 private int |null $ timeLimit = 30 ;
3435
36+ /**
37+ * Indicates whether GNU coreutils is found in the system; in particular, we are looking for the timeout command inside coreutils.
38+ *
39+ * If null, indicates we haven't checked this yet.
40+ *
41+ * Always null in Windows since Windows-side does not require GNU coreutils.
42+ * @var bool|null
43+ */
44+ private static bool |null $ hasGnuCoreUtils = null ;
45+
46+ /**
47+ * The name of the found timeout command inside GNU coreutils.
48+ *
49+ * It is known that older MacOS environments might have "gtimeout" instead of "timeout".
50+ * @var bool|null
51+ */
52+ private static bool |null $ timeoutCmdName = false ;
53+
3554 /**
3655 * Creates an AsyncTask instance.
3756 * @param Closure|AsyncTaskInterface $theTask The task to be executed in the background.
@@ -97,7 +116,25 @@ public function start(): void
97116 // check time limit settings
98117 $ timeoutClause = "" ;
99118 if ($ this ->timeLimit > 0 ) {
100- $ timeoutClause = "timeout {$ this ->timeLimit }" ;
119+ // do we really have timeout here?
120+ if (static ::$ hasGnuCoreUtils === null ) {
121+ // haven't checked before; check
122+ $ tmpOut = [];
123+ if (!exec ("command -v timeout || command -v gtimeout " , $ tmpOut )) {
124+ // can't even check this
125+ throw new RuntimeException ("AsyncTask failed to check whether GNU coreutils is installed " );
126+ }
127+ // extract details
128+ $ cmdName = $ tmpOut [0 ] ?? null ;
129+ static ::$ hasGnuCoreUtils = $ cmdName ? true : false ;
130+ static ::$ timeoutCmdName = $ cmdName ;
131+ unset($ cmdName );
132+ }
133+ if (static ::$ hasGnuCoreUtils === false ) {
134+ // can't do anything without GNU coreutils!
135+ throw new RuntimeException ("AsyncTask time limit requires GNU coreutils, but GNU coreutils was not installed " );
136+ }
137+ $ timeoutClause = static ::$ timeoutCmdName . " {$ this ->timeLimit }" ;
101138 }
102139 $ this ->runnerProcess = Process::quietly ()->start ("nohup $ timeoutClause $ baseCommand " );
103140 }
0 commit comments