Skip to content

Commit a9c21db

Browse files
committed
Make run-tests.php check for tcp fwrite edge cases
When the recipient is busy or the payload is large, fwrite can block or return a value smaller than the length of the stream. workers in run-tests.php communicates over tcp sockets with the manager. https://cirrus-ci.com/task/5315675320221696?logs=tests#L130 showed notices for fwrite/unserialize This is a similar approach to that used in https://github.com/phan/phan/blob/v5/src/Phan/LanguageServer/ProtocolStreamWriter.php for the tcp language server writing.
1 parent b48fe14 commit a9c21db

File tree

1 file changed

+36
-1
lines changed

1 file changed

+36
-1
lines changed

run-tests.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1729,11 +1729,46 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested): v
17291729
}
17301730
}
17311731

1732+
/**
1733+
* Calls fwrite and retries when network writes fail with errors such as "Resource temporarily unavailable"
1734+
*
1735+
* @param resource $stream the stream to fwrite to
1736+
* @param string $data
1737+
* @return int|false
1738+
*/
1739+
function safe_fwrite($stream, string $data)
1740+
{
1741+
// safe_fwrite was tested by adding $message['unused'] = str_repeat('a', 20_000_000); in send_message()
1742+
// fwrites on tcp sockets can return false or less than strlen if the recipient is busy.
1743+
// (e.g. fwrite(): Send of 577 bytes failed with errno=35 Resource temporarily unavailable)
1744+
$bytesWritten = 0;
1745+
$retryAttempts = 0;
1746+
while ($bytesWritten < strlen($data)) {
1747+
$n = @fwrite($stream, substr($data, $bytesWritten));
1748+
if ($n === false) {
1749+
if ($retryAttempts >= 10) {
1750+
echo "ERROR: send_message() Failed to write chunk after 10 retries: " . error_get_last()['message'] . "\n";
1751+
return false;
1752+
}
1753+
$writeSockets = [$stream];
1754+
$readSockets = [];
1755+
$exceptSockets = [];
1756+
/* Wait for a second and retry up to 10 times. Use a finite timeout and retry count in case stream_select returns inaccurate values or returns immediately. */
1757+
stream_select($readSockets, $writeSockets, $exceptSockets, 1);
1758+
$retryAttempts++;
1759+
continue;
1760+
}
1761+
$bytesWritten += $n;
1762+
$retryAttempts = 0;
1763+
}
1764+
return $bytesWritten;
1765+
}
1766+
17321767
function send_message($stream, array $message): void
17331768
{
17341769
$blocking = stream_get_meta_data($stream)["blocked"];
17351770
stream_set_blocking($stream, true);
1736-
fwrite($stream, base64_encode(serialize($message)) . "\n");
1771+
safe_fwrite($stream, base64_encode(serialize($message)) . "\n");
17371772
stream_set_blocking($stream, $blocking);
17381773
}
17391774

0 commit comments

Comments
 (0)