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 java.sql.Connection;
009: import java.sql.SQLException;
010:
011: import org.apache.commons.logging.Log;
012: import org.apache.commons.logging.LogFactory;
013:
014: /**
015: * Responsible for prototyping connections for all pools
016: * @version $Revision: 1.14 $, $Date: 2006/03/23 11:44:57 $
017: * @author bill
018: * @author $Author: billhorsman $ (current maintainer)
019: * @since Proxool 0.8
020: */
021: public class Prototyper {
022:
023: private ConnectionPool connectionPool;
024:
025: private Log log = LogFactory.getLog(Prototyper.class);
026:
027: private long connectionCount;
028:
029: private final Object lock = new Integer(1);
030:
031: private boolean sweepNeeded = true;
032:
033: /** This allows us to have a unique ID for each connection */
034: private long nextConnectionId = 1;
035:
036: /**
037: * Calling {@link #cancel} will set this to true and stop all
038: * current prototyping immediately
039: */
040: private boolean cancel;
041:
042: /**
043: * The number of connections currently being made (actually in
044: * progress)
045: */
046: private int connectionsBeingMade;
047:
048: /**
049: * The builder that will create *real* connections for us.
050: * Currently initialized to the default implementation until we fully
051: * support pluggable ConnectionBuilders
052: */
053: private ConnectionBuilderIF connectionBuilder = new DefaultConnectionBuilder();
054:
055: public Prototyper(ConnectionPool connectionPool) {
056: this .connectionPool = connectionPool;
057: this .log = connectionPool.getLog();
058: }
059:
060: protected boolean isSweepNeeded() {
061: return sweepNeeded;
062: }
063:
064: protected void triggerSweep() {
065: sweepNeeded = true;
066: }
067:
068: /**
069: * Trigger prototyping immediately
070: * @return true if something was prototyped
071: */
072: protected boolean sweep() {
073:
074: boolean somethingDone = false;
075: try {
076:
077: while (!cancel && connectionPool.isConnectionPoolUp()) {
078:
079: // if (log.isDebugEnabled()) {
080: // log.debug("Prototyping");
081: // }
082:
083: String reason = null;
084: if (connectionCount >= getDefinition()
085: .getMaximumConnectionCount()) {
086: // We don't want to make any more that the maximum
087: break;
088: } else if (connectionCount < getDefinition()
089: .getMinimumConnectionCount()) {
090: reason = "to achieve minimum of "
091: + getDefinition()
092: .getMinimumConnectionCount();
093: } else if (connectionPool.getAvailableConnectionCount() < getDefinition()
094: .getPrototypeCount()) {
095: reason = "to keep "
096: + getDefinition().getPrototypeCount()
097: + " available";
098: } else {
099: // Nothing to do
100: break;
101: }
102:
103: ProxyConnectionIF freshlyBuiltProxyConnection = null;
104: try {
105: // If it has been shutdown then we should just stop now.
106: if (!connectionPool.isConnectionPoolUp()) {
107: break;
108: }
109: freshlyBuiltProxyConnection = buildConnection(
110: ConnectionInfoIF.STATUS_AVAILABLE, reason);
111: somethingDone = true;
112: } catch (Throwable e) {
113: log.error("Prototype", e);
114: // If there's been an exception, perhaps we should stop
115: // prototyping for a while. Otherwise if the database
116: // has problems we end up trying the connection every 2ms
117: // or so and then the log grows pretty fast.
118: break;
119: // Don't wory, we'll start again the next time the
120: // housekeeping thread runs.
121: }
122: if (freshlyBuiltProxyConnection == null) {
123: // That's strange. No double the buildConnection() method logged the
124: // error, but we should have build a connection here.
125: }
126: }
127: } catch (Throwable t) {
128: log.error("Unexpected error", t);
129: }
130:
131: return somethingDone;
132: }
133:
134: /**
135: * Build a new connection
136: * @param status the initial status it will be created as (this allows us
137: * to create it as {@link ConnectionInfoIF#STATUS_ACTIVE ACTIVE} and avoid
138: * another thread grabbing it before we can)
139: * @param creator for log audit
140: * @return the new connection
141: */
142: protected ProxyConnection buildConnection(int status, String creator)
143: throws SQLException, ProxoolException {
144:
145: long id = 0;
146: synchronized (lock) {
147:
148: // Check that we are allowed to make another connection
149: if (connectionCount >= getDefinition()
150: .getMaximumConnectionCount()) {
151: throw new ProxoolException("ConnectionCount is "
152: + connectionCount
153: + ". Maximum connection count of "
154: + getDefinition().getMaximumConnectionCount()
155: + " cannot be exceeded.");
156: }
157:
158: checkSimultaneousBuildThrottle();
159:
160: connectionsBeingMade++;
161: connectionCount++;
162: id = nextConnectionId++;
163: }
164:
165: ProxyConnection proxyConnection = null;
166: Connection realConnection = null;
167:
168: try {
169: // get a new *real* connection
170: final ConnectionPoolDefinition definition = connectionPool
171: .getDefinition();
172: realConnection = connectionBuilder
173: .buildConnection(definition);
174:
175: // build a proxy around it
176:
177: //TODO BRE: the connection builder has made a new connection using the information
178: // supplied in the ConnectionPoolDefinition. That's where it got the URL...
179: // The ProxyConnection is passed the ConnectionPoolDefinition as well so it doesn't
180: // need the url in its constructor...
181: String url = definition.getUrl();
182: proxyConnection = new ProxyConnection(realConnection, id,
183: url, connectionPool, definition, status);
184:
185: try {
186: connectionPool.onBirth(realConnection);
187: } catch (Exception e) {
188: log.error("Problem during onBirth (ignored)", e);
189: }
190:
191: //TODO BRE: the actual pool of connections is maintained by the ConnectionPool. I'm not
192: // very happy with the idea of letting the Prototyper add the newly build connection
193: // into the pool itself. It should rather be the pool that does it, after it got a new
194: // connection from the Prototyper. This would clearly separate the responsibilities:
195: // ConnectionPool maintains the pool and its integrity, the Prototyper creates new
196: // connections when instructed.
197: boolean added = connectionPool
198: .addProxyConnection(proxyConnection);
199: if (log.isDebugEnabled()) {
200: StringBuffer out = new StringBuffer(connectionPool
201: .displayStatistics());
202: out.append(" - Connection #");
203: out.append(proxyConnection.getId());
204: if (getDefinition().isVerbose()) {
205: out.append(" (");
206: out.append(Integer.toHexString(proxyConnection
207: .hashCode()));
208: out.append(")");
209: }
210: out.append(" created ");
211: out.append(creator);
212: out.append(" = ");
213: out.append(ConnectionPool
214: .getStatusDescription(proxyConnection
215: .getStatus()));
216: if (getDefinition().isVerbose()) {
217: out.append(" -> ");
218: out.append(getDefinition().getUrl());
219: out.append(" (");
220: out.append(Integer.toHexString(proxyConnection
221: .getConnection().hashCode()));
222: out.append(") by thread ");
223: out.append(Thread.currentThread().getName());
224: }
225: log.debug(out);
226: if (!added) {
227: out = new StringBuffer(connectionPool
228: .displayStatistics());
229: out.append(" - Connection #");
230: out.append(proxyConnection.getId());
231: out
232: .append(" has been discarded immediately because the definition it was built with is out of date");
233: log.debug(out);
234: }
235: }
236: if (!added) {
237: proxyConnection.reallyClose();
238: }
239:
240: } catch (SQLException e) {
241: // log.error(displayStatistics() + " - Couldn't initialise connection #" + proxyConnection.getId() + ": " + e);
242: throw e;
243: } catch (RuntimeException e) {
244: if (log.isDebugEnabled()) {
245: log.debug("Prototyping problem", e);
246: }
247: throw e;
248: } catch (Throwable t) {
249: if (log.isDebugEnabled()) {
250: log.debug("Prototyping problem", t);
251: }
252: throw new ProxoolException(
253: "Unexpected prototyping problem", t);
254: } finally {
255: synchronized (lock) {
256: if (proxyConnection == null) {
257: // If there has been an exception then we won't be using this one and
258: // we need to decrement the counter
259: connectionCount--;
260: }
261: connectionsBeingMade--;
262: }
263:
264: }
265:
266: return proxyConnection;
267: }
268:
269: /**
270: * This needs to be called _everytime_ a connection is removed.
271: */
272: protected void connectionRemoved() {
273: connectionCount--;
274: }
275:
276: /**
277: * Checks whether we are currently already building too many connections
278: * @throws SQLException if the throttle has been reached
279: */
280: protected void checkSimultaneousBuildThrottle() throws SQLException {
281: // Check we aren't making too many simultaneously
282: if (connectionsBeingMade > getDefinition()
283: .getSimultaneousBuildThrottle()) {
284: throw new SQLException(
285: "We are already in the process of making "
286: + connectionsBeingMade
287: + " connections and the number of simultaneous builds has been throttled to "
288: + getDefinition()
289: .getSimultaneousBuildThrottle());
290: }
291: }
292:
293: /**
294: * The total number of connections, including those being built right
295: * now
296: * @return connectionCount;
297: */
298: public long getConnectionCount() {
299: return connectionCount;
300: }
301:
302: /**
303: * Utility method
304: * @return definition
305: */
306: private ConnectionPoolDefinitionIF getDefinition() {
307: return connectionPool.getDefinition();
308: }
309:
310: /**
311: * Cancel all current prototyping
312: */
313: public void cancel() {
314: cancel = true;
315: }
316:
317: /**
318: * The alias of the pool we are prototyping for
319: * @return alias
320: */
321: public String getAlias() {
322: return getDefinition().getAlias();
323: }
324:
325: /**
326: * Give a quick answer to whether we should attempt to build a connection. This can be quicker
327: * if we are massively overloaded rather than cycling through each connection in the pool to
328: * see if it's free
329: * @throws SQLException if it is a waste of time even trying to get a connaction. Just because this method
330: * doesn't throw an exception it doesn't guarantee that one will be available. There is a slight
331: * risk that we might tell the client to give up when a connection could become available in the next few
332: * milliseconds but our policy is to refuse connections quickly when overloaded.
333: */
334: public void quickRefuse() throws SQLException {
335: if (connectionCount >= getDefinition()
336: .getMaximumConnectionCount()
337: && connectionPool.getAvailableConnectionCount() < 1) {
338: throw new SQLException(
339: "Couldn't get connection because we are at maximum connection count ("
340: + connectionCount
341: + "/"
342: + getDefinition()
343: .getMaximumConnectionCount()
344: + ") and there are none available");
345: }
346: }
347: }
348:
349: /*
350: Revision history:
351: $Log: Prototyper.java,v $
352: Revision 1.14 2006/03/23 11:44:57 billhorsman
353: More information when quickly refusing
354:
355: Revision 1.13 2006/01/18 14:40:01 billhorsman
356: Unbundled Jakarta's Commons Logging.
357:
358: Revision 1.12 2006/01/16 23:10:41 billhorsman
359: Doh. /Do/ decrement connectionCount if we didn't make one.
360:
361: Revision 1.11 2005/10/02 12:36:30 billhorsman
362: New quickRefuse() method checks whether we are overloaded without checking whole pool for an available connection.
363:
364: Revision 1.10 2005/05/04 16:27:54 billhorsman
365: Check to see whether a new connection was really added to the pool.
366:
367: Revision 1.9 2004/03/25 22:02:15 brenuart
368: First step towards pluggable ConnectionBuilderIF & ConnectionValidatorIF.
369: Include some minor refactoring that lead to deprecation of some PrototyperController methods.
370:
371: Revision 1.7 2003/09/30 18:39:08 billhorsman
372: New test-before-use, test-after-use and fatal-sql-exception-wrapper-class properties.
373:
374: Revision 1.6 2003/09/11 10:44:54 billhorsman
375: Catch throwable not just exception during creation of connection
376: (this will catch ClassNotFoundError too)
377:
378: Revision 1.5 2003/04/10 08:22:33 billhorsman
379: removed some very frequent debug
380:
381: Revision 1.4 2003/03/11 14:51:52 billhorsman
382: more concurrency fixes relating to snapshots
383:
384: Revision 1.3 2003/03/10 23:43:10 billhorsman
385: reapplied checkstyle that i'd inadvertently let
386: IntelliJ change...
387:
388: Revision 1.2 2003/03/10 15:26:47 billhorsman
389: refactoringn of concurrency stuff (and some import
390: optimisation)
391:
392: Revision 1.1 2003/03/05 18:42:33 billhorsman
393: big refactor of prototyping and house keeping to
394: drastically reduce the number of threads when using
395: many pools
396:
397: */
|