Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,16 @@ available, calls to getConnection() will block for up to ``connectionTimeout`` m
before timing out. Please read [about pool sizing](https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing).
*Default: 10*

&#128290;``maximumPendingConnections``<br/>
This property controls the maximum number of threads that are allowed to simultaneously wait
for a connection from the pool. When this limit is exceeded, subsequent calls to
``getConnection()`` will fail immediately with a ``SQLTransientConnectionException`` instead of
blocking for up to ``connectionTimeout`` milliseconds. This acts as a load-shedding mechanism
that prevents unbounded thread accumulation during traffic spikes or database outages, avoiding
thundering-herd timeouts and reducing the blast radius of pool exhaustion.
A value of 0 means unlimited — all threads will wait up to ``connectionTimeout``.
*Default: 0 (unlimited)*

&#128200;``metricRegistry``<br/>
This property is only available via programmatic configuration or IoC container. This property
allows you to specify an instance of a *Codahale/Dropwizard* ``MetricRegistry`` to be used by the
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/com/zaxxer/hikari/HikariConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public class HikariConfig implements HikariConfigMXBean
private Properties healthCheckProperties;

private long keepaliveTime;
private int maximumPendingConnections;

private volatile boolean sealed;

Expand Down Expand Up @@ -738,6 +739,36 @@ public void addHealthCheckProperty(String key, String value)
healthCheckProperties.setProperty(key, value);
}

/**
* Get the maximum number of threads that are allowed to simultaneously wait for a connection from the pool.
* When this limit is exceeded, subsequent calls to {@code getConnection()} will fail immediately with a
* {@code SQLTransientConnectionException} instead of blocking for {@code connectionTimeout}.
*
* @return the maximum number of pending connection requests, or 0 (default) for unlimited
*/
public int getMaximumPendingConnections()
{
return maximumPendingConnections;
}

/**
* Set the maximum number of threads that are allowed to simultaneously wait for a connection from the pool.
* When this limit is exceeded, subsequent calls to {@code getConnection()} will fail immediately with a
* {@code SQLTransientConnectionException} instead of blocking for {@code connectionTimeout}.
* <p>
* A value of 0 (default) means unlimited — all threads will wait up to {@code connectionTimeout}.
*
* @param maximumPendingConnections the maximum number of pending connection requests, or 0 for unlimited
*/
public void setMaximumPendingConnections(int maximumPendingConnections)
{
checkIfSealed();
if (maximumPendingConnections < 0) {
throw new IllegalArgumentException("maximumPendingConnections cannot be negative");
}
this.maximumPendingConnections = maximumPendingConnections;
}

/**
* This property controls the keepalive interval for a connection in the pool. An in-use connection will never be
* tested by the keepalive thread, only when it is idle will it be tested.
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/zaxxer/hikari/pool/HikariPool.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public HikariPool(final HikariConfig config)
{
super(config);

this.connectionBag = new ConcurrentBag<>(this);
this.connectionBag = new ConcurrentBag<>(this, config.getMaximumPendingConnections());
Copy link
Author

@vlsi vlsi Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drawback of passing getMaximumPendingConnections is that ConcurrentBag won't react to the changes of maximumPendingConnections.

An alternative options could be:
a) Pass HikariConfig to ConcurrentBag
b) Add interface ConcurrentBagConfig { int getMaximumWaiters(); int getMaximumObjects();} so the bag could query the required limits

Any thoughts?

this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;

this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseab
private final ThreadLocal<List<Object>> threadLocalList;
private final IBagStateListener listener;
private final AtomicInteger waiters;
private final int maxWaiters;
private volatile boolean closed;

private final SynchronousQueue<T> handoffQueue;
Expand Down Expand Up @@ -106,10 +107,12 @@ public interface IBagStateListener
* Construct a ConcurrentBag with the specified listener.
*
* @param listener the IBagStateListener to attach to this bag
* @param maxWaiters the maximum number of threads allowed to wait for a bag item, or 0 for unlimited
*/
public ConcurrentBag(final IBagStateListener listener)
public ConcurrentBag(final IBagStateListener listener, final int maxWaiters)
{
this.listener = listener;
this.maxWaiters = maxWaiters;
this.useWeakThreadLocals = useWeakThreadLocals();

this.handoffQueue = new SynchronousQueue<>(true);
Expand Down Expand Up @@ -155,6 +158,13 @@ public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedExcepti
}
}

// TODO: should the condition include getTotalConnections() < config.getMaximumPoolSize()?
// It would allow threads to wait for the initial connection establishment, and it would
// protect against the pile of the connection requests if the pool is already full.
if (maxWaiters > 0 && waiting > maxWaiters) {
return null;
}

listener.addBagItem(waiting);

timeout = timeUnit.toNanos(timeout);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public static void teardown()
@Test
public void testConcurrentBag() throws Exception
{
try (ConcurrentBag<PoolEntry> bag = new ConcurrentBag<>(x -> CompletableFuture.completedFuture(Boolean.TRUE))) {
try (ConcurrentBag<PoolEntry> bag = new ConcurrentBag<>(x -> CompletableFuture.completedFuture(Boolean.TRUE), 0)) {
assertEquals(0, bag.values(8).size());

PoolEntry reserved = pool.newPoolEntry(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public static class FauxWebContext
@SuppressWarnings({"ResultOfMethodCallIgnored"})
public void createConcurrentBag() throws InterruptedException
{
try (ConcurrentBag<PoolEntry> bag = new ConcurrentBag<>(x -> CompletableFuture.completedFuture(Boolean.TRUE))) {
try (ConcurrentBag<PoolEntry> bag = new ConcurrentBag<>(x -> CompletableFuture.completedFuture(Boolean.TRUE), 0)) {

PoolEntry entry = new PoolEntry();
bag.add(entry);
Expand Down