1
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
118 @Override
119 public String toString()
120 {
121 return poolName;
122 }
123
124 abstract void recycle(final PoolEntry poolEntry);
125
126
127
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
141 }
142 finally {
143 connection.close();
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
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
266
267
268
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
304
305
306
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
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
380 if (metricsTracker != null) {
381 metricsTracker.recordConnectionCreated(elapsedMillis(start));
382 }
383 }
384 }
385
386
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
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
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
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
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
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
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
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
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
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
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
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);
646 return sb.toString();
647 }
648
649
650
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
667 private static class SynchronousExecutor implements Executor
668 {
669
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
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
754 static final class NopMetricsTrackerDelegate implements IMetricsTrackerDelegate {}
755 }
756