Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📲 Install BuildsAndroid
|
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Move the okhttp changelog entry into a new Unreleased section, as 8.54.0 was released on main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0xadam-brown
left a comment
There was a problem hiding this comment.
Thanks for this 💯 !
One comment worth addressing; otherwise looking good.
Keep both Unreleased changelog entries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bb23afd to
b508070
Compare
0xadam-brown
left a comment
There was a problem hiding this comment.
Excellent! One tweak more to satisfy the EventListener.Factory contract, and I think we'll be there 🥇
| // callEnd()/callFailed(), so there is not always a listener bound to the call. Create one on | ||
| // the fly in that case, but do not put it in the map: nothing would remove it again, because | ||
| // a call that is canceled before it starts never gets a callEnd() or callFailed(). | ||
| val originalEventListener = |
There was a problem hiding this comment.
We're close! (and thanks for the great updates)
We still need to preserve the contract of EventListener.Factory that ensures only one EventListener instance is produced per Call lifecycle. Ie, the listener returned by the factory for a given call needs to be the listener that captures i) all of that call's lifecycle and ii) no other call's lifecycle.
We've fixed (ii), but we're still violating (i) in the case of cancelation because we're creating an extra listener for early and late cancel() invocations.
Possible solution
Thoughts about using a weak per-call map for the wrapped listener instead? Something like a WeakHashMap<Call, EventListener> guarded by synchronized, with a getOrCreateOriginalEventListener(call) helper used by both callStart and canceled().
That'd^^ let us avoid removing entries on callEnd / callFailed, and completed calls would be gc'd as soon as the Call instance is unreachable.
| } | ||
|
|
||
| @Test | ||
| fun `cancel before callStart is delegated`() { |
There was a problem hiding this comment.
Thanks for the new tests 💯
Bonus points if our cancellation tests can assert the stronger factory-contract invariant 👍
(Right now cancel before callStart is delegated and cancel after callEnd is delegated prove that some listener receives canceled(), rather than that a single listener receives all lifecycle callbacks.)
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 728129d. Configure here.
|
Lurking - hi @0xadam-brown! Crazy seeing you around, hope you're doing well! |
@victorlai-stripe – so good to see your name show up here! Definitely doing well (and enjoying Sentry). Hope Stripe's been an excellent change-up on your end too 💯 ! |
0xadam-brown
left a comment
There was a problem hiding this comment.
A few quick comments, but no blockers.
| @@ -65,7 +70,9 @@ public open class SentryOkHttpEventListener( | |||
|
|
|||
| public constructor( | |||
There was a problem hiding this comment.
m: Given that we're deviating from the EventListener contract by ignoring cancel() calls outside callStart() and callEnd(), could be sensible to add KDocs to the constructors to put folks on notice.
Eg, maybe something like:
/**
* Wraps a single [okhttp3.EventListener] instance and shares it across [Call]s.
*
* All callbacks are forwarded to the listener, including those delivered outside
* the normal `callStart()`..`callEnd()` / `callFailed()` window.
*/
public constructor(originalEventListener: EventListener) /**
* Wraps an [okhttp3.EventListener.Factory] and creates a separate
* [okhttp3.EventListener] per [Call].
*
* Callbacks that OkHttp may deliver before `callStart()` or after `callEnd()` /
* `callFailed()`, such as [okhttp3.EventListener.canceled], are not guaranteed to
* be forwarded to the listener.
*/
public constructor(originalEventListenerFactory: Factory) | } | ||
|
|
||
| @Test | ||
| fun `cancel before callStart is not delegated and creates no listener`() { |
There was a problem hiding this comment.
l: Might be worth a quick KDoc noting that we're deliberately deviating from the EventListener contract here + why we're doing so. (Same for relevant tests below.)

📜 Description
SentryOkHttpEventListenerheld the wrappedEventListenerin a single mutable field thatcallStartoverwrote for each call. It is now kept in a per-Callmap, the same pattern the classalready uses for
eventMap. No public API change.💡 Motivation and Context
OkHttp uses one listener instance for all calls, thus concurrent calls were all delegated to the
listener made for the call that started last. This breaks the
EventListener.Factorycontract andloses the terminal
callEnd/callFailedof every overlapping call.💚 How did you test it?
Added unit tests.
📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps