diff --git a/psalm.xml b/psalm.xml index 17dc32fc..add49247 100644 --- a/psalm.xml +++ b/psalm.xml @@ -6,6 +6,7 @@ findUnusedBaselineEntry="false" findUnusedCode="false" errorBaseline="psalm-baseline.xml" + phpVersion="8.1" > diff --git a/src/Application.php b/src/Application.php index 92a9dc92..71af3e14 100644 --- a/src/Application.php +++ b/src/Application.php @@ -256,6 +256,7 @@ private function createServer(SocketServer $config, Inspector $inspector): Serve return Server::init( $config->port, payloadSize: 524_288, + acceptPeriod: .001, clientInflector: $clientInflector, logger: $this->logger, ); diff --git a/src/Sender/Console/Renderer/Sentry/Exceptions.php b/src/Sender/Console/Renderer/Sentry/Exceptions.php index 66b467f6..aff68504 100644 --- a/src/Sender/Console/Renderer/Sentry/Exceptions.php +++ b/src/Sender/Console/Renderer/Sentry/Exceptions.php @@ -87,26 +87,27 @@ private static function renderTrace(OutputInterface $output, array $frames, bool $class = empty($class) ? '' : $class . '::'; $function = $getValue($frame, 'function'); - $renderer = static fn() => $output->writeln( - \sprintf( - "%s%s%s\n%s%s%s()", - \str_pad("#$i", $numPad, ' '), - (string) $file, - $line !== '' ? ":$line" : '', - \str_repeat(' ', $numPad), - $class, - (string) $function, - ), + $renderedLine = \sprintf( + "%s%s%s\n%s%s%s()", + \str_pad("#$i", $numPad, ' '), + (string) $file, + $line !== '' ? ":$line" : '', + \str_repeat(' ', $numPad), + $class, + (string) $function, ); if ($isFirst) { $isFirst = false; $output->writeln('Stacktrace:'); - $renderer(); + $output->writeln($renderedLine); self::renderCodeSnippet($output, $frame, padding: $numPad); continue; } + $renderer = static function () use ($output, $renderedLine): void { + $output->writeln($renderedLine); + }; if (!$verbose && \str_starts_with(\ltrim(\str_replace('\\', '/', $file), './'), 'vendor/')) { $vendorLines[] = $renderer; continue; diff --git a/src/Socket/Client.php b/src/Socket/Client.php index 61d0ec55..bd9a63ef 100644 --- a/src/Socket/Client.php +++ b/src/Socket/Client.php @@ -27,13 +27,17 @@ final class Client implements Destroyable private \Closure $onClose; + private Timer $selectTimer; + /** * @param positive-int $payloadSize */ private function __construct( private readonly \Socket $socket, private readonly int $payloadSize, + float $selectPeriod, ) { + $this->selectTimer = new Timer($selectPeriod); \socket_set_nonblock($this->socket); $this->setOnPayload(static fn(string $payload) => null); $this->setOnClose(static fn() => null); @@ -41,12 +45,18 @@ private function __construct( /** * @param positive-int $payloadSize Max payload size. + * @param float $selectPeriod Time to wait between socket_select() calls in seconds. */ public static function init( \Socket $socket, int $payloadSize = 10485760, + float $selectPeriod = .001, ): self { - return new self($socket, $payloadSize); + return new self( + socket: $socket, + payloadSize: $payloadSize, + selectPeriod: $selectPeriod, + ); } public function destroy(): void @@ -103,6 +113,7 @@ public function process(): void throw new ClientDisconnected(); } \Fiber::suspend(); + $this->selectTimer->reset()->wait(); } while (true); } diff --git a/src/Socket/Server.php b/src/Socket/Server.php index b4b79270..b518330e 100644 --- a/src/Socket/Server.php +++ b/src/Socket/Server.php @@ -10,7 +10,6 @@ use Buggregator\Trap\Processable; use Buggregator\Trap\Socket\Exception\ClientDisconnected; use Buggregator\Trap\Socket\Exception\ServerStopped; -use Socket; /** * @internal @@ -27,13 +26,18 @@ final class Server implements Processable, Cancellable, Destroyable private bool $cancelled = false; + /** Timestamp with microseconds when last socket_accept() was called */ + private float $lastAccept = 0; + /** * @param null|\Closure(Client, int $id): void $clientInflector * @param positive-int $payloadSize Max payload size. + * @param float $acceptPeriod Time to wait between socket_accept() calls in seconds. */ private function __construct( int $port, private readonly int $payloadSize, + private readonly float $acceptPeriod, private readonly ?\Closure $clientInflector, private readonly Logger $logger, ) { @@ -44,21 +48,23 @@ private function __construct( \socket_set_nonblock($this->socket); - $logger->status('Application', 'Server started on 127.0.0.1:%s', $port); + $logger->status('App', 'Server started on 127.0.0.1:%s', $port); } /** * @param int<1, 65535> $port * @param positive-int $payloadSize Max payload size. + * @param float $acceptPeriod Time to wait between socket_accept() calls in seconds. * @param null|\Closure(Client, int $id): void $clientInflector */ public static function init( int $port = 9912, int $payloadSize = 10485760, + float $acceptPeriod = .001, ?\Closure $clientInflector = null, Logger $logger = new Logger(), ): self { - return new self($port, $payloadSize, $clientInflector, $logger); + return new self($port, $payloadSize, $acceptPeriod, $clientInflector, $logger); } public function destroy(): void @@ -82,11 +88,16 @@ public function destroy(): void public function process(): void { // /** @psalm-suppress PossiblyInvalidArgument */ - while (!$this->cancelled and false !== ($socket = \socket_accept($this->socket))) { + while (match(true) { + $this->cancelled, + \microtime(true) - $this->lastAccept <= $this->acceptPeriod => false, + default => false !== ($socket = \socket_accept($this->socket)), + }) { + $this->lastAccept = \microtime(true); $client = null; try { - /** @psalm-suppress MixedArgument */ - $client = Client::init($socket, $this->payloadSize); + /** @var \Socket $socket */ + $client = Client::init($socket, $this->payloadSize, $this->acceptPeriod); $key = (int) \array_key_last($this->clients) + 1; $this->clients[$key] = $client; $this->clientInflector !== null and ($this->clientInflector)($client, $key);