@@ -69,6 +69,13 @@ class Command
69
69
*/
70
70
public $ nonBlockingMode ;
71
71
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
+
72
79
/**
73
80
* @var null|string the locale to temporarily set before calling
74
81
* `escapeshellargs()`. Default is `null` for none.
@@ -376,6 +383,7 @@ public function execute()
376
383
in_array (get_resource_type ($ this ->_stdIn ), array ('file ' , 'stream ' ));
377
384
$ isInputString = is_string ($ this ->_stdIn );
378
385
$ hasInput = $ isInputStream || $ isInputString ;
386
+ $ hasTimeout = $ this ->timeout !== null && $ this ->timeout > 0 ;
379
387
380
388
$ descriptors = array (
381
389
1 => array ('pipe ' ,'w ' ),
@@ -385,10 +393,12 @@ public function execute()
385
393
$ descriptors [0 ] = array ('pipe ' , 'r ' );
386
394
}
387
395
396
+
388
397
// Issue #20 Set non-blocking mode to fix hanging processes
389
398
$ nonBlocking = $ this ->nonBlockingMode === null ?
390
399
!$ this ->getIsWindows () : $ this ->nonBlockingMode ;
391
400
401
+ $ startTime = $ hasTimeout ? time () : 0 ;
392
402
$ process = proc_open ($ command , $ descriptors , $ pipes , $ this ->procCwd , $ this ->procEnv , $ this ->procOptions );
393
403
394
404
if (is_resource ($ process )) {
@@ -457,8 +467,25 @@ public function execute()
457
467
$ this ->_stdErr .= $ err ;
458
468
}
459
469
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
+
460
476
if (!$ isRunning ) {
461
477
$ 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
+ }
462
489
fclose ($ pipes [1 ]);
463
490
fclose ($ pipes [2 ]);
464
491
proc_close ($ process );
0 commit comments