|
10 | 10 | use Psl\TCP; |
11 | 11 | use Revolt\EventLoop; |
12 | 12 |
|
| 13 | +use function error_clear_last; |
13 | 14 | use function error_get_last; |
14 | 15 | use function fclose; |
15 | 16 | use function is_resource; |
| 17 | +use function str_contains; |
16 | 18 | use function stream_socket_accept; |
17 | 19 |
|
18 | 20 | /** |
@@ -51,38 +53,42 @@ public function __construct(mixed $impl, int $idleConnections = self::DEFAULT_ID |
51 | 53 | [$receiver, $sender] = Channel\bounded($idleConnections); |
52 | 54 |
|
53 | 55 | $this->receiver = $receiver; |
54 | | - $this->watcher = EventLoop::onReadable( |
55 | | - $impl, |
56 | | - /** |
57 | | - * @param resource $resource |
58 | | - */ |
59 | | - static function (string $watcher, mixed $resource) use ($sender): void { |
60 | | - try { |
| 56 | + $this->watcher = EventLoop::onReadable($impl, static function (string $watcher, mixed $resource) use ( |
| 57 | + $sender, |
| 58 | + ): void { |
| 59 | + try { |
| 60 | + while (true) { |
| 61 | + error_clear_last(); |
61 | 62 | $sock = @stream_socket_accept($resource, timeout: 0.0); |
62 | | - if (false !== $sock) { |
| 63 | + if ($sock !== false) { |
63 | 64 | $sender->send([true, new Stream($sock)]); |
64 | | - |
65 | | - return; |
| 65 | + continue; |
66 | 66 | } |
67 | 67 |
|
68 | 68 | // @codeCoverageIgnoreStart |
69 | | - /** @var array{file: string, line: int, message: string, type: int} $err */ |
70 | 69 | $err = error_get_last(); |
71 | | - $sender->send([ |
72 | | - false, |
73 | | - new Network\Exception\RuntimeException( |
74 | | - 'Failed to accept incoming connection: ' . $err['message'], |
75 | | - $err['type'], |
76 | | - ), |
77 | | - ]); |
78 | | - // @codeCoverageIgnoreEnd |
79 | | - } catch (Channel\Exception\ClosedChannelException) { |
80 | | - EventLoop::cancel($watcher); |
| 70 | + if ($err !== null && !str_contains($err['message'], 'Accept failed')) { |
| 71 | + // OS error (e.g., EMFILE, ENFILE, ENOBUFS) |
| 72 | + $sender->send([ |
| 73 | + false, |
| 74 | + new Network\Exception\RuntimeException( |
| 75 | + 'Failed to accept incoming connection: ' . $err['message'], |
| 76 | + $err['type'], |
| 77 | + ), |
| 78 | + ]); |
81 | 79 |
|
82 | | - return; |
| 80 | + return; |
| 81 | + } |
| 82 | + |
| 83 | + // No more pending connections (EAGAIN / timeout with no backlog). |
| 84 | + break; |
| 85 | + // @codeCoverageIgnoreEnd |
83 | 86 | } |
84 | | - }, |
85 | | - ); |
| 87 | + } catch (Channel\Exception\ClosedChannelException) { |
| 88 | + EventLoop::cancel($watcher); |
| 89 | + return; |
| 90 | + } |
| 91 | + }); |
86 | 92 | } |
87 | 93 |
|
88 | 94 | #[Override] |
|
0 commit comments