Skip to content

Replace synchronized in OkHttpCall with atomics - #4755

Closed
HuzaifaChaudary wants to merge 1 commit into
lysine-dev:trunkfrom
HuzaifaChaudary:okhttpcall-atomics
Closed

Replace synchronized in OkHttpCall with atomics#4755
HuzaifaChaudary wants to merge 1 commit into
lysine-dev:trunkfrom
HuzaifaChaudary:okhttpcall-atomics

Conversation

@HuzaifaChaudary

Copy link
Copy Markdown

Closes #4297

OkHttpCall guarded three fields with the object monitor: the lazily created okhttp3.Call, the throwable that creating it threw, and the executed flag. Holding a monitor across call creation pins the carrier thread when the caller is a virtual thread, which is what the issue is about.

Following the suggestion on the issue, this uses atomics rather than a ReentrantLock.

executed becomes an AtomicBoolean, so the one-shot check in enqueue and execute is a single compareAndSet instead of a read and a write under a lock.

rawCall and creationFailure become a single AtomicReference holding whichever of the two happened. Exactly one of them does, and both have to be remembered so later callers see the same outcome, so one field models it better than two. Creation is no longer serialised: two threads arriving together will both create a call and one wins the swap. An unexecuted okhttp3.Call holds no connection, so discarding the loser costs nothing, which is the tradeoff you described.

request(), timeout(), isExecuted(), cancel() and isCanceled() no longer take the monitor at all.

No new test. The behaviour is unchanged and the existing suite already covers it, including the "Already executed." case in CallTest, and a concurrency test for this would be timing dependent without proving much. :retrofit:java-test:testJdk21 passes, 343 tests across 15 classes, and nothing outside OkHttpCall referenced either removed field.

The class guarded three fields with the monitor: the lazily created
okhttp3.Call, the throwable that creating it threw, and the executed flag.
Holding a monitor across call creation pins a carrier thread when the caller
is a virtual thread.

executed becomes an AtomicBoolean, so the one shot check is a single
compareAndSet rather than a read and a write under a lock.

rawCall and creationFailure become one AtomicReference holding whichever of
the two happened, since exactly one of them does and both have to be
remembered for later callers. Creation is no longer serialised: two threads
arriving together both create a call and one wins the swap. An unexecuted
call holds no connection so discarding the loser costs nothing, which is the
tradeoff suggested on the issue.
Copilot AI lite review requested due to automatic review settings September 2, 2026 21:19

This comment was marked as spam.

@JakeWharton

Copy link
Copy Markdown
Collaborator

LLM usage is forbidden. Next occurrence results in a ban.

@JakeWharton

Copy link
Copy Markdown
Collaborator

Didn't we already determine this is harmless anyway? We're guarding trivial CPU work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Replace use of synchronized in OkHttpCall with ReentrantLock

3 participants