@@ -69,6 +69,13 @@ class Command
6969 */
7070 public $ nonBlockingMode ;
7171
72+ /**
73+ * @var int the time in seconds after which a command should be terminated.
74+ * This only works in non-blocking mode. Default is `null` which means the
75+ * process is never terminated.
76+ */
77+ public $ timeout ;
78+
7279 /**
7380 * @var null|string the locale to temporarily set before calling
7481 * `escapeshellargs()`. Default is `null` for none.
@@ -376,6 +383,7 @@ public function execute()
376383 in_array (get_resource_type ($ this ->_stdIn ), array ('file ' , 'stream ' ));
377384 $ isInputString = is_string ($ this ->_stdIn );
378385 $ hasInput = $ isInputStream || $ isInputString ;
386+ $ hasTimeout = $ this ->timeout !== null && $ this ->timeout > 0 ;
379387
380388 $ descriptors = array (
381389 1 => array ('pipe ' ,'w ' ),
@@ -385,10 +393,12 @@ public function execute()
385393 $ descriptors [0 ] = array ('pipe ' , 'r ' );
386394 }
387395
396+
388397 // Issue #20 Set non-blocking mode to fix hanging processes
389398 $ nonBlocking = $ this ->nonBlockingMode === null ?
390399 !$ this ->getIsWindows () : $ this ->nonBlockingMode ;
391400
401+ $ startTime = $ hasTimeout ? time () : 0 ;
392402 $ process = proc_open ($ command , $ descriptors , $ pipes , $ this ->procCwd , $ this ->procEnv , $ this ->procOptions );
393403
394404 if (is_resource ($ process )) {
@@ -457,8 +467,25 @@ public function execute()
457467 $ this ->_stdErr .= $ err ;
458468 }
459469
470+ $ runTime = $ hasTimeout ? time () - $ startTime : 0 ;
471+ if ($ isRunning && $ hasTimeout && $ runTime >= $ this ->timeout ) {
472+ // Only send a SIGTERM and handle status in the next cycle
473+ proc_terminate ($ process );
474+ }
475+
460476 if (!$ isRunning ) {
461477 $ this ->_exitCode = $ status ['exitcode ' ];
478+ if ($ this ->_exitCode !== 0 && empty ($ this ->_stdErr )) {
479+ if ($ status ['stopped ' ]) {
480+ $ signal = $ status ['stopsig ' ];
481+ $ this ->_stdErr = "Command stopped by signal $ signal " ;
482+ } elseif ($ status ['signaled ' ]) {
483+ $ signal = $ status ['termsig ' ];
484+ $ this ->_stdErr = "Command terminated by signal $ signal " ;
485+ } else {
486+ $ this ->_stdErr = 'Command unexpectedly terminated without error message ' ;
487+ }
488+ }
462489 fclose ($ pipes [1 ]);
463490 fclose ($ pipes [2 ]);
464491 proc_close ($ process );
0 commit comments