@@ -156,9 +156,28 @@ public function testStartWithExcessiveNumberOfFileDescriptorsWillThrow()
156156 $ this ->markTestSkipped ('Unable to determine limit of open files (ulimit not available?) ' );
157157 }
158158
159+ // create 70% (usually ~700) dummy file handles in this parent
160+ $ limit = (int ) ($ ulimit * 0.7 );
161+
162+ $ memory = ini_get ('memory_limit ' );
163+ if ($ memory === '-1 ' ) {
164+ $ memory = PHP_INT_MAX ;
165+ } elseif (preg_match ('/^\d+G$/i ' , $ memory )) {
166+ $ memory = ((int ) $ memory ) * 1024 * 1024 * 1024 ;
167+ } elseif (preg_match ('/^\d+M$/i ' , $ memory )) {
168+ $ memory = ((int ) $ memory ) * 1024 * 1024 ;
169+ } elseif (preg_match ('/^\d+K$/i ' , $ memory )) {
170+ $ memory = ((int ) $ memory ) * 1024 ;
171+ }
172+
173+ // each file descriptor takes ~600 bytes of memory, so skip test if this would exceed memory_limit
174+ if ($ limit * 600 > $ memory ) {
175+ $ this ->markTestSkipped ('Test requires ~ ' . round ($ limit * 600 / 1024 / 1024 ) . '/ ' . round ($ memory / 1024 / 1024 ) . ' MiB memory with ' . $ ulimit . ' file descriptors ' );
176+ }
177+
159178 $ loop = $ this ->createLoop ();
160179
161- // create 70% (usually ~700) dummy file handles in this parent dummy
180+ // create ~700 dummy file handles in this parent
162181 $ limit = (int ) ($ ulimit * 0.7 );
163182 $ fds = array ();
164183 for ($ i = 0 ; $ i < $ limit ; ++$ i ) {
@@ -579,6 +598,31 @@ public function testProcessWillExitFasterThanExitInterval()
579598 $ this ->assertLessThan (0.1 , $ time );
580599 }
581600
601+ /**
602+ * @requires PHP 8
603+ */
604+ public function testProcessWithSocketIoWillExitFasterThanExitInterval ()
605+ {
606+ $ loop = $ this ->createLoop ();
607+ $ process = new Process (
608+ DIRECTORY_SEPARATOR === '\\' ? 'cmd /c echo hi ' : 'echo hi ' ,
609+ null ,
610+ null ,
611+ array (
612+ array ('socket ' ),
613+ array ('socket ' ),
614+ array ('socket ' )
615+ )
616+ );
617+ $ process ->start ($ loop , 2 );
618+
619+ $ time = microtime (true );
620+ $ loop ->run ();
621+ $ time = microtime (true ) - $ time ;
622+
623+ $ this ->assertLessThan (0.1 , $ time );
624+ }
625+
582626 public function testDetectsClosingStdoutWithoutHavingToWaitForExit ()
583627 {
584628 if (DIRECTORY_SEPARATOR === '\\' ) {
@@ -606,6 +650,39 @@ public function testDetectsClosingStdoutWithoutHavingToWaitForExit()
606650 $ this ->assertTrue ($ closed );
607651 }
608652
653+ /**
654+ * @requires PHP 8
655+ */
656+ public function testDetectsClosingStdoutSocketWithoutHavingToWaitForExit ()
657+ {
658+ $ loop = $ this ->createLoop ();
659+ $ process = new Process (
660+ (DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ' ) . $ this ->getPhpBinary () . ' -r ' . escapeshellarg ('fclose(STDOUT); sleep(1); ' ),
661+ null ,
662+ null ,
663+ array (
664+ array ('socket ' ),
665+ array ('socket ' ),
666+ array ('socket ' )
667+ )
668+ );
669+ $ process ->start ($ loop );
670+
671+ $ closed = false ;
672+ $ process ->stdout ->on ('close ' , function () use (&$ closed , $ loop ) {
673+ $ closed = true ;
674+ $ loop ->stop ();
675+ });
676+
677+ // run loop for maximum of 0.5s only
678+ $ loop ->addTimer (0.5 , function () use ($ loop ) {
679+ $ loop ->stop ();
680+ });
681+ $ loop ->run ();
682+
683+ $ this ->assertTrue ($ closed );
684+ }
685+
609686 public function testKeepsRunningEvenWhenAllStdioPipesHaveBeenClosed ()
610687 {
611688 if (DIRECTORY_SEPARATOR === '\\' ) {
@@ -642,6 +719,48 @@ public function testKeepsRunningEvenWhenAllStdioPipesHaveBeenClosed()
642719 $ this ->assertTrue ($ process ->isRunning ());
643720 }
644721
722+ /**
723+ * @requires PHP 8
724+ */
725+ public function testKeepsRunningEvenWhenAllStdioSocketsHaveBeenClosed ()
726+ {
727+ $ loop = $ this ->createLoop ();
728+ $ process = new Process (
729+ (DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ' ) . $ this ->getPhpBinary () . ' -r ' . escapeshellarg ('fclose(STDIN);fclose(STDOUT);fclose(STDERR);sleep(1); ' ),
730+ null ,
731+ null ,
732+ array (
733+ array ('socket ' ),
734+ array ('socket ' ),
735+ array ('socket ' )
736+ )
737+ );
738+ $ process ->start ($ loop );
739+
740+ $ closed = 0 ;
741+ $ process ->stdout ->on ('close ' , function () use (&$ closed , $ loop ) {
742+ ++$ closed ;
743+ if ($ closed === 2 ) {
744+ $ loop ->stop ();
745+ }
746+ });
747+ $ process ->stderr ->on ('close ' , function () use (&$ closed , $ loop ) {
748+ ++$ closed ;
749+ if ($ closed === 2 ) {
750+ $ loop ->stop ();
751+ }
752+ });
753+
754+ // run loop for maximum 0.5s only
755+ $ loop ->addTimer (0.5 , function () use ($ loop ) {
756+ $ loop ->stop ();
757+ });
758+ $ loop ->run ();
759+
760+ $ this ->assertEquals (2 , $ closed );
761+ $ this ->assertTrue ($ process ->isRunning ());
762+ }
763+
645764 public function testDetectsClosingProcessEvenWhenAllStdioPipesHaveBeenClosed ()
646765 {
647766 if (DIRECTORY_SEPARATOR === '\\' ) {
@@ -662,16 +781,42 @@ public function testDetectsClosingProcessEvenWhenAllStdioPipesHaveBeenClosed()
662781 $ this ->assertSame (0 , $ process ->getExitCode ());
663782 }
664783
665- public function testDetectsClosingProcessEvenWhenStartedWithoutPipes ()
784+ /**
785+ * @requires PHP 8
786+ */
787+ public function testDetectsClosingProcessEvenWhenAllStdioSocketsHaveBeenClosed ()
666788 {
667789 $ loop = $ this ->createLoop ();
790+ $ process = new Process (
791+ (DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ' ) . $ this ->getPhpBinary () . ' -r ' . escapeshellarg ('fclose(STDIN);fclose(STDOUT);fclose(STDERR);usleep(10000); ' ),
792+ null ,
793+ null ,
794+ array (
795+ array ('socket ' ),
796+ array ('socket ' ),
797+ array ('socket ' )
798+ )
799+ );
800+ $ process ->start ($ loop , 0.001 );
668801
669- if (DIRECTORY_SEPARATOR === '\\' ) {
670- $ process = new Process ('cmd /c exit 0 ' , null , null , array ());
671- } else {
672- $ process = new Process ('exit 0 ' , null , null , array ());
673- }
802+ $ time = microtime (true );
803+ $ loop ->run ();
804+ $ time = microtime (true ) - $ time ;
805+
806+ $ this ->assertLessThan (0.5 , $ time );
807+ $ this ->assertSame (0 , $ process ->getExitCode ());
808+ }
674809
810+ public function testDetectsClosingProcessEvenWhenStartedWithoutPipes ()
811+ {
812+ $ loop = $ this ->createLoop ();
813+
814+ $ process = new Process (
815+ (DIRECTORY_SEPARATOR === '\\' ? 'cmd /c ' : '' ) . 'exit 0 ' ,
816+ null ,
817+ null ,
818+ array ()
819+ );
675820 $ process ->start ($ loop , 0.001 );
676821
677822 $ time = microtime (true );
0 commit comments