1010
1111class Fork
1212{
13- public const DEFAULT_TERMINATOR = '__WPBROWSER_SEPARATOR__ ' ;
13+ private const TERMINATOR = '__WPBROWSER_SEPARATOR__ ' ;
1414 private const IPC_SOCKET_CHUNK_SIZE = 2048 ;
1515 private const TIMEOUT = 30 ;
1616
1717 private static ?\Closure $ childShutdownHandler = null ;
1818 private static bool $ shutdownHookRegistered = false ;
19- private \Closure $ callback ;
2019
2120 /**
2221 * Call from the global bootstrap, before Codeception registers its ErrorHandler shutdown handler.
@@ -41,16 +40,6 @@ public static function registerChildShutdownHandler(): void
4140 }
4241
4342 public static function executeClosure (\Closure $ callback ): mixed
44- {
45- return (new self ($ callback ))->execute ();
46- }
47-
48- public function __construct (\Closure $ callback )
49- {
50- $ this ->callback = $ callback ;
51- }
52-
53- public function execute (): mixed
5443 {
5544 if (!(function_exists ('pcntl_fork ' ) && function_exists ('posix_kill ' ))) {
5645 throw new \RuntimeException ('pcntl and posix extensions missing. ' );
@@ -72,18 +61,18 @@ public function execute(): mixed
7261 }
7362
7463 if ($ pid === 0 ) {
75- $ this -> executeFork ($ sockets );
64+ self :: executeFork ($ callback , $ sockets );
7665 // Unreachable unless the self-SIGKILL failed: never fall through into the parent flow.
7766 exit (0 );
7867 }
7968
80- return $ this -> executeMain ($ pid , $ sockets );
69+ return self :: executeMain ($ pid , $ sockets );
8170 }
8271
8372 /**
8473 * @param array{0: resource, 1: resource} $sockets
8574 */
86- private function executeFork (array $ sockets ): void
75+ private static function executeFork (\ Closure $ callback , array $ sockets ): void
8776 {
8877 fclose ($ sockets [1 ]);
8978 $ ipcSocket = $ sockets [0 ];
@@ -94,18 +83,21 @@ private function executeFork(array $sockets): void
9483 die ('Failed to get pid ' );
9584 }
9685
97- self ::$ childShutdownHandler = function () use ($ pid , $ ipcSocket , &$ didWritePayload ) {
86+ self ::$ childShutdownHandler = static function () use ($ pid , $ ipcSocket , &$ didWritePayload ) {
9887 if (!$ didWritePayload ) {
9988 // Reached on `exit`: flush pending output buffers now to capture throwing callbacks.
10089 try {
101- $ level = ob_get_level ();
102- while (ob_get_level () > 0 && $ level -- > 0 ) {
90+ for ($ level = ob_get_level (); $ level > 0 ; $ level --) {
10391 ob_end_flush ();
10492 }
105- $ throwable = new \RuntimeException ('Fork child exited before returning a result. ' );
93+ $ lastError = error_get_last ();
94+ $ throwable = new \RuntimeException (
95+ 'Fork child exited before returning a result. '
96+ . ($ lastError !== null ? ' Last error: ' . $ lastError ['message ' ] : '' )
97+ );
10698 } catch (\Throwable $ throwable ) {
10799 }
108- $ this -> writeResultPayload ($ ipcSocket , serialize (new SerializableThrowable ($ throwable )));
100+ self :: writeResultPayload ($ ipcSocket , serialize (new SerializableThrowable ($ throwable )));
109101 $ didWritePayload = true ;
110102 }
111103 fclose ($ ipcSocket );
@@ -116,7 +108,7 @@ private function executeFork(array $sockets): void
116108 self ::registerChildShutdownHandler ();
117109
118110 try {
119- $ result = ( $ this -> callback ) ();
111+ $ result = $ callback ();
120112 $ resultClosure = new PackedClosure (static function () use ($ result ) {
121113 return $ result ;
122114 });
@@ -125,7 +117,7 @@ private function executeFork(array $sockets): void
125117 $ resultPayload = serialize (new SerializableThrowable ($ throwable ));
126118 }
127119
128- $ this -> writeResultPayload ($ ipcSocket , $ resultPayload );
120+ self :: writeResultPayload ($ ipcSocket , $ resultPayload );
129121 $ didWritePayload = true ;
130122 fclose ($ ipcSocket );
131123
@@ -139,7 +131,7 @@ private function executeFork(array $sockets): void
139131 *
140132 * @param resource $ipcSocket
141133 */
142- private function writeResultPayload ($ ipcSocket , string $ resultPayload ): void
134+ private static function writeResultPayload ($ ipcSocket , string $ resultPayload ): void
143135 {
144136 $ encoded = base64_encode ($ resultPayload );
145137 $ length = strlen ($ encoded );
@@ -156,26 +148,27 @@ private function writeResultPayload($ipcSocket, string $resultPayload): void
156148 $ offset += $ written ;
157149 }
158150
159- fwrite ($ ipcSocket , self ::DEFAULT_TERMINATOR );
151+ fwrite ($ ipcSocket , self ::TERMINATOR );
160152 }
161153
162154 /**
163155 * @param array{0: resource, 1: resource} $sockets
164156 * @throws \Throwable
165157 */
166- private function executeMain (int $ pid , array $ sockets ): mixed
158+ private static function executeMain (int $ pid , array $ sockets ): mixed
167159 {
168160 fclose ($ sockets [0 ]);
169161 $ ipcSocket = $ sockets [1 ];
170162 $ resultPayload = '' ;
171163 $ deadline = Debug::isEnabled () ? INF : microtime (true ) + self ::TIMEOUT ;
172164
173- while (!str_ends_with ($ resultPayload , self ::DEFAULT_TERMINATOR ) && !feof ($ ipcSocket )) {
165+ while (!str_ends_with ($ resultPayload , self ::TERMINATOR ) && !feof ($ ipcSocket )) {
174166 $ read = [$ ipcSocket ];
175167 $ write = null ;
176168 $ except = null ;
177169
178- if (stream_select ($ read , $ write , $ except , 1 ) > 0 ) {
170+ // Silenced: EINTR makes stream_select return false with a warning; the loop just retries.
171+ if (@stream_select ($ read , $ write , $ except , 1 ) > 0 ) {
179172 $ resultPayload .= (string )fread ($ ipcSocket , self ::IPC_SOCKET_CHUNK_SIZE );
180173 }
181174
@@ -195,7 +188,7 @@ private function executeMain(int $pid, array $sockets): mixed
195188 /** @noinspection PhpComposerExtensionStubsInspection */
196189 pcntl_waitpid ($ pid , $ status );
197190
198- if (!str_ends_with ($ resultPayload , self ::DEFAULT_TERMINATOR )) {
191+ if (!str_ends_with ($ resultPayload , self ::TERMINATOR )) {
199192 $ exitDetail = pcntl_wifsignaled ($ status ) ?
200193 'killed by signal ' . pcntl_wtermsig ($ status )
201194 : 'exited with status ' . pcntl_wexitstatus ($ status );
@@ -205,7 +198,7 @@ private function executeMain(int $pid, array $sockets): mixed
205198 );
206199 }
207200
208- $ resultPayload = substr ($ resultPayload , 0 , -strlen (self ::DEFAULT_TERMINATOR ));
201+ $ resultPayload = substr ($ resultPayload , 0 , -strlen (self ::TERMINATOR ));
209202
210203 $ unserializedPayload = @unserialize ((string )base64_decode ($ resultPayload , true ));
211204
@@ -220,12 +213,6 @@ private function executeMain(int $pid, array $sockets): mixed
220213 );
221214 }
222215
223- $ result = $ unserializedPayload ->getClosure ()();
224-
225- if ($ result instanceof \Throwable) {
226- throw $ result ;
227- }
228-
229- return $ result ;
216+ return $ unserializedPayload ->getClosure ()();
230217 }
231218}
0 commit comments