Fix message handler spans appear disconnected from the incoming SQS trace#11511
Conversation
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
This comment has been minimized.
This comment has been minimized.
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.
2cacb35 to
2fffa01
Compare
There was a problem hiding this comment.
💡 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".
7a239a2 to
73cf6c8
Compare
73cf6c8 to
f4c80bc
Compare
vandonr
left a comment
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
I think a comment why we need to instrument this internal class would be nice
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
don't we need to register SqsReceiveResponseInternalAccess as a helper for this advice ?
There was a problem hiding this comment.
vandonr
left a comment
There was a problem hiding this comment.
approving to unblock things, but really more code comments would be nice
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
[email protected] cancelled this merge request build |
|
/merge -c |
|
View all feedbacks in Devflow UI.
|
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 |
|
/merge |
|
View all feedbacks in Devflow UI.
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.
devflow unqueued this merge request: It did not become mergeable within the expected time |
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
|
…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]>
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
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 aTracingList. That wrapping only happens when the instrumentation can find the originalQueueUrlattached to theReceiveMessageResponse.The old code attached
QueueUrlinafterExecution, butafterExecution.context.response()is not the final response instance returned to user code. AWS SDK core rebuilds the response before returning it, inBaseClientHandler.attachHttpResponseToResult():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.ReceiveMessagespan existed, for example in thisassertTracesblock. 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.ReceiveMessagebecause it was created duringMessageMD5ChecksumInterceptor.afterExecution.TracingExecutionInterceptor.afterExecutionandMessageMD5ChecksumInterceptor.afterExecutionrun in the same AWS SDKafterExecutioninterceptor phase, with the sameafterExecution.context.response()object. The old Datadog interceptor attachedQueueUrlto that exact pre-rebuild response instance, so when the checksum interceptor calledmessages()on the same object, our instrumentation foundQueueUrland returned aTracingList.User code saw a different object: the final response instance created by
BaseClientHandler.attachHttpResponseToResult(). In the old implementation, that final response did not haveQueueUrlattached, so user-codemessages()was not wrapped.Solution
Move
QueueUrlcapture fromafterExecutiontomodifyResponseinTracingExecutionInterceptor.modifyResponseis the right hook because it runs after unmarshalling, while both the originalReceiveMessageRequestand the pre-rebuildReceiveMessageResponseare available, but before AWS SDK core creates the final response copy.If
QueueUrlis captured inafterExecution, that final response copy already exists. At that point we can still attachQueueUrltoafterExecution.context.response(), but the SDK has already calledtoBuilder()andbuild(), so the builder advices have already missed their chance to carry the value to the response instance returned to user code.The instrumentation stores
QueueUrlon the pre-rebuildReceiveMessageResponse, then propagates it through the SDK copy path:This is done with two advices:
ReceiveMessageResponse.toBuilder()copiesQueueUrlfrom the response context store to the builder context store.ReceiveMessageResponse$BuilderImpl.build()copiesQueueUrlfrom the builder context store to the newly built response.After that, when user code calls
messages()on the final response instance, the advice can findQueueUrl, return aTracingList, and create the SQS consumer span during user message iteration.The change also marks
MessageMD5ChecksumInterceptor.afterExecutionas SDK-internal access, so its internalmessages()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
type:and (comp:orinst:) labels in addition to any other useful labelsclose,fix, or any linking keywords when referencing an issueUse
solvesinstead, and assign the PR milestone to the issue/merge. You can also:/merge --commit-message "..."/merge -c/merge -f --reason "reason"; please use this judiciously, as some checks do not run at the PR-levelJira ticket: APMS-19562