Talking about maxLifetime attribute and evict operation of hikari connection pool

  jdbc

Order

This article mainly studies the maxLifetime attribute and evict operation of hikari connection pool.

MaxLifetime attribute and evict operation

maxLifetime

Used to set the survival time of a connection in the connection pool, the default is 1800000, that is, 30 minutes. If set to 0, the survival time is infinite. If it is not equal to 0 and less than 30 seconds, it will be reset back to 30 minutes.

evict

Used to mark that the connection in the connection pool is not available, so when borrow is connected, if evict is marked, the connection will continue to be acquired.

   /**
    * Get a connection from the pool, or timeout after the specified number of milliseconds.
    *
    * @param hardTimeout the maximum time to wait for a connection from the pool
    * @return a java.sql.Connection instance
    * @throws SQLException thrown if a timeout occurs trying to obtain a connection
    */
   public Connection getConnection(final long hardTimeout) throws SQLException
   {
      suspendResumeLock.acquire();
      final long startTime = currentTime();

      try {
         long timeout = hardTimeout;
         do {
            PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
            if (poolEntry == null) {
               break; // We timed out... break and throw exception
            }

            final long now = currentTime();
            if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(poolEntry.connection))) {
               closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
               timeout = hardTimeout - elapsedMillis(startTime);
            }
            else {
               metricsTracker.recordBorrowStats(poolEntry, startTime);
               return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
            }
         } while (timeout > 0L);

         metricsTracker.recordBorrowTimeoutStats(startTime);
         throw createTimeoutException(startTime);
      }
      catch (InterruptedException e) {
         Thread.currentThread().interrupt();
         throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
      }
      finally {
         suspendResumeLock.release();
      }
   }

Note that if the connection retrieved here is poolEntry.isMarkedEvicted (), it will close, update the timeout value at the same time, and then continue looping the borrow connection.

HikariPool.evictConnection

HikariCP-2.7.6-sources.jar! /com/zaxxer/hikari/pool/HikariPool.java

   /**
    * Evict a Connection from the pool.
    *
    * @param connection the Connection to evict (actually a {@link ProxyConnection})
    */
   public void evictConnection(Connection connection)
   {
      ProxyConnection proxyConnection = (ProxyConnection) connection;
      proxyConnection.cancelLeakTask();

      try {
         softEvictConnection(proxyConnection.getPoolEntry(), "(connection evicted by user)", !connection.isClosed() /* owner */);
      }
      catch (SQLException e) {
         // unreachable in HikariCP, but we're still forced to catch it
      }
   }

This is the scene where evictConnection is actively called.

HikariPool.createPoolEntry

HikariCP-2.7.6-sources.jar! /com/zaxxer/hikari/pool/HikariPool.java

   /**
    * Creating new poolEntry.  If maxLifetime is configured, create a future End-of-life task with 2.5% variance from
    * the maxLifetime time to ensure there is no massive die-off of Connections in the pool.
    */
   private PoolEntry createPoolEntry()
   {
      try {
         final PoolEntry poolEntry = newPoolEntry();

         final long maxLifetime = config.getMaxLifetime();
         if (maxLifetime > 0) {
            // variance up to 2.5% of the maxlifetime
            final long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;
            final long lifetime = maxLifetime - variance;
            poolEntry.setFutureEol(houseKeepingExecutorService.schedule(
               () -> {
                  if (softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */)) {
                     addBagItem(connectionBag.getWaitingThreadCount());
                  }
               },
               lifetime, MILLISECONDS));
         }

         return poolEntry;
      }
      catch (Exception e) {
         if (poolState == POOL_NORMAL) { // we check POOL_NORMAL to avoid a flood of messages if shutdown() is running concurrently
            LOGGER.debug("{} - Cannot acquire connection from data source", poolName, (e instanceof ConnectionSetupException ? e.getCause() : e));
         }
         return null;
      }
   }

   /**
    * "Soft" evict a Connection (/PoolEntry) from the pool.  If this method is being called by the user directly
    * through {@link com.zaxxer.hikari.HikariDataSource#evictConnection(Connection)} then {@code owner} is {@code true}.
    *
    * If the caller is the owner, or if the Connection is idle (i.e. can be "reserved" in the {@link ConcurrentBag}),
    * then we can close the connection immediately.  Otherwise, we leave it "marked" for eviction so that it is evicted
    * the next time someone tries to acquire it from the pool.
    *
    * @param poolEntry the PoolEntry (/Connection) to "soft" evict from the pool
    * @param reason the reason that the connection is being evicted
    * @param owner true if the caller is the owner of the connection, false otherwise
    * @return true if the connection was evicted (closed), false if it was merely marked for eviction
    */
   private boolean softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner)
   {
      poolEntry.markEvicted();
      if (owner || connectionBag.reserve(poolEntry)) {
         closeConnection(poolEntry, reason);
         return true;
      }

      return false;
   }

Note that a delayed task was registered at the time of createpoolEntry and set to PoolEntry via poolEntry.setFutureEol.

SoftEvictConnection, markEvicted first. Then if it is called by the user himself, the connection is directly closed; If the mark from connectionBag is not borrow successful, the connection is closed.

This timing task is to randomly set a variance according to maxLifetime and trigger evict after maxLifetime-variance every time createPoolEntry is created.

Summary

The maxLifetime of the hikari connection pool is used to mark the connection’s lifetime in the connection pool, and a value of 0 indicates an indefinite period. The expiration operation mainly relies on registering a delay task when creating a poolEntry, triggering evit before the connection survival will reach maxLifetime, which is used to prevent large-area connections from failing at the same time due to maxLifetime. In addition to this delayed task, the user can also actively call the evict tag to connect to evict.

The difference between the trigger time and maxlifetime is based on maxLifetime > 10_000? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;
To calculate (up to 2.5% of the maxlifetime)。

Marked evict only indicates that the connection in the connection pool is unavailable, but it is still in the connection pool and will be borrow. it is only judged by getConnection. if it is isMarkedEvicted, the connection will be removed from the connection pool and then close.

doc