Skip to content

Commit 736b90d

Browse files
committed
8308310: HttpClient: Avoid logging or locking from within synchronized blocks
Reviewed-by: jpai
1 parent 7764f46 commit 736b90d

32 files changed

+795
-407
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2023, 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
@@ -57,6 +57,7 @@ public class BufferingSubscriber<T> implements TrustedSubscriber<T>
5757
private volatile DownstreamSubscription downstreamSubscription;
5858

5959
/** Must be held when accessing the internal buffers. */
60+
// using a monitor here is fine: no logging while holding it
6061
private final Object buffersLock = new Object();
6162
/** The internal buffers holding the buffered data. */
6263
private ArrayList<ByteBuffer> internalBuffers;

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

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2023, 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
@@ -40,6 +40,7 @@
4040
import java.util.Objects;
4141
import java.util.Optional;
4242
import java.util.concurrent.Flow;
43+
import java.util.concurrent.locks.ReentrantLock;
4344
import java.util.stream.Collectors;
4445
import jdk.internal.net.http.common.FlowTube;
4546
import jdk.internal.net.http.common.Logger;
@@ -57,11 +58,12 @@ final class ConnectionPool {
5758

5859
// Pools of idle connections
5960

61+
private final ReentrantLock stateLock = new ReentrantLock();
6062
private final HashMap<CacheKey,LinkedList<HttpConnection>> plainPool;
6163
private final HashMap<CacheKey,LinkedList<HttpConnection>> sslPool;
6264
private final ExpiryList expiryList;
6365
private final String dbgTag; // used for debug
64-
boolean stopped;
66+
volatile boolean stopped;
6567

6668
/**
6769
* Entries in connection pool are keyed by destination address and/or
@@ -139,7 +141,7 @@ final String dbgString() {
139141
return dbgTag;
140142
}
141143

142-
synchronized void start() {
144+
void start() {
143145
assert !stopped : "Already stopped";
144146
}
145147

@@ -149,9 +151,21 @@ static CacheKey cacheKey(boolean secure, InetSocketAddress destination,
149151
return new CacheKey(secure, destination, proxy);
150152
}
151153

152-
synchronized HttpConnection getConnection(boolean secure,
153-
InetSocketAddress addr,
154-
InetSocketAddress proxy) {
154+
HttpConnection getConnection(boolean secure,
155+
InetSocketAddress addr,
156+
InetSocketAddress proxy) {
157+
if (stopped) return null;
158+
stateLock.lock();
159+
try {
160+
return getConnection0(secure, addr, proxy);
161+
} finally {
162+
stateLock.unlock();
163+
}
164+
}
165+
166+
private HttpConnection getConnection0(boolean secure,
167+
InetSocketAddress addr,
168+
InetSocketAddress proxy) {
155169
if (stopped) return null;
156170
// for plain (unsecure) proxy connection the destination address is irrelevant.
157171
addr = secure || proxy == null ? addr : null;
@@ -185,7 +199,8 @@ void returnToPool(HttpConnection conn, Instant now, long keepAlive) {
185199

186200
// it's possible that cleanup may have been called.
187201
HttpConnection toClose = null;
188-
synchronized(this) {
202+
stateLock.lock();
203+
try {
189204
if (cleanup.isDone()) {
190205
return;
191206
} else if (stopped) {
@@ -203,6 +218,8 @@ void returnToPool(HttpConnection conn, Instant now, long keepAlive) {
203218
putConnection(conn, sslPool);
204219
}
205220
expiryList.add(conn, now, keepAlive);
221+
} finally {
222+
stateLock.unlock();
206223
}
207224
if (toClose != null) {
208225
if (debug.on()) {
@@ -243,7 +260,7 @@ private CleanupTrigger registerCleanupTrigger(HttpConnection conn) {
243260
removeFromPool(HttpConnection c,
244261
HashMap<CacheKey,LinkedList<HttpConnection>> pool) {
245262
//System.out.println("cacheCleaner removing: " + c);
246-
assert Thread.holdsLock(this);
263+
assert stateLock.isHeldByCurrentThread();
247264
CacheKey k = c.cacheKey();
248265
List<HttpConnection> l = pool.get(k);
249266
if (l == null || l.isEmpty()) {
@@ -288,7 +305,8 @@ long purgeExpiredConnectionsAndReturnNextDeadline(Instant now) {
288305
if (!expiryList.purgeMaybeRequired()) return nextPurge;
289306

290307
List<HttpConnection> closelist;
291-
synchronized (this) {
308+
stateLock.lock();
309+
try {
292310
closelist = expiryList.purgeUntil(now);
293311
for (HttpConnection c : closelist) {
294312
if (c instanceof PlainHttpConnection) {
@@ -302,6 +320,8 @@ long purgeExpiredConnectionsAndReturnNextDeadline(Instant now) {
302320
nextPurge = now.until(
303321
expiryList.nextExpiryDeadline().orElse(now),
304322
ChronoUnit.MILLIS);
323+
} finally {
324+
stateLock.unlock();
305325
}
306326
closelist.forEach(this::close);
307327
return nextPurge;
@@ -316,14 +336,17 @@ private void close(HttpConnection c) {
316336
void stop() {
317337
List<HttpConnection> closelist = Collections.emptyList();
318338
try {
319-
synchronized (this) {
339+
stateLock.lock();
340+
try {
320341
stopped = true;
321342
closelist = expiryList.stream()
322343
.map(e -> e.connection)
323344
.collect(Collectors.toList());
324345
expiryList.clear();
325346
plainPool.clear();
326347
sslPool.clear();
348+
} finally {
349+
stateLock.unlock();
327350
}
328351
} finally {
329352
closelist.forEach(this::close);
@@ -354,28 +377,25 @@ private static final class ExpiryList {
354377

355378
// A loosely accurate boolean whose value is computed
356379
// at the end of each operation performed on ExpiryList;
357-
// Does not require synchronizing on the ConnectionPool.
380+
// Does not require holding the ConnectionPool stateLock.
358381
boolean purgeMaybeRequired() {
359382
return mayContainEntries;
360383
}
361384

362385
// Returns the next expiry deadline
363-
// should only be called while holding a synchronization
364-
// lock on the ConnectionPool
386+
// should only be called while holding the ConnectionPool stateLock.
365387
Optional<Instant> nextExpiryDeadline() {
366388
if (list.isEmpty()) return Optional.empty();
367389
else return Optional.of(list.getLast().expiry);
368390
}
369391

370-
// should only be called while holding a synchronization
371-
// lock on the ConnectionPool
392+
// should only be called while holding the ConnectionPool stateLock.
372393
HttpConnection removeOldest() {
373394
ExpiryEntry entry = list.pollLast();
374395
return entry == null ? null : entry.connection;
375396
}
376397

377-
// should only be called while holding a synchronization
378-
// lock on the ConnectionPool
398+
// should only be called while holding the ConnectionPool stateLock.
379399
void add(HttpConnection conn) {
380400
add(conn, Instant.now(), KEEP_ALIVE_TIMEOUT);
381401
}
@@ -408,8 +428,7 @@ void add(HttpConnection conn, Instant now, long keepAlive) {
408428
mayContainEntries = true;
409429
}
410430

411-
// should only be called while holding a synchronization
412-
// lock on the ConnectionPool
431+
// should only be called while holding the ConnectionPool stateLock.
413432
void remove(HttpConnection c) {
414433
if (c == null || list.isEmpty()) return;
415434
ListIterator<ExpiryEntry> li = list.listIterator();
@@ -423,8 +442,7 @@ void remove(HttpConnection c) {
423442
}
424443
}
425444

426-
// should only be called while holding a synchronization
427-
// lock on the ConnectionPool.
445+
// should only be called while holding the ConnectionPool stateLock.
428446
// Purge all elements whose deadline is before now (now included).
429447
List<HttpConnection> purgeUntil(Instant now) {
430448
if (list.isEmpty()) return Collections.emptyList();
@@ -450,25 +468,22 @@ List<HttpConnection> purgeUntil(Instant now) {
450468
return closelist;
451469
}
452470

453-
// should only be called while holding a synchronization
454-
// lock on the ConnectionPool
471+
// should only be called while holding the ConnectionPool stateLock.
455472
java.util.stream.Stream<ExpiryEntry> stream() {
456473
return list.stream();
457474
}
458475

459-
// should only be called while holding a synchronization
460-
// lock on the ConnectionPool
476+
// should only be called while holding the ConnectionPool stateLock.
461477
void clear() {
462478
list.clear();
463479
mayContainEntries = false;
464480
}
465481
}
466482

467483
// Remove a connection from the pool.
468-
// should only be called while holding a synchronization
469-
// lock on the ConnectionPool
484+
// should only be called while holding the ConnectionPool stateLock.
470485
private void removeFromPool(HttpConnection c) {
471-
assert Thread.holdsLock(this);
486+
assert stateLock.isHeldByCurrentThread();
472487
if (c instanceof PlainHttpConnection) {
473488
removeFromPool(c, plainPool);
474489
} else {
@@ -478,7 +493,16 @@ private void removeFromPool(HttpConnection c) {
478493
}
479494

480495
// Used by tests
481-
synchronized boolean contains(HttpConnection c) {
496+
boolean contains(HttpConnection c) {
497+
stateLock.lock();
498+
try {
499+
return contains0(c);
500+
} finally {
501+
stateLock.unlock();
502+
}
503+
}
504+
505+
private boolean contains0(HttpConnection c) {
482506
final CacheKey key = c.cacheKey();
483507
List<HttpConnection> list;
484508
if ((list = plainPool.get(key)) != null) {
@@ -494,9 +518,12 @@ void cleanup(HttpConnection c, Throwable error) {
494518
if (debug.on())
495519
debug.log("%s : ConnectionPool.cleanup(%s)",
496520
String.valueOf(c.getConnectionFlow()), error);
497-
synchronized(this) {
521+
stateLock.lock();
522+
try {
498523
removeFromPool(c);
499524
expiryList.remove(c);
525+
} finally {
526+
stateLock.unlock();
500527
}
501528
c.close();
502529
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ private void checkCancelled() {
288288
IOException cause = null;
289289
CompletableFuture<? extends ExchangeImpl<T>> cf = null;
290290
if (failed != null) {
291-
synchronized(this) {
291+
synchronized (this) {
292292
cause = failed;
293293
impl = exchImpl;
294294
cf = exchangeCF;
@@ -371,7 +371,7 @@ synchronized IOException getCancelCause() {
371371
// instead - as we need CAS semantics.
372372
synchronized (this) { exchangeCF = cf; };
373373
res = cf.whenComplete((r,x) -> {
374-
synchronized(Exchange.this) {
374+
synchronized (Exchange.this) {
375375
if (exchangeCF == cf) exchangeCF = null;
376376
}
377377
});

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2023, 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
@@ -419,7 +419,7 @@ void clear() {
419419
}
420420

421421
void subscribe(Http1AsyncDelegate delegate) {
422-
synchronized(this) {
422+
synchronized (this) {
423423
pendingDelegateRef.set(delegate);
424424
}
425425
if (queue.isEmpty()) {
@@ -443,12 +443,16 @@ long remaining() {
443443
}
444444

445445
void unsubscribe(Http1AsyncDelegate delegate) {
446-
synchronized(this) {
446+
boolean unsubscribed = false;
447+
synchronized (this) {
447448
if (this.delegate == delegate) {
448-
if (debug.on()) debug.log("Unsubscribed %s", delegate);
449449
this.delegate = null;
450+
unsubscribed = true;
450451
}
451452
}
453+
if (unsubscribed) {
454+
if (debug.on()) debug.log("Unsubscribed %s", delegate);
455+
}
452456
}
453457

454458
// Callback: Consumer of ByteBuffer

0 commit comments

Comments
 (0)