1 22 23 24 package com.mchange.v2.c3p0.stmt; 25 26 import java.util.*; 27 import java.sql.*; 28 import java.lang.reflect.*; 29 import com.mchange.v2.async.AsynchronousRunner; 30 import com.mchange.v2.sql.SqlUtils; 31 import com.mchange.v2.util.ResourceClosedException; 32 import com.mchange.v2.log.*; 33 import com.mchange.v1.db.sql.StatementUtils; 34 35 import java.io.StringWriter ; 36 import java.io.PrintWriter ; 37 import java.io.IOException ; 38 import com.mchange.v2.io.IndentedWriter; 39 40 public abstract class GooGooStatementCache 41 { 42 private final static MLogger logger = MLog.getLogger( GooGooStatementCache.class ); 43 44 private final static int DESTROY_NEVER = 0; 45 private final static int DESTROY_IF_CHECKED_IN = 1 << 0; 46 private final static int DESTROY_IF_CHECKED_OUT = 1 << 1; 47 private final static int DESTROY_ALWAYS = (DESTROY_IF_CHECKED_IN | DESTROY_IF_CHECKED_OUT); 48 49 50 51 ConnectionStatementManager cxnStmtMgr; 54 55 HashMap stmtToKey = new HashMap(); 58 59 HashMap keyToKeyRec = new HashMap(); 63 64 HashSet checkedOut = new HashSet(); 68 69 70 71 72 73 AsynchronousRunner blockingTaskAsyncRunner; 74 75 HashSet removalPending = new HashSet(); 83 84 85 86 public GooGooStatementCache(AsynchronousRunner blockingTaskAsyncRunner) 87 { 88 this.blockingTaskAsyncRunner = blockingTaskAsyncRunner; 89 this.cxnStmtMgr = createConnectionStatementManager(); 90 } 91 92 public synchronized int getNumStatements() 93 { return this.isClosed() ? -1 : countCachedStatements(); } 94 95 public synchronized int getNumStatementsCheckedOut() 96 { return this.isClosed() ? -1 : checkedOut.size(); } 97 98 public synchronized int getNumConnectionsWithCachedStatements() 99 { return isClosed() ? -1 : cxnStmtMgr.getNumConnectionsWithCachedStatements(); } 100 101 public synchronized String dumpStatementCacheStatus() 102 { 103 if (isClosed()) 104 return this + "status: Closed."; 105 else 106 { 107 StringWriter sw = new StringWriter (2048); 108 IndentedWriter iw = new IndentedWriter( sw ); 109 try 110 { 111 iw.print(this); 112 iw.println(" status:"); 113 iw.upIndent(); 114 iw.println("core stats:"); 115 iw.upIndent(); 116 iw.print("num cached statements: "); 117 iw.println( this.countCachedStatements() ); 118 iw.print("num cached statements in use: "); 119 iw.println( checkedOut.size() ); 120 iw.print("num connections with cached statements: "); 121 iw.println(cxnStmtMgr.getNumConnectionsWithCachedStatements()); 122 iw.downIndent(); 123 iw.println("cached statement dump:"); 124 iw.upIndent(); 125 for (Iterator ii = cxnStmtMgr.connectionSet().iterator(); ii.hasNext();) 126 { 127 Connection pcon = (Connection) ii.next(); 128 iw.print(pcon); 129 iw.println(':'); 130 iw.upIndent(); 131 for (Iterator jj = cxnStmtMgr.statementSet(pcon).iterator(); jj.hasNext();) 132 iw.println(jj.next()); 133 iw.downIndent(); 134 } 135 136 iw.downIndent(); 137 iw.downIndent(); 138 return sw.toString(); 139 } 140 catch (IOException e) 141 { 142 if (logger.isLoggable(MLevel.SEVERE)) 143 logger.log(MLevel.SEVERE, "Huh? We've seen an IOException writing to s StringWriter?!", e); 144 return e.toString(); 145 } 146 } 147 } 148 149 abstract ConnectionStatementManager createConnectionStatementManager(); 150 151 public synchronized Object checkoutStatement( Connection physicalConnection, 152 Method stmtProducingMethod, 153 Object [] args ) 154 throws SQLException, ResourceClosedException 155 { 156 try 157 { 158 Object out = null; 159 160 StatementCacheKey key = StatementCacheKey.find( physicalConnection, 161 stmtProducingMethod, 162 args ); 163 LinkedList l = checkoutQueue( key ); 164 if (l == null || l.isEmpty()) { 166 out = acquireStatement( physicalConnection, stmtProducingMethod, args ); 169 170 if ( prepareAssimilateNewStatement( physicalConnection ) ) 171 assimilateNewCheckedOutStatement( key, physicalConnection, out ); 172 } 176 else { 178 if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) 179 logger.finest(this.getClass().getName() + " ----> CACHE HIT"); 180 182 out = l.get(0); 183 l.remove(0); 184 if (! checkedOut.add( out )) 185 throw new RuntimeException ("Internal inconsistency: " + 186 "Checking out a statement marked " + 187 "as already checked out!"); 188 removeStatementFromDeathmarches( out, physicalConnection ); 189 } 190 191 if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) 192 { 193 if (logger.isLoggable(MLevel.FINEST)) 196 logger.finest("checkoutStatement: " + statsString()); 197 } 198 199 return out; 200 } 201 catch (NullPointerException npe) 202 { 203 if (checkedOut == null) { 205 if (logger.isLoggable(MLevel.FINE)) 206 logger.log( MLevel.FINE, 207 "A client attempted to work with a closed Statement cache, " + "" + 208 "provoking a NullPointerException. c3p0 recovers, but this should be rare.", 209 npe); 210 throw new ResourceClosedException( npe ); 211 } 212 else 213 throw npe; 214 } 215 } 216 217 public synchronized void checkinStatement( Object pstmt ) 218 throws SQLException 219 { 220 if (checkedOut == null) { 222 synchronousDestroyStatement( pstmt ); 223 224 return; 225 } 226 else if (! checkedOut.remove( pstmt ) ) 227 { 228 if (! ourResource( pstmt ) ) destroyStatement( pstmt ); 232 return; 233 } 234 235 try 236 { refreshStatement( (PreparedStatement) pstmt ); } 237 catch (Exception e) 238 { 239 if (Debug.DEBUG) 240 { 241 if (logger.isLoggable(MLevel.INFO)) 244 logger.log(MLevel.INFO, "Problem with checked-in Statement, discarding.", e); 245 } 246 247 checkedOut.add( pstmt ); 252 253 removeStatement( pstmt, DESTROY_ALWAYS ); return; 255 } 256 257 StatementCacheKey key = (StatementCacheKey) stmtToKey.get( pstmt ); 258 if (Debug.DEBUG && key == null) 259 throw new RuntimeException ("Internal inconsistency: " + 260 "A checked-out statement has no key associated with it!"); 261 262 LinkedList l = checkoutQueue( key ); 263 l.add( pstmt ); 264 addStatementToDeathmarches( pstmt, key.physicalConnection ); 265 266 if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) 267 { 268 if (logger.isLoggable(MLevel.FINEST)) 271 logger.finest("checkinStatement(): " + statsString()); 272 } 273 } 274 275 276 public synchronized void checkinAll(Connection pcon) 277 throws SQLException 278 { 279 281 Set stmtSet = cxnStmtMgr.statementSet( pcon ); 282 if (stmtSet != null) 283 { 284 for (Iterator ii = stmtSet.iterator(); ii.hasNext(); ) 285 { 286 Object stmt = ii.next(); 287 if (checkedOut.contains( stmt )) 288 checkinStatement( stmt ); 289 } 290 } 291 292 if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) 293 { 294 if (logger.isLoggable(MLevel.FINEST)) 297 logger.log(MLevel.FINEST, "checkinAll(): " + statsString()); 298 } 299 } 300 301 306 public void closeAll(Connection pcon) throws SQLException 307 { 308 311 313 if (! this.isClosed()) 314 { 315 if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) 316 { 317 if (logger.isLoggable(MLevel.FINEST)) 318 { 319 logger.log(MLevel.FINEST, "ENTER METHOD: closeAll( " + pcon + " )! -- num_connections: " + 320 cxnStmtMgr.getNumConnectionsWithCachedStatements()); 321 } 323 } 324 325 Set stmtSet = null; 326 synchronized (this) 327 { 328 Set cSet = cxnStmtMgr.statementSet( pcon ); 329 330 if (cSet != null) 331 { 332 stmtSet = new HashSet( cSet ); 334 336 for (Iterator ii = stmtSet.iterator(); ii.hasNext(); ) 337 { 338 Object stmt = ii.next(); 339 removeStatement( stmt, DESTROY_NEVER ); 342 } 343 } 344 } 345 346 if ( stmtSet != null ) 347 { 348 for (Iterator ii = stmtSet.iterator(); ii.hasNext(); ) 349 { 350 Object stmt = ii.next(); 351 synchronousDestroyStatement( stmt ); 352 } 353 } 354 355 if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) 356 { 357 if (logger.isLoggable(MLevel.FINEST)) 358 logger.finest("closeAll(): " + statsString()); 359 } 360 } 361 } 369 370 public synchronized void close() 371 throws SQLException 372 { 373 375 if (! isClosed()) 376 { 377 for (Iterator ii = stmtToKey.keySet().iterator(); ii.hasNext(); ) 378 synchronousDestroyStatement( ii.next() ); 379 380 cxnStmtMgr = null; 381 stmtToKey = null; 382 keyToKeyRec = null; 383 checkedOut = null; 384 } 385 else 386 { 387 if (logger.isLoggable(MLevel.FINE)) 388 logger.log(MLevel.FINE, this + ": duplicate call to close() [not harmful! -- debug only!]", new Exception ("DUPLICATE CLOSE DEBUG STACK TRACE.")); 389 } 390 391 } 392 393 394 public synchronized boolean isClosed() 395 { return cxnStmtMgr == null; } 396 397 398 399 private void destroyStatement( final Object pstmt ) 400 { 401 class StatementCloseTask implements Runnable 402 { 403 public void run() 404 { StatementUtils.attemptClose( (PreparedStatement) pstmt ); } 405 } 406 407 Runnable r = new StatementCloseTask(); 408 409 blockingTaskAsyncRunner.postRunnable(r); 410 } 411 412 private void synchronousDestroyStatement( final Object pstmt ) 413 { StatementUtils.attemptClose( (PreparedStatement) pstmt ); } 414 415 416 417 418 419 420 421 abstract boolean prepareAssimilateNewStatement(Connection pcon); 422 423 abstract void addStatementToDeathmarches( Object pstmt, Connection physicalConnection ); 424 abstract void removeStatementFromDeathmarches( Object pstmt, Connection physicalConnection ); 425 426 final int countCachedStatements() 427 { return stmtToKey.size(); } 428 429 private void assimilateNewCheckedOutStatement( StatementCacheKey key, 430 Connection pConn, 431 Object ps ) 432 { 433 stmtToKey.put( ps, key ); 434 HashSet ks = keySet( key ); 435 if (ks == null) 436 keyToKeyRec.put( key, new KeyRec() ); 437 else 438 { 439 if (logger.isLoggable(MLevel.INFO)) 441 logger.info("Multiply prepared statement! " + key.stmtText ); 442 if (Debug.DEBUG && logger.isLoggable(MLevel.FINE)) 443 logger.fine("(The same statement has already been prepared by this Connection, " + 444 "and that other instance has not yet been closed, so the statement pool " + 445 "has to prepare a second PreparedStatement object rather than reusing " + 446 "the previously-cached Statement. The new Statement will be cached, in case " + 447 "you frequently need multiple copies of this Statement.)"); 448 } 449 keySet( key ).add( ps ); 450 cxnStmtMgr.addStatementForConnection( ps, pConn ); 451 452 if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) 453 { 454 if (logger.isLoggable(MLevel.FINEST)) 457 logger.finest("cxnStmtMgr.statementSet( " + pConn + " ).size(): " + 458 cxnStmtMgr.statementSet( pConn ).size()); 459 } 460 461 checkedOut.add( ps ); 462 } 463 464 private void removeStatement( Object ps , int destruction_policy ) 465 { 466 synchronized (removalPending) 467 { 468 if ( removalPending.contains( ps ) ) 469 return; 470 else 471 removalPending.add(ps); 472 } 473 474 StatementCacheKey sck = (StatementCacheKey) stmtToKey.remove( ps ); 475 removeFromKeySet( sck, ps ); 476 Connection pConn = sck.physicalConnection; 477 478 boolean checked_in = !checkedOut.contains( ps ); 479 480 if ( checked_in ) 481 { 482 removeStatementFromDeathmarches( ps, pConn ); 483 removeFromCheckoutQueue( sck , ps ); 484 if ((destruction_policy & DESTROY_IF_CHECKED_IN) != 0) 485 destroyStatement( ps ); 486 } 487 else 488 { 489 checkedOut.remove( ps ); 490 if ((destruction_policy & DESTROY_IF_CHECKED_OUT) != 0) 491 destroyStatement( ps ); 492 } 493 494 495 boolean check = cxnStmtMgr.removeStatementForConnection( ps, pConn ); 496 if (Debug.DEBUG && check == false) 497 { 498 if (logger.isLoggable(MLevel.WARNING)) 500 logger.log(MLevel.WARNING, 501 this + " removed a statement that apparently wasn't in a statement set!!!", 502 new Exception ("LOG STACK TRACE")); 503 } 504 505 synchronized (removalPending) 506 { removalPending.remove(ps); } 507 } 508 509 private Object acquireStatement(final Connection pConn, 510 final Method stmtProducingMethod, 511 final Object [] args ) 512 throws SQLException 513 { 514 try 515 { 516 final Object [] outHolder = new Object [1]; 517 final SQLException[] exceptionHolder = new SQLException[1]; 518 519 class StmtAcquireTask implements Runnable 520 { 521 public void run() 522 { 523 try 524 { 525 outHolder[0] = 526 stmtProducingMethod.invoke( pConn, 527 args ); 528 } 529 catch ( InvocationTargetException e ) 530 { 531 Throwable targetException = e.getTargetException(); 532 if ( targetException instanceof SQLException ) 533 exceptionHolder[0] = (SQLException) targetException; 534 else 535 exceptionHolder[0] 536 = SqlUtils.toSQLException(targetException); 537 } 538 catch ( Exception e ) 539 { exceptionHolder[0] = SqlUtils.toSQLException(e); } 540 finally 541 { 542 synchronized ( GooGooStatementCache.this ) 543 { GooGooStatementCache.this.notifyAll(); } 544 } 545 } 546 } 547 548 Runnable r = new StmtAcquireTask(); 549 blockingTaskAsyncRunner.postRunnable(r); 550 551 while ( outHolder[0] == null && exceptionHolder[0] == null ) 552 this.wait(); if (exceptionHolder[0] != null) 554 throw exceptionHolder[0]; 555 else 556 { 557 Object out = outHolder[0]; 558 return out; 559 } 560 } 561 catch ( InterruptedException e ) 562 { throw SqlUtils.toSQLException( e ); } 563 } 564 565 private KeyRec keyRec( StatementCacheKey key ) 566 { return ((KeyRec) keyToKeyRec.get( key )); } 567 568 private HashSet keySet( StatementCacheKey key ) 569 { 570 KeyRec rec = keyRec( key ); 571 return (rec == null ? null : rec.allStmts); 572 } 573 574 private boolean removeFromKeySet( StatementCacheKey key, Object pstmt ) 575 { 576 boolean out; 577 HashSet stmtSet = keySet( key ); 578 out = stmtSet.remove( pstmt ); 579 if (stmtSet.isEmpty() && checkoutQueue( key ).isEmpty()) 580 keyToKeyRec.remove( key ); 581 return out; 582 } 583 584 private LinkedList checkoutQueue( StatementCacheKey key ) 585 { 586 KeyRec rec = keyRec( key ); 587 return ( rec == null ? null : rec.checkoutQueue ); 588 } 589 590 private boolean removeFromCheckoutQueue( StatementCacheKey key, Object pstmt ) 591 { 592 boolean out; 593 LinkedList q = checkoutQueue( key ); 594 out = q.remove( pstmt ); 595 if (q.isEmpty() && keySet( key ).isEmpty()) 596 keyToKeyRec.remove( key ); 597 return out; 598 } 599 600 private boolean ourResource( Object ps ) 601 { return stmtToKey.keySet().contains( ps ); } 602 603 private void refreshStatement( PreparedStatement ps ) throws Exception 604 { ps.clearParameters(); } 605 606 private void printStats() 607 { 608 int total_size = this.countCachedStatements(); 610 int checked_out_size = checkedOut.size(); 611 int num_connections = cxnStmtMgr.getNumConnectionsWithCachedStatements(); 612 int num_keys = keyToKeyRec.size(); 613 System.err.print(this.getClass().getName() + " stats -- "); 614 System.err.print("total size: " + total_size); 615 System.err.print("; checked out: " + checked_out_size); 616 System.err.print("; num connections: " + num_connections); 617 System.err.println("; num keys: " + num_keys); 618 } 619 620 private String statsString() 621 { 622 int total_size = this.countCachedStatements(); 623 int checked_out_size = checkedOut.size(); 624 int num_connections = cxnStmtMgr.getNumConnectionsWithCachedStatements(); 625 int num_keys = keyToKeyRec.size(); 626 627 StringBuffer sb = new StringBuffer (255); 628 sb.append(this.getClass().getName()); 629 sb.append(" stats -- "); 630 sb.append("total size: "); 631 sb.append(total_size); 632 sb.append("; checked out: "); 633 sb.append(checked_out_size); 634 sb.append("; num connections: "); 635 sb.append(num_connections); 636 sb.append("; num keys: "); 637 sb.append(num_keys); 638 return sb.toString(); 639 } 640 641 642 private static class KeyRec 643 { 644 HashSet allStmts = new HashSet(); 645 LinkedList checkoutQueue = new LinkedList(); 646 } 647 648 protected class Deathmarch 649 { 650 TreeMap longsToStmts = new TreeMap(); 651 HashMap stmtsToLongs = new HashMap(); 652 653 long last_long = -1; 654 655 public void deathmarchStatement( Object ps ) 656 { 657 if (Debug.DEBUG) 659 { 660 Long old = (Long ) stmtsToLongs.get( ps ); 661 if (old != null) 662 throw new RuntimeException ("Internal inconsistency: " + 663 "A statement is being double-deathmatched. no checked-out statements should be in a deathmarch already; " + 664 "no already checked-in statement should be deathmarched!"); 665 } 666 667 Long youth = getNextLong(); 668 stmtsToLongs.put( ps, youth ); 669 longsToStmts.put( youth, ps ); 670 } 671 672 public void undeathmarchStatement( Object ps ) 673 { 674 Long old = (Long ) stmtsToLongs.remove( ps ); 675 if (Debug.DEBUG && old == null) 676 throw new RuntimeException ("Internal inconsistency: " + 677 "A (not new) checking-out statement is not in deathmarch."); 678 Object check = longsToStmts.remove( old ); 679 if (Debug.DEBUG && old == null) 680 throw new RuntimeException ("Internal inconsistency: " + 681 "A (not new) checking-out statement is not in deathmarch."); 682 } 683 684 public boolean cullNext() 685 { 686 if ( longsToStmts.isEmpty() ) 687 return false; 688 else 689 { 690 Long l = (Long ) longsToStmts.firstKey(); 691 Object ps = longsToStmts.get( l ); 692 if (Debug.DEBUG && Debug.TRACE == Debug.TRACE_MAX) 693 { 694 if (logger.isLoggable(MLevel.FINEST)) 697 logger.finest("CULLING: " + ((StatementCacheKey) stmtToKey.get(ps)).stmtText); 698 } 699 removeStatement( ps, DESTROY_ALWAYS ); 702 if (Debug.DEBUG && this.contains( ps )) 703 throw new RuntimeException ("Inconsistency!!! Statement culled from deathmarch failed to be removed by removeStatement( ... )!"); 704 return true; 705 } 706 } 707 708 public boolean contains( Object ps ) 709 { return stmtsToLongs.keySet().contains( ps ); } 710 711 public int size() 712 { return longsToStmts.size(); } 713 714 private Long getNextLong() 715 { return new Long ( ++last_long ); } 716 } 717 718 protected static abstract class ConnectionStatementManager 719 { 720 Map cxnToStmtSets = new HashMap(); 721 722 public int getNumConnectionsWithCachedStatements() 723 { return cxnToStmtSets.size(); } 724 725 public Set connectionSet() 726 { return cxnToStmtSets.keySet(); } 727 728 public Set statementSet( Connection pcon ) 729 { return (Set) cxnToStmtSets.get( pcon ); } 730 731 public int getNumStatementsForConnection( Connection pcon ) 732 { 733 Set stmtSet = statementSet( pcon ); 734 return (stmtSet == null ? 0 : stmtSet.size()); 735 } 736 737 public void addStatementForConnection( Object ps, Connection pcon ) 738 { 739 Set stmtSet = statementSet( pcon ); 740 if (stmtSet == null) 741 { 742 stmtSet = new HashSet(); 743 cxnToStmtSets.put( pcon, stmtSet ); 744 } 745 stmtSet.add( ps ); 746 } 747 748 public boolean removeStatementForConnection( Object ps, Connection pcon ) 749 { 750 boolean out; 751 752 Set stmtSet = statementSet( pcon ); 753 if ( stmtSet != null ) 754 { 755 out = stmtSet.remove( ps ); 756 if (stmtSet.isEmpty()) 757 cxnToStmtSets.remove( pcon ); 758 } 759 else 760 out = false; 761 762 return out; 763 } 764 } 765 766 protected static final class SimpleConnectionStatementManager extends ConnectionStatementManager 769 {} 770 771 protected final class DeathmarchConnectionStatementManager extends ConnectionStatementManager 772 { 773 Map cxnsToDms = new HashMap(); 774 775 public void addStatementForConnection( Object ps, Connection pcon ) 776 { 777 super.addStatementForConnection( ps, pcon ); 778 Deathmarch dm = (Deathmarch) cxnsToDms.get( pcon ); 779 if (dm == null) 780 { 781 dm = new Deathmarch(); 782 cxnsToDms.put( pcon, dm ); 783 } 784 } 785 786 public boolean removeStatementForConnection( Object ps, Connection pcon ) 787 { 788 boolean out = super.removeStatementForConnection( ps, pcon ); 789 if (out) 790 { 791 if ( statementSet( pcon ) == null ) 792 cxnsToDms.remove( pcon ); 793 } 794 return out; 795 } 796 797 public Deathmarch getDeathmarch( Connection pcon ) 798 { return (Deathmarch) cxnsToDms.get( pcon ); } 799 } 800 } 801 802 | Popular Tags |