001: /*
002: * This software is released under a licence similar to the Apache Software Licence.
003: * See org.logicalcobwebs.proxool.package.html for details.
004: * The latest version is available at http://proxool.sourceforge.net
005: */
006: package org.logicalcobwebs.proxool;
007:
008: import org.apache.commons.logging.Log;
009: import org.apache.commons.logging.LogFactory;
010:
011: import java.sql.Connection;
012: import java.sql.Statement;
013:
014: /**
015: * Responisble for house keeping one pool
016: *
017: * @version $Revision: 1.5 $, $Date: 2006/01/18 14:40:01 $
018: * @author bill
019: * @author $Author: billhorsman $ (current maintainer)
020: * @since Proxool 0.8
021: */
022: class HouseKeeper {
023:
024: private static final Log LOG = LogFactory.getLog(HouseKeeper.class);
025:
026: private ConnectionPool connectionPool;
027:
028: private long timeLastSwept;
029:
030: public HouseKeeper(ConnectionPool connectionPool) {
031: this .connectionPool = connectionPool;
032: }
033:
034: protected void sweep() throws ProxoolException {
035: ConnectionPoolDefinitionIF definition = connectionPool
036: .getDefinition();
037: Log log = connectionPool.getLog();
038: Statement testStatement = null;
039: try {
040:
041: connectionPool.acquirePrimaryReadLock();
042:
043: // Right, now we know we're the right thread then we can carry on house keeping
044: Connection connection = null;
045: ProxyConnectionIF proxyConnection = null;
046:
047: int recentlyStartedActiveConnectionCountTemp = 0;
048:
049: // sanity check
050: int[] verifiedConnectionCountByState = new int[4];
051:
052: ProxyConnectionIF[] proxyConnections = connectionPool
053: .getProxyConnections();
054: for (int i = 0; i < proxyConnections.length; i++) {
055: proxyConnection = proxyConnections[i];
056: connection = proxyConnection.getConnection();
057:
058: if (!connectionPool.isConnectionPoolUp()) {
059: break;
060: }
061:
062: // First lets check whether the connection still works. We should only validate
063: // connections that are not is use! SetOffline only succeeds if the connection
064: // is available.
065: if (proxyConnection.setStatus(
066: ProxyConnectionIF.STATUS_AVAILABLE,
067: ProxyConnectionIF.STATUS_OFFLINE)) {
068: try {
069: testStatement = connection.createStatement();
070:
071: // Some DBs return an object even if DB is shut down
072: if (proxyConnection.isReallyClosed()) {
073: proxyConnection.setStatus(
074: ProxyConnectionIF.STATUS_OFFLINE,
075: ProxyConnectionIF.STATUS_NULL);
076: connectionPool.removeProxyConnection(
077: proxyConnection,
078: "it appears to be closed",
079: ConnectionPool.FORCE_EXPIRY, true);
080: }
081:
082: String sql = definition
083: .getHouseKeepingTestSql();
084: if (sql != null && sql.length() > 0) {
085: // A Test Statement has been provided. Execute it!
086: boolean testResult = false;
087: try {
088: testResult = testStatement.execute(sql);
089: } finally {
090: if (log.isDebugEnabled()
091: && definition.isVerbose()) {
092: log.debug(connectionPool
093: .displayStatistics()
094: + " - Testing connection "
095: + proxyConnection.getId()
096: + (testResult ? ": True"
097: : ": False"));
098: }
099: }
100: }
101:
102: proxyConnection.setStatus(
103: ProxyConnectionIF.STATUS_OFFLINE,
104: ProxyConnectionIF.STATUS_AVAILABLE);
105: } catch (Throwable e) {
106: // There is a problem with this connection. Let's remove it!
107: proxyConnection.setStatus(
108: ProxyConnectionIF.STATUS_OFFLINE,
109: ProxyConnectionIF.STATUS_NULL);
110: connectionPool.removeProxyConnection(
111: proxyConnection, "it has problems: "
112: + e,
113: ConnectionPool.REQUEST_EXPIRY, true);
114: } finally {
115: try {
116: testStatement.close();
117: } catch (Throwable t) {
118: // Never mind.
119: }
120: }
121: } // END if (poolableConnection.setOffline())
122: // Now to check whether the connection is due for expiry
123: if (proxyConnection.getAge() > definition
124: .getMaximumConnectionLifetime()) {
125: final String reason = "age is "
126: + proxyConnection.getAge() + "ms";
127: // Check whether we can make it offline
128: if (proxyConnection.setStatus(
129: ProxyConnectionIF.STATUS_AVAILABLE,
130: ProxyConnectionIF.STATUS_OFFLINE)) {
131: if (proxyConnection.setStatus(
132: ProxyConnectionIF.STATUS_OFFLINE,
133: ProxyConnectionIF.STATUS_NULL)) {
134: // It is. Expire it now .
135: connectionPool.expireProxyConnection(
136: proxyConnection, reason,
137: ConnectionPool.REQUEST_EXPIRY);
138: }
139: } else {
140: // Oh no, it's in use. Never mind, we'll mark it for expiry
141: // next time it is available. This will happen in the
142: // putConnection() method.
143: proxyConnection.markForExpiry(reason);
144: if (log.isDebugEnabled()) {
145: log
146: .debug(connectionPool
147: .displayStatistics()
148: + " - #"
149: + FormatHelper
150: .formatMediumNumber(proxyConnection
151: .getId())
152: + " marked for expiry.");
153: }
154: } // END if (poolableConnection.setOffline())
155: } // END if (poolableConnection.getAge() > maximumConnectionLifetime)
156:
157: // Now let's see if this connection has been active for a
158: // suspiciously long time.
159: if (proxyConnection.isActive()) {
160:
161: long activeTime = System.currentTimeMillis()
162: - proxyConnection.getTimeLastStartActive();
163:
164: if (activeTime < definition
165: .getRecentlyStartedThreshold()) {
166:
167: // This connection hasn't been active for all that long
168: // after all. And as long as we have at least one
169: // connection that is "actively active" then we don't
170: // consider the pool to be down.
171: recentlyStartedActiveConnectionCountTemp++;
172: }
173:
174: if (activeTime > definition.getMaximumActiveTime()) {
175:
176: // This connection has been active for way too long. We're
177: // going to kill it :)
178: connectionPool.removeProxyConnection(
179: proxyConnection,
180: "it has been active for too long",
181: ConnectionPool.FORCE_EXPIRY, true);
182: String lastSqlCallMsg;
183: if (proxyConnection.getLastSqlCall() != null) {
184: lastSqlCallMsg = ", and the last SQL it performed is '"
185: + proxyConnection.getLastSqlCall()
186: + "'.";
187: } else if (!proxyConnection.getDefinition()
188: .isTrace()) {
189: lastSqlCallMsg = ", but the last SQL it performed is unknown because the trace property is not enabled.";
190: } else {
191: lastSqlCallMsg = ", but the last SQL it performed is unknown.";
192: }
193: log
194: .warn("#"
195: + FormatHelper
196: .formatMediumNumber(proxyConnection
197: .getId())
198: + " was active for "
199: + activeTime
200: + " milliseconds and has been removed automaticaly. The Thread responsible was named '"
201: + proxyConnection
202: .getRequester() + "'"
203: + lastSqlCallMsg);
204:
205: }
206:
207: }
208:
209: // What have we got?
210: verifiedConnectionCountByState[proxyConnection
211: .getStatus()]++;
212:
213: }
214:
215: calculateUpState(recentlyStartedActiveConnectionCountTemp);
216: } catch (Throwable e) {
217: // We don't want the housekeeping thread to fall over!
218: log.error("Housekeeping log.error( :", e);
219: } finally {
220: connectionPool.releasePrimaryReadLock();
221: timeLastSwept = System.currentTimeMillis();
222: if (definition.isVerbose()) {
223: if (log.isDebugEnabled()) {
224: log.debug(connectionPool.displayStatistics()
225: + " - House keeping triggerSweep done");
226: }
227: }
228: }
229:
230: PrototyperController.triggerSweep(definition.getAlias());
231:
232: }
233:
234: /**
235: * Get the time since the last sweep was completed
236: * @return timeSinceLastSweep (milliseconds)
237: */
238: private long getTimeSinceLastSweep() {
239: return System.currentTimeMillis() - timeLastSwept;
240: }
241:
242: /**
243: * Should we sleep
244: * @return true if the time since the last sweep was completed is greater
245: * than the {@link ConnectionPoolDefinitionIF#getHouseKeepingSleepTime houseKeepingSleepTime}
246: * property.
247: */
248: protected boolean isSweepDue() {
249: if (connectionPool.isConnectionPoolUp()) {
250: return (getTimeSinceLastSweep() > connectionPool
251: .getDefinition().getHouseKeepingSleepTime());
252: } else {
253: LOG
254: .warn("House keeper is still being asked to sweep despite the connection pool being down");
255: return false;
256: }
257: }
258:
259: private void calculateUpState(
260: int recentlyStartedActiveConnectionCount) {
261:
262: try {
263:
264: int calculatedUpState = StateListenerIF.STATE_QUIET;
265:
266: /* We're up if the last time we tried to make a connection it
267: * was successful
268: */
269:
270: /* I've changed the way we do this. Just because we failed to create
271: * a connection doesn't mean we're down. As long as we have some
272: * available connections, or the active ones we have aren't locked
273: * up then we should be able to struggle on. The last thing we want
274: * to do is say we're down when we're not!
275: */
276:
277: // if (this.lastCreateWasSuccessful) {
278: final int availableConnectionCount = connectionPool
279: .getAvailableConnectionCount();
280: if (availableConnectionCount > 0
281: || recentlyStartedActiveConnectionCount > 0) {
282:
283: /* Defintion of overloaded is that we refused a connection
284: * (because we were too busy) within the last minute.
285: */
286:
287: if (connectionPool.getTimeOfLastRefusal() > (System
288: .currentTimeMillis() - connectionPool
289: .getDefinition()
290: .getOverloadWithoutRefusalLifetime())) {
291: calculatedUpState = StateListenerIF.STATE_OVERLOADED;
292: } else if (connectionPool.getActiveConnectionCount() > 0) {
293: /* Are we doing anything at all?
294: */
295: calculatedUpState = StateListenerIF.STATE_BUSY;
296: }
297:
298: } else {
299: calculatedUpState = StateListenerIF.STATE_DOWN;
300: }
301:
302: connectionPool.setUpState(calculatedUpState);
303:
304: } catch (Exception e) {
305: LOG.error(e);
306: }
307: }
308:
309: /**
310: * Identifies the pool we are sweeping
311: * @return alias
312: */
313: protected String getAlias() {
314: return connectionPool.getDefinition().getAlias();
315: }
316:
317: }
318:
319: /*
320: Revision history:
321: $Log: HouseKeeper.java,v $
322: Revision 1.5 2006/01/18 14:40:01 billhorsman
323: Unbundled Jakarta's Commons Logging.
324:
325: Revision 1.4 2005/10/02 12:35:06 billhorsman
326: Improve message when closing a connection that has been active for too long
327:
328: Revision 1.3 2003/09/11 23:57:48 billhorsman
329: Test SQL now traps Throwable, not just SQLException.
330:
331: Revision 1.2 2003/03/10 15:26:46 billhorsman
332: refactoringn of concurrency stuff (and some import
333: optimisation)
334:
335: Revision 1.1 2003/03/05 18:42:33 billhorsman
336: big refactor of prototyping and house keeping to
337: drastically reduce the number of threads when using
338: many pools
339:
340: */
|