Skip to content

Commit b0d814f

Browse files
committed
test: simplify Fork to a static API and tighten diagnostics
Silence EINTR warnings from stream_select in the read loop, include the last PHP error in the exit-without-result report, drop the single-use instance API and the dead returned-Throwable rethrow.
1 parent db10496 commit b0d814f

1 file changed

Lines changed: 23 additions & 36 deletions

File tree

tests/_support/Fork.php

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@
1010

1111
class 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

Comments
 (0)