001: /*
002: * $Id: ConnectionPool.java,v 1.16 2002/09/16 08:05:03 jkl Exp $
003: *
004: * Copyright (c) 2002 Njet Communications Ltd. All Rights Reserved.
005: *
006: * Use is subject to license terms, as defined in
007: * Anvil Sofware License, Version 1.1. See LICENSE
008: * file, or http://njet.org/license-1.1.txt
009: */
010: package anvil.database;
011:
012: import java.util.ArrayList;
013: import anvil.Log;
014: import anvil.server.PoolPreferences;
015: import anvil.server.Zone;
016:
017: /**
018: * <p>Pool for storing limited amount of connections as <code>PooledConnection</code>.
019: * Pool is used by <code>ConnectionAccessQueue</code> which takes care of the situations
020: * when connections aren't immediately available.</p>
021: *
022: * <p>Methods which might alter the connection vectors are internally synchronized so
023: * that only one thread at a time can perform operations on them. However several threads
024: * may be creating connections at the same time.</p>
025: *
026: * @see PooledConnection
027: * @see ConnectionAccessQueue
028: * @see ConnectionFactory
029: * @version $Revision: 1.16 $
030: * @author Jani Lehtimäki
031: */
032: public class ConnectionPool implements ConnectionMonitor {
033:
034: private String poolName;
035:
036: private Zone zone;
037:
038: private PoolPreferences prefs;
039:
040: private ConnectionMonitor connectionMonitor = null;
041:
042: /**
043: * Connection factory.
044: */
045: private ConnectionFactory connectionFactory;
046:
047: /**
048: * List containing <code>PooledConnection</code> instances of free connections.
049: */
050: private ArrayList freeConnections;
051:
052: /**
053: * Number of connections currently being created.
054: */
055: private int pendingConnections;
056:
057: /**
058: * List containing <code>PooledConnection</code> instances of active connections.
059: */
060: private ArrayList activeConnections;
061:
062: /**
063: * Thread responsible for checking out the connections.
064: */
065: private ConnectionAllocator allocatorThread;
066:
067: /**
068: * Observer being notified when more free connections are available.
069: */
070: private ConnectionObserver connectionObserver = null;
071:
072: /**
073: * System still running.
074: */
075: private boolean running = true;
076:
077: /**
078: * Constructs the pool.
079: *
080: * @param name Name of pool
081: * @param factory Connection factory
082: * @param min Recommended minimum amount of connections
083: * @param max Absolute maximum amount of connections
084: * @param lifetime Lifetime of connection (ms)
085: * @param checkTimeout Period after which connections are checked out (ms)
086: *
087: */
088: public ConnectionPool(Zone zone, PoolPreferences prefs) {
089: this .zone = zone;
090: this .prefs = prefs;
091: this .poolName = prefs.getName();
092: this .freeConnections = new ArrayList(prefs.getMaxConnections());
093: this .activeConnections = new ArrayList(prefs
094: .getMaxConnections());
095: this .pendingConnections = 0;
096:
097: try {
098: String monitor = prefs.getMonitor();
099: if (monitor != null && monitor.length() > 0) {
100: this .connectionMonitor = (ConnectionMonitor) Class
101: .forName(monitor).newInstance();
102: }
103: this .connectionMonitor.initialize(this );
104: } catch (Exception e) {
105: this .connectionException(
106: "Couldn't create connection monitor: "
107: + prefs.getMonitor(), e);
108: }
109:
110: try {
111: this .connectionFactory = (ConnectionFactory) Class.forName(
112: prefs.getFactory()).newInstance();
113: this .connectionFactory.initialize(this );
114: } catch (Exception e) {
115: this .connectionException(
116: "Couldn't create connection factory: "
117: + prefs.getFactory(), e);
118: }
119:
120: this .allocatorThread = new ConnectionAllocator("Pool-"
121: + poolName);
122: this .allocatorThread.setDaemon(true);
123: this .allocatorThread.start();
124: }
125:
126: /**
127: * Gets the name of pool.
128: *
129: * @return Name of pool
130: */
131: public String getName() {
132: return poolName;
133: }
134:
135: /**
136: * Gets the class of driver.
137: *
138: * @return Connection timeout, milliseconds
139: */
140: public String getDriver() {
141: return prefs.getDriver();
142: }
143:
144: /**
145: * Gets the zhe for this pool.
146: *
147: * @return Connection timeout, milliseconds
148: */
149: public Zone getZone() {
150: return zone;
151: }
152:
153: public String getURL() {
154: return prefs.getURL();
155: }
156:
157: public String getUsername() {
158: return prefs.getUsername();
159: }
160:
161: public String getPassword() {
162: return prefs.getPassword();
163: }
164:
165: /**
166: * Gets the recommended minimum amount of connections.
167: *
168: * @return Minimum amount of connections
169: */
170: public int getMinConnections() {
171: return prefs.getMinConnections();
172: }
173:
174: /**
175: * Gets the maximum amount of connections.
176: *
177: * @return Maximum amount of connections
178: */
179: public int getMaxConnections() {
180: return prefs.getMaxConnections();
181: }
182:
183: /**
184: * Gets the connection lifetime. Lifetime may be exceed if the connection
185: * is reserved.
186: *
187: * @return Connection lifetime, milliseconds
188: */
189: public int getConnectionLifetime() {
190: return prefs.getLifetime() * 1000;
191: }
192:
193: /**
194: * Gets the connection reservation timeout. When this period
195: * is exceed connection is unconditionally closed.
196: *
197: * @return Connection timeout, milliseconds
198: */
199: public int getConnectionTimeout() {
200: return prefs.getTimeout() * 1000;
201: }
202:
203: /**
204: * Gets the connection reservation timeout. When this period
205: * is exceed connection is unconditionally closed.
206: *
207: * @return Connection timeout, milliseconds
208: */
209: public int getConnectionAcquireTimeout() {
210: return prefs.getAcquireTimeout();
211: }
212:
213: /**
214: * Gets preference by name from PoolPreferences.
215: *
216: */
217: public Object getPreference(String name) {
218: return prefs.getPreference(name);
219: }
220:
221: /**
222: * Gets the current amount of connections active.
223: *
224: * @return Amount of active connections
225: */
226: public int getActiveConnections() {
227: return activeConnections.size();
228: }
229:
230: /**
231: * Gets the current amount of pending connections, ie. connections
232: * that currently being opened.
233: *
234: * @return Amount of pending connections
235: */
236: public int getPendingConnections() {
237: return pendingConnections;
238: }
239:
240: /**
241: * Gets the current amount of connections free.
242: *
243: * @return Amount of free connections
244: */
245: public int getFreeConnections() {
246: return freeConnections.size();
247: }
248:
249: /**
250: * Gets the current amount of connections on this pool.
251: *
252: * @return Amount of connections
253: */
254: public int getTotalConnections() {
255: return freeConnections.size() + pendingConnections
256: + activeConnections.size();
257: }
258:
259: /**
260: * Sets the connection observer. (Package visibility.)
261: *
262: * @param Connection observer
263: */
264: void setObserver(ConnectionObserver observer) {
265: connectionObserver = observer;
266: }
267:
268: /**
269: * Creates new connection.
270: *
271: * @return <code>PooledConnection</code> instance,
272: * or <code>null</code> if it couldn't be created.
273: */
274: private PooledConnection create() throws Exception {
275: if (connectionFactory == null) {
276: return null;
277: }
278: try {
279: return connectionFactory.create();
280: } catch (Exception exception) {
281: this .connectionException("PooledConnection.create()",
282: exception);
283: exception.fillInStackTrace();
284: throw exception;
285: }
286: }
287:
288: /**
289: * Marks connection reserved. Connection is added
290: * to the <code>activeConnections</code> vector, and marked
291: * reserved. It is assumed that at this point the
292: * connection is usable.
293: *
294: * @see PooledConnection#reserve
295: * @param connectionImpl Connection
296: * @param timeout Timeout override (if > 0)
297: */
298: private void doReserve(PooledConnection connectionImpl, int timeout) {
299: activeConnections.add(connectionImpl);
300:
301: connectionImpl.reserve(timeout);
302: }
303:
304: /**
305: * Marks connection released. Connection is added
306: * to the <code>freeConnections</code> vector, and
307: * connection observer is notified about.
308: *
309: * @see PooledConnection#reserve
310: * @param connectionImpl Connection
311: */
312: private void doRelease(PooledConnection connectionImpl) {
313: freeConnections.add(connectionImpl);
314:
315: if (connectionObserver != null) {
316: connectionObserver.connectionsAvailable();
317: }
318: }
319:
320: /**
321: * Acquires new connection from the pool. Strategy:
322: * <ul>
323: * <li><code>freeConnections</code> vector is scanned through for
324: * a usable connection from the beginning until one is found.
325: * <li>If total amount of connections is less than maximum amount of
326: * connections, a new connection is created.
327: * </ul>
328: *
329: * @param timeout Timeout override (if > 0)
330: * @return Connection or <code>null</code> if it couldn't be created
331: */
332: public PooledConnection acquire(int timeout) throws Exception {
333: PooledConnection conn = null;
334:
335: synchronized (this ) {
336:
337: /*
338: * Let's scan through the free connections and stop on
339: * first good connection, others are thrashed.
340: */
341: while ((conn == null) && (freeConnections.size() > 0)) {
342: conn = (PooledConnection) freeConnections.remove(0);
343: if (!conn.isAlive()) {
344: conn.close();
345: conn = null;
346: }
347: }
348:
349: /*
350: * Create new connection if there is space available on the pool.
351: */
352: if (conn != null) {
353: /*
354: * We have connection, mark it reserved and get out.
355: */
356: doReserve(conn, timeout);
357:
358: interruptAllocatorIfRequired();
359:
360: return conn;
361: } else {
362: /*
363: * We don't have a connection - do we have space to create
364: * a new one? (At this point there are no freeConnections
365: * so therefore they aren't included in the check below)
366: */
367: if ((activeConnections.size() + pendingConnections) < prefs
368: .getMaxConnections()) {
369: pendingConnections++;
370: } else {
371: /*
372: * Sorry, can't create.
373: */
374: return null;
375: }
376: }
377: }
378:
379: Exception createException = null;
380: /**
381: * Try to create a new connection
382: */
383: try {
384: conn = create();
385: } catch (Exception exception) {
386: createException = exception;
387: }
388:
389: synchronized (this ) {
390: if (pendingConnections > 0) {
391: pendingConnections--;
392: }
393:
394: if (conn != null) {
395: doReserve(conn, timeout);
396: }
397:
398: interruptAllocatorIfRequired();
399: }
400:
401: if (createException != null) {
402: createException.fillInStackTrace();
403: throw createException;
404: }
405:
406: return conn;
407: }
408:
409: /**
410: * Shuts the pool down. Allocator thread is stopped and all connections
411: * are unconditionally released and closed.
412: *
413: * @see ConnectionPool.ConnectionAllocator
414: */
415: void stop() {
416: zone.log().info(
417: "ConnectionPool '" + poolName + "' shutting down.");
418:
419: PooledConnection conn;
420:
421: int i;
422: int n;
423:
424: running = false;
425:
426: allocatorThread.shutdown();
427:
428: try {
429: allocatorThread.join();
430: } catch (InterruptedException e) {
431: }
432:
433: /**
434: * Wait for pending threads
435: */
436: while (pendingConnections > 0) {
437: try {
438: Thread.sleep(50);
439: } catch (InterruptedException exception) {
440: }
441: }
442:
443: synchronized (this ) {
444: n = freeConnections.size();
445: for (i = n - 1; i >= 0; i--) {
446: conn = (PooledConnection) freeConnections.remove(i);
447: conn.close();
448: }
449:
450: n = activeConnections.size();
451: for (i = n - 1; i >= 0; i--) {
452: conn = (PooledConnection) activeConnections.remove(i);
453: conn.close();
454: }
455: }
456:
457: zone.log().info(
458: "ConnectionPool '" + poolName + "' shutdown complete.");
459: }
460:
461: /**
462: * Interrupts the allocator thread if number of free connections
463: * is below recommended minimum and maximum is not yet reached.
464: *
465: * @see ConnectionPool.ConnectionAllocator
466: */
467: private void interruptAllocatorIfRequired() {
468: if (canAllocateMore()) {
469: allocatorThread.interrupt();
470: }
471: }
472:
473: /**
474: * Checks if more connections can be allocated on the background
475: *
476: * @return Boolean
477: */
478: private boolean canAllocateMore() {
479: int active = activeConnections.size();
480: int free = freeConnections.size();
481: int available = prefs.getMaxConnections() - active
482: - pendingConnections - free;
483:
484: return (available > 0) && (free < prefs.getMinConnections());
485: }
486:
487: /**
488: * Notifies pool about the connection being released.
489: * Allocator thread is interrupted immediately because active
490: * connections now contains released connection whose state
491: * need to be checked.
492: *
493: * @param connection Connection
494: */
495: public void release(PooledConnection connection) {
496: allocatorThread.interrupt();
497: }
498:
499: /**
500: * Checks out the connections.
501: *
502: * @see ConnectionPool.AllocatorThread
503: */
504: private void checkConnections() {
505: PooledConnection conn;
506:
507: int i;
508: int n;
509:
510: synchronized (this ) {
511:
512: /*
513: * Iterate through active connections, from top to down, so that
514: * elements can be removed while going downwards. Connections
515: * that are no longer reserved will be moved into free connections.
516: */
517: n = activeConnections.size();
518: for (i = n - 1; i >= 0; i--) {
519: conn = (PooledConnection) activeConnections.get(i);
520: if (!conn.isReserved()) {
521: activeConnections.remove(i);
522: freeConnections.add(conn);
523:
524: this .connectionReleased(conn);
525: } else if (conn.hasTimedOut()) {
526: activeConnections.remove(i);
527: conn.timeout();
528: } else if (!conn.isAlive()) {
529: activeConnections.remove(i);
530: conn.close();
531: }
532: }
533:
534: /*
535: * Iterate through free connections
536: */
537: n = freeConnections.size();
538: for (i = n - 1; i >= 0; i--) {
539: conn = (PooledConnection) freeConnections.get(i);
540:
541: if (conn.hasTimedOut()) {
542: freeConnections.remove(i);
543: conn.timeout();
544: } else if (!conn.isAlive()) {
545: freeConnections.remove(i);
546: conn.close();
547: }
548: }
549: }
550:
551: while (running) {
552:
553: /*
554: * Let's give another threads change to claim
555: * the synchronization first
556: */
557: Thread.currentThread().yield();
558:
559: /*
560: * Only one create at a time is synchronized so it is passed
561: * to waiting thread as soon as it has been created.
562: */
563: boolean create = false;
564:
565: conn = null;
566:
567: synchronized (this ) {
568: if (canAllocateMore()) {
569: create = true;
570: pendingConnections++;
571: } else {
572:
573: /* break from the loop */
574: break;
575: }
576: }
577:
578: if (create) {
579: try {
580: conn = create();
581: } catch (Exception exception) {
582: }
583: }
584:
585: synchronized (this ) {
586: if (create) {
587: if (pendingConnections > 0) {
588: pendingConnections--;
589: }
590:
591: if (conn != null) {
592: doRelease(conn);
593: }
594: }
595: }
596: }
597: }
598:
599: /**
600: * Connection allocator runs through the loop sleeping specified amount on
601: * each loop and invoking checkConnections method on each loop. Allocator
602: * is also interrupted immediately by other threads when new connections
603: * can be created.
604: *
605: * @see ConnectionPool#checkConnections()
606: */
607: public class ConnectionAllocator extends Thread {
608: private boolean running = true;
609:
610: /**
611: * Creates the allocator.
612: */
613: public ConnectionAllocator(String name) {
614: super (name);
615: }
616:
617: /**
618: * Shuts down the allocator.
619: */
620: public void shutdown() {
621: running = false;
622: this .interrupt();
623: }
624:
625: /**
626: * Runs the allocator.
627: */
628: public void run() {
629: zone.log().info(
630: "Connection allocator thread '" + poolName
631: + "' starting...");
632: while (running) {
633: ConnectionPool.this .checkConnections();
634: if (interrupted()) {
635: continue;
636: }
637: try {
638: Thread.sleep(prefs.getCheckTimeout());
639: } catch (InterruptedException e) {
640: }
641: }
642: zone.log().info(
643: "Connection allocator thread '" + poolName
644: + "' shutting down...");
645: }
646: }
647:
648: public void initialize(ConnectionPool pool) {
649: }
650:
651: public void connectionCreated(PooledConnection connection,
652: long duration) {
653: if (connectionMonitor != null) {
654: connectionMonitor.connectionCreated(connection, duration);
655: }
656: }
657:
658: public void connectionReserved(PooledConnection connection,
659: long duration) {
660: if (connectionMonitor != null) {
661: connectionMonitor.connectionReserved(connection, duration);
662: }
663: }
664:
665: public void connectionAcquireTimedout(ConnectionPool pool,
666: long duration) {
667: if (connectionMonitor != null) {
668: connectionMonitor.connectionAcquireTimedout(pool, duration);
669: }
670: }
671:
672: public void connectionReleased(PooledConnection connection) {
673: if (connectionMonitor != null) {
674: connectionMonitor.connectionReleased(connection);
675: }
676: }
677:
678: public void connectionClosed(PooledConnection connection) {
679: if (connectionMonitor != null) {
680: connectionMonitor.connectionClosed(connection);
681: }
682: }
683:
684: public void connectionTimeout(PooledConnection connection) {
685: if (connectionMonitor != null) {
686: connectionMonitor.connectionTimeout(connection);
687: }
688: }
689:
690: public void connectionException(String source, Exception exception) {
691: if (connectionMonitor != null) {
692: connectionMonitor.connectionException(source, exception);
693: }
694: }
695:
696: }
|