Add coroutine-safe Sentry runtime contexts - #564
Merged
Conversation
Use the SDK's caller-provided Hub API at execution boundaries and record the resulting allocation and lookup savings.
Use the current Sentry PHP development branch in both the monorepo and split-package manifests so Hypervel can consume the merged runtime-context storage and caller-provided Hub APIs before their first tagged release. Keep the two constraints aligned so local development and standalone package installs resolve the same SDK contract.
Dispatch ConnectionOpening after the bridged request is installed but before handshake validation and routing. Dispatch ConnectionClosing before handler resolution and onClose execution while preserving the existing cancellation, failure-reporting, and cleanup policies. Guard both events with hasListeners() so unused events add no callback allocation or dispatch cost. Document the complete WebSocket lifecycle and cover event order, payloads, failures, cancellation, and connection cleanup.
Clear Hypervel-owned runtime-context storage registration before reinitializing the SDK Hub in the shared PHPUnit cleanup subscriber. Cover active-context discard, Hub replacement, and removal of the prior storage registration so Sentry state cannot leak between Testbench application lifecycles or ordinary tests.
Store SDK runtime contexts in CoroutineContext and retain shared ownership when application child coroutines inherit an execution. Return the context only from the final release so the SDK flushes buffered logs and metrics exactly once. Keep the shared holder out of generic context copies, make explicit inheritance idempotent for repeated startup hooks, and provide a small execution-boundary service that reuses nested contexts and always schedules cleanup before application callbacks run. Cover root and child ownership, both release orders, duplicate inheritance, nested boundaries, exact Hub reuse, and non-coroutine behavior.
Register coroutine runtime storage before Sentry features boot, then start contexts at HTTP, queue, schedule, and WebSocket callback boundaries using Hypervel's existing coroutine-aware Hub. Share each execution context with its application child coroutines while preserving eager scope and request snapshots. Skip all propagation for delivery coroutines, avoid SDK context construction on the child hot path, and flush global console telemetry only when no bounded execution is active. Preserve scope configuration made before client resolution and keep optional WebSocket support dependency-free. Add focused coverage for provider ordering, inactive applications, request middleware, concurrent isolation, WebSocket lifecycles, child and grandchild ownership, duplicate hooks, and the delivery fast path.
Remove the per-scope and terminal-event flush workarounds now that runtime contexts own buffered logs and metrics for the full execution lifetime. Queue failures and scheduled-task completion now finish their spans while the outer context performs the single final flush. Route bounded shutdown drains through the SDK flush facade before waiting on the pooled transport generation. Remove the superseded internal flush helper and cover publication ordering, execution-end scheduling behavior, queue shutdown, root console termination, and worker-exit drains.
Remove the obsolete warnings for Sentry Logs and Trace Metrics now that runtime contexts isolate their buffers across requests, jobs, scheduled tasks, WebSocket callbacks, and application child coroutines. Restore the SDK default for trace metrics, explain that the deprecated enable flags are compatibility no-ops, and point users to the before-send callbacks when they need to drop either telemetry type. Update configuration and integration coverage to match the supported behavior.
Run the process-priority assertion in a child process so it cannot permanently raise a reusable parallel test worker's niceness. Measure the change from the inherited baseline instead of assuming the child starts at priority zero. This keeps the test valid when the parent process already has a nonzero priority while still verifying the requested adjustment.
Record the implemented ownership model, provider ordering, child propagation fast paths, execution boundaries, flush semantics, configuration behavior, documentation changes, and focused regression coverage. Keep the final verification requirements explicit and include the SDK cleanup and Horizon process-isolation corrections so the plan matches the reviewed codebase without retaining superseded design language.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR makes Sentry Logs and Trace Metrics safe to use in Hypervel's concurrent Swoole workers.
It adopts the runtime-context APIs added in getsentry/sentry-php#2190 and getsentry/sentry-php#2191. Hypervel supplies coroutine-local storage, its coroutine-aware Hub, and the execution boundaries, while the SDK continues to own the runtime context resources and flush behavior.
The Sentry dependency temporarily tracks
dev-masterbecause these APIs have been merged but haven't been included in a tagged release yet.Motivation
Hypervel already isolates Sentry scopes between coroutines through its custom Hub. Sentry Logs and Trace Metrics were different: their buffers belonged to the SDK's process-wide runtime context, so concurrent requests or jobs could add to and flush the same buffers.
The package documented those features as unsupported and used explicit flushes around scope and job cleanup. Those flushes couldn't provide isolation, and some ran before all child coroutine work had finished.
The SDK now exposes the pieces needed for the framework to model this directly. A Hypervel execution can have one Sentry runtime context, and that context can stay alive until the execution and its application child coroutines are done.
Design
The integration starts a runtime context at each framework-owned execution boundary:
Nested boundaries in the same coroutine reuse the active context. Root console telemetry continues to use the SDK's global context and is flushed when the application terminates.
Runtime contexts are stored in
CoroutineContext. When an execution creates child coroutines, they keep the existing eager snapshot of the parent's Sentry scope and request, but share the execution's Logs and Metrics buffers. A small counted holder keeps the context alive until the last owner exits, at which point the SDK flushes it once.The holder implements
NonCopyableContext, so general context copies can't duplicate ownership without retaining it. Sentry's explicit child hook is the only path that shares the holder. The hook also handles repeated Testbench registration without retaining the same child twice.Hypervel passes its existing coroutine-aware Hub directly to
SentrySdk::startContext(). This avoids constructing a throwaway SDK Hub and Scope at every execution boundary. Child coroutines don't construct another runtime context, Hub, Scope, or pair of telemetry aggregators.Sentry delivery coroutines are marked before child startup hooks run. They skip scope, request, and runtime-context propagation, so sending an envelope can't inherit or flush application telemetry.
WebSocket lifecycle
The WebSocket server now dispatches two guarded lifecycle events:
ConnectionOpeningafter the bridged request is installed and before handshake validation or routing;ConnectionClosingbefore handler resolution andonClose().These fill the two gaps where the existing events happened after application code had already started or completed. Both use
hasListeners()guards, so unused events create no event object and perform no dispatch.The existing error, cancellation, and cleanup behavior is preserved. Opening failures still follow the handshake rendering path, while closing listener failures are reported without skipping later close callbacks. Cancellation stops later callbacks but still releases connection state.
Flushing and shutdown
Execution-end flushing replaces the explicit flushes previously attached to scope pops, failed jobs, and scheduled-task terminal events. This keeps telemetry buffered for the complete execution and lets the final runtime-context owner flush it once.
Integration::drainEvents()now uses the SDK flush facade to publish Logs and Metrics before performing Hypervel's bounded wait for accepted pooled deliveries. Graceful queue shutdown and worker-exit drains retain their existing bounded behavior. Application termination doesn't flush the global context while a bounded execution context is still active.The old internal
Integration::flushEvents()path and its duplicate Logs/Metrics flush code are removed.Configuration and compatibility
The existing middleware, facade, configuration keys, tracing behavior, and pooled transport API remain intact.
The deprecated
enable_logsandenable_metricskeys remain for Sentry Laravel configuration compatibility, but the SDK no longer uses them as feature switches. The documentation now points tobefore_send_logandbefore_send_metricwhen an application needs to discard that telemetry. Trace Metrics return to the SDK's default configuration.Sentry remains independent of the WebSocket Server package. The provider registers optional event class names without adding a package dependency.
SDK state registered by Hypervel is reset from the shared PHPUnit cleanup subscriber so it can't leak between application lifecycles. The full parallel suite also exposed a Horizon niceness test that permanently changed a reusable worker's priority; that test now runs in a separate process and asserts the change from its inherited baseline.
Performance
Inactive Sentry applications register no execution listeners or child propagation hook. WebSocket applications without listeners only pay a guarded listener lookup and don't construct events or contexts.
Benchmarks showed the active child path without a parent context remained within measurement noise, while the Sentry delivery path improved after removing repeated context lookups and cloning. A child inside an active execution adds about 1.2 microseconds for one retain, one context write, one defer, and the matching release. It doesn't create SDK runtime objects.
An active WebSocket callback pays the fixed cost of one SDK runtime context and its Logs and Metrics aggregators. That work only occurs when Sentry is active and is required to isolate telemetry between concurrent messages.
Verification