Skip to content

Commit 9d8ad2e

Browse files
committed
8267990: Revisit some uses of synchronized in the HttpClient API
Reviewed-by: chegar
1 parent 36dc268 commit 9d8ad2e

File tree

13 files changed

+99
-43
lines changed

13 files changed

+99
-43
lines changed

src/java.net.http/share/classes/jdk/internal/net/http/Http1AsyncReceiver.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -167,7 +167,7 @@ public void cancel() {
167167
private final ConcurrentLinkedDeque<ByteBuffer> queue
168168
= new ConcurrentLinkedDeque<>();
169169
private final SequentialScheduler scheduler =
170-
SequentialScheduler.synchronizedScheduler(this::flush);
170+
SequentialScheduler.lockingScheduler(this::flush);
171171
final MinimalFuture<Void> whenFinished;
172172
private final Executor executor;
173173
private final Http1TubeSubscriber subscriber = new Http1TubeSubscriber();

src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -628,7 +628,7 @@ final class Http1Publisher implements FlowTube.TubePublisher {
628628
final Http1WriteSubscription subscription = new Http1WriteSubscription();
629629
final Demand demand = new Demand();
630630
final SequentialScheduler writeScheduler =
631-
SequentialScheduler.synchronizedScheduler(new WriteTask());
631+
SequentialScheduler.lockingScheduler(new WriteTask());
632632

633633
@Override
634634
public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> s) {

src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -1292,7 +1292,7 @@ final class Http2TubeSubscriber implements TubeSubscriber {
12921292
private final ConcurrentLinkedQueue<ByteBuffer> queue
12931293
= new ConcurrentLinkedQueue<>();
12941294
private final SequentialScheduler scheduler =
1295-
SequentialScheduler.synchronizedScheduler(this::processQueue);
1295+
SequentialScheduler.lockingScheduler(this::processQueue);
12961296
private final HttpClientImpl client;
12971297

12981298
Http2TubeSubscriber(HttpClientImpl client) {

src/java.net.http/share/classes/jdk/internal/net/http/LineSubscriberAdapter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -165,7 +165,7 @@ private LineSubscription(Flow.Subscription s,
165165
newline = separator;
166166
upstream = Objects.requireNonNull(subscriber);
167167
cf = Objects.requireNonNull(completion);
168-
scheduler = SequentialScheduler.synchronizedScheduler(this::loop);
168+
scheduler = SequentialScheduler.lockingScheduler(this::loop);
169169
}
170170

171171
@Override

src/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -583,7 +583,7 @@ private static final class AggregateSubscription
583583
AggregateSubscription(List<BodyPublisher> bodies, Flow.Subscriber<? super ByteBuffer> subscriber) {
584584
this.bodies = new ConcurrentLinkedQueue<>(bodies);
585585
this.subscriber = subscriber;
586-
this.scheduler = SequentialScheduler.synchronizedScheduler(this::run);
586+
this.scheduler = SequentialScheduler.lockingScheduler(this::run);
587587
}
588588

589589
@Override

src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -36,6 +36,8 @@
3636
import java.nio.channels.SelectionKey;
3737
import java.nio.channels.SocketChannel;
3838
import java.util.ArrayList;
39+
import java.util.concurrent.locks.Lock;
40+
import java.util.concurrent.locks.ReentrantLock;
3941
import java.util.function.Consumer;
4042
import java.util.function.Supplier;
4143
import jdk.internal.net.http.common.BufferSupplier;
@@ -163,16 +165,22 @@ void signalClosed() {
163165
*/
164166
private static class SocketFlowTask implements RestartableTask {
165167
final Runnable task;
166-
private final Object monitor = new Object();
168+
private final Lock lock = new ReentrantLock();
167169
SocketFlowTask(Runnable task) {
168170
this.task = task;
169171
}
170172
@Override
171173
public final void run(DeferredCompleter taskCompleter) {
172174
try {
173-
// non contentious synchronized for visibility.
174-
synchronized(monitor) {
175+
// The logics of the sequential scheduler should ensure that
176+
// the restartable task is running in only one thread at
177+
// a given time: there should never be contention.
178+
boolean locked = lock.tryLock();
179+
assert locked : "contention detected in SequentialScheduler";
180+
try {
175181
task.run();
182+
} finally {
183+
if (locked) lock.unlock();
176184
}
177185
} finally {
178186
taskCompleter.complete();

src/java.net.http/share/classes/jdk/internal/net/http/Stream.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class Stream<T> extends ExchangeImpl<T> {
101101

102102
final ConcurrentLinkedQueue<Http2Frame> inputQ = new ConcurrentLinkedQueue<>();
103103
final SequentialScheduler sched =
104-
SequentialScheduler.synchronizedScheduler(this::schedule);
104+
SequentialScheduler.lockingScheduler(this::schedule);
105105
final SubscriptionBase userSubscription =
106106
new SubscriptionBase(sched, this::cancel, this::onSubscriptionError);
107107

@@ -840,7 +840,7 @@ class RequestSubscriber implements Flow.Subscriber<ByteBuffer> {
840840
this.contentLength = contentLen;
841841
this.remainingContentLength = contentLen;
842842
this.sendScheduler =
843-
SequentialScheduler.synchronizedScheduler(this::trySend);
843+
SequentialScheduler.lockingScheduler(this::trySend);
844844
}
845845

846846
@Override

src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -271,7 +271,7 @@ public void run() {
271271

272272
Reader() {
273273
super();
274-
scheduler = SequentialScheduler.synchronizedScheduler(
274+
scheduler = SequentialScheduler.lockingScheduler(
275275
new ReaderDownstreamPusher());
276276
this.readBuf = ByteBuffer.allocate(1024);
277277
readBuf.limit(0); // keep in read mode
@@ -777,10 +777,10 @@ private void processData() {
777777
try {
778778
if (debugw.on())
779779
debugw.log("processData, writeList remaining:"
780-
+ Utils.remaining(writeList) + ", hsTriggered:"
780+
+ Utils.synchronizedRemaining(writeList) + ", hsTriggered:"
781781
+ hsTriggered() + ", needWrap:" + needWrap());
782782

783-
while (Utils.remaining(writeList) > 0 || hsTriggered() || needWrap()) {
783+
while (Utils.synchronizedRemaining(writeList) > 0 || hsTriggered() || needWrap()) {
784784
ByteBuffer[] outbufs = writeList.toArray(Utils.EMPTY_BB_ARRAY);
785785
EngineResult result = wrapBuffers(outbufs);
786786
if (debugw.on())
@@ -823,7 +823,7 @@ private void processData() {
823823
}
824824
}
825825
}
826-
if (completing && Utils.remaining(writeList) == 0) {
826+
if (completing && Utils.synchronizedRemaining(writeList) == 0) {
827827
if (!completed) {
828828
completed = true;
829829
writeList.clear();

src/java.net.http/share/classes/jdk/internal/net/http/common/SequentialScheduler.java

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,8 @@
2727

2828
import java.util.concurrent.Executor;
2929
import java.util.concurrent.atomic.AtomicInteger;
30+
import java.util.concurrent.locks.Lock;
31+
import java.util.concurrent.locks.ReentrantLock;
3032

3133
import static java.util.Objects.requireNonNull;
3234

@@ -177,6 +179,36 @@ protected void run() {
177179
}
178180
}
179181

182+
/**
183+
* A task that runs its main loop within a block protected by a lock to provide
184+
* memory visibility between runs. Since the main loop can't run concurrently,
185+
* the lock shouldn't be contended and no deadlock should ever be possible.
186+
*/
187+
public static final class LockingRestartableTask
188+
extends CompleteRestartableTask {
189+
190+
private final Runnable mainLoop;
191+
private final Lock lock = new ReentrantLock();
192+
193+
public LockingRestartableTask(Runnable mainLoop) {
194+
this.mainLoop = mainLoop;
195+
}
196+
197+
@Override
198+
protected void run() {
199+
// The logics of the sequential scheduler should ensure that
200+
// the restartable task is running in only one thread at
201+
// a given time: there should never be contention.
202+
boolean locked = lock.tryLock();
203+
assert locked : "contention detected in SequentialScheduler";
204+
try {
205+
mainLoop.run();
206+
} finally {
207+
if (locked) lock.unlock();
208+
}
209+
}
210+
}
211+
180212
private static final int OFFLOAD = 1;
181213
private static final int AGAIN = 2;
182214
private static final int BEGIN = 4;
@@ -359,4 +391,20 @@ public void stop() {
359391
public static SequentialScheduler synchronizedScheduler(Runnable mainLoop) {
360392
return new SequentialScheduler(new SynchronizedRestartableTask(mainLoop));
361393
}
394+
395+
/**
396+
* Returns a new {@code SequentialScheduler} that executes the provided
397+
* {@code mainLoop} from within a {@link LockingRestartableTask}.
398+
*
399+
* @apiNote This is equivalent to calling
400+
* {@code new SequentialScheduler(new LockingRestartableTask(mainLoop))}
401+
* The main loop must not perform any blocking operation.
402+
*
403+
* @param mainLoop The main loop of the new sequential scheduler
404+
* @return a new {@code SequentialScheduler} that executes the provided
405+
* {@code mainLoop} from within a {@link LockingRestartableTask}.
406+
*/
407+
public static SequentialScheduler lockingScheduler(Runnable mainLoop) {
408+
return new SequentialScheduler(new LockingRestartableTask(mainLoop));
409+
}
362410
}

src/java.net.http/share/classes/jdk/internal/net/http/common/SubscriberWrapper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -96,7 +96,7 @@ public SubscriberWrapper()
9696
errorCommon(t);
9797
});
9898
this.pushScheduler =
99-
SequentialScheduler.synchronizedScheduler(new DownstreamPusher());
99+
SequentialScheduler.lockingScheduler(new DownstreamPusher());
100100
this.downstreamSubscription = new SubscriptionBase(pushScheduler,
101101
this::downstreamCompletion);
102102
}

0 commit comments

Comments
 (0)