Skip to content

Kotlin coroutine interceptor can leave an async trace block open #14044

Description

@cj848

Bug description

ResumeWithInterceptor can leave the coroutine span event opened on a ChildTrace when another async integration changes the AsyncContext's current trace while BaseContinuationImpl.resumeWith(...) is executing.

AsyncContextSpanEventSimpleAroundInterceptor.before() obtains a trace with continueAsyncTraceObject(...) and starts a span event. Its after() resolves the trace again through currentAsyncTraceObject(). If that lookup returns null or a different trace after a nested Reactor callback, the original event is not closed. When Reactor later closes the child trace's asynchronous invocation block, Pinpoint reports a mismatched stack ID because the coroutine event is still on top.

Observed warning

Corrupted call stack found
PinpointException: not matched stack id. expected=1001, current=-1,
spanEvent=SpanEvent{stackId=-1, serviceType=8901, ...}
PinpointException: not empty call stack
PinpointException: already closed trace

The application request succeeds, but its trace can be corrupted or dropped and the agent emits repeated warning stack traces.

Environment

  • Pinpoint agent: 3.1.0
  • Spring WebFlux / Reactor Core: 3.8.6
  • Kotlin coroutines: 1.11.0
  • JDK: 26
  • Coroutine execution uses a virtual-thread dispatcher

Expected behavior

The interceptor should close the same trace block that it opened in before(), regardless of changes to the async context's current trace during the intercepted continuation.

Proposed fix

Use the block-aware async interceptor base for ResumeWithInterceptor. AsyncContextSpanEventBlockSimpleAroundInterceptor passes the exact TraceBlock returned by before() into after(), avoiding a second lookup of currentAsyncTraceObject().

I am preparing a pull request with focused regression coverage.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions