Skip to content

Fix message handler spans appear disconnected from the incoming SQS trace#11511

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 8 commits into
masterfrom
ygree/aws-sdk-sqs-bug
Jun 26, 2026
Merged

Fix message handler spans appear disconnected from the incoming SQS trace#11511
gh-worker-dd-mergequeue-cf854d[bot] merged 8 commits into
masterfrom
ygree/aws-sdk-sqs-bug

Conversation

@ygree

@ygree ygree commented May 30, 2026

Copy link
Copy Markdown
Contributor

What Does This Do

Add regression test for sync SQS receive response copy context loss
Preserve SQS receive context across AWS SDK response rebuilding
Migrate SQS client tests to JUnit

Motivation

  • message handling spans are not attached to an SQS consume/inbound span
  • trace continuity from producer to consumer is broken
  • extracted upstream context and time-in-queue are lost
  • the app still processes messages, but Datadog shows handler work as detached roots or under the wrong active span instead of under the SQS receive/consume flow

Additional Notes

Problem Statement

For sync AWS SDK v2 SQS ReceiveMessage, consumer spans were not created when application code iterated received messages.

The immediate cause was that ReceiveMessageResponse.messages() was not returning a TracingList. That wrapping only happens when the instrumentation can find the original QueueUrl attached to the ReceiveMessageResponse.

The old code attached QueueUrl in afterExecution, but afterExecution.context.response() is not the final response instance returned to user code. AWS SDK core rebuilds the response before returning it, in BaseClientHandler.attachHttpResponseToResult():

return ((response, httpFullResponse) ->
    (OutputT) response.toBuilder().sdkHttpResponse(httpFullResponse).build());

So the queue URL was attached to the pre-rebuild response, while user code called messages() on the rebuilt response. The context lookup missed, the list was not wrapped, and no consumer spans were created.

Why tests didn't catch it

The old tests only proved that a Sqs.ReceiveMessage span existed, for example in this assertTraces block. They did not prove it was created during application/user iteration.

The new test creates a span inside the user message-iteration callback and verifies that this span is a child of the SQS consumer span.

The old test could see Sqs.ReceiveMessage because it was created during MessageMD5ChecksumInterceptor.afterExecution. TracingExecutionInterceptor.afterExecution and MessageMD5ChecksumInterceptor.afterExecution run in the same AWS SDK afterExecution interceptor phase, with the same afterExecution.context.response() object. The old Datadog interceptor attached QueueUrl to that exact pre-rebuild response instance, so when the checksum interceptor called messages() on the same object, our instrumentation found QueueUrl and returned a TracingList.

User code saw a different object: the final response instance created by BaseClientHandler.attachHttpResponseToResult(). In the old implementation, that final response did not have QueueUrl attached, so user-code messages() was not wrapped.

Solution

Move QueueUrl capture from afterExecution to modifyResponse in TracingExecutionInterceptor.

modifyResponse is the right hook because it runs after unmarshalling, while both the original ReceiveMessageRequest and the pre-rebuild ReceiveMessageResponse are available, but before AWS SDK core creates the final response copy.

If QueueUrl is captured in afterExecution, that final response copy already exists. At that point we can still attach QueueUrl to afterExecution.context.response(), but the SDK has already called toBuilder() and build(), so the builder advices have already missed their chance to carry the value to the response instance returned to user code.

The instrumentation stores QueueUrl on the pre-rebuild ReceiveMessageResponse, then propagates it through the SDK copy path:

pre-rebuild response -> response builder -> final response returned to user code

This is done with two advices:

  • ReceiveMessageResponse.toBuilder() copies QueueUrl from the response context store to the builder context store.
  • ReceiveMessageResponse$BuilderImpl.build() copies QueueUrl from the builder context store to the newly built response.

After that, when user code calls messages() on the final response instance, the advice can find QueueUrl, return a TracingList, and create the SQS consumer span during user message iteration.

The change also marks MessageMD5ChecksumInterceptor.afterExecution as SDK-internal access, so its internal messages() call does not wrap the list or create consumer spans during checksum verification. Consumer spans are created when application code consumes the messages.

Contributor Checklist

  • Format the title according to the contribution guidelines
  • Assign the type: and (comp: or inst:) labels in addition to any other useful labels
  • Avoid using close, fix, or any linking keywords when referencing an issue
    Use solves instead, and assign the PR milestone to the issue
  • Update the CODEOWNERS file on source file addition, migration, or deletion
  • Update public documentation with any new configuration flags or behaviors
  • Add your completed PR to the merge queue by commenting /merge. You can also:
    • Customize the commit message associated with the merge with /merge --commit-message "..."
    • Remove your PR from the merge queue with /merge -c
    • Skip all merge queue checks with /merge -f --reason "reason"; please use this judiciously, as some checks do not run at the PR-level
    • Get more information in this doc

Jira ticket: APMS-19562

@ygree ygree self-assigned this May 30, 2026
@ygree ygree added type: bug fix Bug fix inst: aws sdk AWS SDK instrumentation labels May 30, 2026
@dd-octo-sts

dd-octo-sts Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 14.02 s 14.02 s [-0.8%; +0.7%] (no difference)
startup:insecure-bank:tracing:Agent 12.89 s 13.00 s [-1.4%; -0.2%] (maybe better)
startup:petclinic:appsec:Agent 16.87 s 16.01 s [+0.9%; +9.8%] (maybe worse)
startup:petclinic:iast:Agent 16.69 s 16.90 s [-2.1%; -0.2%] (maybe better)
startup:petclinic:profiling:Agent 16.82 s 16.42 s [-2.3%; +7.1%] (no difference)
startup:petclinic:sca:Agent 16.85 s 16.62 s [+0.5%; +2.3%] (maybe worse)
startup:petclinic:tracing:Agent 15.91 s 16.20 s [-2.7%; -0.9%] (maybe better)

Commit: 37361f38 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@datadog-datadog-prod-us1

This comment has been minimized.

ygree added 2 commits May 29, 2026 18:16
Store the receive queue URL before the SDK rebuilds
ReceiveMessageResponse
and propagate it through the response builder so messages() can still
wrap
the final list in TracingList.

Also avoid wrapping messages during the SDK's internal
MessageMD5ChecksumInterceptor pass to prevent creating consumer spans
before user code actually consumes the messages.
@ygree
ygree force-pushed the ygree/aws-sdk-sqs-bug branch from 2cacb35 to 2fffa01 Compare May 30, 2026 01:31
@ygree ygree changed the title Message handler spans appear disconnected from the incoming SQS trace Fix message handler spans appear disconnected from the incoming SQS trace May 30, 2026
@ygree
ygree marked this pull request as ready for review June 3, 2026 19:31
@ygree
ygree requested review from a team as code owners June 3, 2026 19:31
@ygree
ygree requested review from vandonr and removed request for a team June 3, 2026 19:31

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2fffa01794

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ygree
ygree force-pushed the ygree/aws-sdk-sqs-bug branch from 7a239a2 to 73cf6c8 Compare June 19, 2026 01:31
@ygree
ygree force-pushed the ygree/aws-sdk-sqs-bug branch from 73cf6c8 to f4c80bc Compare June 19, 2026 05:51

@vandonr vandonr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this deserves a bit more explanations, if possible in comments.
I'm not sure I get exactly what's going on, like, in the new advices, we copy the queue URL from the builder to the response in the builder advice, but from the response to the builder in the response advice ?


@Override
public String instrumentedType() {
return "software.amazon.awssdk.services.sqs.internal.MessageMD5ChecksumInterceptor";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a comment why we need to instrument this internal class would be nice

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, this is part of the commit message. It's needed to avoid wrapping messages during the SDK's internal MessageMD5ChecksumInterceptor pass to prevent creating consumer spans
before user code actually consumes the messages.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah but when reading the code (in the editor, after merging), you don't go and look at the commit messages in the blame to understand what's going on, which is why I think having a comment in the code explaining things is better

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with including this in the code; I just didn't want to run CI again just for a single comment line :)

public static class AfterExecutionAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter() {
SqsReceiveResponseInternalAccess.enter();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't we need to register SqsReceiveResponseInternalAccess as a helper for this advice ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ygree
ygree requested a review from vandonr June 23, 2026 21:52

@vandonr vandonr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approving to unblock things, but really more code comments would be nice

@ygree

ygree commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Jun 25, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-06-25 16:17:58 UTC ℹ️ Start processing command /merge


2026-06-25 16:18:03 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 1h (p90).


2026-06-25 17:55:11 UTC ⚠️ MergeQueue: This merge request build was cancelled

[email protected] cancelled this merge request build

@ygree

ygree commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

/merge -c

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Jun 25, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-06-25 17:55:00 UTC ℹ️ Start processing command /merge -c

@ygree

ygree commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

I think this deserves a bit more explanations, if possible in comments. I'm not sure I get exactly what's going on, like, in the new advices, we copy the queue URL from the builder to the response in the builder advice, but from the response to the builder in the response advice ?

Sorry, I must have overlooked this comment. I’ve added a few code comments around the handoff points, and a more detailed problem/solution write-up in the PR description.

AWS SDK core creates the final response with response.toBuilder().sdkHttpResponse(...).build(). So the toBuilder() advice copies the queue URL from the pre-rebuild response to the builder, and the build() advice copies it from the builder to the newly built response. That final response is the one user code calls messages() on.

@ygree

ygree commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Jun 25, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-06-25 23:23:19 UTC ℹ️ Start processing command /merge


2026-06-25 23:23:28 UTC ℹ️ MergeQueue: waiting for PR to be ready

This pull request is not mergeable according to GitHub. Common reasons include pending required checks, missing approvals, or merge conflicts — but it could also be blocked by other repository rules or settings.
It will be added to the queue as soon as checks pass and/or get approvals. View in MergeQueue UI.
Note: if you pushed new commits since the last approval, you may need additional approval.
You can remove it from the waiting list with /remove command.


2026-06-26 03:46:06 UTC ⚠️ MergeQueue: This merge request was unqueued

devflow unqueued this merge request: It did not become mergeable within the expected time

@ygree ygree added this to the 1.64.0 milestone Jun 26, 2026
@ygree

ygree commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Jun 26, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-06-26 17:27:58 UTC ℹ️ Start processing command /merge


2026-06-26 17:28:04 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 1h (p90).


2026-06-26 19:14:00 UTC ℹ️ MergeQueue: This merge request was merged

@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit ffb48ae into master Jun 26, 2026
582 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the ygree/aws-sdk-sqs-bug branch June 26, 2026 19:13
TophrC-dd pushed a commit that referenced this pull request Jun 29, 2026
…race (#11511)

Add regression test for sync SQS receive response copy context loss

Preserve SQS receive context across AWS SDK response rebuilding

Store the receive queue URL before the SDK rebuilds
ReceiveMessageResponse
and propagate it through the response builder so messages() can still
wrap
the final list in TracingList.

Also avoid wrapping messages during the SDK's internal
MessageMD5ChecksumInterceptor pass to prevent creating consumer spans
before user code actually consumes the messages.

Merge branch 'master' into ygree/aws-sdk-sqs-bug

Migrate SQS client tests to JUnit

Document SQS checksum interceptor instrumentation

add clarifying comments

Merge branch 'master' into ygree/aws-sdk-sqs-bug

Merge branch 'master' into ygree/aws-sdk-sqs-bug

Co-authored-by: yury.gribkov <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

inst: aws sdk AWS SDK instrumentation type: bug fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants