Skip to content

Lambda mode shares one CLS context and facade segment across concurrent invocations (Lambda Managed Instances) #760

Description

@dreamorosi

Summary

In Lambda mode, the SDK creates a single process-wide CLS context at initialization and never creates another (lib/env/aws_lambda.js):

var namespace = contextUtils.getNamespace();
namespace.enter(namespace.createContext());
contextUtils.setSegment(facadeSegment());

With Lambda Managed Instances (perExecutionEnvironmentMaxConcurrency > 1), multiple invocations are multiplexed into the same execution environment concurrently. Two pieces of state are then shared by all in-flight invocations:

  1. The active-segment slot. setSegment() writes into the single shared context, so any code that manages subsegments via getSegment()/setSegment() (rather than captureAsyncFunc) resolves another invocation's subsegment once invocations interleave. Annotations, metadata, and instrumented-call subsegments attach to the wrong invocation - potentially another tenant's trace.

  2. The facade segment itself. There is one facade per process, and resolveLambdaTraceData() mutates its trace_id/id/notTraced in place whenever getSegment() observes a new trace header. Under interleaving, subsegment flush() reads this.segment.trace_id at emission time, so even captureAsyncFunc users can emit subsegments onto whichever trace the facade was last resolved to.

The runtime already provides the primitive needed to fix this: globalThis.awslambda.InvokeStore, an AsyncLocalStorage that the runtime scopes around each invocation - and which resolveLambdaTraceData() already consults for the trace id.

Proposal

In Lambda mode, when awslambda.InvokeStore is available:

  • scope the current-segment storage per invocation (e.g. key the segment slot by InvokeStore identity instead of the single CLS slot), and
  • maintain a per-invocation facade (or at minimum snapshot the resolved trace data onto subsegments at creation time rather than reading the shared facade at flush time),

falling back to today's single-context behavior on runtimes without InvokeStore. This would make both captureAsyncFunc and getSegment()/setSegment()-based instrumentation safe under LMI concurrency without API changes.

Reproduction

Deploy any function using getSegment()/addNewSubsegment()/setSegment() per invocation on a capacity provider with perExecutionEnvironmentMaxConcurrency > 1, add a distinctive annotation per invocation, and force multiplexing: annotations land on other invocations' subsegments, and subsegment documents are emitted with the wrong trace_id.

Downstream report with full trace evidence: aws-powertools/powertools-lambda-typescript#5434.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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