0001: /*
0002: * Copyright (c) 1998 - 2005 Versant Corporation
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * Versant Corporation - initial API and implementation
0010: */
0011: package com.versant.core.jdbc.conn;
0012:
0013: import com.versant.core.common.Debug;
0014: import com.versant.core.jdo.PoolStatus;
0015: import com.versant.core.metric.BaseMetric;
0016: import com.versant.core.metric.Metric;
0017: import com.versant.core.metric.PercentageMetric;
0018: import com.versant.core.logging.LogEventStore;
0019: import com.versant.core.jdbc.JdbcConfig;
0020: import com.versant.core.jdbc.logging.JdbcConnectionEvent;
0021: import com.versant.core.jdbc.logging.JdbcLogEvent;
0022: import com.versant.core.jdbc.logging.JdbcPoolEvent;
0023: import com.versant.core.jdbc.JdbcConnectionSource;
0024: import com.versant.core.jdbc.sql.SqlDriver;
0025: import com.versant.core.util.BeanUtils;
0026:
0027: import java.sql.*;
0028: import java.util.*;
0029:
0030: import com.versant.core.common.BindingSupportImpl;
0031: import com.versant.core.metric.HasMetrics;
0032:
0033: /**
0034: * JDBC connection pool with a PreparedStatement cache for each connection.
0035: *
0036: * @see PooledConnection
0037: */
0038: public final class JDBCConnectionPool implements Runnable,
0039: JdbcConnectionSource, HasMetrics {
0040:
0041: private final Driver jdbcDriver;
0042: private final SqlDriver sqlDriver;
0043: private final Properties props;
0044: private final String url;
0045: private final LogEventStore pes;
0046: private boolean clearBatch;
0047: private final boolean waitForConOnStartup;
0048: private boolean jdbcDisablePsCache;
0049: private int psCacheMax;
0050: private int maxActive;
0051: private int maxIdle;
0052: private int minIdle;
0053: private int reserved;
0054: private final boolean testOnAlloc;
0055: private final boolean testOnReturn;
0056: private final boolean testOnException;
0057: private boolean testWhenIdle;
0058: private String initSQL;
0059: private boolean commitAfterInitSQL;
0060: private String validateSQL;
0061: private final int retryIntervalMs;
0062: private final int retryCount;
0063: private boolean closed;
0064: private int conTimeout;
0065: private int testInterval;
0066: private boolean blockWhenFull;
0067: private int maxConAge;
0068: private int isolationLevel;
0069:
0070: private PooledConnection idleHead; // next con to be given out
0071: private PooledConnection idleTail; // last con returned
0072: private PooledConnection idleHeadAC; // next autocommit con to be given out
0073: private PooledConnection idleTailAC; // last autocommit con returned
0074: private int idleCount;
0075:
0076: private PooledConnection activeHead; // most recently allocated con
0077: private PooledConnection activeTail; // least recently allocated con
0078: private int activeCount;
0079:
0080: private Thread cleanupThread;
0081: private long timeLastTest = System.currentTimeMillis();
0082:
0083: private Properties userProps;
0084:
0085: private BaseMetric metricActive;
0086: private BaseMetric metricIdle;
0087: private BaseMetric metricMaxActive;
0088: private BaseMetric metricCreated;
0089: private BaseMetric metricClosed;
0090: private BaseMetric metricAllocated;
0091: private BaseMetric metricValidated;
0092: private BaseMetric metricBad;
0093: private BaseMetric metricTimedOut;
0094: private BaseMetric metricExpired;
0095: private BaseMetric metricWait;
0096: private BaseMetric metricFull;
0097:
0098: private int createdCount;
0099: private int closedCount;
0100: private int allocatedCount;
0101: private int validatedCount;
0102: private int badCount;
0103: private int timedOutCount;
0104: private int waitCount;
0105: private int fullCount;
0106: private int expiredCount;
0107:
0108: private static final String CAT_POOL = "Con Pool";
0109:
0110: /**
0111: * Create the pool. Note that changes to jdbcConfig have no effect on the
0112: * pool after construction i.e. fields in jdbcConfig are copied not
0113: * referenced. The sqlDriver parameter is used to customize the pool
0114: * to workaround bugs and so on in the JDBC driver or database. It can
0115: * be null.
0116: */
0117: public JDBCConnectionPool(JdbcConfig jdbcConfig, LogEventStore pes,
0118: Driver jdbcDriver, SqlDriver sqlDriver) {
0119: this .pes = pes;
0120: this .jdbcDriver = jdbcDriver;
0121: this .sqlDriver = sqlDriver;
0122:
0123: url = jdbcConfig.url;
0124: props = new Properties();
0125: if (jdbcConfig.user != null) {
0126: props.put("user", jdbcConfig.user);
0127: }
0128: if (jdbcConfig.password != null) {
0129: props.put("password", jdbcConfig.password);
0130: }
0131: if (jdbcConfig.properties != null) {
0132: BeanUtils.parseProperties(jdbcConfig.properties, props);
0133: }
0134:
0135: //create a props for user
0136: userProps = new Properties();
0137: if (jdbcConfig.user != null) {
0138: userProps.put("user", jdbcConfig.user);
0139: }
0140: userProps.put("url", url);
0141: userProps.put("driver", jdbcDriver.getClass().getName());
0142: if (jdbcConfig.properties != null) {
0143: BeanUtils.parseProperties(jdbcConfig.properties, userProps);
0144: }
0145:
0146: jdbcDisablePsCache = jdbcConfig.jdbcDisablePsCache;
0147: psCacheMax = jdbcConfig.psCacheMax;
0148: waitForConOnStartup = jdbcConfig.waitForConOnStartup;
0149: maxActive = jdbcConfig.maxActive;
0150: maxIdle = jdbcConfig.maxIdle;
0151: minIdle = jdbcConfig.minIdle;
0152: reserved = jdbcConfig.reserved;
0153: testOnAlloc = jdbcConfig.testOnAlloc;
0154: testOnReturn = jdbcConfig.testOnRelease;
0155: testOnException = jdbcConfig.testOnException;
0156: testWhenIdle = jdbcConfig.testWhenIdle;
0157: retryIntervalMs = jdbcConfig.retryIntervalMs > 0 ? jdbcConfig.retryIntervalMs
0158: : 100;
0159: retryCount = jdbcConfig.retryCount;
0160: conTimeout = jdbcConfig.conTimeout;
0161: testInterval = jdbcConfig.testInterval;
0162: blockWhenFull = jdbcConfig.blockWhenFull;
0163: maxConAge = jdbcConfig.maxConAge;
0164: isolationLevel = jdbcConfig.isolationLevel;
0165:
0166: setValidateSQL(trimToNull(jdbcConfig.validateSQL));
0167: setInitSQL(trimToNull(jdbcConfig.initSQL));
0168:
0169: if (sqlDriver != null) {
0170: if (psCacheMax == 0) {
0171: psCacheMax = sqlDriver.getDefaultPsCacheMax();
0172: }
0173: clearBatch = sqlDriver.isClearBatchRequired();
0174: if (validateSQL == null) {
0175: setValidateSQL(sqlDriver.getConnectionValidateSQL());
0176: }
0177: if (initSQL == null) {
0178: setInitSQL(sqlDriver.getConnectionInitSQL());
0179: }
0180: if (!sqlDriver.isSetTransactionIsolationLevelSupported()) {
0181: isolationLevel = 0;
0182: }
0183: }
0184:
0185: // sanity check some settings
0186: if (maxIdle > maxActive)
0187: maxIdle = maxActive;
0188: if (minIdle > maxIdle)
0189: minIdle = maxIdle;
0190: }
0191:
0192: private String trimToNull(String s) {
0193: if (s == null) {
0194: return null;
0195: }
0196: s = s.trim();
0197: if (s.length() == 0) {
0198: return null;
0199: }
0200: return s;
0201: }
0202:
0203: public void init() {
0204: if (cleanupThread != null)
0205: return;
0206: cleanupThread = new Thread(this , "VOA Pool " + url);
0207: cleanupThread.setDaemon(true);
0208: cleanupThread.setPriority(Thread.MIN_PRIORITY);
0209: cleanupThread.start();
0210: }
0211:
0212: /**
0213: * Check that we can connect to the database and that the initSQL (if any)
0214: * works.
0215: */
0216: public void check() throws Exception {
0217: PooledConnection con = null;
0218: try {
0219: if (waitForConOnStartup) {
0220: con = createPooledConnection(0, 10000);
0221: } else {
0222: con = createPooledConnection(-1, 10000);
0223: }
0224: if (!validateConnection(con)) {
0225: throw BindingSupportImpl.getInstance().runtime(
0226: "First connection failed validation SQL:\n"
0227: + validateSQL);
0228: }
0229: } finally {
0230: if (con != null)
0231: destroy(con);
0232: }
0233: }
0234:
0235: /**
0236: * Create a connection. If this fails then sleep for millis and try again.
0237: * If retryCount is 0 then retry is done forever, if < 0 then there are
0238: * no retries.
0239: */
0240: private Connection createRealConWithRetry(int retryCount, int millis) {
0241: for (int n = 0;;) {
0242: try {
0243: return createRealCon();
0244: } catch (RuntimeException e) {
0245: if (BindingSupportImpl.getInstance()
0246: .isOwnDatastoreException(e)) {
0247: if (retryCount < 0
0248: || (retryCount > 0 && ++n > retryCount))
0249: throw e;
0250: if (pes.isWarning()) {
0251: JdbcLogEvent ev = new JdbcLogEvent(0,
0252: JdbcLogEvent.POOL_CON_FAILED, n + " "
0253: + e.toString());
0254: ev.updateTotalMs();
0255: pes.log(ev);
0256: }
0257: try {
0258: Thread.sleep(millis);
0259: } catch (InterruptedException e1) {
0260: // ignore the interruption
0261: }
0262: } else {
0263: throw e;
0264: }
0265: }
0266: }
0267: }
0268:
0269: public int getIsolationLevel() {
0270: return isolationLevel;
0271: }
0272:
0273: /**
0274: * If isolationLevel != 0 then the isolation level of newly created
0275: * Connections is set to this.
0276: *
0277: * @see Connection#TRANSACTION_READ_COMMITTED etc
0278: */
0279: public void setIsolationLevel(int isolationLevel) {
0280: this .isolationLevel = isolationLevel;
0281: }
0282:
0283: public void setClearBatch(boolean clearBatch) {
0284: this .clearBatch = clearBatch;
0285: }
0286:
0287: public boolean isClearBatch() {
0288: return clearBatch;
0289: }
0290:
0291: public boolean isJdbcDisablePsCache() {
0292: return jdbcDisablePsCache;
0293: }
0294:
0295: public void setJdbcDisablePsCache(boolean jdbcDisablePsCache) {
0296: this .jdbcDisablePsCache = jdbcDisablePsCache;
0297: }
0298:
0299: public int getPsCacheMax() {
0300: return psCacheMax;
0301: }
0302:
0303: public void setPsCacheMax(int psCacheMax) {
0304: this .psCacheMax = psCacheMax;
0305: }
0306:
0307: public String getInitSQL() {
0308: return initSQL;
0309: }
0310:
0311: public void setInitSQL(String initSQL) {
0312: String s = endsWithCommit(initSQL);
0313: if (s != null) {
0314: commitAfterInitSQL = true;
0315: this .initSQL = s;
0316: } else {
0317: commitAfterInitSQL = false;
0318: this .initSQL = initSQL;
0319: }
0320: }
0321:
0322: /**
0323: * If s ends with ;[{ws}]commit[;][{ws}] then return s minus this part. Otherwise
0324: * return null.
0325: */
0326: private String endsWithCommit(String s) {
0327: if (s == null)
0328: return null;
0329: s = s.trim();
0330: if (!s.endsWith("commit") && !s.endsWith("COMMIT"))
0331: return null;
0332: for (int i = s.length() - 7; i >= 0; i--) {
0333: char c = s.charAt(i);
0334: if (!Character.isWhitespace(c)) {
0335: if (c == ';') {
0336: return s.substring(0, i);
0337: } else {
0338: break;
0339: }
0340: }
0341: }
0342: return null;
0343: }
0344:
0345: public String getValidateSQL() {
0346: return validateSQL;
0347: }
0348:
0349: public void setValidateSQL(String validateSQL) {
0350: this .validateSQL = validateSQL;
0351: }
0352:
0353: public int getConTimeout() {
0354: return conTimeout;
0355: }
0356:
0357: public void setConTimeout(int conTimeout) {
0358: this .conTimeout = conTimeout;
0359: }
0360:
0361: public int getTestInterval() {
0362: return testInterval;
0363: }
0364:
0365: public void setTestInterval(int testInterval) {
0366: this .testInterval = testInterval;
0367: }
0368:
0369: public boolean isTestWhenIdle() {
0370: return testWhenIdle;
0371: }
0372:
0373: public void setTestWhenIdle(boolean on) {
0374: testWhenIdle = on;
0375: }
0376:
0377: public boolean isBlockWhenFull() {
0378: return blockWhenFull;
0379: }
0380:
0381: public void setBlockWhenFull(boolean blockWhenFull) {
0382: this .blockWhenFull = blockWhenFull;
0383: }
0384:
0385: public int getMaxIdle() {
0386: return maxIdle;
0387: }
0388:
0389: public void setMaxIdle(int maxIdle) {
0390: this .maxIdle = maxIdle;
0391: if (cleanupThread != null)
0392: cleanupThread.interrupt();
0393: }
0394:
0395: public int getMinIdle() {
0396: return minIdle;
0397: }
0398:
0399: public void setMinIdle(int minIdle) {
0400: this .minIdle = minIdle;
0401: if (cleanupThread != null)
0402: cleanupThread.interrupt();
0403: }
0404:
0405: public int getMaxActive() {
0406: return maxActive;
0407: }
0408:
0409: public void setMaxActive(int maxActive) {
0410: this .maxActive = maxActive;
0411: if (cleanupThread != null)
0412: cleanupThread.interrupt();
0413: }
0414:
0415: public int getReserved() {
0416: return reserved;
0417: }
0418:
0419: public void setReserved(int reserved) {
0420: this .reserved = reserved;
0421: }
0422:
0423: public int getIdleCount() {
0424: return idleCount;
0425: }
0426:
0427: public int getActiveCount() {
0428: return activeCount;
0429: }
0430:
0431: public int getMaxConAge() {
0432: return maxConAge;
0433: }
0434:
0435: public void setMaxConAge(int maxConAge) {
0436: this .maxConAge = maxConAge;
0437: }
0438:
0439: /**
0440: * Fill s with status info for this pool.
0441: */
0442: public void fillStatus(PoolStatus s) {
0443: s.fill(maxActive, activeCount, maxIdle, idleCount);
0444: }
0445:
0446: /**
0447: * Get our JDBC URL.
0448: */
0449: public String getURL() {
0450: return url;
0451: }
0452:
0453: /**
0454: * Return the connection prop of the pool.
0455: */
0456: public Properties getConnectionProperties() {
0457: return userProps;
0458: }
0459:
0460: /**
0461: * Get the JDBC driver instance.
0462: */
0463: public Driver getJdbcDriver() {
0464: return jdbcDriver;
0465: }
0466:
0467: /**
0468: * Get the JDBC driver class or null if not known.
0469: */
0470: public String getDriverName() {
0471: return jdbcDriver == null ? "(unknown)" : jdbcDriver.getClass()
0472: .getName();
0473: }
0474:
0475: /**
0476: * Allocate a PooledConnection from the pool.
0477: *
0478: * @param highPriority If this is true then reserved high priority
0479: * @param autoCommit Must the connection have autoCommit set?
0480: */
0481: public Connection getConnection(boolean highPriority,
0482: boolean autoCommit) throws SQLException {
0483: // adjust local maxActive to maintain reserved connections if needed
0484: int maxActive = this .maxActive - (highPriority ? 0 : reserved);
0485: PooledConnection con = null;
0486: for (;;) {
0487: synchronized (this ) {
0488: for (;;) {
0489: if (closed) {
0490: throw BindingSupportImpl.getInstance().fatal(
0491: "Connection pool has been closed");
0492: }
0493: if (activeCount >= maxActive) {
0494: if (pes.isWarning()) {
0495: JdbcPoolEvent event = new JdbcPoolEvent(0,
0496: JdbcLogEvent.POOL_FULL,
0497: highPriority);
0498: update(event);
0499: pes.log(event);
0500: fullCount++;
0501: }
0502: if (blockWhenFull) {
0503: try {
0504: wait();
0505: } catch (InterruptedException e) {
0506: // ignore
0507: }
0508: } else {
0509: throw BindingSupportImpl.getInstance()
0510: .poolFull(
0511: "JDBC connection pool is full: "
0512: + this );
0513: }
0514: } else {
0515: con = removeFromIdleHead(autoCommit);
0516: ++activeCount;
0517: break;
0518: }
0519: }
0520: }
0521: if (con == null) {
0522: try {
0523: waitCount++;
0524: con = createPooledConnection(retryCount,
0525: retryIntervalMs);
0526: } finally {
0527: if (con == null) {
0528: synchronized (this ) {
0529: --activeCount;
0530: }
0531: }
0532: }
0533: break;
0534: } else {
0535: if (testOnAlloc && !validateConnection(con)) {
0536: destroy(con);
0537: synchronized (this ) {
0538: --activeCount;
0539: }
0540: } else {
0541: break;
0542: }
0543: }
0544: }
0545: // make sure the autoCommit status on the con is correct
0546: if (con.getCachedAutoCommit() != autoCommit) {
0547: if (autoCommit) { // do rollback if going from false -> true
0548: con.rollback();
0549: }
0550: con.setAutoCommit(autoCommit);
0551: }
0552: con.updateLastActivityTime();
0553: addToActiveHead(con);
0554: log(0, JdbcLogEvent.POOL_ALLOC, con, highPriority);
0555: allocatedCount++;
0556: return con;
0557: }
0558:
0559: /**
0560: * Test 1 idle connection. If it fails, close it and repeat.
0561: */
0562: public void testIdleConnections() {
0563: for (;;) {
0564: PooledConnection con;
0565: synchronized (this ) {
0566: // don't test if the pool is almost full - this will just
0567: // reduce throughput by making some thread wait for a con
0568: if (activeCount >= (maxActive - reserved - 1))
0569: break;
0570: con = removeFromIdleHead(false);
0571: if (con == null)
0572: break;
0573: ++activeCount;
0574: }
0575: if (validateConnection(con)) {
0576: synchronized (this ) {
0577: addToIdleTail(con);
0578: --activeCount;
0579: }
0580: break;
0581: } else {
0582: destroy(con);
0583: synchronized (this ) {
0584: --activeCount;
0585: }
0586: }
0587: }
0588: }
0589:
0590: /**
0591: * Check the integrity of the double linked with head and tail.
0592: */
0593: private void checkList(PooledConnection tail,
0594: PooledConnection head, int size) {
0595: if (tail == null) {
0596: testTrue(head == null);
0597: return;
0598: }
0599: if (head == null) {
0600: testTrue(tail == null);
0601: return;
0602: }
0603: checkList(tail, size);
0604: checkList(head, size);
0605: testTrue(tail.prev == null);
0606: testTrue(head.next == null);
0607: }
0608:
0609: /**
0610: * Check the integrity of the double linked list containing pc.
0611: */
0612: private void checkList(PooledConnection pc, int size) {
0613: if (pc == null)
0614: return;
0615: int c = -1;
0616: // check links to tail
0617: for (PooledConnection i = pc; i != null; i = i.prev) {
0618: if (i.prev != null)
0619: testTrue(i.prev.next == i);
0620: ++c;
0621: }
0622: // check links to head
0623: for (PooledConnection i = pc; i != null; i = i.next) {
0624: if (i.next != null)
0625: testTrue(i.next.prev == i);
0626: ++c;
0627: }
0628: if (size >= 0) {
0629: testEquals(size, c);
0630: }
0631: }
0632:
0633: private static void testEquals(int a, int b) {
0634: if (a != b) {
0635: throw BindingSupportImpl.getInstance().internal(
0636: "assertion failed: expected " + a + " got " + b);
0637: }
0638: }
0639:
0640: private static void testTrue(boolean t) {
0641: if (!t) {
0642: throw BindingSupportImpl.getInstance().internal(
0643: "assertion failed: expected true");
0644: }
0645: }
0646:
0647: public void returnConnection(Connection con) throws SQLException {
0648: con.close();
0649: }
0650:
0651: /**
0652: * Return a PooledConnection to the pool. This is called by
0653: * PooledConnection when it is closed. This is a NOP if the connection
0654: * has been destroyed.
0655: *
0656: * @see PooledConnection#close
0657: */
0658: public void returnConnection(PooledConnection con) {
0659: if (con.isDestroyed())
0660: return;
0661: if ((testOnReturn || testOnException && con.isNeedsValidation())
0662: && !validateConnection(con)) {
0663: removeFromActiveList(con);
0664: destroy(con);
0665: } else if (maxConAge > 0 && ++con.age >= maxConAge) {
0666: ++expiredCount;
0667: if (pes.isFine()) {
0668: JdbcLogEvent ev = new JdbcLogEvent(0,
0669: JdbcLogEvent.POOL_CON_EXPIRED, Integer
0670: .toString(con.age));
0671: ev.updateTotalMs();
0672: pes.log(ev);
0673: }
0674: removeFromActiveList(con);
0675: destroy(con);
0676: } else {
0677: synchronized (this ) {
0678: removeFromActiveList(con);
0679: addToIdleTail(con);
0680: }
0681: }
0682: log(0, JdbcLogEvent.POOL_RELEASE, con, false);
0683: }
0684:
0685: /**
0686: * Close all idle connections. This method closes idleCount connections.
0687: * If another thread is making new connections at the same time the
0688: * idle list will not be empty on return.
0689: */
0690: public void closeIdleConnections() {
0691: for (int i = idleCount; i > 0; i--) {
0692: PooledConnection con = removeFromIdleHead(false);
0693: if (con == null)
0694: break;
0695: destroy(con);
0696: }
0697: if (cleanupThread != null)
0698: cleanupThread.interrupt();
0699: }
0700:
0701: /**
0702: * Destroy a connection silently ignoring SQLException's.
0703: */
0704: private void destroy(PooledConnection con) {
0705: closedCount++;
0706: con.destroy();
0707: }
0708:
0709: /**
0710: * Close all connections and shutdown the pool.
0711: */
0712: public void destroy() {
0713: closed = true;
0714: if (cleanupThread != null)
0715: cleanupThread.interrupt();
0716: for (;;) {
0717: PooledConnection con = removeFromIdleHead(false);
0718: if (con == null)
0719: break;
0720: destroy(con);
0721: }
0722: // this will cause any threads waiting for connections to get a
0723: // closed exception
0724: synchronized (this ) {
0725: notifyAll();
0726: }
0727: }
0728:
0729: /**
0730: * Close excess idle connections or create new idle connections if less
0731: * than minIdle (but do not exceed maxActive in total).
0732: */
0733: public void checkIdleConnections() throws Exception {
0734: // close excess idle connections
0735: for (; idleCount > maxIdle;) {
0736: PooledConnection con = removeFromIdleHead(false);
0737: if (con == null)
0738: break;
0739: destroy(con);
0740: }
0741: // Start creating a new connection if there is space in the pool for 2
0742: // more. Only add the connection to the pool once created if there is
0743: // space for 1 more.
0744: for (;;) {
0745: if (!needMoreIdleConnections(1))
0746: break;
0747: PooledConnection con = createPooledConnection(retryCount,
0748: retryIntervalMs);
0749: synchronized (this ) {
0750: if (needMoreIdleConnections(0)) {
0751: addToIdleTail(con);
0752: continue;
0753: }
0754: }
0755: destroy(con);
0756: break;
0757: }
0758: }
0759:
0760: private synchronized boolean needMoreIdleConnections(int space) {
0761: return idleCount < minIdle
0762: && idleCount + activeCount < (maxActive - space);
0763: }
0764:
0765: /**
0766: * Close active connections that have been out of the pool for too long.
0767: * This is a NOP if activeTimeout <= 0.
0768: */
0769: public void closeTimedOutConnections() {
0770: if (conTimeout <= 0)
0771: return;
0772: for (;;) {
0773: PooledConnection con;
0774: synchronized (this ) {
0775: if (activeTail == null)
0776: return;
0777: long t = activeTail.getLastActivityTime();
0778: if (t == 0)
0779: return;
0780: int s = (int) ((System.currentTimeMillis() - t) / 1000);
0781: if (s < conTimeout)
0782: return;
0783: con = activeTail;
0784: removeFromActiveList(con);
0785: }
0786: timedOutCount++;
0787: if (pes.isWarning()) {
0788: JdbcPoolEvent event = new JdbcPoolEvent(0,
0789: JdbcLogEvent.POOL_CON_TIMEOUT, false);
0790: event.setConnectionID(System.identityHashCode(con));
0791: update(event);
0792: pes.log(event);
0793: }
0794: destroy(con);
0795: }
0796: }
0797:
0798: /**
0799: * Return the con at the head of the idle list removing it from the list.
0800: * If there is no idle con then null is returned. The con has its idle
0801: * flag cleared.
0802: *
0803: * @param autoCommit An attempt is made to return a connction with
0804: * autoCommit in this state. If this is not possible a connection
0805: * with a different setting may be returned.
0806: */
0807: private synchronized PooledConnection removeFromIdleHead(
0808: boolean autoCommit) {
0809: PooledConnection ans = removeFromIdleHeadImp(autoCommit);
0810: return ans == null ? removeFromIdleHeadImp(!autoCommit) : ans;
0811: }
0812:
0813: private PooledConnection removeFromIdleHeadImp(boolean autoCommit) {
0814: PooledConnection con;
0815: if (autoCommit) {
0816: con = idleHeadAC;
0817: if (con == null)
0818: return null;
0819: idleHeadAC = con.prev;
0820: con.prev = null;
0821: if (idleHeadAC == null) {
0822: idleTailAC = null;
0823: } else {
0824: idleHeadAC.next = null;
0825: }
0826: } else {
0827: con = idleHead;
0828: if (con == null)
0829: return null;
0830: idleHead = con.prev;
0831: con.prev = null;
0832: if (idleHead == null) {
0833: idleTail = null;
0834: } else {
0835: idleHead.next = null;
0836: }
0837: }
0838: --idleCount;
0839: con.idle = false;
0840: if (Debug.DEBUG) {
0841: checkList(idleTail, idleHead, -1);
0842: checkList(idleTailAC, idleHeadAC, -1);
0843: }
0844: return con;
0845: }
0846:
0847: /**
0848: * Add con to the tail of the idle list. The con has its idle flag set.
0849: * This will notify any blocked threads so they can get the newly idle
0850: * connection.
0851: */
0852: private synchronized void addToIdleTail(PooledConnection con) {
0853: if (Debug.DEBUG) {
0854: if (con.prev != null || con.next != null) {
0855: throw BindingSupportImpl.getInstance().internal(
0856: "con belongs to a list");
0857: }
0858: }
0859: con.idle = true;
0860: if (con.getCachedAutoCommit()) {
0861: if (idleTailAC == null) {
0862: idleHeadAC = idleTailAC = con;
0863: } else {
0864: con.next = idleTailAC;
0865: idleTailAC.prev = con;
0866: idleTailAC = con;
0867: }
0868: } else {
0869: if (idleTail == null) {
0870: idleHead = idleTail = con;
0871: } else {
0872: con.next = idleTail;
0873: idleTail.prev = con;
0874: idleTail = con;
0875: }
0876: }
0877: ++idleCount;
0878: if (Debug.DEBUG) {
0879: checkList(idleTail, idleHead, -1);
0880: checkList(idleTailAC, idleHeadAC, -1);
0881: }
0882: notify();
0883: }
0884:
0885: /**
0886: * Add con to the head of the active list. Note that this does not
0887: * bump up activeCount.
0888: */
0889: private synchronized void addToActiveHead(PooledConnection con) {
0890: if (Debug.DEBUG) {
0891: if (con.prev != null || con.next != null) {
0892: throw BindingSupportImpl.getInstance().internal(
0893: "con belongs to a list");
0894: }
0895: }
0896: if (activeHead == null) {
0897: activeHead = activeTail = con;
0898: } else {
0899: con.prev = activeHead;
0900: activeHead.next = con;
0901: activeHead = con;
0902: }
0903: if (Debug.DEBUG)
0904: checkList(activeTail, activeHead, -1);
0905: }
0906:
0907: /**
0908: * Remove con from the active list.
0909: */
0910: private synchronized void removeFromActiveList(PooledConnection con) {
0911: if (con.prev != null) {
0912: con.prev.next = con.next;
0913: } else {
0914: activeTail = con.next;
0915: }
0916: if (con.next != null) {
0917: con.next.prev = con.prev;
0918: } else {
0919: activeHead = con.prev;
0920: }
0921: con.prev = con.next = null;
0922: --activeCount;
0923: if (Debug.DEBUG)
0924: checkList(activeTail, activeHead, -1);
0925: }
0926:
0927: private JdbcPoolEvent log(long txId, int type,
0928: PooledConnection con, boolean highPriority) {
0929: if (pes.isFine()) {
0930: JdbcPoolEvent event = new JdbcPoolEvent(txId, type,
0931: highPriority);
0932: event.setAutoCommit(con.getCachedAutoCommit());
0933: event.setConnectionID(System.identityHashCode(con));
0934: update(event);
0935: pes.log(event);
0936: return event;
0937: }
0938: return null;
0939: }
0940:
0941: private void update(JdbcPoolEvent ev) {
0942: ev.update(maxActive, activeCount, maxIdle, idleCount);
0943: }
0944:
0945: private Connection createRealCon() {
0946: JdbcConnectionEvent ev = null;
0947: if (pes.isFine()) {
0948: ev = new JdbcConnectionEvent(0, null, url,
0949: JdbcConnectionEvent.CON_OPEN);
0950: pes.log(ev);
0951: }
0952: Connection realCon = null;
0953: try {
0954: realCon = jdbcDriver.connect(url, props);
0955: if (realCon == null) {
0956: throw BindingSupportImpl.getInstance().fatalDatastore(
0957: formatConnectionErr("Unable to connect to "
0958: + url));
0959: }
0960: } catch (SQLException x) {
0961: RuntimeException e = sqlDriver.mapException(x,
0962: formatConnectionErr("Unable to connect to " + url
0963: + ":\n" + x), false);
0964: if (ev != null) {
0965: ev.setErrorMsg(e);
0966: }
0967: throw e;
0968: } catch (RuntimeException x) {
0969: if (ev != null) {
0970: ev.setErrorMsg(x);
0971: }
0972: throw x;
0973: } finally {
0974: if (ev != null) {
0975: ev.updateTotalMs();
0976: }
0977: }
0978: createdCount++;
0979: return realCon;
0980: }
0981:
0982: private String formatConnectionErr(String msg) {
0983: StringBuffer s = new StringBuffer();
0984: s.append(msg);
0985: s.append("\nJDBC Driver: " + jdbcDriver.getClass().getName());
0986: ArrayList a = new ArrayList(props.keySet());
0987: Collections.sort(a);
0988: for (Iterator i = a.iterator(); i.hasNext();) {
0989: String p = (String) i.next();
0990: Object v = "password".equals(p) ? "(hidden)" : props.get(p);
0991: s.append('\n');
0992: s.append(p);
0993: s.append('=');
0994: s.append(v);
0995: }
0996: return s.toString();
0997: }
0998:
0999: /**
1000: * Create an initialize a PooledConnection. This will run the initSQL
1001: * and do setAutoCommit(false). It will not add it to the idle or active
1002: * lists.
1003: */
1004: private PooledConnection createPooledConnection(int retryCount,
1005: int retryIntervalMs) throws SQLException {
1006: Connection realCon = createRealConWithRetry(retryCount,
1007: retryIntervalMs);
1008: PooledConnection pooledCon = new PooledConnection(
1009: JDBCConnectionPool.this , realCon, pes,
1010: !jdbcDisablePsCache, psCacheMax);
1011: boolean ok = false;
1012: try {
1013: initConnection(pooledCon);
1014: pooledCon.setAutoCommit(false);
1015: if (isolationLevel != 0) {
1016: pooledCon.setTransactionIsolation(isolationLevel);
1017: }
1018: ok = true;
1019: } finally {
1020: if (!ok && realCon != null) {
1021: try {
1022: pooledCon.closeRealConnection();
1023: } catch (SQLException x) {
1024: // ignore
1025: }
1026: }
1027: }
1028: return pooledCon;
1029: }
1030:
1031: /**
1032: * Initialize the connection with the initSQL (if any).
1033: */
1034: private void initConnection(Connection con) {
1035: if (initSQL == null)
1036: return;
1037: Statement stat = null;
1038: try {
1039: stat = con.createStatement();
1040: stat.execute(initSQL);
1041: } catch (SQLException e) {
1042: throw sqlDriver.mapException(e,
1043: "Error executing initSQL on new Connection: " + e
1044: + "\n" + initSQL, true);
1045: } finally {
1046: if (stat != null) {
1047: try {
1048: stat.close();
1049: } catch (SQLException e) {
1050: // ignore
1051: }
1052: }
1053: }
1054: if (commitAfterInitSQL) {
1055: try {
1056: con.commit();
1057: } catch (SQLException e) {
1058: throw sqlDriver.mapException(e,
1059: "Error doing commit after executing initSQL on new Connection: "
1060: + e + "\n" + initSQL, true);
1061: }
1062: }
1063: }
1064:
1065: /**
1066: * Check that the connection is open and if so validate the connection with
1067: * validateSQL (if any). Returns true if the connection is ok.
1068: */
1069: private boolean validateConnection(PooledConnection con) {
1070: validatedCount++;
1071: boolean ans = validateConnectionImp(con);
1072: if (!ans)
1073: badCount++;
1074: return ans;
1075: }
1076:
1077: private boolean validateConnectionImp(PooledConnection con) {
1078: try {
1079: if (con.isClosed())
1080: return false;
1081: if (validateSQL != null) {
1082: PreparedStatement ps = null;
1083: ResultSet rs = null;
1084: try {
1085: ps = con.prepareStatement(validateSQL);
1086: rs = ps.executeQuery();
1087: if (!rs.next()) {
1088: if (pes.isWarning()) {
1089: JdbcLogEvent ev = new JdbcLogEvent(0,
1090: JdbcLogEvent.POOL_BAD_CON,
1091: "No row returned");
1092: ev.updateTotalMs();
1093: pes.log(ev);
1094: }
1095: return false;
1096: }
1097: } finally {
1098: if (rs != null)
1099: rs.close();
1100: if (ps != null)
1101: ps.close();
1102: }
1103: if (!con.getCachedAutoCommit()) {
1104: con.rollback();
1105: }
1106: }
1107: return true;
1108: } catch (SQLException e) {
1109: if (pes.isWarning()) {
1110: JdbcLogEvent ev = new JdbcLogEvent(0,
1111: JdbcLogEvent.POOL_BAD_CON, e.toString());
1112: ev.updateTotalMs();
1113: pes.log(ev);
1114: }
1115: return false;
1116: }
1117: }
1118:
1119: /**
1120: * Perform maintenance operations on the pool at periodic intervals.
1121: */
1122: public void run() {
1123:
1124: for (;;) {
1125:
1126: try {
1127: Thread.sleep(5000);
1128: } catch (InterruptedException e) {
1129: // ignore
1130: }
1131:
1132: if (closed)
1133: break;
1134:
1135: if (duration(timeLastTest) >= testInterval) {
1136: timeLastTest = System.currentTimeMillis();
1137: if (testWhenIdle)
1138: testIdleConnections();
1139: if (conTimeout > 0)
1140: closeTimedOutConnections();
1141: }
1142:
1143: try {
1144: checkIdleConnections();
1145: } catch (Exception e) {
1146: // ignore
1147: }
1148: }
1149: }
1150:
1151: private long duration(long start) {
1152: return (System.currentTimeMillis() - start) / 1000;
1153: }
1154:
1155: /**
1156: * Return our name and status.
1157: */
1158: public String toString() {
1159: PoolStatus ps = new PoolStatus(url);
1160: fillStatus(ps);
1161: return ps.toString();
1162: }
1163:
1164: /**
1165: * Add all BaseMetric's for this store to the List.
1166: */
1167: public void addMetrics(List list) {
1168: list.add(metricActive = new BaseMetric("JDBCPoolActive",
1169: "Pool Active", CAT_POOL,
1170: "Number of active JDBC connections in pool", 0,
1171: Metric.CALC_AVERAGE));
1172: list.add(metricMaxActive = new BaseMetric("JDBCPoolMaxActive",
1173: "Pool Max Active", CAT_POOL,
1174: "Max number of JDBC connections allowed in pool", 0,
1175: Metric.CALC_AVERAGE));
1176: list.add(metricIdle = new BaseMetric("JDBCPoolIdle",
1177: "Pool Idle", CAT_POOL,
1178: "Number of idle JDBC connections in pool", 0,
1179: Metric.CALC_AVERAGE));
1180: list
1181: .add(metricWait = new BaseMetric(
1182: "JDBCPoolWait",
1183: "Pool Wait",
1184: CAT_POOL,
1185: "Number of times that a caller had to wait for a connection",
1186: 0, Metric.CALC_DELTA));
1187: list
1188: .add(metricFull = new BaseMetric(
1189: "JDBCPoolFull",
1190: "Pool Full",
1191: CAT_POOL,
1192: "Number of times that the pool was full and a connection was needed",
1193: 0, Metric.CALC_DELTA));
1194: list
1195: .add(metricTimedOut = new BaseMetric(
1196: "JDBCConTimedOut",
1197: "Con Timed Out",
1198: CAT_POOL,
1199: "Number of active JDBC connections timed out and closed",
1200: 0, Metric.CALC_DELTA));
1201: list
1202: .add(metricExpired = new BaseMetric(
1203: "JDBCConExpired",
1204: "Con Expired",
1205: CAT_POOL,
1206: "Number of JDBC connections closed due to their age reaching the maximum lifespan",
1207: 0, Metric.CALC_DELTA));
1208: list
1209: .add(metricBad = new BaseMetric(
1210: "JDBCConBad",
1211: "Con Bad",
1212: CAT_POOL,
1213: "Number of JDBC connections that failed validation test",
1214: 0, Metric.CALC_DELTA));
1215: list.add(metricCreated = new BaseMetric("JDBCConCreated",
1216: "Con Created", CAT_POOL,
1217: "Number of JDBC connections created", 0,
1218: Metric.CALC_DELTA));
1219: list.add(metricClosed = new BaseMetric("JDBCConClosed",
1220: "Con Closed", CAT_POOL,
1221: "Number of JDBC connections closed", 0,
1222: Metric.CALC_DELTA));
1223: list.add(metricAllocated = new BaseMetric("JDBCConAllocated",
1224: "Con Allocated", CAT_POOL,
1225: "Number of JDBC connections given out by the pool", 3,
1226: Metric.CALC_DELTA_PER_SECOND));
1227: list.add(metricValidated = new BaseMetric("JDBCConValidated",
1228: "Con Validated", CAT_POOL,
1229: "Number of JDBC connections tested by the pool", 0,
1230: Metric.CALC_DELTA));
1231: list.add(new PercentageMetric("JdbcPoolPercentFull",
1232: "Pool % Full ", CAT_POOL,
1233: "Active connections as a percentage of the maximum",
1234: metricActive, metricMaxActive));
1235: }
1236:
1237: /**
1238: * Get values for our metrics.
1239: */
1240: public void sampleMetrics(int[][] buf, int pos) {
1241: buf[metricActive.getIndex()][pos] = activeCount;
1242: buf[metricMaxActive.getIndex()][pos] = maxActive;
1243: buf[metricIdle.getIndex()][pos] = idleCount;
1244: buf[metricWait.getIndex()][pos] = waitCount;
1245: buf[metricFull.getIndex()][pos] = fullCount;
1246: buf[metricTimedOut.getIndex()][pos] = timedOutCount;
1247: buf[metricExpired.getIndex()][pos] = expiredCount;
1248: buf[metricBad.getIndex()][pos] = badCount;
1249: buf[metricAllocated.getIndex()][pos] = allocatedCount;
1250: buf[metricValidated.getIndex()][pos] = validatedCount;
1251: buf[metricCreated.getIndex()][pos] = createdCount;
1252: buf[metricClosed.getIndex()][pos] = closedCount;
1253: }
1254:
1255: }
|