Connection pool (1): Why can I share a database connection when it is closed?

Keywords: Database Apache IE

There has always been a question about the use of database connection pools:

During the use of database connection, the exclusive requirement must be met (other threads cannot access it). Close the database connection after use. The problem is that the database connection has been closed. How can other threads use it?

With this in mind, we read the implementation source of "org.apache.commons.dbcp2.BasicDataSource" together and found that the sequence of calls to get connections from the data source is as follows:
1. BasicDataSource.getConnection();
2. PoolingDataSource.getConnection();
3. GenericObjectPool.borrowObject();
4. PoolableConnectionFactory.makeObject()

The connection now occurs as a result of how the PoolableConnectionFactory produces a data connection (makeObject) from the following sources (omitted, so the code is excerpted):

//  _connFactory is an instance of DriverConnectionFactory and the method to create a database connection is Driver.connect(), so this is a real database connection
Connection conn = _connFactory.createConnection();
//  If this connection fails to be created, subsequent connections will be useless
if (conn == null) {
    throw new IllegalStateException("Connection factory returned null from createConnection");
}
//  Initialization configuration code omitted
//  Returns the wrapped result of the connection
PoolableConnection pc = new PoolableConnection(conn,_pool, connJmxName,
                          _disconnectionSqlCodes, _fastFailValidation);
return new DefaultPooledObject<>(pc);

As you can see here, during the actual running of the project, the connection types we get from the database connection pool are PoolableConnection, and the classes are declared as follows:

public class PoolableConnection extends DelegatingConnection<Connection>
        implements PoolableConnectionMXBean {
}

DelegatingConnection implements the Connection interface, so PoolableConnection also implements the Connection interface.

public class DelegatingConnection<C extends Connection> extends AbandonedTrace
        implements Connection {

        }

Now we focus on the close() method of PoolableConnection as follows:

public synchronized void close() throws SQLException {
        //  Only here is the real shutdown, native Connection.close()
        if (isClosedInternal()) {
            return;
        }

        boolean isUnderlyingConectionClosed;
        try {
            //  Get the real database connection object to determine if the native database connection is really closed
            isUnderlyingConectionClosed = getDelegateInternal().isClosed();
        } catch (SQLException e) {
            //  Destroy invalid objects in database connection pool
            try {
                _pool.invalidateObject(this);
            } catch(IllegalStateException ise) {
                // Destroy object if connection pool is closed
                passivate();
                getInnermostDelegate().close();
            } catch (Exception ie) {
            }
            throw new SQLException("Cannot close connection (isClosed check failed)", e);
        }

        /* Connections cannot be closed until database connection verification is complete
         * Connections cannot be closed when database connections are returned to the pool
         */
        if (isUnderlyingConectionClosed) {
             try {
                _pool.invalidateObject(this);
            } catch(IllegalStateException e) {
                passivate();
                getInnermostDelegate().close();
            } catch (Exception e) {
                throw new SQLException("Cannot close connection (invalidating pooled object failed)", e);
            }
        } else {
            try {
                _pool.returnObject(this);
            } catch(IllegalStateException e) {
                passivate();
                getInnermostDelegate().close();
            } catch(SQLException e) {
                throw e;
            } catch(RuntimeException e) {
                throw e;
            } catch(Exception e) {
                throw new SQLException("Cannot close connection (return to pool failed)", e);
            }
        }
    }

From the code above, it can be seen that the so-called closing only gives the database connection pool the connection, and does not really close the database connection (invoking the closing method of the native database connection), a lot of logic is determining whether the native connection is really valid (not closed).

conclusion

The closure of the database connection we are using does not really mean disconnecting the database connection, but rather making a usability flag (such as adding an additional boolean field to determine the status) to facilitate the connection pool's judgment of valid connections.

For this reason, database connection pools also have drawbacks, such as errors where there are no available database connections during development, when the number of initialized connections set by code is too large, the number of actual connections to the development server may not be sufficient.

Posted by NeMoD on Thu, 13 Jun 2019 09:26:08 -0700