30
30
import java .util .Iterator ;
31
31
import java .util .concurrent .ConcurrentLinkedDeque ;
32
32
import java .util .concurrent .TimeUnit ;
33
- import java .util .concurrent .atomic .AtomicInteger ;
34
33
import java .util .concurrent .locks .Condition ;
35
34
import java .util .concurrent .locks .ReentrantLock ;
36
- import java .util .concurrent .locks .ReentrantReadWriteLock ;
37
35
import java .util .function .Consumer ;
38
36
import java .util .function .Supplier ;
39
37
40
38
import static com .mongodb .assertions .Assertions .assertNotNull ;
41
39
import static com .mongodb .assertions .Assertions .assertTrue ;
42
40
import static com .mongodb .assertions .Assertions .notNull ;
43
41
import static com .mongodb .internal .Locks .lockInterruptibly ;
44
- import static com .mongodb .internal .Locks .lockInterruptiblyUnfair ;
45
- import static com .mongodb .internal .Locks .withUnfairLock ;
42
+ import static com .mongodb .internal .Locks .withLock ;
46
43
import static com .mongodb .internal .VisibleForTesting .AccessModifier .PRIVATE ;
47
44
import static com .mongodb .internal .thread .InterruptionUtil .interruptAndCreateMongoInterruptedException ;
48
45
@@ -321,48 +318,17 @@ private static final class StateAndPermits {
321
318
private volatile boolean closed ;
322
319
private final int maxPermits ;
323
320
private volatile int permits ;
324
- /** When there are not enough available permits to serve all threads requesting a permit, threads are queued and wait on
325
- * {@link #permitAvailableOrClosedOrPausedCondition}. Because of this waiting, we want threads to acquire the lock fairly,
326
- * to avoid a situation when some threads are sitting in the queue for a long time while others barge in and acquire
327
- * the lock without waiting in the queue. Fair locking reduces high percentiles of {@link #acquirePermit(long, TimeUnit)} latencies
328
- * but reduces its throughput: it makes latencies roughly equally high for everyone, while keeping them lower than the highest
329
- * latencies with unfair locking. The fair approach is in accordance with the
330
- * <a href="https://github.com/mongodb/specifications/blob/568093ce7f0e1394cf4952c417e1e7dacc5fef53/source/connection-monitoring-and-pooling/connection-monitoring-and-pooling.rst#waitqueue">
331
- * connection pool specification</a>.
332
- * <p>
333
- * When there are enough available permits to serve all threads requesting a permit, threads still have to acquire the lock,
334
- * and still are queued, but since they are not waiting on {@link #permitAvailableOrClosedOrPausedCondition},
335
- * threads spend less time in the queue. This results in having smaller high percentiles
336
- * of {@link #acquirePermit(long, TimeUnit)} latencies, and we do not want to sacrifice the throughput
337
- * to further reduce the high percentiles by acquiring the lock fairly.</p>
338
- * <p>
339
- * While there is a chance that the expressed reasoning is flawed, it is supported by the results of experiments reported in
340
- * comments in <a href="https://jira.mongodb.org/browse/JAVA-4452">JAVA-4452</a>.</p>
341
- * <p>
342
- * {@link ReentrantReadWriteLock#hasWaiters(Condition)} requires holding the lock to be called, therefore we cannot use it
343
- * to discriminate between the two cases described above, and we use {@link #waitersEstimate} instead.
344
- * This approach results in sometimes acquiring a lock unfairly when it should have been acquired fairly, and vice versa.
345
- * But it appears to be a good enough compromise, that results in having enough throughput when there are enough
346
- * available permits and tolerable high percentiles of latencies when there are not enough available permits.</p>
347
- * <p>
348
- * It may seem viable to use {@link #permits} > 0 as a way to decide that there are likely no waiters,
349
- * but benchmarking shows that with this approach high percentiles of contended {@link #acquirePermit(long, TimeUnit)} latencies
350
- * (when the number of threads that use the pool is higher than the maximum pool size) become similar to a situation when no
351
- * fair locking is used. That is, this approach does not result in the behavior we want.</p>
352
- */
353
- private final AtomicInteger waitersEstimate ;
354
321
@ Nullable
355
322
private Supplier <MongoException > causeSupplier ;
356
323
357
324
StateAndPermits (final int maxPermits , final Supplier <MongoServerUnavailableException > poolClosedExceptionSupplier ) {
358
325
this .poolClosedExceptionSupplier = poolClosedExceptionSupplier ;
359
- lock = new ReentrantLock (true );
326
+ lock = new ReentrantLock ();
360
327
permitAvailableOrClosedOrPausedCondition = lock .newCondition ();
361
328
paused = false ;
362
329
closed = false ;
363
330
this .maxPermits = maxPermits ;
364
331
permits = maxPermits ;
365
- waitersEstimate = new AtomicInteger ();
366
332
causeSupplier = null ;
367
333
}
368
334
@@ -371,7 +337,7 @@ int permits() {
371
337
}
372
338
373
339
boolean acquirePermitImmediateUnfair () {
374
- return withUnfairLock (lock , () -> {
340
+ return withLock (lock , () -> {
375
341
throwIfClosedOrPaused ();
376
342
if (permits > 0 ) {
377
343
//noinspection NonAtomicOperationOnVolatileField
@@ -391,17 +357,12 @@ boolean acquirePermitImmediateUnfair() {
391
357
*/
392
358
boolean acquirePermit (final long timeout , final TimeUnit unit ) throws MongoInterruptedException {
393
359
long remainingNanos = unit .toNanos (timeout );
394
- if (waitersEstimate .get () == 0 ) {
395
- lockInterruptiblyUnfair (lock );
396
- } else {
397
- lockInterruptibly (lock );
398
- }
360
+ lockInterruptibly (lock );
399
361
try {
400
362
while (permits == 0
401
363
// the absence of short-circuiting is of importance
402
364
& !throwIfClosedOrPaused ()) {
403
365
try {
404
- waitersEstimate .incrementAndGet ();
405
366
if (timeout < 0 || remainingNanos == Long .MAX_VALUE ) {
406
367
permitAvailableOrClosedOrPausedCondition .await ();
407
368
} else if (remainingNanos >= 0 ) {
@@ -411,8 +372,6 @@ boolean acquirePermit(final long timeout, final TimeUnit unit) throws MongoInter
411
372
}
412
373
} catch (InterruptedException e ) {
413
374
throw interruptAndCreateMongoInterruptedException (null , e );
414
- } finally {
415
- waitersEstimate .decrementAndGet ();
416
375
}
417
376
}
418
377
assertTrue (permits > 0 );
@@ -425,7 +384,7 @@ boolean acquirePermit(final long timeout, final TimeUnit unit) throws MongoInter
425
384
}
426
385
427
386
void releasePermit () {
428
- withUnfairLock (lock , () -> {
387
+ withLock (lock , () -> {
429
388
assertTrue (permits < maxPermits );
430
389
//noinspection NonAtomicOperationOnVolatileField
431
390
permits ++;
@@ -434,7 +393,7 @@ void releasePermit() {
434
393
}
435
394
436
395
void pause (final Supplier <MongoException > causeSupplier ) {
437
- withUnfairLock (lock , () -> {
396
+ withLock (lock , () -> {
438
397
if (!paused ) {
439
398
this .paused = true ;
440
399
permitAvailableOrClosedOrPausedCondition .signalAll ();
@@ -445,7 +404,7 @@ void pause(final Supplier<MongoException> causeSupplier) {
445
404
446
405
void ready () {
447
406
if (paused ) {
448
- withUnfairLock (lock , () -> {
407
+ withLock (lock , () -> {
449
408
this .paused = false ;
450
409
this .causeSupplier = null ;
451
410
});
@@ -457,7 +416,7 @@ void ready() {
457
416
*/
458
417
boolean close () {
459
418
if (!closed ) {
460
- return withUnfairLock (lock , () -> {
419
+ return withLock (lock , () -> {
461
420
if (!closed ) {
462
421
closed = true ;
463
422
permitAvailableOrClosedOrPausedCondition .signalAll ();
0 commit comments