Skip to content

Refactor OkHttpCall to use CAS instead of synchronized (#4297) #4301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: trunk
Choose a base branch
from

Conversation

qwexter
Copy link

@qwexter qwexter commented Feb 24, 2025

Replace synchronized blocks with atomic compare-and-swap logic.
It prevent virtual thread pinning and reduce contention.
See #4297 for more details.


  • CHANGELOG.md's "Unreleased" section has been updated, if applicable.

@qwexter qwexter marked this pull request as ready for review February 28, 2025 07:38
Replace synchronized blocks with atomic compare-and-swap logic. It prevent virtual thread pinning and reduce contention.
See square#4297 for more details.
@qwexter qwexter force-pushed the refactorOkHttpCallToUseCAS branch from 86cde28 to 91b4ebf Compare February 28, 2025 12:09
Copy link
Collaborator

@JakeWharton JakeWharton left a comment

Choose a reason for hiding this comment

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

Still think this needs some work. I would probably look to simplify things, such as merge the failure and call reference into one, and potentially even include the cancel state as new Object() tombstone reference. I need to spend more time with this code before I can move forward with it.

Comment on lines +107 to +108
rawCallRef.compareAndSet(null, newCall);
return rawCallRef.get();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
rawCallRef.compareAndSet(null, newCall);
return rawCallRef.get();
return rawCallRef.compareAndSet(null, newCall)
? newCall
: rawCallRef.get();

Comment on lines +127 to +128
call = createRawCall();
rawCallRef.compareAndSet(null, call);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This code is now racy. Another thread could be performing the same operation in parallel and its okhttp3.Call might "win" causing this instance to be a duplicate.

Copy link
Author

Choose a reason for hiding this comment

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

I agree. If you have some ideas, pls share it. I'll hope I can get back to this with more resources at the end of September. If it will be needed at that time.

if (call == null && failure == null) {
try {
call = createRawCall();
rawCallRef.compareAndSet(null, call);

Choose a reason for hiding this comment

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

Circling back on this in case it's still active. As @JakeWharton noted, another thread could enter this block and call createRawCall(). Even though only one compareAndSet(...) might succeed, the other still ends up creating a duplicate instance.

Hi @qwexter, just curious if this race condition was considered acceptable here, or if there's a plan to mitigate it without reintroducing locking?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I agree. I think I agree that the winner-takes-all approach mentioned in the discussion is too straightforward.

I didn't have any issues with my unit tests, but I might have missed something.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants