001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 002// The Molly framework is freely distributable under the terms of an 003// MIT-style license. For details, see the molly pages web site at: 004// http://www.mollypages.org/. Use, modify, have fun ! 005 006package fc.jdbc; 007 008import java.sql.*; 009import java.io.*; 010import java.util.*; 011import java.util.concurrent.*; 012 013import fc.io.*; 014import fc.util.*; 015 016//import EDU.oswego.cs.dl.util.concurrent.*; 017 018/** 019A pooled connection manager. Connections must be closed when done 020with, which automatically returns them back to the pool. 021 022@author hursh jain 023**/ 024public final class PooledConnectionMgr extends ConnectionMgr 025{ 026/* 027we don't really have any way to get all connections in 028the buffer and close them all - the bounderbuffer class 029doesn't allow this. So we need this class 030*/ 031class MyBoundedBuffer extends ArrayBlockingQueue 032 { 033 int bufsize; 034 035 MyBoundedBuffer(int size) { 036 super(size); 037 this.bufsize = size; 038 } 039 040 //closes all connections, even ones that are checked out 041 void closeAllConnections() 042 { 043 Object[] items = toArray(); 044 for (int n = 0; n < items.length; n++) 045 { 046 try { 047 PooledConnection con = (PooledConnection) items[n]; 048 if (con != null) { 049 if (! con.isClosed() ) { 050 log.warn("PooledConnectionMgr shutdown: closing open database connection #" + con.id); 051 } 052 con.closeReally(); 053 } 054 } 055 catch (SQLException e) 056 { 057 log.warn(e); 058 } 059 } 060 } 061} 062 063public static final int defaultSize = 100; 064 065MyBoundedBuffer buffer; 066boolean dbgCheckOut; 067Map checkedOutInfo; //useful for debugging pool leaks 068 069/** If a connection cannot be obtained within this time, 070then a warning will be logged. The value is in milliseconds. 071*/ 072public static final long ConnectionTimeoutWarning = 1500; 073 074/** 075Constructs a new connection pool with the specified size. 076{@see ConnectionMgr} 077**/ 078public PooledConnectionMgr( 079 String jdbc_url, String jdbc_driver, 080 String jdbc_user, String jdbc_password, 081 String jdbc_catalog, 082 int size) 083throws Exception 084 { 085 super(jdbc_url, jdbc_driver, jdbc_user, jdbc_password, jdbc_catalog); 086 init(size); 087 } 088 089/** 090Constructs a new connection pool with the specified size. The 091size is the number of connections that this pool holds. 092**/ 093public PooledConnectionMgr(PropertyMgr props, int size) 094throws Exception 095 { 096 super(props); 097 init(size); 098 } 099 100/** 101Constructs a new connection pool with the specified size. The 102size is the number of connections that this pool holds. 103**/ 104public PooledConnectionMgr(PropertyMgr props, String prefix, int size) 105throws Exception 106 { 107 super(props, prefix); 108 init(size); 109 } 110 111/** 112Constructs a new connection pool with the specified size. The 113size is the number of connections that this pool holds. 114**/ 115public PooledConnectionMgr( 116 SystemLog log, PropertyMgr props, int size) 117throws Exception 118 { 119 super(log, props); 120 init(size); 121 } 122 123/** 124Constructs a new connection pool with the specified size. The 125size is the number of connections that this pool holds. 126**/ 127public PooledConnectionMgr( 128 SystemLog log, PropertyMgr props, String prefix, int size) 129throws Exception 130 { 131 super(log, props, prefix); 132 init(size); 133 } 134 135private void init(int size) throws Exception 136 { 137 if (size <= 0) { 138 throw new IllegalArgumentException("size must be greater than 0"); 139 } 140 141 buffer = new MyBoundedBuffer(size); 142 log.bug("PooledConnectionMgr.constructor: Constructed new connection pool buffer: ", buffer); 143 144 for (int n = 0; n < size; n++) 145 { 146 Connection con = DriverManager.getConnection(url,user,password); 147 if (con == null) { 148 throw new Exception("PooledConnectionMgr: Cannot connect to database [" + url + "]"); 149 } 150 PooledConnection pooled_con = new PooledConnection(this, con); 151 buffer.put(pooled_con); 152 } 153 154 checkedOutInfo = new Hashtable(); 155 log.bug("PooledConnectionMgr.constructor: added ", buffer.size(), " connections to the connection pool"); 156 } 157 158/** 159Helps to debug connection checkouts. By default, this is 160<tt>false</tt> (since there is a stack trace overhead per 161connection when debugging is enabled). 162 163@param dbg <tt>true</tt> to enable tracking of checked out 164 connections from this pool. <tt>false</tt> 165 stops tracking and clears any existing tracking 166 info. 167**/ 168public void setDebugCheckout(boolean dbg) { 169 dbgCheckOut = dbg; 170 if (! dbg) { 171 checkedOutInfo.clear(); 172 } 173 } 174 175/** 176Returns a iterator over a collection of Exception objects, each 177containing a stack trace for code that has currently checked out 178a connection from this pool. For this to work, make sure that 179{@link #setDebugCheckout} has first been invoked with 180<tt>true</tt>. 181**/ 182public Iterator getCheckoutInfo() { 183 return checkedOutInfo.values().iterator(); 184 } 185 186/** 187Gets a list of connections in the pool such that each connection has a 188transaction that was started more than the specified milliseconds ago but 189has not yet been aborted or commited (this is a good way to see if there 190is a transaction leak somewhere). 191*/ 192public List getHungTransactions(long milliseconds) 193 { 194 long now = System.currentTimeMillis(); 195 Object[] items = buffer.toArray(); 196 List list = new ArrayList(); 197 for (int n = 0; n < items.length; n++) { 198 PooledConnection con = (PooledConnection) items[n]; 199 if (con.transaction_start_time == 0 200 || (con.transaction_start_time + milliseconds) > now) { 201 continue; 202 } 203 list.add(con); 204 } 205 return list; 206 } 207 208/** 209Returns a connection from the pool possibly waiting 210indefinitely until a connection is available. However, if a 211connection is not obtained in {@link 212#ConnectionTimeoutWarning} milliseconds, then a warning is 213logged. 214**/ 215protected Connection getConImpl() throws SQLException 216 { 217 try { 218 PooledConnection con = null; 219 con = (PooledConnection) buffer.poll(ConnectionTimeoutWarning, 220 TimeUnit.MILLISECONDS); 221 222 if (con == null) 223 { 224 log.warn(this, 225 "Could not obtain connection in ", 226 String.valueOf(ConnectionTimeoutWarning), 227 " milliseconds..\n", 228 "Possibly waiting forever for connection here...\n", 229 IOUtil.throwableToString(new Exception("Debug Stack Trace")) 230 ); 231 232 con = (PooledConnection) buffer.take(); 233 log.info(this, "...Connection obtained successfully."); 234 } 235 236 con.setClosed(false); 237 238 if (dbgCheckOut) { 239 checkedOutInfo.put(new Integer(con.getID()), new Exception("==PooledConnectionMgr/Checkout Debug Info==")); 240 } 241 242 return con; 243 } 244 catch (InterruptedException e) { 245 e.printStackTrace(); 246 throw new SQLException(e.getMessage()); 247 } 248 } 249 250protected boolean handleMgrShutdown() { 251 buffer.closeAllConnections(); 252 return true; 253 } 254 255void put(PooledConnection con) throws SQLException 256 { 257 //we know this won't block because our pool only gets stuff 258 //put in it that was previously taken from it 259 try { 260 buffer.put(con); 261 if (dbgCheckOut) { 262 checkedOutInfo.remove( new Integer(con.getID()) ); 263 } 264 } 265 catch (InterruptedException e) 266 { 267 e.printStackTrace(); 268 throw new SQLException(e.getMessage()); 269 } 270 } 271 272public String toString() { 273 return myname + " [capacity=" + buffer.bufsize + "; available=" + buffer.size() + "]"; 274 } 275 276//== testing ================================================= 277 278public static void main(String[] args) throws Exception 279 { 280 Args myargs = new Args(args); 281 Log.getDefault().setLevel(SystemLog.DEBUG); 282 myargs.setUsage("java fc.jdbc.PooledConnectionMgr -conf conf-file -query query-to-execute"); 283 284 String query = myargs.getRequired("query"); 285 String propfile = myargs.getRequired("conf"); 286 final int size = 5; 287 288 //test 1 289 FilePropertyMgr fmgr = new FilePropertyMgr(new File(propfile)); 290 291 PooledConnectionMgr pool = new PooledConnectionMgr(fmgr, size); 292 293 pool.setDebugCheckout(true); 294 295 List list = new ArrayList(); 296 for (int n = 0; n < size/2; n++) { 297 Connection con = pool.getConnection(); 298 list.add(con); 299 } 300 301 System.out.println("Got connections, connection pool="+pool); 302 System.out.println("Now using connections..."); 303 304 int num = 1; 305 for (Iterator it = list.iterator(); it.hasNext(); /*empty*/) { 306 Connection item = (Connection) it.next(); 307 Statement stmt = item.createStatement(); 308 ResultSet rs = stmt.executeQuery(query); 309 System.out.println("=== query using connection [# " + num++ + "]" + item + "====="); 310 QueryUtil.printRS(rs); 311 System.out.println("============================================================="); 312 item.close(); 313 } 314 315 System.out.println("Used and closed all connections in pool."); 316 Connection con = pool.getConnection(); 317 System.out.println("Got new connection:" + con); 318 319 System.out.println("==== [debug] Checkout Info: ===="); 320 for (Iterator it = pool.getCheckoutInfo(); it.hasNext(); /*empty*/) { 321 Throwable item = (Throwable) it.next(); 322 item.printStackTrace(); 323 } 324 System.out.println("==== [debug end] Checkout Info: ===="); 325 326 System.out.println("Closed all connections, connection pool="+pool); 327 328 pool.close(); 329 330 System.out.println("Trying to get connection after closing connection mgr"); 331 try { 332 con = pool.getConnection(); 333 } 334 catch (Exception e) { 335 e.printStackTrace(); 336 } 337 338 //test 2 339 System.out.println("TEST 2...."); 340 341 final PooledConnectionMgr pool2 = 342 new PooledConnectionMgr(fmgr, size); 343 344 Thread t = new Thread() 345 { 346 public void run() 347 { 348 try { 349 List list = new ArrayList(); 350 for (int n = 0; n < size; n++) { 351 Connection con = pool2.getConnection(); 352 list.add(con); 353 System.out.println("Got connection: " + con); 354 } 355 System.out.println("sleeping 10 seconds"); 356 Thread.currentThread().sleep(5000); 357 for (int n = 0; n < list.size(); n++) 358 { 359 System.out.println( 360 "Releasing connection: " + 361 (n+1) + " " + list.get(n) ); 362 ((Connection) list.get(n)).close(); 363 } 364 } 365 catch (Exception e) { 366 e.printStackTrace(); 367 } 368 } 369 }; 370 371 t.start(); 372 373 for (int n = 0; n < size; n++) { 374 System.out.println("Main Thread: Trying to get connection..." + (n+1)); 375 con = pool2.getConnection(); 376 System.out.println("Main Thread: Got connection: " + con); 377 con.close(); 378 } 379 380 pool2.close(); 381 } //main 382} //~class PooledConnectionMgr