From eee9285af0e449745a6a151504241a06a1ccd66b Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 12 Jun 2026 09:46:50 +0800 Subject: [PATCH 01/17] refactor(sentry): replace manual flush with context lifecycle management - Remove SentrySdkAspect (AOP interception for SentrySdk init/hub/context) - Use SentrySdk::startContext()/endContext() in CoroutineAspect - Remove all manual SentrySdk::flush() calls from Metrics listeners (OnBeforeHandle, OnCoroutineServerStart, OnMetricFactoryReady, OnWorkerStart, PoolWatcher, QueueWatcher) - Remove SentrySdk::flush() from Tracing CoroutineAspect and EventHandleListener - Unregister SentrySdkAspect from ConfigProvider --- src/sentry/src/Aspect/CoroutineAspect.php | 7 +- src/sentry/src/Aspect/SentrySdkAspect.php | 72 ------------------- src/sentry/src/ConfigProvider.php | 1 - .../src/Metrics/Listener/OnBeforeHandle.php | 3 - .../Listener/OnCoroutineServerStart.php | 3 - .../Metrics/Listener/OnMetricFactoryReady.php | 3 - .../src/Metrics/Listener/OnWorkerStart.php | 3 - .../src/Metrics/Listener/PoolWatcher.php | 3 - .../src/Metrics/Listener/QueueWatcher.php | 3 - .../src/Tracing/Aspect/CoroutineAspect.php | 6 +- .../Tracing/Listener/EventHandleListener.php | 2 - 11 files changed, 6 insertions(+), 100 deletions(-) delete mode 100644 src/sentry/src/Aspect/SentrySdkAspect.php diff --git a/src/sentry/src/Aspect/CoroutineAspect.php b/src/sentry/src/Aspect/CoroutineAspect.php index 151c2cb70..3b22b225b 100644 --- a/src/sentry/src/Aspect/CoroutineAspect.php +++ b/src/sentry/src/Aspect/CoroutineAspect.php @@ -52,14 +52,17 @@ protected function handleCreate(ProceedingJoinPoint $proceedingJoinPoint): void $callable = $proceedingJoinPoint->arguments['keys']['callable']; $cid = Co::id(); + // Propagate the Context to the new Coroutine. + SentrySdk::startContext(); + $proceedingJoinPoint->arguments['keys']['callable'] = function () use ($callable, $cid) { // Restore the Context in the new Coroutine. foreach (self::CONTEXT_KEYS as $key) { Context::getOrSet($key, fn () => Context::get($key, coroutineId: $cid)); } - // Defer the flushing of events until the coroutine completes. - defer(fn () => SentrySdk::flush()); + // End the Context when the Coroutine ends. + defer(fn () => SentrySdk::endContext()); // Continue the callable in the new Coroutine. $callable(); diff --git a/src/sentry/src/Aspect/SentrySdkAspect.php b/src/sentry/src/Aspect/SentrySdkAspect.php deleted file mode 100644 index 6954b1635..000000000 --- a/src/sentry/src/Aspect/SentrySdkAspect.php +++ /dev/null @@ -1,72 +0,0 @@ -methodName) { - 'init' => $this->handleInit($proceedingJoinPoint), - 'setCurrentHub' => $this->handleSetCurrentHub($proceedingJoinPoint), - 'getRuntimeContextManager' => $this->handleGetRuntimeContextManager($proceedingJoinPoint), - default => $proceedingJoinPoint->process(), - }; - } - - private function handleInit(ProceedingJoinPoint $proceedingJoinPoint) - { - Context::set( - RuntimeContextManager::class, - new RuntimeContextManager(make(HubInterface::class)) - ); - - return SentrySdk::getCurrentHub(); - } - - private function handleSetCurrentHub(ProceedingJoinPoint $proceedingJoinPoint) - { - $arguments = $proceedingJoinPoint->arguments['keys'] ?? []; - $hub = $arguments['hub']; - // @phpstan-ignore-next-line - Closure::bind(fn () => static::getRuntimeContextManager()->setCurrentHub($hub), null, SentrySdk::class)(); - - return $hub; - } - - private function handleGetRuntimeContextManager(ProceedingJoinPoint $proceedingJoinPoint) - { - return Context::getOrSet( - RuntimeContextManager::class, - fn () => new RuntimeContextManager(make(HubInterface::class)) - ); - } -} diff --git a/src/sentry/src/ConfigProvider.php b/src/sentry/src/ConfigProvider.php index a1be9bed8..ed94ce4df 100644 --- a/src/sentry/src/ConfigProvider.php +++ b/src/sentry/src/ConfigProvider.php @@ -28,7 +28,6 @@ public function __invoke(): array Aspect\LoggerAspect::class, Aspect\RedisAspect::class, // Aspect\SingletonAspect::class, - Aspect\SentrySdkAspect::class, Metrics\Aspect\CounterAspect::class, Metrics\Aspect\HistogramAspect::class, Tracing\Aspect\AmqpProducerAspect::class, diff --git a/src/sentry/src/Metrics/Listener/OnBeforeHandle.php b/src/sentry/src/Metrics/Listener/OnBeforeHandle.php index 74940eea5..bd28da0cc 100644 --- a/src/sentry/src/Metrics/Listener/OnBeforeHandle.php +++ b/src/sentry/src/Metrics/Listener/OnBeforeHandle.php @@ -21,7 +21,6 @@ use Hyperf\Event\Contract\ListenerInterface; use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; -use Sentry\SentrySdk; use Sentry\Unit; use function FriendsOfHyperf\Sentry\metrics; @@ -114,8 +113,6 @@ function () use ($metrics) { ['worker' => '0'], Unit::megabyte() ); - - SentrySdk::flush(); } ); } diff --git a/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php b/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php index e7b28db88..9985eb36b 100644 --- a/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php +++ b/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php @@ -19,7 +19,6 @@ use Hyperf\Server\Event\MainCoroutineServerStart; use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; -use Sentry\SentrySdk; use Sentry\Unit; use function FriendsOfHyperf\Sentry\metrics; @@ -112,8 +111,6 @@ function () use ($metrics) { ['worker' => '0'], Unit::megabyte() ); - - SentrySdk::flush(); } ); } diff --git a/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php b/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php index 247e080eb..b2066e07d 100644 --- a/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php +++ b/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php @@ -21,7 +21,6 @@ use Hyperf\Event\Contract\ListenerInterface; use Hyperf\Support\System; use Psr\Container\ContainerInterface; -use Sentry\SentrySdk; use Sentry\Unit; use Swoole\Server as SwooleServer; @@ -129,8 +128,6 @@ function () use ($metrics, $serverStatsFactory, $workerId) { ['worker' => (string) $workerId], Unit::megabyte() ); - - SentrySdk::flush(); } ); } diff --git a/src/sentry/src/Metrics/Listener/OnWorkerStart.php b/src/sentry/src/Metrics/Listener/OnWorkerStart.php index 3a2638c6f..613585a0d 100644 --- a/src/sentry/src/Metrics/Listener/OnWorkerStart.php +++ b/src/sentry/src/Metrics/Listener/OnWorkerStart.php @@ -19,7 +19,6 @@ use Hyperf\Framework\Event\BeforeWorkerStart; use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; -use Sentry\SentrySdk; use Sentry\Unit; use Swoole\Server; @@ -119,8 +118,6 @@ function () use ($metrics, $event) { ['worker' => (string) ($event->workerId ?? 0)], Unit::megabyte() ); - - SentrySdk::flush(); } ); } diff --git a/src/sentry/src/Metrics/Listener/PoolWatcher.php b/src/sentry/src/Metrics/Listener/PoolWatcher.php index 1c651cbaf..52ccfb96f 100644 --- a/src/sentry/src/Metrics/Listener/PoolWatcher.php +++ b/src/sentry/src/Metrics/Listener/PoolWatcher.php @@ -18,7 +18,6 @@ use Hyperf\Pool\Pool; use Hyperf\Server\Event\MainCoroutineServerStart; use Psr\Container\ContainerInterface; -use Sentry\SentrySdk; use function FriendsOfHyperf\Sentry\metrics; @@ -89,8 +88,6 @@ function () use ($pool, $workerId, $poolName) { 'worker' => (string) $workerId, ] ); - - SentrySdk::flush(); } ); } diff --git a/src/sentry/src/Metrics/Listener/QueueWatcher.php b/src/sentry/src/Metrics/Listener/QueueWatcher.php index 70c93ec5e..5f7a0d682 100644 --- a/src/sentry/src/Metrics/Listener/QueueWatcher.php +++ b/src/sentry/src/Metrics/Listener/QueueWatcher.php @@ -18,7 +18,6 @@ use Hyperf\Coordinator\Timer; use Hyperf\Event\Contract\ListenerInterface; use Psr\Container\ContainerInterface; -use Sentry\SentrySdk; use function FriendsOfHyperf\Sentry\metrics; @@ -83,8 +82,6 @@ function () { ['queue' => $name] ); } - - SentrySdk::flush(); } ); } diff --git a/src/sentry/src/Tracing/Aspect/CoroutineAspect.php b/src/sentry/src/Tracing/Aspect/CoroutineAspect.php index 7e3471826..d113e9973 100644 --- a/src/sentry/src/Tracing/Aspect/CoroutineAspect.php +++ b/src/sentry/src/Tracing/Aspect/CoroutineAspect.php @@ -18,7 +18,6 @@ use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; use Hyperf\Engine\Coroutine as Co; -use Sentry\SentrySdk; use Sentry\State\Scope; use Sentry\Tracing\SpanContext; @@ -80,10 +79,7 @@ function (Scope $scope) use ($proceedingJoinPoint, $callingOnFunction) { ); // Defer the finishing of the transaction and flushing of events until the coroutine completes. - defer(function () use ($transaction) { - $transaction->finish(); - SentrySdk::flush(); - }); + defer(fn () => $transaction->finish()); return trace( fn () => $callable(), diff --git a/src/sentry/src/Tracing/Listener/EventHandleListener.php b/src/sentry/src/Tracing/Listener/EventHandleListener.php index 2d10cfe06..4dcb28fae 100644 --- a/src/sentry/src/Tracing/Listener/EventHandleListener.php +++ b/src/sentry/src/Tracing/Listener/EventHandleListener.php @@ -452,8 +452,6 @@ protected function handleCommandFinished(CommandEvent\AfterExecute $event): void $parentSpan->finish(); SentrySdk::getCurrentHub()->popScope(); } - - SentrySdk::flush(); } } From 9724b53eea8f9a8d3cad49a0116b0036f9745975 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 12 Jun 2026 09:47:48 +0800 Subject: [PATCH 02/17] chore(sentry): bump sentry/sentry minimum version to ^4.28.0 --- src/sentry/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/composer.json b/src/sentry/composer.json index 6ff6596e9..b9eee367b 100644 --- a/src/sentry/composer.json +++ b/src/sentry/composer.json @@ -33,7 +33,7 @@ "hyperf/http-server": "~3.1.0", "hyperf/support": "~3.1.0", "hyperf/tappable": "~3.1.0", - "sentry/sentry": "^4.21.0", + "sentry/sentry": "^4.28.0", "symfony/polyfill-php85": "^1.33" }, "suggest": { From e78ed946e5d02a0697af0583120ee4c58f6a91fd Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 12 Jun 2026 09:48:47 +0800 Subject: [PATCH 03/17] chore(sentry): sync sentry/sentry version in root composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 86578ac95..f9c46a752 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "psr/http-factory-implementation": "*", "psy/psysh": "^0.10.0 || ^0.11.0", "ramsey/uuid": "^4.7", - "sentry/sentry": "^4.21.0", + "sentry/sentry": "^4.28.0", "symfony/console": "^5.3 || ^6.0 || ^7.0", "symfony/http-foundation": "^5.3 || ^6.0 || ^7.0", "symfony/polyfill-php84": "^1.33", From 9348304a0800292d23f0adee08c9f77ac5c22f12 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 12 Jun 2026 09:54:42 +0800 Subject: [PATCH 04/17] =?UTF-8?q?=20=E2=99=BB=EF=B8=8F=20refactor(sentry):?= =?UTF-8?q?=20remove=20unused=20SingletonAspect=20and=20clean=20up=20comme?= =?UTF-8?q?nts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/src/Aspect/SingletonAspect.php | 73 ------------------- src/sentry/src/ConfigProvider.php | 1 - .../src/Tracing/Aspect/CoroutineAspect.php | 3 + 3 files changed, 3 insertions(+), 74 deletions(-) delete mode 100644 src/sentry/src/Aspect/SingletonAspect.php diff --git a/src/sentry/src/Aspect/SingletonAspect.php b/src/sentry/src/Aspect/SingletonAspect.php deleted file mode 100644 index a04e63183..000000000 --- a/src/sentry/src/Aspect/SingletonAspect.php +++ /dev/null @@ -1,73 +0,0 @@ -className; - $arguments = $proceedingJoinPoint->getArguments(); - - if (array_key_exists(0, $arguments)) { - $key .= '#' . $arguments[0]; - } - - return match ($className) { - // Singleton Classes - // \Sentry\State\HubAdapter::class, - \Sentry\Logs\Logs::class => Context::getOrSet($key, function () use ($className) { - return Closure::bind(fn () => new $className(), null, $className)(); - }), - \Sentry\Metrics\TraceMetrics::class => Context::getOrSet($key, function () use ($className) { - return new $className(); - }), - - // !!! Don't enable this for now, it may cause some unexpected issues !!! - // \Sentry\Integration\IntegrationRegistry::class => $proceedingJoinPoint->process(), - - // Enums - // \Sentry\CheckInStatus::class, - // \Sentry\EventType::class, - // \Sentry\MonitorScheduleUnit::class, - // \Sentry\Logs\LogLevel::class, - // \Sentry\Tracing\SpanStatus::class, - // \Sentry\Tracing\TransactionSource::class, - // \Sentry\Transport\ResultStatus::class, - // \Sentry\Unit::class => $proceedingJoinPoint->process(), - default => $proceedingJoinPoint->process(), - }; - } -} diff --git a/src/sentry/src/ConfigProvider.php b/src/sentry/src/ConfigProvider.php index ed94ce4df..215a2e454 100644 --- a/src/sentry/src/ConfigProvider.php +++ b/src/sentry/src/ConfigProvider.php @@ -27,7 +27,6 @@ public function __invoke(): array Aspect\GuzzleHttpClientAspect::class, Aspect\LoggerAspect::class, Aspect\RedisAspect::class, - // Aspect\SingletonAspect::class, Metrics\Aspect\CounterAspect::class, Metrics\Aspect\HistogramAspect::class, Tracing\Aspect\AmqpProducerAspect::class, diff --git a/src/sentry/src/Tracing/Aspect/CoroutineAspect.php b/src/sentry/src/Tracing/Aspect/CoroutineAspect.php index d113e9973..750027042 100644 --- a/src/sentry/src/Tracing/Aspect/CoroutineAspect.php +++ b/src/sentry/src/Tracing/Aspect/CoroutineAspect.php @@ -26,6 +26,9 @@ use function Hyperf\Coroutine\defer; use function Sentry\continueTrace; +/** + * Run after FriendsOfHyperf\Sentry\Aspect\CoroutineAspect. + */ class CoroutineAspect extends AbstractAspect { public const CONTEXT_KEYS = [ From 3947e48d3c41b57963d2fdb9f49adb9134e719ec Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:00:46 +0800 Subject: [PATCH 05/17] =?UTF-8?q?=20=F0=9F=90=9B=20fix(sentry):=20move=20s?= =?UTF-8?q?tartContext=20into=20coroutine=20after=20context=20restore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/src/Aspect/CoroutineAspect.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sentry/src/Aspect/CoroutineAspect.php b/src/sentry/src/Aspect/CoroutineAspect.php index 3b22b225b..aeacaef19 100644 --- a/src/sentry/src/Aspect/CoroutineAspect.php +++ b/src/sentry/src/Aspect/CoroutineAspect.php @@ -52,15 +52,15 @@ protected function handleCreate(ProceedingJoinPoint $proceedingJoinPoint): void $callable = $proceedingJoinPoint->arguments['keys']['callable']; $cid = Co::id(); - // Propagate the Context to the new Coroutine. - SentrySdk::startContext(); - $proceedingJoinPoint->arguments['keys']['callable'] = function () use ($callable, $cid) { // Restore the Context in the new Coroutine. foreach (self::CONTEXT_KEYS as $key) { Context::getOrSet($key, fn () => Context::get($key, coroutineId: $cid)); } + // Propagate the Context to the new Coroutine. + SentrySdk::startContext(); + // End the Context when the Coroutine ends. defer(fn () => SentrySdk::endContext()); From 449e87d6f27748e7f8aefe9d5dd667c0b13c081f Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:55:38 +0800 Subject: [PATCH 06/17] =?UTF-8?q?=20=E2=9C=A8=20feat(sentry):=20add=20coro?= =?UTF-8?q?utine-safe=20RuntimeContextManager=20via=20class=5Fmap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../class_map/RuntimeContextManager.php | 293 ++++++++++++++++++ src/sentry/src/ConfigProvider.php | 4 +- src/sentry/src/Util/ContextArrayObject.php | 57 ++++ 3 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 src/sentry/class_map/RuntimeContextManager.php create mode 100644 src/sentry/src/Util/ContextArrayObject.php diff --git a/src/sentry/class_map/RuntimeContextManager.php b/src/sentry/class_map/RuntimeContextManager.php new file mode 100644 index 000000000..9eeb42e1c --- /dev/null +++ b/src/sentry/class_map/RuntimeContextManager.php @@ -0,0 +1,293 @@ + map of execution context keys to active runtime context IDs + */ + private $executionContextToRuntimeContext; + + public function __construct(HubInterface $baseHub) + { + $this->baseHub = $baseHub; + $this->globalContext = null; + $context = new ContextArrayObject(); + $this->activeContexts = $context; + $this->executionContextToRuntimeContext = $context; + } + + /** + * Sets the current hub with context-aware behavior. + * + * If a runtime context is active for the current execution key, the hub is + * updated only for that active context. Otherwise, the baseline/global hub + * template is updated. + * + * @return bool Whether the hub was set on an active runtime context + */ + public function setCurrentHub(HubInterface $hub): bool + { + $executionContextKey = $this->getExecutionContextKey(); + + if ($this->hasActiveContextForExecutionContextKey($executionContextKey)) { + $runtimeContextId = $this->executionContextToRuntimeContext[$executionContextKey]; + $this->activeContexts[$runtimeContextId]->setHub($hub); + + return true; + } + + $this->baseHub = $hub; + + if ($this->globalContext !== null) { + $this->globalContext->setHub($hub); + } + + return false; + } + + public function getCurrentHub(): HubInterface + { + return $this->getCurrentContext()->getHub(); + } + + public function getCurrentContext(): RuntimeContext + { + $executionContextKey = $this->getExecutionContextKey(); + + if ($this->hasActiveContextForExecutionContextKey($executionContextKey)) { + $runtimeContextId = $this->executionContextToRuntimeContext[$executionContextKey]; + + return $this->activeContexts[$runtimeContextId]; + } + + return $this->getGlobalContext(); + } + + public function hasActiveContext(): bool + { + return $this->hasActiveContextForExecutionContextKey($this->getExecutionContextKey()); + } + + /** + * Starts an isolated context for the current execution key. + */ + public function startContext(): void + { + $executionContextKey = $this->getExecutionContextKey(); + + if ($this->hasActiveContextForExecutionContextKey($executionContextKey)) { + // Nested start calls for the same execution key should be a no-op. + return; + } + + $this->createContextForExecutionContextKey($executionContextKey); + } + + /** + * Ends and flushes the active context for the current execution key. + * + * When no context is active for the key this is a no-op. + */ + public function endContext(?int $timeout = null): void + { + $executionContextKey = $this->getExecutionContextKey(); + + if (! $this->hasActiveContextForExecutionContextKey($executionContextKey)) { + return; + } + + $runtimeContextId = $this->executionContextToRuntimeContext[$executionContextKey]; + unset($this->executionContextToRuntimeContext[$executionContextKey]); + + $this->removeContextById($runtimeContextId, $timeout); + } + + private function createContextForExecutionContextKey(string $executionContextKey): void + { + $runtimeContextId = $this->generateRuntimeContextId(); + $runtimeContext = new RuntimeContext($runtimeContextId, $this->createHubFromBaseHub()); + + $this->activeContexts[$runtimeContextId] = $runtimeContext; + $this->executionContextToRuntimeContext[$executionContextKey] = $runtimeContextId; + } + + private function removeContextById(string $runtimeContextId, ?int $timeout = null): void + { + if (! isset($this->activeContexts[$runtimeContextId])) { + return; + } + + $runtimeContext = $this->activeContexts[$runtimeContextId]; + unset($this->activeContexts[$runtimeContextId]); + // Remove any key mappings that may still reference this context. + $this->removeExecutionContextMappingsForRuntimeContext($runtimeContextId); + + $logger = $this->getLoggerFromHub($runtimeContext->getHub()); + + $this->flushRuntimeContextResources($runtimeContext, $timeout, $logger); + } + + private function flushRuntimeContextResources(RuntimeContext $runtimeContext, ?int $timeout, LoggerInterface $logger): void + { + $hub = $runtimeContext->getHub(); + + // captureEvent can throw before transport send (for example from scope event processors + // or before_send callbacks), so we isolate failures and continue flushing other resources. + try { + $runtimeContext->getLogsAggregator()->flush($hub); + } catch (Throwable $exception) { + $logger->error('Failed to flush logs while ending a runtime context.', [ + 'exception' => $exception, + 'runtime_context_id' => $runtimeContext->getId(), + ]); + } + + // Keep metrics flush independent from logs flush so one bad callback does not block the rest. + try { + $runtimeContext->getMetricsAggregator()->flush($hub); + } catch (Throwable $exception) { + $logger->error('Failed to flush trace metrics while ending a runtime context.', [ + 'exception' => $exception, + 'runtime_context_id' => $runtimeContext->getId(), + ]); + } + + $client = $hub->getClient(); + + if ($client === null) { + return; + } + + // Custom transports may throw from close(); endContext must stay best-effort and non-fatal. + try { + $client->flush($timeout); + } catch (Throwable $exception) { + $logger->error('Failed to flush the client transport while ending a runtime context.', [ + 'exception' => $exception, + 'runtime_context_id' => $runtimeContext->getId(), + ]); + } + } + + private function removeExecutionContextMappingsForRuntimeContext(string $runtimeContextId): void + { + foreach ($this->executionContextToRuntimeContext as $executionContextKey => $mappedRuntimeContextId) { + if ($mappedRuntimeContextId === $runtimeContextId) { + unset($this->executionContextToRuntimeContext[$executionContextKey]); + } + } + } + + private function hasActiveContextForExecutionContextKey(string $executionContextKey): bool + { + if (! isset($this->executionContextToRuntimeContext[$executionContextKey])) { + return false; + } + + $runtimeContextId = $this->executionContextToRuntimeContext[$executionContextKey]; + + if (! isset($this->activeContexts[$runtimeContextId])) { + // Mapping points to a context that was already evicted/ended; drop the stale index entry. + unset($this->executionContextToRuntimeContext[$executionContextKey]); + + return false; + } + + return true; + } + + private function createHubFromBaseHub(): HubInterface + { + if (! $this->baseHub instanceof Hub) { + return new Hub($this->baseHub->getClient()); + } + + $clonedScope = null; + + $this->baseHub->configureScope(static function (Scope $scope) use (&$clonedScope): void { + $clonedScope = clone $scope; + // Do not inherit active traces into a new runtime context. + $clonedScope->setSpan(null); + $clonedScope->setPropagationContext(PropagationContext::fromDefaults()); + }); + + return new Hub($this->baseHub->getClient(), $clonedScope ?? new Scope()); + } + + private function getLoggerFromHub(HubInterface $hub): LoggerInterface + { + $client = $hub->getClient(); + + if ($client === null) { + return new NullLogger(); + } + + return $client->getOptions()->getLoggerOrNullLogger(); + } + + private function generateRuntimeContextId(): string + { + return \sprintf('%s-%d', str_replace('.', '', uniqid('', true)), mt_rand()); + } + + private function getExecutionContextKey(): string + { + // All supported runtime modes currently use a process-local execution key. + return self::PROCESS_EXECUTION_CONTEXT_KEY; + } + + private function getGlobalContext(): RuntimeContext + { + if ($this->globalContext === null) { + // Lazy fallback keeps baseline behavior when users do not opt into explicit context lifecycle. + $this->globalContext = new RuntimeContext('global', $this->baseHub); + } + + return $this->globalContext; + } +} diff --git a/src/sentry/src/ConfigProvider.php b/src/sentry/src/ConfigProvider.php index 215a2e454..f26b950d9 100644 --- a/src/sentry/src/ConfigProvider.php +++ b/src/sentry/src/ConfigProvider.php @@ -74,7 +74,9 @@ public function __invoke(): array ], 'annotations' => [ 'scan' => [ - 'class_map' => [], + 'class_map' => [ + \Sentry\State\RuntimeContextManager::class => __DIR__ . '/../class_map/RuntimeContextManager.php', + ], ], ], 'publish' => [ diff --git a/src/sentry/src/Util/ContextArrayObject.php b/src/sentry/src/Util/ContextArrayObject.php new file mode 100644 index 000000000..634f50653 --- /dev/null +++ b/src/sentry/src/Util/ContextArrayObject.php @@ -0,0 +1,57 @@ +getArrayObject()[$offset]); + } + + public function offsetGet($offset): mixed + { + return $this->getArrayObject()[$offset] ?? null; + } + + public function offsetSet($offset, $value): void + { + $this->getArrayObject()[$offset] = $value; + } + + public function offsetUnset($offset): void + { + unset($this->getArrayObject()[$offset]); + } + + public function count(): int + { + return count($this->getArrayObject()); + } + + public function getIterator(): Traversable + { + return new ArrayIterator($this->getArrayObject() ?? []); + } + + private function getArrayObject(?int $id = null): ?ArrayObject + { + return \Hyperf\Engine\Coroutine::getContextFor($id); + } +} From 32760b8d31717557c98cba945c29288f7972296c Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:56:27 +0800 Subject: [PATCH 07/17] =?UTF-8?q?=20=F0=9F=93=9D=20docs(sentry):=20add=20c?= =?UTF-8?q?omment=20clarifying=20ContextArrayObject=20usage=20in=20constru?= =?UTF-8?q?ctor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/class_map/RuntimeContextManager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sentry/class_map/RuntimeContextManager.php b/src/sentry/class_map/RuntimeContextManager.php index 9eeb42e1c..a4727e1da 100644 --- a/src/sentry/class_map/RuntimeContextManager.php +++ b/src/sentry/class_map/RuntimeContextManager.php @@ -56,6 +56,7 @@ public function __construct(HubInterface $baseHub) { $this->baseHub = $baseHub; $this->globalContext = null; + // Using plain arrays here since the manager is designed to be used in a single-threaded execution environment and does not require the overhead of thread-safe structures. $context = new ContextArrayObject(); $this->activeContexts = $context; $this->executionContextToRuntimeContext = $context; From 59f842d6a7a465acd97486b3cb291c95d53cb122 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 12 Jun 2026 19:11:30 +0800 Subject: [PATCH 08/17] =?UTF-8?q?=20=F0=9F=90=9B=20fix(sentry):=20ensure?= =?UTF-8?q?=20baseHub=20has=20a=20valid=20client=20in=20RuntimeContextMana?= =?UTF-8?q?ger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/class_map/RuntimeContextManager.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sentry/class_map/RuntimeContextManager.php b/src/sentry/class_map/RuntimeContextManager.php index a4727e1da..62b882343 100644 --- a/src/sentry/class_map/RuntimeContextManager.php +++ b/src/sentry/class_map/RuntimeContextManager.php @@ -17,6 +17,8 @@ use Sentry\Tracing\PropagationContext; use Throwable; +use function Hyperf\Support\make; + /** * Manages runtime-local SDK state across different execution models. * @@ -54,6 +56,9 @@ final class RuntimeContextManager public function __construct(HubInterface $baseHub) { + if (! $baseHub->getClient()) { + $baseHub = make(HubInterface::class); + } $this->baseHub = $baseHub; $this->globalContext = null; // Using plain arrays here since the manager is designed to be used in a single-threaded execution environment and does not require the overhead of thread-safe structures. From 6978a6c7b77ead259cab15f7f5d7255299ccddf1 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Mon, 15 Jun 2026 09:22:37 +0800 Subject: [PATCH 09/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(sentry):=20?= =?UTF-8?q?move=20context=20lifecycle=20into=20tracing=20aspect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/src/Aspect/CoroutineAspect.php | 7 ------- src/sentry/src/Tracing/Aspect/CoroutineAspect.php | 8 +++++++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/sentry/src/Aspect/CoroutineAspect.php b/src/sentry/src/Aspect/CoroutineAspect.php index aeacaef19..0611d7da1 100644 --- a/src/sentry/src/Aspect/CoroutineAspect.php +++ b/src/sentry/src/Aspect/CoroutineAspect.php @@ -58,13 +58,6 @@ protected function handleCreate(ProceedingJoinPoint $proceedingJoinPoint): void Context::getOrSet($key, fn () => Context::get($key, coroutineId: $cid)); } - // Propagate the Context to the new Coroutine. - SentrySdk::startContext(); - - // End the Context when the Coroutine ends. - defer(fn () => SentrySdk::endContext()); - - // Continue the callable in the new Coroutine. $callable(); }; } diff --git a/src/sentry/src/Tracing/Aspect/CoroutineAspect.php b/src/sentry/src/Tracing/Aspect/CoroutineAspect.php index 750027042..07665d281 100644 --- a/src/sentry/src/Tracing/Aspect/CoroutineAspect.php +++ b/src/sentry/src/Tracing/Aspect/CoroutineAspect.php @@ -18,6 +18,7 @@ use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; use Hyperf\Engine\Coroutine as Co; +use Sentry\SentrySdk; use Sentry\State\Scope; use Sentry\Tracing\SpanContext; @@ -72,6 +73,8 @@ function (Scope $scope) use ($proceedingJoinPoint, $callingOnFunction) { Context::getOrSet($key, fn () => Context::get($key, coroutineId: $cid)); } + SentrySdk::startContext(); + // Start a new transaction for the coroutine preparation phase. $transaction = startTransaction( continueTrace($span->toTraceparent(), $span->toBaggage()) @@ -82,7 +85,10 @@ function (Scope $scope) use ($proceedingJoinPoint, $callingOnFunction) { ); // Defer the finishing of the transaction and flushing of events until the coroutine completes. - defer(fn () => $transaction->finish()); + defer(function () use ($transaction) { + $transaction->finish(); + SentrySdk::endContext(); + }); return trace( fn () => $callable(), From f7b5fce4f7de71af2fdb30b340ddb4e203fd46d4 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Mon, 15 Jun 2026 09:29:12 +0800 Subject: [PATCH 10/17] =?UTF-8?q?=F0=9F=93=9D=20docs(sentry):=20correct=20?= =?UTF-8?q?type=20hints=20and=20comment=20for=20ContextArrayObject?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/class_map/RuntimeContextManager.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sentry/class_map/RuntimeContextManager.php b/src/sentry/class_map/RuntimeContextManager.php index 62b882343..8ddc2dd57 100644 --- a/src/sentry/class_map/RuntimeContextManager.php +++ b/src/sentry/class_map/RuntimeContextManager.php @@ -45,12 +45,12 @@ final class RuntimeContextManager private $globalContext; /** - * @var array{string: RuntimeContext} map of active runtime contexts by their internal ID + * @var ContextArrayObject{string: RuntimeContext} map of active runtime contexts by their internal ID */ private $activeContexts; /** - * @var array map of execution context keys to active runtime context IDs + * @var ContextArrayObject{string: string} map of execution context keys to active runtime context IDs */ private $executionContextToRuntimeContext; @@ -61,7 +61,7 @@ public function __construct(HubInterface $baseHub) } $this->baseHub = $baseHub; $this->globalContext = null; - // Using plain arrays here since the manager is designed to be used in a single-threaded execution environment and does not require the overhead of thread-safe structures. + // Using ContextArrayObject here since the manager is designed to be used in a single-threaded execution environment and does not require the overhead of thread-safe structures. $context = new ContextArrayObject(); $this->activeContexts = $context; $this->executionContextToRuntimeContext = $context; From fd289caf3a1d41dd329d6780d86cd28f59095c22 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:49:16 +0800 Subject: [PATCH 11/17] =?UTF-8?q?=F0=9F=90=9B=20fix(sentry):=20run=20metri?= =?UTF-8?q?cs=20collection=20within=20a=20coroutine=20context?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Metrics/Listener/OnBeforeHandle.php | 31 ++++----- .../Listener/OnCoroutineServerStart.php | 33 +++++----- .../Metrics/Listener/OnMetricFactoryReady.php | 63 ++++++++++--------- .../src/Metrics/Listener/OnWorkerStart.php | 55 ++++++++-------- .../src/Metrics/Listener/PoolWatcher.php | 51 ++++++++------- .../src/Metrics/Listener/QueueWatcher.php | 55 ++++++++-------- 6 files changed, 153 insertions(+), 135 deletions(-) diff --git a/src/sentry/src/Metrics/Listener/OnBeforeHandle.php b/src/sentry/src/Metrics/Listener/OnBeforeHandle.php index bd28da0cc..96089f3c5 100644 --- a/src/sentry/src/Metrics/Listener/OnBeforeHandle.php +++ b/src/sentry/src/Metrics/Listener/OnBeforeHandle.php @@ -24,6 +24,7 @@ use Sentry\Unit; use function FriendsOfHyperf\Sentry\metrics; +use function Hyperf\Coroutine\wait; class OnBeforeHandle implements ListenerInterface { @@ -98,21 +99,23 @@ public function process(object $event): void $this->timer->tick( $this->feature->getMetricsInterval(), function () use ($metrics) { - $this->trySet('gc_', $metrics, gc_status()); - $this->trySet('', $metrics, getrusage()); + wait(function () use ($metrics) { + $this->trySet('gc_', $metrics, gc_status()); + $this->trySet('', $metrics, getrusage()); - metrics()->gauge( - 'memory_usage', - memory_get_usage(true) / 1024 / 1024, - ['worker' => '0'], - Unit::megabyte() - ); - metrics()->gauge( - 'memory_peak_usage', - memory_get_peak_usage(true) / 1024 / 1024, - ['worker' => '0'], - Unit::megabyte() - ); + metrics()->gauge( + 'memory_usage', + memory_get_usage(true) / 1024 / 1024, + ['worker' => '0'], + Unit::megabyte() + ); + metrics()->gauge( + 'memory_peak_usage', + memory_get_peak_usage(true) / 1024 / 1024, + ['worker' => '0'], + Unit::megabyte() + ); + }); } ); } diff --git a/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php b/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php index 9985eb36b..5ea06c375 100644 --- a/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php +++ b/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php @@ -22,6 +22,7 @@ use Sentry\Unit; use function FriendsOfHyperf\Sentry\metrics; +use function Hyperf\Coroutine\wait; class OnCoroutineServerStart implements ListenerInterface { @@ -96,21 +97,23 @@ public function process(object $event): void $this->timer->tick( $this->feature->getMetricsInterval(), function () use ($metrics) { - $this->trySet('gc_', $metrics, gc_status()); - $this->trySet('', $metrics, getrusage()); - - metrics()->gauge( - 'memory_usage', - memory_get_usage(true) / 1024 / 1024, - ['worker' => '0'], - Unit::megabyte() - ); - metrics()->gauge( - 'memory_peak_usage', - memory_get_peak_usage(true) / 1024 / 1024, - ['worker' => '0'], - Unit::megabyte() - ); + wait(function () use ($metrics) { + $this->trySet('gc_', $metrics, gc_status()); + $this->trySet('', $metrics, getrusage()); + + metrics()->gauge( + 'memory_usage', + memory_get_usage(true) / 1024 / 1024, + ['worker' => '0'], + Unit::megabyte() + ); + metrics()->gauge( + 'memory_peak_usage', + memory_get_peak_usage(true) / 1024 / 1024, + ['worker' => '0'], + Unit::megabyte() + ); + }); } ); } diff --git a/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php b/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php index b2066e07d..fbaaa87f3 100644 --- a/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php +++ b/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php @@ -25,6 +25,7 @@ use Swoole\Server as SwooleServer; use function FriendsOfHyperf\Sentry\metrics; +use function Hyperf\Coroutine\wait; class OnMetricFactoryReady implements ListenerInterface { @@ -98,36 +99,38 @@ public function process(object $event): void $this->timer->tick( $this->feature->getMetricsInterval(), function () use ($metrics, $serverStatsFactory, $workerId) { - $this->trySet('', $metrics, Co::stats(), $workerId); - $this->trySet('timer_', $metrics, Timer::stats(), $workerId); - - if ($serverStatsFactory) { - $this->trySet('', $metrics, $serverStatsFactory(), $workerId); - } - - if (class_exists('Swoole\Timer')) { - $this->trySet('swoole_timer_', $metrics, \Swoole\Timer::stats(), $workerId); - } - - $load = sys_getloadavg(); - - metrics()->gauge( - 'sys_load', - round($load[0] / System::getCpuCoresNum(), 2), - ['worker' => (string) $workerId], - ); - metrics()->gauge( - 'metric_process_memory_usage', - memory_get_usage(true) / 1024 / 1024, - ['worker' => (string) $workerId], - Unit::megabyte() - ); - metrics()->gauge( - 'metric_process_memory_peak_usage', - memory_get_peak_usage(true) / 1024 / 1024, - ['worker' => (string) $workerId], - Unit::megabyte() - ); + wait(function () use ($metrics, $serverStatsFactory, $workerId) { + $this->trySet('', $metrics, Co::stats(), $workerId); + $this->trySet('timer_', $metrics, Timer::stats(), $workerId); + + if ($serverStatsFactory) { + $this->trySet('', $metrics, $serverStatsFactory(), $workerId); + } + + if (class_exists('Swoole\Timer')) { + $this->trySet('swoole_timer_', $metrics, \Swoole\Timer::stats(), $workerId); + } + + $load = sys_getloadavg(); + + metrics()->gauge( + 'sys_load', + round($load[0] / System::getCpuCoresNum(), 2), + ['worker' => (string) $workerId], + ); + metrics()->gauge( + 'metric_process_memory_usage', + memory_get_usage(true) / 1024 / 1024, + ['worker' => (string) $workerId], + Unit::megabyte() + ); + metrics()->gauge( + 'metric_process_memory_peak_usage', + memory_get_peak_usage(true) / 1024 / 1024, + ['worker' => (string) $workerId], + Unit::megabyte() + ); + }); } ); } diff --git a/src/sentry/src/Metrics/Listener/OnWorkerStart.php b/src/sentry/src/Metrics/Listener/OnWorkerStart.php index 613585a0d..c2497211e 100644 --- a/src/sentry/src/Metrics/Listener/OnWorkerStart.php +++ b/src/sentry/src/Metrics/Listener/OnWorkerStart.php @@ -23,6 +23,7 @@ use Swoole\Server; use function FriendsOfHyperf\Sentry\metrics; +use function Hyperf\Coroutine\wait; class OnWorkerStart implements ListenerInterface { @@ -91,33 +92,35 @@ public function process(object $event): void $this->timer->tick( $this->feature->getMetricsInterval(), function () use ($metrics, $event) { - $server = $this->container->get(Server::class); - $serverStats = $server->stats(); - $this->trySet('gc_', $metrics, gc_status()); - $this->trySet('', $metrics, getrusage()); + wait(function () use ($metrics, $event) { + $server = $this->container->get(Server::class); + $serverStats = $server->stats(); + $this->trySet('gc_', $metrics, gc_status()); + $this->trySet('', $metrics, getrusage()); - metrics()->gauge( - 'worker_request_count', - (float) $serverStats['worker_request_count'], - ['worker' => (string) ($event->workerId ?? 0)], - ); - metrics()->gauge( - 'worker_dispatch_count', - (float) $serverStats['worker_dispatch_count'], - ['worker' => (string) ($event->workerId ?? 0)], - ); - metrics()->gauge( - 'memory_usage', - memory_get_usage(true) / 1024 / 1024, - ['worker' => (string) ($event->workerId ?? 0)], - Unit::megabyte() - ); - metrics()->gauge( - 'memory_peak_usage', - memory_get_peak_usage(true) / 1024 / 1024, - ['worker' => (string) ($event->workerId ?? 0)], - Unit::megabyte() - ); + metrics()->gauge( + 'worker_request_count', + (float) $serverStats['worker_request_count'], + ['worker' => (string) ($event->workerId ?? 0)], + ); + metrics()->gauge( + 'worker_dispatch_count', + (float) $serverStats['worker_dispatch_count'], + ['worker' => (string) ($event->workerId ?? 0)], + ); + metrics()->gauge( + 'memory_usage', + memory_get_usage(true) / 1024 / 1024, + ['worker' => (string) ($event->workerId ?? 0)], + Unit::megabyte() + ); + metrics()->gauge( + 'memory_peak_usage', + memory_get_peak_usage(true) / 1024 / 1024, + ['worker' => (string) ($event->workerId ?? 0)], + Unit::megabyte() + ); + }); } ); } diff --git a/src/sentry/src/Metrics/Listener/PoolWatcher.php b/src/sentry/src/Metrics/Listener/PoolWatcher.php index 52ccfb96f..10723ad64 100644 --- a/src/sentry/src/Metrics/Listener/PoolWatcher.php +++ b/src/sentry/src/Metrics/Listener/PoolWatcher.php @@ -20,6 +20,7 @@ use Psr\Container\ContainerInterface; use function FriendsOfHyperf\Sentry\metrics; +use function Hyperf\Coroutine\wait; abstract class PoolWatcher implements ListenerInterface { @@ -64,30 +65,32 @@ public function watch(Pool $pool, string $poolName, int $workerId): void $this->timer->tick( $this->feature->getMetricsInterval(), function () use ($pool, $workerId, $poolName) { - metrics()->gauge( - $this->getPrefix() . '_connections_in_use', - (float) $pool->getCurrentConnections(), - [ - 'pool' => $poolName, - 'worker' => (string) $workerId, - ] - ); - metrics()->gauge( - $this->getPrefix() . '_connections_in_waiting', - (float) $pool->getConnectionsInChannel(), - [ - 'pool' => $poolName, - 'worker' => (string) $workerId, - ] - ); - metrics()->gauge( - $this->getPrefix() . '_max_connections', - (float) $pool->getOption()->getMaxConnections(), - [ - 'pool' => $poolName, - 'worker' => (string) $workerId, - ] - ); + wait(function () use ($pool, $workerId, $poolName) { + metrics()->gauge( + $this->getPrefix() . '_connections_in_use', + (float) $pool->getCurrentConnections(), + [ + 'pool' => $poolName, + 'worker' => (string) $workerId, + ] + ); + metrics()->gauge( + $this->getPrefix() . '_connections_in_waiting', + (float) $pool->getConnectionsInChannel(), + [ + 'pool' => $poolName, + 'worker' => (string) $workerId, + ] + ); + metrics()->gauge( + $this->getPrefix() . '_max_connections', + (float) $pool->getOption()->getMaxConnections(), + [ + 'pool' => $poolName, + 'worker' => (string) $workerId, + ] + ); + }); } ); } diff --git a/src/sentry/src/Metrics/Listener/QueueWatcher.php b/src/sentry/src/Metrics/Listener/QueueWatcher.php index 5f7a0d682..55b669762 100644 --- a/src/sentry/src/Metrics/Listener/QueueWatcher.php +++ b/src/sentry/src/Metrics/Listener/QueueWatcher.php @@ -20,6 +20,7 @@ use Psr\Container\ContainerInterface; use function FriendsOfHyperf\Sentry\metrics; +use function Hyperf\Coroutine\wait; class QueueWatcher implements ListenerInterface { @@ -54,34 +55,36 @@ public function process(object $event): void $this->timer->tick( $this->feature->getMetricsInterval(), function () { - $config = $this->container->get(ConfigInterface::class); - $queues = array_keys($config->get('async_queue', [])); + wait(function () { + $config = $this->container->get(ConfigInterface::class); + $queues = array_keys($config->get('async_queue', [])); - foreach ($queues as $name) { - $queue = $this->container->get(DriverFactory::class)->get($name); - $info = $queue->info(); + foreach ($queues as $name) { + $queue = $this->container->get(DriverFactory::class)->get($name); + $info = $queue->info(); - metrics()->gauge( - 'queue_waiting', - (float) $info['waiting'], - ['queue' => $name] - ); - metrics()->gauge( - 'queue_delayed', - (float) $info['delayed'], - ['queue' => $name] - ); - metrics()->gauge( - 'queue_failed', - (float) $info['failed'], - ['queue' => $name] - ); - metrics()->gauge( - 'queue_timeout', - (float) $info['timeout'], - ['queue' => $name] - ); - } + metrics()->gauge( + 'queue_waiting', + (float) $info['waiting'], + ['queue' => $name] + ); + metrics()->gauge( + 'queue_delayed', + (float) $info['delayed'], + ['queue' => $name] + ); + metrics()->gauge( + 'queue_failed', + (float) $info['failed'], + ['queue' => $name] + ); + metrics()->gauge( + 'queue_timeout', + (float) $info['timeout'], + ['queue' => $name] + ); + } + }); } ); } From bced65bbedaaa2638e340cfc0f84eaa59c28fa1b Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:50:33 +0800 Subject: [PATCH 12/17] =?UTF-8?q?=F0=9F=90=9B=20fix(sentry):=20disable=20c?= =?UTF-8?q?oordinator=20tracing=20span=20by=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/src/Tracing/Aspect/CoordinatorAspect.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php b/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php index 8f58d5390..49e2dec0b 100644 --- a/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php +++ b/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php @@ -32,7 +32,7 @@ public function __construct(protected Feature $feature) public function process(ProceedingJoinPoint $proceedingJoinPoint) { - if (! $this->feature->isTracingSpanEnabled('coordinator')) { + if (! $this->feature->isTracingSpanEnabled('coordinator', false)) { return $proceedingJoinPoint->process(); } From 62ed785f3dc283ea09fb65bc22e23b1275304286 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:47:10 +0800 Subject: [PATCH 13/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(sentry):=20?= =?UTF-8?q?remove=20coordinator=20tracing=20aspect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/src/ConfigProvider.php | 1 - src/sentry/src/Tracing/Aspect/CoordinatorAspect.php | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/sentry/src/ConfigProvider.php b/src/sentry/src/ConfigProvider.php index f26b950d9..ef5999564 100644 --- a/src/sentry/src/ConfigProvider.php +++ b/src/sentry/src/ConfigProvider.php @@ -32,7 +32,6 @@ public function __invoke(): array Tracing\Aspect\AmqpProducerAspect::class, Tracing\Aspect\AsyncQueueJobMessageAspect::class, Tracing\Aspect\CacheAspect::class, - Tracing\Aspect\CoordinatorAspect::class, Tracing\Aspect\CoroutineAspect::class, Tracing\Aspect\DbAspect::class, Tracing\Aspect\DbConnectionAspect::class, diff --git a/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php b/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php index 49e2dec0b..f81dfeaac 100644 --- a/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php +++ b/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php @@ -32,10 +32,6 @@ public function __construct(protected Feature $feature) public function process(ProceedingJoinPoint $proceedingJoinPoint) { - if (! $this->feature->isTracingSpanEnabled('coordinator', false)) { - return $proceedingJoinPoint->process(); - } - $timeout = $proceedingJoinPoint->arguments['keys']['timeout'] ?? -1; return trace( From 4b159ae8b7f699a5b9a46b74e02950debc5f4bd6 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:57:52 +0800 Subject: [PATCH 14/17] =?UTF-8?q?Revert=20"=F0=9F=90=9B=20fix(sentry):=20r?= =?UTF-8?q?un=20metrics=20collection=20within=20a=20coroutine=20context"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit fd289caf3a1d41dd329d6780d86cd28f59095c22. --- .../src/Metrics/Listener/OnBeforeHandle.php | 31 +++++---- .../Listener/OnCoroutineServerStart.php | 33 +++++----- .../Metrics/Listener/OnMetricFactoryReady.php | 63 +++++++++---------- .../src/Metrics/Listener/OnWorkerStart.php | 55 ++++++++-------- .../src/Metrics/Listener/PoolWatcher.php | 51 +++++++-------- .../src/Metrics/Listener/QueueWatcher.php | 55 ++++++++-------- 6 files changed, 135 insertions(+), 153 deletions(-) diff --git a/src/sentry/src/Metrics/Listener/OnBeforeHandle.php b/src/sentry/src/Metrics/Listener/OnBeforeHandle.php index 96089f3c5..bd28da0cc 100644 --- a/src/sentry/src/Metrics/Listener/OnBeforeHandle.php +++ b/src/sentry/src/Metrics/Listener/OnBeforeHandle.php @@ -24,7 +24,6 @@ use Sentry\Unit; use function FriendsOfHyperf\Sentry\metrics; -use function Hyperf\Coroutine\wait; class OnBeforeHandle implements ListenerInterface { @@ -99,23 +98,21 @@ public function process(object $event): void $this->timer->tick( $this->feature->getMetricsInterval(), function () use ($metrics) { - wait(function () use ($metrics) { - $this->trySet('gc_', $metrics, gc_status()); - $this->trySet('', $metrics, getrusage()); + $this->trySet('gc_', $metrics, gc_status()); + $this->trySet('', $metrics, getrusage()); - metrics()->gauge( - 'memory_usage', - memory_get_usage(true) / 1024 / 1024, - ['worker' => '0'], - Unit::megabyte() - ); - metrics()->gauge( - 'memory_peak_usage', - memory_get_peak_usage(true) / 1024 / 1024, - ['worker' => '0'], - Unit::megabyte() - ); - }); + metrics()->gauge( + 'memory_usage', + memory_get_usage(true) / 1024 / 1024, + ['worker' => '0'], + Unit::megabyte() + ); + metrics()->gauge( + 'memory_peak_usage', + memory_get_peak_usage(true) / 1024 / 1024, + ['worker' => '0'], + Unit::megabyte() + ); } ); } diff --git a/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php b/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php index 5ea06c375..9985eb36b 100644 --- a/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php +++ b/src/sentry/src/Metrics/Listener/OnCoroutineServerStart.php @@ -22,7 +22,6 @@ use Sentry\Unit; use function FriendsOfHyperf\Sentry\metrics; -use function Hyperf\Coroutine\wait; class OnCoroutineServerStart implements ListenerInterface { @@ -97,23 +96,21 @@ public function process(object $event): void $this->timer->tick( $this->feature->getMetricsInterval(), function () use ($metrics) { - wait(function () use ($metrics) { - $this->trySet('gc_', $metrics, gc_status()); - $this->trySet('', $metrics, getrusage()); - - metrics()->gauge( - 'memory_usage', - memory_get_usage(true) / 1024 / 1024, - ['worker' => '0'], - Unit::megabyte() - ); - metrics()->gauge( - 'memory_peak_usage', - memory_get_peak_usage(true) / 1024 / 1024, - ['worker' => '0'], - Unit::megabyte() - ); - }); + $this->trySet('gc_', $metrics, gc_status()); + $this->trySet('', $metrics, getrusage()); + + metrics()->gauge( + 'memory_usage', + memory_get_usage(true) / 1024 / 1024, + ['worker' => '0'], + Unit::megabyte() + ); + metrics()->gauge( + 'memory_peak_usage', + memory_get_peak_usage(true) / 1024 / 1024, + ['worker' => '0'], + Unit::megabyte() + ); } ); } diff --git a/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php b/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php index fbaaa87f3..b2066e07d 100644 --- a/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php +++ b/src/sentry/src/Metrics/Listener/OnMetricFactoryReady.php @@ -25,7 +25,6 @@ use Swoole\Server as SwooleServer; use function FriendsOfHyperf\Sentry\metrics; -use function Hyperf\Coroutine\wait; class OnMetricFactoryReady implements ListenerInterface { @@ -99,38 +98,36 @@ public function process(object $event): void $this->timer->tick( $this->feature->getMetricsInterval(), function () use ($metrics, $serverStatsFactory, $workerId) { - wait(function () use ($metrics, $serverStatsFactory, $workerId) { - $this->trySet('', $metrics, Co::stats(), $workerId); - $this->trySet('timer_', $metrics, Timer::stats(), $workerId); - - if ($serverStatsFactory) { - $this->trySet('', $metrics, $serverStatsFactory(), $workerId); - } - - if (class_exists('Swoole\Timer')) { - $this->trySet('swoole_timer_', $metrics, \Swoole\Timer::stats(), $workerId); - } - - $load = sys_getloadavg(); - - metrics()->gauge( - 'sys_load', - round($load[0] / System::getCpuCoresNum(), 2), - ['worker' => (string) $workerId], - ); - metrics()->gauge( - 'metric_process_memory_usage', - memory_get_usage(true) / 1024 / 1024, - ['worker' => (string) $workerId], - Unit::megabyte() - ); - metrics()->gauge( - 'metric_process_memory_peak_usage', - memory_get_peak_usage(true) / 1024 / 1024, - ['worker' => (string) $workerId], - Unit::megabyte() - ); - }); + $this->trySet('', $metrics, Co::stats(), $workerId); + $this->trySet('timer_', $metrics, Timer::stats(), $workerId); + + if ($serverStatsFactory) { + $this->trySet('', $metrics, $serverStatsFactory(), $workerId); + } + + if (class_exists('Swoole\Timer')) { + $this->trySet('swoole_timer_', $metrics, \Swoole\Timer::stats(), $workerId); + } + + $load = sys_getloadavg(); + + metrics()->gauge( + 'sys_load', + round($load[0] / System::getCpuCoresNum(), 2), + ['worker' => (string) $workerId], + ); + metrics()->gauge( + 'metric_process_memory_usage', + memory_get_usage(true) / 1024 / 1024, + ['worker' => (string) $workerId], + Unit::megabyte() + ); + metrics()->gauge( + 'metric_process_memory_peak_usage', + memory_get_peak_usage(true) / 1024 / 1024, + ['worker' => (string) $workerId], + Unit::megabyte() + ); } ); } diff --git a/src/sentry/src/Metrics/Listener/OnWorkerStart.php b/src/sentry/src/Metrics/Listener/OnWorkerStart.php index c2497211e..613585a0d 100644 --- a/src/sentry/src/Metrics/Listener/OnWorkerStart.php +++ b/src/sentry/src/Metrics/Listener/OnWorkerStart.php @@ -23,7 +23,6 @@ use Swoole\Server; use function FriendsOfHyperf\Sentry\metrics; -use function Hyperf\Coroutine\wait; class OnWorkerStart implements ListenerInterface { @@ -92,35 +91,33 @@ public function process(object $event): void $this->timer->tick( $this->feature->getMetricsInterval(), function () use ($metrics, $event) { - wait(function () use ($metrics, $event) { - $server = $this->container->get(Server::class); - $serverStats = $server->stats(); - $this->trySet('gc_', $metrics, gc_status()); - $this->trySet('', $metrics, getrusage()); + $server = $this->container->get(Server::class); + $serverStats = $server->stats(); + $this->trySet('gc_', $metrics, gc_status()); + $this->trySet('', $metrics, getrusage()); - metrics()->gauge( - 'worker_request_count', - (float) $serverStats['worker_request_count'], - ['worker' => (string) ($event->workerId ?? 0)], - ); - metrics()->gauge( - 'worker_dispatch_count', - (float) $serverStats['worker_dispatch_count'], - ['worker' => (string) ($event->workerId ?? 0)], - ); - metrics()->gauge( - 'memory_usage', - memory_get_usage(true) / 1024 / 1024, - ['worker' => (string) ($event->workerId ?? 0)], - Unit::megabyte() - ); - metrics()->gauge( - 'memory_peak_usage', - memory_get_peak_usage(true) / 1024 / 1024, - ['worker' => (string) ($event->workerId ?? 0)], - Unit::megabyte() - ); - }); + metrics()->gauge( + 'worker_request_count', + (float) $serverStats['worker_request_count'], + ['worker' => (string) ($event->workerId ?? 0)], + ); + metrics()->gauge( + 'worker_dispatch_count', + (float) $serverStats['worker_dispatch_count'], + ['worker' => (string) ($event->workerId ?? 0)], + ); + metrics()->gauge( + 'memory_usage', + memory_get_usage(true) / 1024 / 1024, + ['worker' => (string) ($event->workerId ?? 0)], + Unit::megabyte() + ); + metrics()->gauge( + 'memory_peak_usage', + memory_get_peak_usage(true) / 1024 / 1024, + ['worker' => (string) ($event->workerId ?? 0)], + Unit::megabyte() + ); } ); } diff --git a/src/sentry/src/Metrics/Listener/PoolWatcher.php b/src/sentry/src/Metrics/Listener/PoolWatcher.php index 10723ad64..52ccfb96f 100644 --- a/src/sentry/src/Metrics/Listener/PoolWatcher.php +++ b/src/sentry/src/Metrics/Listener/PoolWatcher.php @@ -20,7 +20,6 @@ use Psr\Container\ContainerInterface; use function FriendsOfHyperf\Sentry\metrics; -use function Hyperf\Coroutine\wait; abstract class PoolWatcher implements ListenerInterface { @@ -65,32 +64,30 @@ public function watch(Pool $pool, string $poolName, int $workerId): void $this->timer->tick( $this->feature->getMetricsInterval(), function () use ($pool, $workerId, $poolName) { - wait(function () use ($pool, $workerId, $poolName) { - metrics()->gauge( - $this->getPrefix() . '_connections_in_use', - (float) $pool->getCurrentConnections(), - [ - 'pool' => $poolName, - 'worker' => (string) $workerId, - ] - ); - metrics()->gauge( - $this->getPrefix() . '_connections_in_waiting', - (float) $pool->getConnectionsInChannel(), - [ - 'pool' => $poolName, - 'worker' => (string) $workerId, - ] - ); - metrics()->gauge( - $this->getPrefix() . '_max_connections', - (float) $pool->getOption()->getMaxConnections(), - [ - 'pool' => $poolName, - 'worker' => (string) $workerId, - ] - ); - }); + metrics()->gauge( + $this->getPrefix() . '_connections_in_use', + (float) $pool->getCurrentConnections(), + [ + 'pool' => $poolName, + 'worker' => (string) $workerId, + ] + ); + metrics()->gauge( + $this->getPrefix() . '_connections_in_waiting', + (float) $pool->getConnectionsInChannel(), + [ + 'pool' => $poolName, + 'worker' => (string) $workerId, + ] + ); + metrics()->gauge( + $this->getPrefix() . '_max_connections', + (float) $pool->getOption()->getMaxConnections(), + [ + 'pool' => $poolName, + 'worker' => (string) $workerId, + ] + ); } ); } diff --git a/src/sentry/src/Metrics/Listener/QueueWatcher.php b/src/sentry/src/Metrics/Listener/QueueWatcher.php index 55b669762..5f7a0d682 100644 --- a/src/sentry/src/Metrics/Listener/QueueWatcher.php +++ b/src/sentry/src/Metrics/Listener/QueueWatcher.php @@ -20,7 +20,6 @@ use Psr\Container\ContainerInterface; use function FriendsOfHyperf\Sentry\metrics; -use function Hyperf\Coroutine\wait; class QueueWatcher implements ListenerInterface { @@ -55,36 +54,34 @@ public function process(object $event): void $this->timer->tick( $this->feature->getMetricsInterval(), function () { - wait(function () { - $config = $this->container->get(ConfigInterface::class); - $queues = array_keys($config->get('async_queue', [])); + $config = $this->container->get(ConfigInterface::class); + $queues = array_keys($config->get('async_queue', [])); - foreach ($queues as $name) { - $queue = $this->container->get(DriverFactory::class)->get($name); - $info = $queue->info(); + foreach ($queues as $name) { + $queue = $this->container->get(DriverFactory::class)->get($name); + $info = $queue->info(); - metrics()->gauge( - 'queue_waiting', - (float) $info['waiting'], - ['queue' => $name] - ); - metrics()->gauge( - 'queue_delayed', - (float) $info['delayed'], - ['queue' => $name] - ); - metrics()->gauge( - 'queue_failed', - (float) $info['failed'], - ['queue' => $name] - ); - metrics()->gauge( - 'queue_timeout', - (float) $info['timeout'], - ['queue' => $name] - ); - } - }); + metrics()->gauge( + 'queue_waiting', + (float) $info['waiting'], + ['queue' => $name] + ); + metrics()->gauge( + 'queue_delayed', + (float) $info['delayed'], + ['queue' => $name] + ); + metrics()->gauge( + 'queue_failed', + (float) $info['failed'], + ['queue' => $name] + ); + metrics()->gauge( + 'queue_timeout', + (float) $info['timeout'], + ['queue' => $name] + ); + } } ); } From d66bd09d73c9fa8bdd07fe8b74459e57ecfe2b62 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Mon, 15 Jun 2026 15:27:50 +0800 Subject: [PATCH 15/17] =?UTF-8?q?=F0=9F=90=9B=20fix(sentry):=20isolate=20s?= =?UTF-8?q?entry=20context=20per=20coroutine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/src/Aspect/CoroutineAspect.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sentry/src/Aspect/CoroutineAspect.php b/src/sentry/src/Aspect/CoroutineAspect.php index 0611d7da1..c736a7111 100644 --- a/src/sentry/src/Aspect/CoroutineAspect.php +++ b/src/sentry/src/Aspect/CoroutineAspect.php @@ -58,6 +58,10 @@ protected function handleCreate(ProceedingJoinPoint $proceedingJoinPoint): void Context::getOrSet($key, fn () => Context::get($key, coroutineId: $cid)); } + SentrySdk::startContext(); + + defer(fn () => SentrySdk::endContext()); + $callable(); }; } From 6e612c2f68f5014ac3ba9e98324345adc1def5b2 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Tue, 16 Jun 2026 09:28:59 +0800 Subject: [PATCH 16/17] =?UTF-8?q?=F0=9F=90=9B=20fix(sentry):=20treat=20sen?= =?UTF-8?q?try=20metrics/transport=20as=20coroutine=20backtrace=20break=20?= =?UTF-8?q?points?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/src/Util/CoroutineBacktraceHelper.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sentry/src/Util/CoroutineBacktraceHelper.php b/src/sentry/src/Util/CoroutineBacktraceHelper.php index f523fe30a..acdc2e46b 100644 --- a/src/sentry/src/Util/CoroutineBacktraceHelper.php +++ b/src/sentry/src/Util/CoroutineBacktraceHelper.php @@ -29,6 +29,10 @@ class CoroutineBacktraceHelper 'Multiplex\Socket\Client->loop', 'Multiplex\Socket\Client->heartbeat', 'FriendsOfHyperf\Sentry\HttpClient\HttpClient->loop', 'FriendsOfHyperf\Sentry\Transport\CoHttpTransport->loop', + 'FriendsOfHyperf\Sentry\Transport\CoHttpTransport->FriendsOfHyperf\Sentry\Transport\{closure}', + 'FriendsOfHyperf\Sentry\Metrics\Listener\OnBeforeHandle->process', + 'FriendsOfHyperf\Sentry\Metrics\Listener\OnMetricFactoryReady->process', + 'FriendsOfHyperf\Sentry\Metrics\Listener\QueueWatcher->process', 'Hyperf\Kafka\Producer->loop', 'Hyperf\Metric\Listener\OnMetricFactoryReady->process', 'Hyperf\Metric\Listener\QueueWatcher->process', From e3ddd4bf6ce9c52ba6308e5bc1ea3a968d86936f Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Tue, 16 Jun 2026 09:31:50 +0800 Subject: [PATCH 17/17] =?UTF-8?q?=F0=9F=93=A6=20build(sentry):=20require?= =?UTF-8?q?=20hyperf/coordinator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sentry/composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sentry/composer.json b/src/sentry/composer.json index b9eee367b..7ca409df3 100644 --- a/src/sentry/composer.json +++ b/src/sentry/composer.json @@ -24,6 +24,7 @@ "require": { "friendsofhyperf/support": "~3.1.61", "hyperf/command": "~3.1.0", + "hyperf/coordinator": "~3.1.70", "hyperf/context": "~3.1.0", "hyperf/coroutine": "~3.1.0", "hyperf/di": "~3.1.0",