diff --git a/src/FreeDSx/Socket/Timeout/SwooleTimerEnforcer.php b/src/FreeDSx/Socket/Timeout/SwooleTimerEnforcer.php index d903156..7f6aa54 100644 --- a/src/FreeDSx/Socket/Timeout/SwooleTimerEnforcer.php +++ b/src/FreeDSx/Socket/Timeout/SwooleTimerEnforcer.php @@ -21,6 +21,7 @@ use function error_clear_last; use function fwrite; use function sprintf; +use function stream_set_blocking; use function substr; /** @@ -68,6 +69,11 @@ private function writeWithinCoroutine( string $data, int $timeout, ): void { + stream_set_blocking( + $stream, + true, + ); + $remaining = $data; $coroutineId = Coroutine::getCid(); $timeoutMs = $timeout * 1000; diff --git a/tests/unit/Timeout/SwooleTimerEnforcerTest.php b/tests/unit/Timeout/SwooleTimerEnforcerTest.php index 1df76f5..04cdb9e 100644 --- a/tests/unit/Timeout/SwooleTimerEnforcerTest.php +++ b/tests/unit/Timeout/SwooleTimerEnforcerTest.php @@ -106,6 +106,36 @@ public function test_it_throws_a_write_timeout_inside_a_coroutine_when_the_peer_ ); } + public function test_it_throws_a_write_timeout_inside_a_coroutine_when_a_non_blocking_stream_stalls(): void + { + $caught = null; + $this->runInCoroutine(function () use (&$caught): void { + [$local, $remote] = $this->createSocketPair(); + stream_set_blocking( + $local, + false, + ); + + try { + $this->subject->write( + $local, + str_repeat('x', 32 * 1024 * 1024), + 1, + ); + } catch (Throwable $e) { + $caught = $e; + } finally { + fclose($local); + fclose($remote); + } + }); + + self::assertInstanceOf( + WriteTimeoutException::class, + $caught, + ); + } + private function runInCoroutine(callable $callback): void { Runtime::enableCoroutine(SWOOLE_HOOK_ALL);