Skip to content

Calling exit before the request lifecycle ends causes Scope: missing call to Scope::detach() for scope #1899

@zaalbarxx

Description

@zaalbarxx

Hello, maybe you'd be able to guide us, if it is possible to handle it correctly, but:

  • we are using Laravel application with the service provider which registers the tracing like this
$resource = ResourceInfo::create(
                Attributes::create([
                    'service.name' => config('otel.service_name'),
                    'deployment.environment' => config('app.deployed_env'),
                ])
            );

            $transport = (new OtlpHttpTransportFactory)->create(
                config('otel.endpoint'),
                Protocols::contentType(config('otel.protocol')),
                ['X-SF-Token' => config('otel.sf_token')],
            );

            $tracerProvider = TracerProvider::builder()
                ->setResource($resource)
                ->addSpanProcessor(new BatchSpanProcessor(new SpanExporter($transport), SystemClock::create()))
                ->build();

            Sdk::builder()
                ->setTracerProvider($tracerProvider)
                ->setAutoShutdown(true)
                ->buildAndRegisterGlobal();

            $this->app->singleton(\OpenTelemetry\API\Trace\TracerInterface::class, fn () => $tracerProvider->getTracer(config('app.name')));

then we have the middleware which is like this:

{
    public function handle(Request $request, Closure $next): Response
    {
        if (isFeatureFlagEnabled('backoffice.disable-otel-splunk-telemetry')) {
            return $next($request);
        }

        $propagator = TraceContextPropagator::getInstance();

        $parentContext = $propagator->extract(
            $request->headers->all(),
            \OpenTelemetry\Context\Propagation\ArrayAccessGetterSetter::getInstance(),
        );

        $tracer = Globals::tracerProvider()->getTracer(config('app.name'));

        $route = $request->route()?->uri() ?? $request->path();

        $span = $tracer
            ->spanBuilder($request->method().' '.$route)
            ->setParent($parentContext)
            ->setSpanKind(SpanKind::KIND_SERVER)
            ->startSpan();

        $scope = $span->activate();

        // ====== SEMANTIC CONVENTIONS (HTTP SERVER) ======
        $span->setAttribute('http.method', $request->method());
        $span->setAttribute('http.route', $route);
        $span->setAttribute('http.target', $request->getRequestUri());
        $span->setAttribute('http.scheme', $request->getScheme());
        $span->setAttribute('http.host', $request->getHost());
        $span->setAttribute('http.user_agent', $request->userAgent() ?? '');
        $span->setAttribute('net.peer.ip', $request->ip());

        try {
            $response = $next($request);

            $statusCode = $response->getStatusCode();

            $span->setAttribute('http.status_code', $statusCode);

            if ($statusCode >= 500) {
                $span->setStatus(StatusCode::STATUS_ERROR);
            }

            // ====== Inject trace headers do response ======
            $propagationSetter = new class implements \OpenTelemetry\Context\Propagation\PropagationSetterInterface
            {
                public function set(&$carrier, string $key, string $value): void
                {
                    $carrier->headers->set($key, $value, false);
                }
            };

            $propagator->inject($response, $propagationSetter, OtelContext::getCurrent());

            return $response;
        } catch (\Throwable $e) {
            $span->recordException($e);
            $span->setStatus(StatusCode::STATUS_ERROR);

            throw $e;
        } finally {
            $span->end();
            $scope->detach();
        }
    }
}

Everything seem to work correctly when the full request lifecycle ends, because the finally clause will be called and detach the scope. But if during the lifecycle I'll call something like dd() which is an function which prints something and calls exit(1) then the finally seem to run after the DebugScope destructor will be called and thus, since the scope has not been detached the error Scope: missing call to Scope::detach() for scope # will be returned along with the echoed/dumped data.

 "message": "Scope: missing call to Scope::detach() for scope #2051, created \n\tat OpenTelemetry.Context.Context.activate(Context.php:82)\n\tat OpenTelemetry.API.Trace.Span.activate(Span.php:56)\n\tat App.Http.Middleware.OtelPropagator.handle(OtelPropagator.php:39)\n\tat Illuminate.Pipeline.Pipeline.{closure:{closure:Illuminate.Pipeline.Pipeline::carry():194}:195}(Pipeline.php:219)\n\tat Illuminate.Http.Middleware.HandleCors.handle(HandleCors.php:61)\n\tat Illuminate.Pipeline.Pipeline.{closure:{closure:Illuminate.Pipeline.Pipeline::carry():194}:195}(Pipeline.php:219)\n\tat Illuminate.Http.Middleware.TrustProxies.handle(TrustProxies.php:58)\n\tat Illuminate.Pipeline.Pipeline.{closure:{closure:Illuminate.Pipeline.Pipeline::carry():194}:195}(Pipeline.php:219)\n\tat Illuminate.Pipeline.Pipeline.then(Pipeline.php:137)\n\tat Illuminate.Foundation.Http.Kernel.sendRequestThroughRouter(Kernel.php:175)\n\tat Illuminate.Foundation.Http.Kernel.handle(Kernel.php:144)\n\tat require(index.php:52)\n\tat {main}(server.php:139)\n",
    "exception": "ErrorException",
    "file": "D:\\vhosts\\fifa.gop.backoffice-backend\\vendor\\open-telemetry\\context\\DebugScope.php",

It is not a breaker, but I was wondering what could be the issue here. It looks like the setAutoShutdown should handle it, but it looks like it is not, I mean the destructor is called even before the callback in register_shutdown_function is called so it does not pass the

if (self::$finalShutdownPhase && $this->fiberId !== self::currentFiberId()) {
                return;
            }
``` condition.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions