44
55namespace OpenTelemetry \Context ;
66
7+ use function assert ;
78use function basename ;
9+ use function class_exists ;
810use function count ;
911use function debug_backtrace ;
1012use const DEBUG_BACKTRACE_IGNORE_ARGS ;
13+ use Fiber ;
14+ use const PHP_VERSION_ID ;
15+ use function register_shutdown_function ;
16+ use function spl_object_id ;
1117use function sprintf ;
1218use function trigger_error ;
1319
1622 */
1723final class DebugScope implements ScopeInterface
1824{
19- private const DEBUG_TRACE_CREATE = ' __debug_trace_create ' ;
20- private const DEBUG_TRACE_DETACH = ' __debug_trace_detach ' ;
25+ private static bool $ shutdownHandlerInitialized = false ;
26+ private static bool $ finalShutdownPhase = false ;
2127
2228 private ContextStorageScopeInterface $ scope ;
29+ private ?int $ fiberId ;
30+ private array $ createdAt ;
31+ private ?array $ detachedAt = null ;
2332
24- public function __construct (ContextStorageScopeInterface $ node )
33+ public function __construct (ContextStorageScopeInterface $ scope )
2534 {
26- $ this ->scope = $ node ;
27- $ this ->scope [self ::DEBUG_TRACE_CREATE ] = debug_backtrace (DEBUG_BACKTRACE_IGNORE_ARGS );
35+ $ this ->scope = $ scope ;
36+ $ this ->fiberId = self ::currentFiberId ();
37+ $ this ->createdAt = debug_backtrace (DEBUG_BACKTRACE_IGNORE_ARGS );
38+
39+ if (!self ::$ shutdownHandlerInitialized ) {
40+ self ::$ shutdownHandlerInitialized = true ;
41+ register_shutdown_function ('register_shutdown_function ' , static fn () => self ::$ finalShutdownPhase = true );
42+ }
2843 }
2944
3045 public function detach (): int
3146 {
32- $ this ->scope [ self :: DEBUG_TRACE_DETACH ] ??= debug_backtrace (DEBUG_BACKTRACE_IGNORE_ARGS );
47+ $ this ->detachedAt ??= debug_backtrace (DEBUG_BACKTRACE_IGNORE_ARGS );
3348
3449 $ flags = $ this ->scope ->detach ();
3550
3651 if (($ flags & ScopeInterface::DETACHED ) !== 0 ) {
3752 trigger_error (sprintf (
3853 'Scope: unexpected call to Scope::detach() for scope #%d, scope was already detached %s ' ,
3954 spl_object_id ($ this ),
40- self ::formatBacktrace ($ this ->scope [ self :: DEBUG_TRACE_DETACH ] ),
55+ self ::formatBacktrace ($ this ->detachedAt ),
4156 ));
4257 } elseif (($ flags & ScopeInterface::MISMATCH ) !== 0 ) {
4358 trigger_error (sprintf (
@@ -56,15 +71,39 @@ public function detach(): int
5671
5772 public function __destruct ()
5873 {
59- if (!isset ($ this ->scope [self ::DEBUG_TRACE_DETACH ])) {
74+ if (!$ this ->detachedAt ) {
75+ // Handle destructors invoked during final shutdown
76+ // DebugScope::__destruct() might be called before fiber finally blocks run
77+ if (self ::$ finalShutdownPhase && $ this ->fiberId !== self ::currentFiberId ()) {
78+ return ;
79+ }
80+
6081 trigger_error (sprintf (
6182 'Scope: missing call to Scope::detach() for scope #%d, created %s ' ,
6283 spl_object_id ($ this ->scope ),
63- self ::formatBacktrace ($ this ->scope [ self :: DEBUG_TRACE_CREATE ] ),
84+ self ::formatBacktrace ($ this ->createdAt ),
6485 ));
6586 }
6687 }
6788
89+ /**
90+ * @phan-suppress PhanUndeclaredClassReference
91+ * @phan-suppress PhanUndeclaredClassMethod
92+ */
93+ private static function currentFiberId (): ?int
94+ {
95+ if (PHP_VERSION_ID < 80100 ) {
96+ return null ;
97+ }
98+
99+ assert (class_exists (Fiber::class, false ));
100+ if (!$ fiber = Fiber::getCurrent ()) {
101+ return null ;
102+ }
103+
104+ return spl_object_id ($ fiber );
105+ }
106+
68107 private static function formatBacktrace (array $ trace ): string
69108 {
70109 $ s = '' ;
0 commit comments