From cf95ffd68830a0244ad136d553c4075d9c04d07b Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 23 Apr 2026 16:21:43 +0200 Subject: [PATCH] [WIP][3.x] Drop passing the event loop around --- src/Connection.php | 7 ++++--- src/Connector.php | 26 ++++++++------------------ src/FdServer.php | 11 ++++------- src/HappyEyeBallsConnectionBuilder.php | 17 ++++++++--------- src/HappyEyeBallsConnector.php | 5 +---- src/SecureConnector.php | 4 ++-- src/SecureServer.php | 5 ++--- src/SocketServer.php | 11 +++++------ src/StreamEncryption.php | 11 +++++------ src/TcpConnector.php | 12 +++++------- src/TcpServer.php | 12 ++++-------- src/TimeoutConnector.php | 10 ++++------ src/UnixConnector.php | 9 +-------- src/UnixServer.php | 12 ++++-------- tests/ConnectionTest.php | 3 +-- 15 files changed, 58 insertions(+), 97 deletions(-) diff --git a/src/Connection.php b/src/Connection.php index 6bc8deb..a12f810 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -3,6 +3,7 @@ namespace React\Socket; use Evenement\EventEmitter; +use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Stream\DuplexResourceStream; use React\Stream\Util; @@ -41,7 +42,7 @@ class Connection extends EventEmitter implements ConnectionInterface private $input; - public function __construct($resource, LoopInterface $loop) + public function __construct($resource) { // Legacy PHP < 7.3.3 (and PHP < 7.2.15) suffers from a bug where feof() // might block with 100% CPU usage on fragmented TLS records. @@ -66,9 +67,9 @@ public function __construct($resource, LoopInterface $loop) $this->input = new DuplexResourceStream( $resource, - $loop, + Loop::get(), $clearCompleteBuffer ? -1 : null, - new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null) + new WritableResourceStream($resource, Loop::get(), null, $limitWriteChunks ? 8192 : null) ); $this->stream = $resource; diff --git a/src/Connector.php b/src/Connector.php index 8a5e994..5d1ac44 100644 --- a/src/Connector.php +++ b/src/Connector.php @@ -5,6 +5,7 @@ use React\Dns\Config\Config as DnsConfig; use React\Dns\Resolver\Factory as DnsFactory; use React\Dns\Resolver\ResolverInterface; +use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use function React\Promise\reject; @@ -37,20 +38,13 @@ final class Connector implements ConnectorInterface * This class takes two optional arguments for more advanced usage: * * ```php - * $connector = new React\Socket\Connector(array $context = [], ?LoopInterface $loop = null); + * $connector = new React\Socket\Connector(array $context = []); * ``` * - * This class takes an optional `LoopInterface|null $loop` parameter that can be used to - * pass the event loop instance to use for this object. You can use a `null` value - * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). - * This value SHOULD NOT be given unless you're sure you want to explicitly use a - * given event loop instance. - * * @param array $context - * @param ?LoopInterface $loop * @throws \InvalidArgumentException for invalid arguments */ - public function __construct(array $context = [], ?LoopInterface $loop = null) + public function __construct(array $context = []) { // apply default options if not explicitly given $context += [ @@ -71,7 +65,6 @@ public function __construct(array $context = [], ?LoopInterface $loop = null) $tcp = $context['tcp']; } else { $tcp = new TcpConnector( - $loop, \is_array($context['tcp']) ? $context['tcp'] : [] ); } @@ -93,12 +86,12 @@ public function __construct(array $context = [], ?LoopInterface $loop = null) $factory = new DnsFactory(); $resolver = $factory->createCached( $config, - $loop + Loop::get() ); } if ($context['happy_eyeballs'] === true) { - $tcp = new HappyEyeBallsConnector($loop, $tcp, $resolver); + $tcp = new HappyEyeBallsConnector($tcp, $resolver); } else { $tcp = new DnsConnector($tcp, $resolver); } @@ -110,8 +103,7 @@ public function __construct(array $context = [], ?LoopInterface $loop = null) if ($context['timeout'] !== false) { $context['tcp'] = new TimeoutConnector( $context['tcp'], - $context['timeout'], - $loop + $context['timeout'] ); } @@ -122,7 +114,6 @@ public function __construct(array $context = [], ?LoopInterface $loop = null) if (!$context['tls'] instanceof ConnectorInterface) { $context['tls'] = new SecureConnector( $tcp, - $loop, \is_array($context['tls']) ? $context['tls'] : [] ); } @@ -130,8 +121,7 @@ public function __construct(array $context = [], ?LoopInterface $loop = null) if ($context['timeout'] !== false) { $context['tls'] = new TimeoutConnector( $context['tls'], - $context['timeout'], - $loop + $context['timeout'] ); } @@ -140,7 +130,7 @@ public function __construct(array $context = [], ?LoopInterface $loop = null) if ($context['unix'] !== false) { if (!$context['unix'] instanceof ConnectorInterface) { - $context['unix'] = new UnixConnector($loop); + $context['unix'] = new UnixConnector(); } $this->connectors['unix'] = $context['unix']; } diff --git a/src/FdServer.php b/src/FdServer.php index b00681c..3f250a7 100644 --- a/src/FdServer.php +++ b/src/FdServer.php @@ -34,7 +34,6 @@ final class FdServer extends EventEmitter implements ServerInterface { private $master; - private $loop; private $unix = false; private $listening = false; @@ -75,7 +74,7 @@ final class FdServer extends EventEmitter implements ServerInterface * @throws \InvalidArgumentException if the listening address is invalid * @throws \RuntimeException if listening on this address fails (already in use etc.) */ - public function __construct($fd, ?LoopInterface $loop = null) + public function __construct($fd) { if (\preg_match('#^php://fd/(\d+)$#', $fd, $m)) { $fd = (int) $m[1]; @@ -87,8 +86,6 @@ public function __construct($fd, ?LoopInterface $loop = null) ); } - $this->loop = $loop ?? Loop::get(); - $errno = 0; $errstr = ''; \set_error_handler(function ($_, $error) use (&$errno, &$errstr) { @@ -173,7 +170,7 @@ public function pause() return; } - $this->loop->removeReadStream($this->master); + Loop::removeReadStream($this->master); $this->listening = false; } @@ -183,7 +180,7 @@ public function resume() return; } - $this->loop->addReadStream($this->master, function ($master) { + Loop::addReadStream($this->master, function ($master) { try { $newSocket = SocketServer::accept($master); } catch (\RuntimeException $e) { @@ -209,7 +206,7 @@ public function close() /** @internal */ public function handleConnection($socket) { - $connection = new Connection($socket, $this->loop); + $connection = new Connection($socket); $connection->unix = $this->unix; $this->emit('connection', [$connection]); diff --git a/src/HappyEyeBallsConnectionBuilder.php b/src/HappyEyeBallsConnectionBuilder.php index 57a94aa..bbf92e6 100644 --- a/src/HappyEyeBallsConnectionBuilder.php +++ b/src/HappyEyeBallsConnectionBuilder.php @@ -4,6 +4,7 @@ use React\Dns\Model\Message; use React\Dns\Resolver\ResolverInterface; +use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\EventLoop\TimerInterface; use React\Promise\Deferred; @@ -31,7 +32,6 @@ final class HappyEyeBallsConnectionBuilder */ const RESOLUTION_DELAY = 0.05; - public $loop; public $connector; public $resolver; public $uri; @@ -54,9 +54,8 @@ final class HappyEyeBallsConnectionBuilder public $lastError6; public $lastError4; - public function __construct(LoopInterface $loop, ConnectorInterface $connector, ResolverInterface $resolver, $uri, $host, $parts) + public function __construct(ConnectorInterface $connector, ResolverInterface $resolver, $uri, $host, $parts) { - $this->loop = $loop; $this->connector = $connector; $this->resolver = $resolver; $this->uri = $uri; @@ -93,12 +92,12 @@ public function connect() // discard all IPv4 addresses if cancelled $ips = []; }); - $timer = $this->loop->addTimer($this::RESOLUTION_DELAY, function () use ($deferred, $ips) { + $timer = Loop::addTimer($this::RESOLUTION_DELAY, function () use ($deferred, $ips) { $deferred->resolve($ips); }); $this->resolverPromises[Message::TYPE_AAAA]->then(function () use ($timer, $deferred, &$ips) { - $this->loop->cancelTimer($timer); + Loop::cancelTimer($timer); $deferred->resolve($ips); }); @@ -139,7 +138,7 @@ public function resolve($type, $reject) // cancel next attempt timer when there are no more IPs to connect to anymore if ($this->nextAttemptTimer !== null && !$this->connectQueue) { - $this->loop->cancelTimer($this->nextAttemptTimer); + Loop::cancelTimer($this->nextAttemptTimer); $this->nextAttemptTimer = null; } @@ -191,7 +190,7 @@ public function check($resolve, $reject) // start next connection attempt immediately on error if ($this->connectQueue) { if ($this->nextAttemptTimer !== null) { - $this->loop->cancelTimer($this->nextAttemptTimer); + Loop::cancelTimer($this->nextAttemptTimer); $this->nextAttemptTimer = null; } @@ -216,7 +215,7 @@ public function check($resolve, $reject) // Allow next connection attempt in 100ms: https://tools.ietf.org/html/rfc8305#section-5 // Only start timer when more IPs are queued or when DNS query is still pending (might add more IPs) if ($this->nextAttemptTimer === null && (\count($this->connectQueue) > 0 || $this->resolved[Message::TYPE_A] === false || $this->resolved[Message::TYPE_AAAA] === false)) { - $this->nextAttemptTimer = $this->loop->addTimer(self::CONNECTION_ATTEMPT_DELAY, function () use ($resolve, $reject) { + $this->nextAttemptTimer = Loop::addTimer(self::CONNECTION_ATTEMPT_DELAY, function () use ($resolve, $reject) { $this->nextAttemptTimer = null; if ($this->connectQueue) { @@ -259,7 +258,7 @@ public function cleanUp() } if ($this->nextAttemptTimer instanceof TimerInterface) { - $this->loop->cancelTimer($this->nextAttemptTimer); + Loop::cancelTimer($this->nextAttemptTimer); $this->nextAttemptTimer = null; } } diff --git a/src/HappyEyeBallsConnector.php b/src/HappyEyeBallsConnector.php index 89ec203..6501dcd 100644 --- a/src/HappyEyeBallsConnector.php +++ b/src/HappyEyeBallsConnector.php @@ -9,13 +9,11 @@ final class HappyEyeBallsConnector implements ConnectorInterface { - private $loop; private $connector; private $resolver; - public function __construct(?LoopInterface $loop, ConnectorInterface $connector, ResolverInterface $resolver) + public function __construct(ConnectorInterface $connector, ResolverInterface $resolver) { - $this->loop = $loop ?? Loop::get(); $this->connector = $connector; $this->resolver = $resolver; } @@ -48,7 +46,6 @@ public function connect($uri) } $builder = new HappyEyeBallsConnectionBuilder( - $this->loop, $this->connector, $this->resolver, $uri, diff --git a/src/SecureConnector.php b/src/SecureConnector.php index 7626b0a..2dc0e7b 100644 --- a/src/SecureConnector.php +++ b/src/SecureConnector.php @@ -13,10 +13,10 @@ final class SecureConnector implements ConnectorInterface private $streamEncryption; private $context; - public function __construct(ConnectorInterface $connector, ?LoopInterface $loop = null, array $context = []) + public function __construct(ConnectorInterface $connector, array $context = []) { $this->connector = $connector; - $this->streamEncryption = new StreamEncryption($loop ?? Loop::get(), false); + $this->streamEncryption = new StreamEncryption(false); $this->context = $context; } diff --git a/src/SecureServer.php b/src/SecureServer.php index 7ef5d94..a34a936 100644 --- a/src/SecureServer.php +++ b/src/SecureServer.php @@ -114,12 +114,11 @@ final class SecureServer extends EventEmitter implements ServerInterface * then close the underlying connection. * * @param ServerInterface|TcpServer $tcp - * @param ?LoopInterface $loop * @param array $context * @see TcpServer * @link https://www.php.net/manual/en/context.ssl.php for TLS context options */ - public function __construct(ServerInterface $tcp, ?LoopInterface $loop = null, array $context = []) + public function __construct(ServerInterface $tcp, array $context = []) { // default to empty passphrase to suppress blocking passphrase prompt $context += [ @@ -127,7 +126,7 @@ public function __construct(ServerInterface $tcp, ?LoopInterface $loop = null, a ]; $this->tcp = $tcp; - $this->encryption = new StreamEncryption($loop ?? Loop::get()); + $this->encryption = new StreamEncryption(); $this->context = $context; $this->tcp->on('connection', function ($connection) { diff --git a/src/SocketServer.php b/src/SocketServer.php index 2106ff3..a779c0f 100644 --- a/src/SocketServer.php +++ b/src/SocketServer.php @@ -27,11 +27,10 @@ final class SocketServer extends EventEmitter implements ServerInterface * * @param string $uri * @param array $context - * @param ?LoopInterface $loop * @throws \InvalidArgumentException if the listening address is invalid * @throws \RuntimeException if listening on this address fails (already in use etc.) */ - public function __construct($uri, array $context = [], ?LoopInterface $loop = null) + public function __construct($uri, array $context = []) { // apply default options if not explicitly given $context += [ @@ -47,9 +46,9 @@ public function __construct($uri, array $context = [], ?LoopInterface $loop = nu } if ($scheme === 'unix') { - $server = new UnixServer($uri, $loop, $context['unix']); + $server = new UnixServer($uri, $context['unix']); } elseif ($scheme === 'php') { - $server = new FdServer($uri, $loop); + $server = new FdServer($uri); } else { if (preg_match('#^(?:\w+://)?\d+$#', $uri)) { throw new \InvalidArgumentException( @@ -58,10 +57,10 @@ public function __construct($uri, array $context = [], ?LoopInterface $loop = nu ); } - $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']); + $server = new TcpServer(str_replace('tls://', '', $uri), $context['tcp']); if ($scheme === 'tls') { - $server = new SecureServer($server, $loop, $context['tls']); + $server = new SecureServer($server, $context['tls']); } } diff --git a/src/StreamEncryption.php b/src/StreamEncryption.php index b03b79b..f5748a9 100644 --- a/src/StreamEncryption.php +++ b/src/StreamEncryption.php @@ -2,6 +2,7 @@ namespace React\Socket; +use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Promise\Deferred; @@ -13,13 +14,11 @@ */ class StreamEncryption { - private $loop; private $method; private $server; - public function __construct(LoopInterface $loop, $server = true) + public function __construct($server = true) { - $this->loop = $loop; $this->server = $server; // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3. @@ -78,21 +77,21 @@ public function toggle(Connection $stream, $toggle) $this->toggleCrypto($socket, $deferred, $toggle, $method); }; - $this->loop->addReadStream($socket, $toggleCrypto); + Loop::addReadStream($socket, $toggleCrypto); if (!$this->server) { $toggleCrypto(); } return $deferred->promise()->then(function () use ($stream, $socket, $toggle) { - $this->loop->removeReadStream($socket); + Loop::removeReadStream($socket); $stream->encryptionEnabled = $toggle; $stream->resume(); return $stream; }, function($error) use ($stream, $socket) { - $this->loop->removeReadStream($socket); + Loop::removeReadStream($socket); $stream->resume(); throw $error; }); diff --git a/src/TcpConnector.php b/src/TcpConnector.php index 0949184..e0c7a2d 100644 --- a/src/TcpConnector.php +++ b/src/TcpConnector.php @@ -9,12 +9,10 @@ final class TcpConnector implements ConnectorInterface { - private $loop; private $context; - public function __construct(?LoopInterface $loop = null, array $context = []) + public function __construct(array $context = []) { - $this->loop = $loop ?? Loop::get(); $this->context = $context; } @@ -84,8 +82,8 @@ public function connect($uri) // wait for connection return new Promise(function ($resolve, $reject) use ($stream, $uri) { - $this->loop->addWriteStream($stream, function ($stream) use ($resolve, $reject, $uri) { - $this->loop->removeWriteStream($stream); + Loop::addWriteStream($stream, function ($stream) use ($resolve, $reject, $uri) { + Loop::removeWriteStream($stream); // The following hack looks like the only way to // detect connection refused errors with PHP's stream sockets. @@ -127,11 +125,11 @@ public function connect($uri) $errno )); } else { - $resolve(new Connection($stream, $this->loop)); + $resolve(new Connection($stream)); } }); }, function () use ($stream, $uri) { - $this->loop->removeWriteStream($stream); + Loop::removeWriteStream($stream); \fclose($stream); throw new \RuntimeException( diff --git a/src/TcpServer.php b/src/TcpServer.php index a49ca9d..a689711 100644 --- a/src/TcpServer.php +++ b/src/TcpServer.php @@ -33,7 +33,6 @@ final class TcpServer extends EventEmitter implements ServerInterface { private $master; - private $loop; private $listening = false; /** @@ -121,15 +120,12 @@ final class TcpServer extends EventEmitter implements ServerInterface * The `backlog` context option defaults to `511` unless given explicitly. * * @param string|int $uri - * @param ?LoopInterface $loop * @param array $context * @throws \InvalidArgumentException if the listening address is invalid * @throws \RuntimeException if listening on this address fails (already in use etc.) */ - public function __construct($uri, ?LoopInterface $loop = null, array $context = []) + public function __construct($uri, array $context = []) { - $this->loop = $loop ?? Loop::get(); - // a single port has been given => assume localhost if ((string)(int)$uri === (string)$uri) { $uri = '127.0.0.1:' . $uri; @@ -212,7 +208,7 @@ public function pause() return; } - $this->loop->removeReadStream($this->master); + Loop::removeReadStream($this->master); $this->listening = false; } @@ -222,7 +218,7 @@ public function resume() return; } - $this->loop->addReadStream($this->master, function ($master) { + Loop::addReadStream($this->master, function ($master) { try { $newSocket = SocketServer::accept($master); } catch (\RuntimeException $e) { @@ -249,7 +245,7 @@ public function close() public function handleConnection($socket) { $this->emit('connection', [ - new Connection($socket, $this->loop) + new Connection($socket) ]); } } diff --git a/src/TimeoutConnector.php b/src/TimeoutConnector.php index 5031a0b..c348ad0 100644 --- a/src/TimeoutConnector.php +++ b/src/TimeoutConnector.php @@ -10,13 +10,11 @@ final class TimeoutConnector implements ConnectorInterface { private $connector; private $timeout; - private $loop; - public function __construct(ConnectorInterface $connector, $timeout, ?LoopInterface $loop = null) + public function __construct(ConnectorInterface $connector, $timeout) { $this->connector = $connector; $this->timeout = $timeout; - $this->loop = $loop ?? Loop::get(); } public function connect($uri) @@ -27,13 +25,13 @@ public function connect($uri) $timer = null; $promise = $promise->then(function ($v) use (&$timer, $resolve) { if ($timer) { - $this->loop->cancelTimer($timer); + Loop::cancelTimer($timer); } $timer = false; $resolve($v); }, function ($v) use (&$timer, $reject) { if ($timer) { - $this->loop->cancelTimer($timer); + Loop::cancelTimer($timer); } $timer = false; $reject($v); @@ -45,7 +43,7 @@ public function connect($uri) } // start timeout timer which will cancel the pending promise - $timer = $this->loop->addTimer($this->timeout, function () use (&$promise, $reject, $uri) { + $timer = Loop::addTimer($this->timeout, function () use (&$promise, $reject, $uri) { $reject(new \RuntimeException( 'Connection to ' . $uri . ' timed out after ' . $this->timeout . ' seconds (ETIMEDOUT)', \defined('SOCKET_ETIMEDOUT') ? \SOCKET_ETIMEDOUT : 110 diff --git a/src/UnixConnector.php b/src/UnixConnector.php index ecc6262..89f8b31 100644 --- a/src/UnixConnector.php +++ b/src/UnixConnector.php @@ -15,13 +15,6 @@ */ final class UnixConnector implements ConnectorInterface { - private $loop; - - public function __construct(?LoopInterface $loop = null) - { - $this->loop = $loop ?? Loop::get(); - } - public function connect($path) { if (\strpos($path, '://') === false) { @@ -42,7 +35,7 @@ public function connect($path) )); } - $connection = new Connection($resource, $this->loop); + $connection = new Connection($resource); $connection->unix = true; return resolve($connection); diff --git a/src/UnixServer.php b/src/UnixServer.php index 8b4e416..2580978 100644 --- a/src/UnixServer.php +++ b/src/UnixServer.php @@ -22,7 +22,6 @@ final class UnixServer extends EventEmitter implements ServerInterface { private $master; - private $loop; private $listening = false; /** @@ -43,15 +42,12 @@ final class UnixServer extends EventEmitter implements ServerInterface * given event loop instance. * * @param string $path - * @param ?LoopInterface $loop * @param array $context * @throws \InvalidArgumentException if the listening address is invalid * @throws \RuntimeException if listening on this address fails (already in use etc.) */ - public function __construct($path, ?LoopInterface $loop = null, array $context = []) + public function __construct($path, array $context = []) { - $this->loop = $loop ?? Loop::get(); - if (\strpos($path, '://') === false) { $path = 'unix://' . $path; } elseif (\substr($path, 0, 7) !== 'unix://') { @@ -108,7 +104,7 @@ public function pause() return; } - $this->loop->removeReadStream($this->master); + Loop::removeReadStream($this->master); $this->listening = false; } @@ -118,7 +114,7 @@ public function resume() return; } - $this->loop->addReadStream($this->master, function ($master) { + Loop::addReadStream($this->master, function ($master) { try { $newSocket = SocketServer::accept($master); } catch (\RuntimeException $e) { @@ -144,7 +140,7 @@ public function close() /** @internal */ public function handleConnection($socket) { - $connection = new Connection($socket, $this->loop); + $connection = new Connection($socket); $connection->unix = true; $this->emit('connection', [ diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 2ef1e5c..4fde72c 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -10,9 +10,8 @@ class ConnectionTest extends TestCase public function testCloseConnectionWillCloseSocketResource() { $resource = fopen('php://memory', 'r+'); - $loop = $this->createMock(LoopInterface::class); - $connection = new Connection($resource, $loop); + $connection = new Connection($resource); $connection->close(); $this->assertFalse(is_resource($resource));