Skip to content

Commit 86cde28

Browse files
committed
Refactor OkHttpCall to use CAS instead of synchronized (#4297)
Replace synchronized blocks with atomic compare-and-swap logic. It prevent virtual thread pinning and reduce contention. See #4297 for more details.
1 parent e673ec7 commit 86cde28

File tree

1 file changed

+41
-53
lines changed

1 file changed

+41
-53
lines changed

retrofit/src/main/java/retrofit2/OkHttpCall.java

+41-53
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919

2020
import java.io.IOException;
2121
import java.util.Objects;
22+
import java.util.concurrent.atomic.AtomicBoolean;
23+
import java.util.concurrent.atomic.AtomicReference;
2224
import javax.annotation.Nullable;
23-
import javax.annotation.concurrent.GuardedBy;
2425
import okhttp3.MediaType;
2526
import okhttp3.Request;
2627
import okhttp3.ResponseBody;
@@ -37,16 +38,11 @@ final class OkHttpCall<T> implements Call<T> {
3738
private final okhttp3.Call.Factory callFactory;
3839
private final Converter<ResponseBody, T> responseConverter;
3940

40-
private volatile boolean canceled;
41-
42-
@GuardedBy("this")
43-
private @Nullable okhttp3.Call rawCall;
44-
45-
@GuardedBy("this") // Either a RuntimeException, non-fatal Error, or IOException.
46-
private @Nullable Throwable creationFailure;
47-
48-
@GuardedBy("this")
49-
private boolean executed;
41+
private final AtomicBoolean canceled = new AtomicBoolean(false);
42+
private final AtomicBoolean executed = new AtomicBoolean(false);
43+
private final AtomicReference<okhttp3.Call> rawCallRef = new AtomicReference<>();
44+
// Either a RuntimeException, non-fatal Error, or IOException.
45+
private final AtomicReference<Throwable> creationFailureRef = new AtomicReference<>();
5046

5147
OkHttpCall(
5248
RequestFactory requestFactory,
@@ -68,7 +64,7 @@ public OkHttpCall<T> clone() {
6864
}
6965

7066
@Override
71-
public synchronized Request request() {
67+
public Request request() {
7268
try {
7369
return getRawCall().request();
7470
} catch (IOException e) {
@@ -77,7 +73,7 @@ public synchronized Request request() {
7773
}
7874

7975
@Override
80-
public synchronized Timeout timeout() {
76+
public Timeout timeout() {
8177
try {
8278
return getRawCall().timeout();
8379
} catch (IOException e) {
@@ -89,12 +85,12 @@ public synchronized Timeout timeout() {
8985
* Returns the raw call, initializing it if necessary. Throws if initializing the raw call throws,
9086
* or has thrown in previous attempts to create it.
9187
*/
92-
@GuardedBy("this")
9388
private okhttp3.Call getRawCall() throws IOException {
94-
okhttp3.Call call = rawCall;
89+
okhttp3.Call call = rawCallRef.get();
9590
if (call != null) return call;
9691

9792
// Re-throw previous failures if this isn't the first attempt.
93+
Throwable creationFailure = creationFailureRef.get();
9894
if (creationFailure != null) {
9995
if (creationFailure instanceof IOException) {
10096
throw (IOException) creationFailure;
@@ -107,10 +103,12 @@ private okhttp3.Call getRawCall() throws IOException {
107103

108104
// Create and remember either the success or the failure.
109105
try {
110-
return rawCall = createRawCall();
106+
final okhttp3.Call newCall = createRawCall();
107+
rawCallRef.compareAndSet(null, newCall);
108+
return rawCallRef.get();
111109
} catch (RuntimeException | Error | IOException e) {
112110
throwIfFatal(e); // Do not assign a fatal error to creationFailure.
113-
creationFailure = e;
111+
creationFailureRef.set(e);
114112
throw e;
115113
}
116114
}
@@ -119,22 +117,19 @@ private okhttp3.Call getRawCall() throws IOException {
119117
public void enqueue(final Callback<T> callback) {
120118
Objects.requireNonNull(callback, "callback == null");
121119

122-
okhttp3.Call call;
123-
Throwable failure;
124-
125-
synchronized (this) {
126-
if (executed) throw new IllegalStateException("Already executed.");
127-
executed = true;
128-
129-
call = rawCall;
130-
failure = creationFailure;
131-
if (call == null && failure == null) {
132-
try {
133-
call = rawCall = createRawCall();
134-
} catch (Throwable t) {
135-
throwIfFatal(t);
136-
failure = creationFailure = t;
137-
}
120+
if (!executed.compareAndSet(false, true)) throw new IllegalStateException("Already executed.");
121+
122+
okhttp3.Call call = rawCallRef.get();
123+
Throwable failure = creationFailureRef.get();
124+
125+
if (call == null && failure == null) {
126+
try {
127+
call = createRawCall();
128+
rawCallRef.compareAndSet(null, call);
129+
} catch (Throwable t) {
130+
throwIfFatal(t);
131+
creationFailureRef.compareAndSet(null, t);
132+
failure = t;
138133
}
139134
}
140135

@@ -143,7 +138,7 @@ public void enqueue(final Callback<T> callback) {
143138
return;
144139
}
145140

146-
if (canceled) {
141+
if (canceled.get()) {
147142
call.cancel();
148143
}
149144

@@ -185,22 +180,19 @@ private void callFailure(Throwable e) {
185180
}
186181

187182
@Override
188-
public synchronized boolean isExecuted() {
189-
return executed;
183+
public boolean isExecuted() {
184+
return executed.get();
190185
}
191186

192187
@Override
193188
public Response<T> execute() throws IOException {
194-
okhttp3.Call call;
195-
196-
synchronized (this) {
197-
if (executed) throw new IllegalStateException("Already executed.");
198-
executed = true;
199-
200-
call = getRawCall();
189+
if (!executed.compareAndSet(false, true)) {
190+
throw new IllegalStateException("Already executed.");
201191
}
202192

203-
if (canceled) {
193+
okhttp3.Call call = getRawCall();
194+
195+
if (canceled.get()) {
204196
call.cancel();
205197
}
206198

@@ -255,25 +247,21 @@ Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
255247

256248
@Override
257249
public void cancel() {
258-
canceled = true;
250+
canceled.set(true);
259251

260-
okhttp3.Call call;
261-
synchronized (this) {
262-
call = rawCall;
263-
}
252+
okhttp3.Call call = rawCallRef.get();
264253
if (call != null) {
265254
call.cancel();
266255
}
267256
}
268257

269258
@Override
270259
public boolean isCanceled() {
271-
if (canceled) {
260+
if (canceled.get()) {
272261
return true;
273262
}
274-
synchronized (this) {
275-
return rawCall != null && rawCall.isCanceled();
276-
}
263+
okhttp3.Call call = rawCallRef.get();
264+
return call != null && call.isCanceled();
277265
}
278266

279267
static final class NoContentResponseBody extends ResponseBody {

0 commit comments

Comments
 (0)