diff --git a/src/sentry/src/Constants.php b/src/sentry/src/Constants.php index 87c4991fa..63f28f50d 100644 --- a/src/sentry/src/Constants.php +++ b/src/sentry/src/Constants.php @@ -11,6 +11,8 @@ namespace FriendsOfHyperf\Sentry; +use Hyperf\Context\Context; + class Constants { public const TRACE_CARRIER = 'sentry.tracing.trace_carrier'; @@ -21,5 +23,15 @@ class Constants public const TRACEPARENT = 'traceparent'; - public static bool $runningInCommand = false; + public const CTX_RUNNING_IN_COMMAND = 'sentry.constants.running_in_command'; + + public static function runningInCommand(): bool + { + return (bool) Context::get(self::CTX_RUNNING_IN_COMMAND, false); + } + + public static function setRunningInCommand(bool $running = true): void + { + Context::set(self::CTX_RUNNING_IN_COMMAND, $running); + } } diff --git a/src/sentry/src/Integration.php b/src/sentry/src/Integration.php index 776ccd165..9c9a0a152 100644 --- a/src/sentry/src/Integration.php +++ b/src/sentry/src/Integration.php @@ -11,6 +11,7 @@ namespace FriendsOfHyperf\Sentry; +use Hyperf\Context\Context; use Sentry\Breadcrumb; use Sentry\Event; use Sentry\Integration\IntegrationInterface; @@ -28,7 +29,7 @@ class Integration implements IntegrationInterface { - private static ?string $transaction = null; + public const CONTEXT_TRANSACTION = 'sentry.integration.transaction'; public function setupOnce(): void { @@ -90,12 +91,12 @@ public static function configureScope(callable $callback): void public static function getTransaction(): ?string { - return self::$transaction; + return Context::get(self::CONTEXT_TRANSACTION); } public static function setTransaction(?string $transaction): void { - self::$transaction = $transaction; + Context::set(self::CONTEXT_TRANSACTION, $transaction); } /** diff --git a/src/sentry/src/Metrics/Listener/OnBeforeHandle.php b/src/sentry/src/Metrics/Listener/OnBeforeHandle.php index f75d48f57..c2255b921 100644 --- a/src/sentry/src/Metrics/Listener/OnBeforeHandle.php +++ b/src/sentry/src/Metrics/Listener/OnBeforeHandle.php @@ -58,7 +58,7 @@ public function process(object $event): void return; } - Constants::$runningInCommand = true; + Constants::setRunningInCommand(); if ($this->feature->isCommandMetricsEnabled() && $this->container->has(EventDispatcherInterface::class)) { $this->container->get(EventDispatcherInterface::class)->dispatch(new MetricFactoryReady()); diff --git a/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php b/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php index e34c294a2..88d238eb3 100644 --- a/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php +++ b/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php @@ -83,7 +83,7 @@ public function process(object $event): void $serverStatsFactory = null; - if (! SentryConstants::$runningInCommand) { + if (! SentryConstants::runningInCommand()) { if ($this->container->has(SwooleServer::class) && $server = $this->container->get(SwooleServer::class)) { if ($server instanceof SwooleServer) { $serverStatsFactory = fn (): array => $server->stats(); diff --git a/tests/Sentry/CoroutineScopedStateTest.php b/tests/Sentry/CoroutineScopedStateTest.php new file mode 100644 index 000000000..a0bc56080 --- /dev/null +++ b/tests/Sentry/CoroutineScopedStateTest.php @@ -0,0 +1,112 @@ +assertGreaterThan(0, Coroutine::getCid()); + + Integration::setTransaction('A'); + $this->assertSame('A', Integration::getTransaction()); + + Integration::setTransaction(null); + $this->assertNull(Integration::getTransaction()); + } + + public function testTransactionIsIsolatedBetweenCoroutines(): void + { + $values = []; + $firstSet = new Channel(1); + $secondChecked = new Channel(1); + $finished = new Channel(2); + + Coroutine::create(function () use ($firstSet, $secondChecked, $finished, &$values): void { + Integration::setTransaction('A'); + $firstSet->push(true); // Signal that 'A' has been set. + $secondChecked->pop(); // Wait until the second coroutine verified its own value. + $values['first'] = Integration::getTransaction(); + $finished->push(true); + }); + + Coroutine::create(function () use ($firstSet, $secondChecked, $finished, &$values): void { + $firstSet->pop(); // Wait until the first coroutine set 'A'. + Integration::setTransaction('B'); + $values['second'] = Integration::getTransaction(); + $secondChecked->push(true); // Resume the first coroutine. + $finished->push(true); + }); + + $finished->pop(); + $finished->pop(); + + $this->assertSame('A', $values['first']); + $this->assertSame('B', $values['second']); + } + + public function testRunningInCommandDefaultsToFalseAndCanBeEnabledWithinCoroutine(): void + { + $this->assertFalse(Constants::runningInCommand()); + + Constants::setRunningInCommand(); + $this->assertTrue(Constants::runningInCommand()); + + Constants::setRunningInCommand(false); + $this->assertFalse(Constants::runningInCommand()); + } + + public function testRunningInCommandIsIsolatedBetweenCoroutines(): void + { + $values = []; + $firstSet = new Channel(1); + $secondChecked = new Channel(1); + $finished = new Channel(2); + + Coroutine::create(function () use ($firstSet, $secondChecked, $finished, &$values): void { + Constants::setRunningInCommand(); + $firstSet->push(true); // Signal that the flag has been set. + $secondChecked->pop(); // Wait until the second coroutine verified its own value. + $values['first'] = Constants::runningInCommand(); + $finished->push(true); + }); + + Coroutine::create(function () use ($firstSet, $secondChecked, $finished, &$values): void { + $firstSet->pop(); // Wait until the first coroutine set the flag. + $values['second'] = Constants::runningInCommand(); // Unset in this coroutine. + $secondChecked->push(true); // Resume the first coroutine. + $finished->push(true); + }); + + $finished->pop(); + $finished->pop(); + + $this->assertTrue($values['first']); + $this->assertFalse($values['second']); + } +}