1 /*
2  * Copyright (C) 2013, 2014 Brett Wooldridge
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15 */

16
17 package com.zaxxer.hikari.pool;
18
19 import com.zaxxer.hikari.HikariConfig;
20 import com.zaxxer.hikari.SQLExceptionOverride;
21 import com.zaxxer.hikari.metrics.IMetricsTracker;
22 import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
23 import com.zaxxer.hikari.util.DriverDataSource;
24 import com.zaxxer.hikari.util.PropertyElf;
25 import com.zaxxer.hikari.util.UtilityElf;
26 import com.zaxxer.hikari.util.UtilityElf.DefaultThreadFactory;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 import javax.management.MBeanServer;
31 import javax.management.ObjectName;
32 import javax.naming.InitialContext;
33 import javax.naming.NamingException;
34 import javax.sql.DataSource;
35 import java.lang.management.ManagementFactory;
36 import java.sql.Connection;
37 import java.sql.SQLException;
38 import java.sql.SQLTransientConnectionException;
39 import java.sql.Statement;
40 import java.util.Properties;
41 import java.util.concurrent.Executor;
42 import java.util.concurrent.Executors;
43 import java.util.concurrent.ThreadFactory;
44 import java.util.concurrent.ThreadPoolExecutor;
45 import java.util.concurrent.atomic.AtomicReference;
46
47 import static com.zaxxer.hikari.pool.ProxyConnection.*;
48 import static com.zaxxer.hikari.util.ClockSource.*;
49 import static com.zaxxer.hikari.util.UtilityElf.createInstance;
50 import static java.util.concurrent.TimeUnit.MILLISECONDS;
51 import static java.util.concurrent.TimeUnit.SECONDS;
52
53 abstract class PoolBase
54 {
55    private final Logger logger = LoggerFactory.getLogger(PoolBase.class);
56
57    public final HikariConfig config;
58    IMetricsTrackerDelegate metricsTracker;
59
60    protected final String poolName;
61
62    volatile String catalog;
63    final AtomicReference<Exception> lastConnectionFailure;
64
65    long connectionTimeout;
66    long validationTimeout;
67
68    SQLExceptionOverride exceptionOverride;
69
70    private static final String[] RESET_STATES = {"readOnly""autoCommit""isolation""catalog""netTimeout""schema"};
71    private static final int UNINITIALIZED = -1;
72    private static final int TRUE = 1;
73    private static final int FALSE = 0;
74
75    private int networkTimeout;
76    private int isNetworkTimeoutSupported;
77    private int isQueryTimeoutSupported;
78    private int defaultTransactionIsolation;
79    private int transactionIsolation;
80    private Executor netTimeoutExecutor;
81    private DataSource dataSource;
82
83    private final String schema;
84    private final boolean isReadOnly;
85    private final boolean isAutoCommit;
86
87    private final boolean isUseJdbc4Validation;
88    private final boolean isIsolateInternalQueries;
89
90    private volatile boolean isValidChecked;
91
92    PoolBase(final HikariConfig config)
93    {
94       this.config = config;
95
96       this.networkTimeout = UNINITIALIZED;
97       this.catalog = config.getCatalog();
98       this.schema = config.getSchema();
99       this.isReadOnly = config.isReadOnly();
100       this.isAutoCommit = config.isAutoCommit();
101       this.exceptionOverride = UtilityElf.createInstance(config.getExceptionOverrideClassName(), SQLExceptionOverride.class);
102       this.transactionIsolation = UtilityElf.getTransactionIsolation(config.getTransactionIsolation());
103
104       this.isQueryTimeoutSupported = UNINITIALIZED;
105       this.isNetworkTimeoutSupported = UNINITIALIZED;
106       this.isUseJdbc4Validation = config.getConnectionTestQuery() == null;
107       this.isIsolateInternalQueries = config.isIsolateInternalQueries();
108
109       this.poolName = config.getPoolName();
110       this.connectionTimeout = config.getConnectionTimeout();
111       this.validationTimeout = config.getValidationTimeout();
112       this.lastConnectionFailure = new AtomicReference<>();
113
114       initializeDataSource();
115    }
116
117    /** {@inheritDoc} */
118    @Override
119    public String toString()
120    {
121       return poolName;
122    }
123
124    abstract void recycle(final PoolEntry poolEntry);
125
126    // ***********************************************************************
127    //                           JDBC methods
128    // ***********************************************************************
129
130    void quietlyCloseConnection(final Connection connection, final String closureReason)
131    {
132       if (connection != null) {
133          try {
134             logger.debug("{} - Closing connection {}: {}", poolName, connection, closureReason);
135
136             try {
137                setNetworkTimeout(connection, SECONDS.toMillis(15));
138             }
139             catch (SQLException e) {
140                // ignore
141             }
142             finally {
143                connection.close(); // continue with the close even if setNetworkTimeout() throws
144             }
145          }
146          catch (Exception e) {
147             logger.debug("{} - Closing connection {} failed", poolName, connection, e);
148          }
149       }
150    }
151
152    boolean isConnectionAlive(final Connection connection)
153    {
154       try {
155          try {
156             setNetworkTimeout(connection, validationTimeout);
157
158             final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;
159
160             if (isUseJdbc4Validation) {
161                return connection.isValid(validationSeconds);
162             }
163
164             try (Statement statement = connection.createStatement()) {
165                if (isNetworkTimeoutSupported != TRUE) {
166                   setQueryTimeout(statement, validationSeconds);
167                }
168
169                statement.execute(config.getConnectionTestQuery());
170             }
171          }
172          finally {
173             setNetworkTimeout(connection, networkTimeout);
174
175             if (isIsolateInternalQueries && !isAutoCommit) {
176                connection.rollback();
177             }
178          }
179
180          return true;
181       }
182       catch (Exception e) {
183          lastConnectionFailure.set(e);
184          logger.warn("{} - Failed to validate connection {} ({}). Possibly consider using a shorter maxLifetime value.",
185                      poolName, connection, e.getMessage());
186          return false;
187       }
188    }
189
190    Exception getLastConnectionFailure()
191    {
192       return lastConnectionFailure.get();
193    }
194
195    public DataSource getUnwrappedDataSource()
196    {
197       return dataSource;
198    }
199
200    // ***********************************************************************
201    //                         PoolEntry methods
202    // ***********************************************************************
203
204    PoolEntry newPoolEntry() throws Exception
205    {
206       return new PoolEntry(newConnection(), this, isReadOnly, isAutoCommit);
207    }
208
209    void resetConnectionState(final Connection connection, final ProxyConnection proxyConnection, final int dirtyBits) throws SQLException
210    {
211       int resetBits = 0;
212
213       if ((dirtyBits & DIRTY_BIT_READONLY) != 0 && proxyConnection.getReadOnlyState() != isReadOnly) {
214          connection.setReadOnly(isReadOnly);
215          resetBits |= DIRTY_BIT_READONLY;
216       }
217
218       if ((dirtyBits & DIRTY_BIT_AUTOCOMMIT) != 0 && proxyConnection.getAutoCommitState() != isAutoCommit) {
219          connection.setAutoCommit(isAutoCommit);
220          resetBits |= DIRTY_BIT_AUTOCOMMIT;
221       }
222
223       if ((dirtyBits & DIRTY_BIT_ISOLATION) != 0 && proxyConnection.getTransactionIsolationState() != transactionIsolation) {
224          connection.setTransactionIsolation(transactionIsolation);
225          resetBits |= DIRTY_BIT_ISOLATION;
226       }
227
228       if ((dirtyBits & DIRTY_BIT_CATALOG) != 0 && catalog != null && !catalog.equals(proxyConnection.getCatalogState())) {
229          connection.setCatalog(catalog);
230          resetBits |= DIRTY_BIT_CATALOG;
231       }
232
233       if ((dirtyBits & DIRTY_BIT_NETTIMEOUT) != 0 && proxyConnection.getNetworkTimeoutState() != networkTimeout) {
234          setNetworkTimeout(connection, networkTimeout);
235          resetBits |= DIRTY_BIT_NETTIMEOUT;
236       }
237
238       if ((dirtyBits & DIRTY_BIT_SCHEMA) != 0 && schema != null && !schema.equals(proxyConnection.getSchemaState())) {
239          connection.setSchema(schema);
240          resetBits |= DIRTY_BIT_SCHEMA;
241       }
242
243       if (resetBits != 0 && logger.isDebugEnabled()) {
244          logger.debug("{} - Reset ({}) on connection {}", poolName, stringFromResetBits(resetBits), connection);
245       }
246    }
247
248    void shutdownNetworkTimeoutExecutor()
249    {
250       if (netTimeoutExecutor instanceof ThreadPoolExecutor) {
251          ((ThreadPoolExecutor) netTimeoutExecutor).shutdownNow();
252       }
253    }
254
255    long getLoginTimeout()
256    {
257       try {
258          return (dataSource != null) ? dataSource.getLoginTimeout() : SECONDS.toSeconds(5);
259       } catch (SQLException e) {
260          return SECONDS.toSeconds(5);
261       }
262    }
263
264    // ***********************************************************************
265    //                       JMX methods
266    // ***********************************************************************
267
268    /**
269     * Register MBeans for HikariConfig and HikariPool.
270     *
271     * @param hikariPool a HikariPool instance
272     */

273    void handleMBeans(final HikariPool hikariPool, final boolean register)
274    {
275       if (!config.isRegisterMbeans()) {
276          return;
277       }
278
279       try {
280          final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
281
282          final ObjectName beanConfigName = new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")");
283          final ObjectName beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")");
284          if (register) {
285             if (!mBeanServer.isRegistered(beanConfigName)) {
286                mBeanServer.registerMBean(config, beanConfigName);
287                mBeanServer.registerMBean(hikariPool, beanPoolName);
288             } else {
289                logger.error("{} - JMX name ({}) is already registered.", poolName, poolName);
290             }
291          }
292          else if (mBeanServer.isRegistered(beanConfigName)) {
293             mBeanServer.unregisterMBean(beanConfigName);
294             mBeanServer.unregisterMBean(beanPoolName);
295          }
296       }
297       catch (Exception e) {
298          logger.warn("{} - Failed to {} management beans.", poolName, (register ? "register" : "unregister"), e);
299       }
300    }
301
302    // ***********************************************************************
303    //                          Private methods
304    // ***********************************************************************
305
306    /**
307     * Create/initialize the underlying DataSource.
308     */

309    private void initializeDataSource()
310    {
311       final String jdbcUrl = config.getJdbcUrl();
312       final String username = config.getUsername();
313       final String password = config.getPassword();
314       final String dsClassName = config.getDataSourceClassName();
315       final String driverClassName = config.getDriverClassName();
316       final String dataSourceJNDI = config.getDataSourceJNDI();
317       final Properties dataSourceProperties = config.getDataSourceProperties();
318
319       DataSource ds = config.getDataSource();
320       if (dsClassName != null && ds == null) {
321          ds = createInstance(dsClassName, DataSource.class);
322          PropertyElf.setTargetFromProperties(ds, dataSourceProperties);
323       }
324       else if (jdbcUrl != null && ds == null) {
325          ds = new DriverDataSource(jdbcUrl, driverClassName, dataSourceProperties, username, password);
326       }
327       else if (dataSourceJNDI != null && ds == null) {
328          try {
329             InitialContext ic = new InitialContext();
330             ds = (DataSource) ic.lookup(dataSourceJNDI);
331          } catch (NamingException e) {
332             throw new PoolInitializationException(e);
333          }
334       }
335
336       if (ds != null) {
337          setLoginTimeout(ds);
338          createNetworkTimeoutExecutor(ds, dsClassName, jdbcUrl);
339       }
340
341       this.dataSource = ds;
342    }
343
344    /**
345     * Obtain connection from data source.
346     *
347     * @return a Connection connection
348     */

349    private Connection newConnection() throws Exception
350    {
351       final long start = currentTime();
352
353       Connection connection = null;
354       try {
355          String username = config.getUsername();
356          String password = config.getPassword();
357
358          connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password);
359          if (connection == null) {
360             throw new SQLTransientConnectionException("DataSource returned null unexpectedly");
361          }
362
363          setupConnection(connection);
364          lastConnectionFailure.set(null);
365          return connection;
366       }
367       catch (Exception e) {
368          if (connection != null) {
369             quietlyCloseConnection(connection, "(Failed to create/setup connection)");
370          }
371          else if (getLastConnectionFailure() == null) {
372             logger.debug("{} - Failed to create/setup connection: {}", poolName, e.getMessage());
373          }
374
375          lastConnectionFailure.set(e);
376          throw e;
377       }
378       finally {
379          // tracker will be null during failFast check
380          if (metricsTracker != null) {
381             metricsTracker.recordConnectionCreated(elapsedMillis(start));
382          }
383       }
384    }
385
386    /**
387     * Setup a connection initial state.
388     *
389     * @param connection a Connection
390     * @throws ConnectionSetupException thrown if any exception is encountered
391     */

392    private void setupConnection(final Connection connection) throws ConnectionSetupException
393    {
394       try {
395          if (networkTimeout == UNINITIALIZED) {
396             networkTimeout = getAndSetNetworkTimeout(connection, validationTimeout);
397          }
398          else {
399             setNetworkTimeout(connection, validationTimeout);
400          }
401
402          if (connection.isReadOnly() != isReadOnly) {
403             connection.setReadOnly(isReadOnly);
404          }
405
406          if (connection.getAutoCommit() != isAutoCommit) {
407             connection.setAutoCommit(isAutoCommit);
408          }
409
410          checkDriverSupport(connection);
411
412          if (transactionIsolation != defaultTransactionIsolation) {
413             connection.setTransactionIsolation(transactionIsolation);
414          }
415
416          if (catalog != null) {
417             connection.setCatalog(catalog);
418          }
419
420          if (schema != null) {
421             connection.setSchema(schema);
422          }
423
424          executeSql(connection, config.getConnectionInitSql(), true);
425
426          setNetworkTimeout(connection, networkTimeout);
427       }
428       catch (SQLException e) {
429          throw new ConnectionSetupException(e);
430       }
431    }
432
433    /**
434     * Execute isValid() or connection test query.
435     *
436     * @param connection a Connection to check
437     */

438    private void checkDriverSupport(final Connection connection) throws SQLException
439    {
440       if (!isValidChecked) {
441          checkValidationSupport(connection);
442          checkDefaultIsolation(connection);
443
444          isValidChecked = true;
445       }
446    }
447
448    /**
449     * Check whether Connection.isValid() is supported, or that the user has test query configured.
450     *
451     * @param connection a Connection to check
452     * @throws SQLException rethrown from the driver
453     */

454    private void checkValidationSupport(final Connection connection) throws SQLException
455    {
456       try {
457          if (isUseJdbc4Validation) {
458             connection.isValid(1);
459          }
460          else {
461             executeSql(connection, config.getConnectionTestQuery(), false);
462          }
463       }
464       catch (Exception | AbstractMethodError e) {
465          logger.error("{} - Failed to execute{} connection test query ({}).", poolName, (isUseJdbc4Validation ? " isValid() for connection, configure" : ""), e.getMessage());
466          throw e;
467       }
468    }
469
470    /**
471     * Check the default transaction isolation of the Connection.
472     *
473     * @param connection a Connection to check
474     * @throws SQLException rethrown from the driver
475     */

476    private void checkDefaultIsolation(final Connection connection) throws SQLException
477    {
478       try {
479          defaultTransactionIsolation = connection.getTransactionIsolation();
480          if (transactionIsolation == -1) {
481             transactionIsolation = defaultTransactionIsolation;
482          }
483       }
484       catch (SQLException e) {
485          logger.warn("{} - Default transaction isolation level detection failed ({}).", poolName, e.getMessage());
486          if (e.getSQLState() != null && !e.getSQLState().startsWith("08")) {
487             throw e;
488          }
489       }
490    }
491
492    /**
493     * Set the query timeout, if it is supported by the driver.
494     *
495     * @param statement a statement to set the query timeout on
496     * @param timeoutSec the number of seconds before timeout
497     */

498    private void setQueryTimeout(final Statement statement, final int timeoutSec)
499    {
500       if (isQueryTimeoutSupported != FALSE) {
501          try {
502             statement.setQueryTimeout(timeoutSec);
503             isQueryTimeoutSupported = TRUE;
504          }
505          catch (Exception e) {
506             if (isQueryTimeoutSupported == UNINITIALIZED) {
507                isQueryTimeoutSupported = FALSE;
508                logger.info("{} - Failed to set query timeout for statement. ({})", poolName, e.getMessage());
509             }
510          }
511       }
512    }
513
514    /**
515     * Set the network timeout, if <code>isUseNetworkTimeout</code> is <code>true</code> and the
516     * driver supports it.  Return the pre-existing value of the network timeout.
517     *
518     * @param connection the connection to set the network timeout on
519     * @param timeoutMs the number of milliseconds before timeout
520     * @return the pre-existing network timeout value
521     */

522    private int getAndSetNetworkTimeout(final Connection connection, final long timeoutMs)
523    {
524       if (isNetworkTimeoutSupported != FALSE) {
525          try {
526             final int originalTimeout = connection.getNetworkTimeout();
527             connection.setNetworkTimeout(netTimeoutExecutor, (int) timeoutMs);
528             isNetworkTimeoutSupported = TRUE;
529             return originalTimeout;
530          }
531          catch (Exception | AbstractMethodError e) {
532             if (isNetworkTimeoutSupported == UNINITIALIZED) {
533                isNetworkTimeoutSupported = FALSE;
534
535                logger.info("{} - Driver does not support get/set network timeout for connections. ({})", poolName, e.getMessage());
536                if (validationTimeout < SECONDS.toMillis(1)) {
537                   logger.warn("{} - A validationTimeout of less than 1 second cannot be honored on drivers without setNetworkTimeout() support.", poolName);
538                }
539                else if (validationTimeout % SECONDS.toMillis(1) != 0) {
540                   logger.warn("{} - A validationTimeout with fractional second granularity cannot be honored on drivers without setNetworkTimeout() support.", poolName);
541                }
542             }
543          }
544       }
545
546       return 0;
547    }
548
549    /**
550     * Set the network timeout, if <code>isUseNetworkTimeout</code> is <code>true</code> and the
551     * driver supports it.
552     *
553     * @param connection the connection to set the network timeout on
554     * @param timeoutMs the number of milliseconds before timeout
555     * @throws SQLException throw if the connection.setNetworkTimeout() call throws
556     */

557    private void setNetworkTimeout(final Connection connection, final long timeoutMs) throws SQLException
558    {
559       if (isNetworkTimeoutSupported == TRUE) {
560          connection.setNetworkTimeout(netTimeoutExecutor, (int) timeoutMs);
561       }
562    }
563
564    /**
565     * Execute the user-specified init SQL.
566     *
567     * @param connection the connection to initialize
568     * @param sql the SQL to execute
569     * @param isCommit whether to commit the SQL after execution or not
570     * @throws SQLException throws if the init SQL execution fails
571     */

572    private void executeSql(final Connection connection, final String sql, final boolean isCommit) throws SQLException
573    {
574       if (sql != null) {
575          try (Statement statement = connection.createStatement()) {
576             // connection was created a few milliseconds before, so set query timeout is omitted (we assume it will succeed)
577             statement.execute(sql);
578          }
579
580          if (isIsolateInternalQueries && !isAutoCommit) {
581             if (isCommit) {
582                connection.commit();
583             }
584             else {
585                connection.rollback();
586             }
587          }
588       }
589    }
590
591    private void createNetworkTimeoutExecutor(final DataSource dataSource, final String dsClassName, final String jdbcUrl)
592    {
593       // Temporary hack for MySQL issue: http://bugs.mysql.com/bug.php?id=75615
594       if ((dsClassName != null && dsClassName.contains("Mysql")) ||
595           (jdbcUrl != null && jdbcUrl.contains("mysql")) ||
596           (dataSource != null && dataSource.getClass().getName().contains("Mysql"))) {
597          netTimeoutExecutor = new SynchronousExecutor();
598       }
599       else {
600          ThreadFactory threadFactory = config.getThreadFactory();
601          threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory(poolName + " network timeout executor"true);
602          ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(threadFactory);
603          executor.setKeepAliveTime(15, SECONDS);
604          executor.allowCoreThreadTimeOut(true);
605          netTimeoutExecutor = executor;
606       }
607    }
608
609    /**
610     * Set the loginTimeout on the specified DataSource.
611     *
612     * @param dataSource the DataSource
613     */

614    private void setLoginTimeout(final DataSource dataSource)
615    {
616       if (connectionTimeout != Integer.MAX_VALUE) {
617          try {
618             dataSource.setLoginTimeout(Math.max(1, (int) MILLISECONDS.toSeconds(500L + connectionTimeout)));
619          }
620          catch (Exception e) {
621             logger.info("{} - Failed to set login timeout for data source. ({})", poolName, e.getMessage());
622          }
623       }
624    }
625
626    /**
627     * This will create a string for debug logging. Given a set of "reset bits"this
628     * method will return a concatenated string, for example:
629     *
630     * Input : 0b00110
631     * Output: "autoCommit, isolation"
632     *
633     * @param bits a set of "reset bits"
634     * @return a string of which states were reset
635     */

636    private String stringFromResetBits(final int bits)
637    {
638       final StringBuilder sb = new StringBuilder();
639       for (int ndx = 0; ndx < RESET_STATES.length; ndx++) {
640          if ( (bits & (0b1 << ndx)) != 0) {
641             sb.append(RESET_STATES[ndx]).append(", ");
642          }
643       }
644
645       sb.setLength(sb.length() - 2);  // trim trailing comma
646       return sb.toString();
647    }
648
649    // ***********************************************************************
650    //                      Private Static Classes
651    // ***********************************************************************
652
653    static class ConnectionSetupException extends Exception
654    {
655       private static final long serialVersionUID = 929872118275916521L;
656
657       ConnectionSetupException(Throwable t)
658       {
659          super(t);
660       }
661    }
662
663    /**
664     * Special executor used only to work around a MySQL issue that has not been addressed.
665     * MySQL issue: http://bugs.mysql.com/bug.php?id=75615
666     */

667    private static class SynchronousExecutor implements Executor
668    {
669       /** {@inheritDoc} */
670       @Override
671       public void execute(Runnable command)
672       {
673          try {
674             command.run();
675          }
676          catch (Exception t) {
677             LoggerFactory.getLogger(PoolBase.class).debug("Failed to execute: {}", command, t);
678          }
679       }
680    }
681
682    interface IMetricsTrackerDelegate extends AutoCloseable
683    {
684       default void recordConnectionUsage(PoolEntry poolEntry) {}
685
686       default void recordConnectionCreated(long connectionCreatedMillis) {}
687
688       default void recordBorrowTimeoutStats(long startTime) {}
689
690       default void recordBorrowStats(final PoolEntry poolEntry, final long startTime) {}
691
692       default void recordConnectionTimeout() {}
693
694       @Override
695       default void close() {}
696    }
697
698    /**
699     * A class that delegates to a MetricsTracker implementation.  The use of a delegate
700     * allows us to use the NopMetricsTrackerDelegate when metrics are disabled, which in
701     * turn allows the JIT to completely optimize away to callsites to record metrics.
702     */

703    static class MetricsTrackerDelegate implements IMetricsTrackerDelegate
704    {
705       final IMetricsTracker tracker;
706
707       MetricsTrackerDelegate(IMetricsTracker tracker)
708       {
709          this.tracker = tracker;
710       }
711
712       @Override
713       public void recordConnectionUsage(final PoolEntry poolEntry)
714       {
715          tracker.recordConnectionUsageMillis(poolEntry.getMillisSinceBorrowed());
716       }
717
718       @Override
719       public void recordConnectionCreated(long connectionCreatedMillis)
720       {
721          tracker.recordConnectionCreatedMillis(connectionCreatedMillis);
722       }
723
724       @Override
725       public void recordBorrowTimeoutStats(long startTime)
726       {
727          tracker.recordConnectionAcquiredNanos(elapsedNanos(startTime));
728       }
729
730       @Override
731       public void recordBorrowStats(final PoolEntry poolEntry, final long startTime)
732       {
733          final long now = currentTime();
734          poolEntry.lastBorrowed = now;
735          tracker.recordConnectionAcquiredNanos(elapsedNanos(startTime, now));
736       }
737
738       @Override
739       public void recordConnectionTimeout() {
740          tracker.recordConnectionTimeout();
741       }
742
743       @Override
744       public void close()
745       {
746          tracker.close();
747       }
748    }
749
750    /**
751     * A no-op implementation of the IMetricsTrackerDelegate that is used when metrics capture is
752     * disabled.
753     */

754    static final class NopMetricsTrackerDelegate implements IMetricsTrackerDelegate {}
755 }
756