From b015f82222394b84a8473c0451e04a4c1dda1b1b Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Wed, 2 Feb 2022 19:19:48 -0500 Subject: [PATCH] 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 the approach used in https://github.com/phan/phan/blob/v5/src/Phan/LanguageServer/ProtocolStreamWriter.php for the tcp language server writing. --- run-tests.php | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/run-tests.php b/run-tests.php index 7e9b61b1c8594..1b40a2dc10129 100755 --- a/run-tests.php +++ b/run-tests.php @@ -1645,11 +1645,47 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested): v } } +/** + * Calls fwrite and retries when network writes fail with errors such as "Resource temporarily unavailable" + * + * @param resource $stream the stream to fwrite to + * @param string $data + * @return int|false + */ +function safe_fwrite($stream, string $data) +{ + // safe_fwrite was tested by adding $message['unused'] = str_repeat('a', 20_000_000); in send_message() + // fwrites on tcp sockets can return false or less than strlen if the recipient is busy. + // (e.g. fwrite(): Send of 577 bytes failed with errno=35 Resource temporarily unavailable) + $bytes_written = 0; + while ($bytes_written < strlen($data)) { + $n = @fwrite($stream, substr($data, $bytes_written)); + if ($n === false) { + $write_streams = [$stream]; + $read_streams = []; + $except_streams = []; + /* Wait for up to 10 seconds for the stream to be ready to write again. */ + $result = stream_select($read_streams, $write_streams, $except_streams, 10); + if (!$result) { + echo "ERROR: send_message() stream_select() failed\n"; + return false; + } + $n = @fwrite($stream, substr($data, $bytes_written)); + if ($n === false) { + echo "ERROR: send_message() Failed to write chunk after stream_select: " . error_get_last()['message'] . "\n"; + return false; + } + } + $bytes_written += $n; + } + return $bytes_written; +} + function send_message($stream, array $message): void { $blocking = stream_get_meta_data($stream)["blocked"]; stream_set_blocking($stream, true); - fwrite($stream, base64_encode(serialize($message)) . "\n"); + safe_fwrite($stream, base64_encode(serialize($message)) . "\n"); stream_set_blocking($stream, $blocking); }