SQLiteConnectionPool.java revision a9be4154e8dac0de3db5ee42e878beb0639e70e6
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.database.sqlite; 18 19import dalvik.system.CloseGuard; 20 21import android.database.sqlite.SQLiteDebug.DbStats; 22import android.os.SystemClock; 23import android.util.Log; 24import android.util.PrefixPrinter; 25import android.util.Printer; 26 27import java.io.Closeable; 28import java.util.ArrayList; 29import java.util.Map; 30import java.util.WeakHashMap; 31import java.util.concurrent.atomic.AtomicBoolean; 32import java.util.concurrent.locks.LockSupport; 33 34/** 35 * Maintains a pool of active SQLite database connections. 36 * <p> 37 * At any given time, a connection is either owned by the pool, or it has been 38 * acquired by a {@link SQLiteSession}. When the {@link SQLiteSession} is 39 * finished with the connection it is using, it must return the connection 40 * back to the pool. 41 * </p><p> 42 * The pool holds strong references to the connections it owns. However, 43 * it only holds <em>weak references</em> to the connections that sessions 44 * have acquired from it. Using weak references in the latter case ensures 45 * that the connection pool can detect when connections have been improperly 46 * abandoned so that it can create new connections to replace them if needed. 47 * </p><p> 48 * The connection pool is thread-safe (but the connections themselves are not). 49 * </p> 50 * 51 * <h2>Exception safety</h2> 52 * <p> 53 * This code attempts to maintain the invariant that opened connections are 54 * always owned. Unfortunately that means it needs to handle exceptions 55 * all over to ensure that broken connections get cleaned up. Most 56 * operations invokving SQLite can throw {@link SQLiteException} or other 57 * runtime exceptions. This is a bit of a pain to deal with because the compiler 58 * cannot help us catch missing exception handling code. 59 * </p><p> 60 * The general rule for this file: If we are making calls out to 61 * {@link SQLiteConnection} then we must be prepared to handle any 62 * runtime exceptions it might throw at us. Note that out-of-memory 63 * is an {@link Error}, not a {@link RuntimeException}. We don't trouble ourselves 64 * handling out of memory because it is hard to do anything at all sensible then 65 * and most likely the VM is about to crash. 66 * </p> 67 * 68 * @hide 69 */ 70public final class SQLiteConnectionPool implements Closeable { 71 private static final String TAG = "SQLiteConnectionPool"; 72 73 // Amount of time to wait in milliseconds before unblocking acquireConnection 74 // and logging a message about the connection pool being busy. 75 private static final long CONNECTION_POOL_BUSY_MILLIS = 30 * 1000; // 30 seconds 76 77 private final CloseGuard mCloseGuard = CloseGuard.get(); 78 79 private final Object mLock = new Object(); 80 private final AtomicBoolean mConnectionLeaked = new AtomicBoolean(); 81 private final SQLiteDatabaseConfiguration mConfiguration; 82 private boolean mIsOpen; 83 private int mNextConnectionId; 84 85 private ConnectionWaiter mConnectionWaiterPool; 86 private ConnectionWaiter mConnectionWaiterQueue; 87 88 // Strong references to all available connections. 89 private final ArrayList<SQLiteConnection> mAvailableNonPrimaryConnections = 90 new ArrayList<SQLiteConnection>(); 91 private SQLiteConnection mAvailablePrimaryConnection; 92 93 // Weak references to all acquired connections. The associated value 94 // is a boolean that indicates whether the connection must be reconfigured 95 // before being returned to the available connection list. 96 // For example, the prepared statement cache size may have changed and 97 // need to be updated. 98 private final WeakHashMap<SQLiteConnection, Boolean> mAcquiredConnections = 99 new WeakHashMap<SQLiteConnection, Boolean>(); 100 101 /** 102 * Connection flag: Read-only. 103 * <p> 104 * This flag indicates that the connection will only be used to 105 * perform read-only operations. 106 * </p> 107 */ 108 public static final int CONNECTION_FLAG_READ_ONLY = 1 << 0; 109 110 /** 111 * Connection flag: Primary connection affinity. 112 * <p> 113 * This flag indicates that the primary connection is required. 114 * This flag helps support legacy applications that expect most data modifying 115 * operations to be serialized by locking the primary database connection. 116 * Setting this flag essentially implements the old "db lock" concept by preventing 117 * an operation from being performed until it can obtain exclusive access to 118 * the primary connection. 119 * </p> 120 */ 121 public static final int CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY = 1 << 1; 122 123 /** 124 * Connection flag: Connection is being used interactively. 125 * <p> 126 * This flag indicates that the connection is needed by the UI thread. 127 * The connection pool can use this flag to elevate the priority 128 * of the database connection request. 129 * </p> 130 */ 131 public static final int CONNECTION_FLAG_INTERACTIVE = 1 << 2; 132 133 private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) { 134 mConfiguration = new SQLiteDatabaseConfiguration(configuration); 135 } 136 137 @Override 138 protected void finalize() throws Throwable { 139 try { 140 dispose(true); 141 } finally { 142 super.finalize(); 143 } 144 } 145 146 /** 147 * Opens a connection pool for the specified database. 148 * 149 * @param configuration The database configuration. 150 * @return The connection pool. 151 * 152 * @throws SQLiteException if a database error occurs. 153 */ 154 public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) { 155 if (configuration == null) { 156 throw new IllegalArgumentException("configuration must not be null."); 157 } 158 159 // Create the pool. 160 SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration); 161 pool.open(); // might throw 162 return pool; 163 } 164 165 // Might throw 166 private void open() { 167 // Open the primary connection. 168 // This might throw if the database is corrupt. 169 mAvailablePrimaryConnection = openConnectionLocked( 170 true /*primaryConnection*/); // might throw 171 172 // Mark the pool as being open for business. 173 mIsOpen = true; 174 mCloseGuard.open("close"); 175 } 176 177 /** 178 * Closes the connection pool. 179 * <p> 180 * When the connection pool is closed, it will refuse all further requests 181 * to acquire connections. All connections that are currently available in 182 * the pool are closed immediately. Any connections that are still in use 183 * will be closed as soon as they are returned to the pool. 184 * </p> 185 * 186 * @throws IllegalStateException if the pool has been closed. 187 */ 188 public void close() { 189 dispose(false); 190 } 191 192 private void dispose(boolean finalized) { 193 if (mCloseGuard != null) { 194 if (finalized) { 195 mCloseGuard.warnIfOpen(); 196 } 197 mCloseGuard.close(); 198 } 199 200 if (!finalized) { 201 // Close all connections. We don't need (or want) to do this 202 // when finalized because we don't know what state the connections 203 // themselves will be in. The finalizer is really just here for CloseGuard. 204 // The connections will take care of themselves when their own finalizers run. 205 synchronized (mLock) { 206 throwIfClosedLocked(); 207 208 mIsOpen = false; 209 210 final int count = mAvailableNonPrimaryConnections.size(); 211 for (int i = 0; i < count; i++) { 212 closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i)); 213 } 214 mAvailableNonPrimaryConnections.clear(); 215 216 if (mAvailablePrimaryConnection != null) { 217 closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection); 218 mAvailablePrimaryConnection = null; 219 } 220 221 final int pendingCount = mAcquiredConnections.size(); 222 if (pendingCount != 0) { 223 Log.i(TAG, "The connection pool for " + mConfiguration.label 224 + " has been closed but there are still " 225 + pendingCount + " connections in use. They will be closed " 226 + "as they are released back to the pool."); 227 } 228 229 wakeConnectionWaitersLocked(); 230 } 231 } 232 } 233 234 /** 235 * Reconfigures the database configuration of the connection pool and all of its 236 * connections. 237 * <p> 238 * Configuration changes are propagated down to connections immediately if 239 * they are available or as soon as they are released. This includes changes 240 * that affect the size of the pool. 241 * </p> 242 * 243 * @param configuration The new configuration. 244 * 245 * @throws IllegalStateException if the pool has been closed. 246 */ 247 public void reconfigure(SQLiteDatabaseConfiguration configuration) { 248 if (configuration == null) { 249 throw new IllegalArgumentException("configuration must not be null."); 250 } 251 252 synchronized (mLock) { 253 throwIfClosedLocked(); 254 255 final boolean poolSizeChanged = mConfiguration.maxConnectionPoolSize 256 != configuration.maxConnectionPoolSize; 257 mConfiguration.updateParametersFrom(configuration); 258 259 if (poolSizeChanged) { 260 int availableCount = mAvailableNonPrimaryConnections.size(); 261 while (availableCount-- > mConfiguration.maxConnectionPoolSize - 1) { 262 SQLiteConnection connection = 263 mAvailableNonPrimaryConnections.remove(availableCount); 264 closeConnectionAndLogExceptionsLocked(connection); 265 } 266 } 267 268 reconfigureAllConnectionsLocked(); 269 270 wakeConnectionWaitersLocked(); 271 } 272 } 273 274 /** 275 * Acquires a connection from the pool. 276 * <p> 277 * The caller must call {@link #releaseConnection} to release the connection 278 * back to the pool when it is finished. Failure to do so will result 279 * in much unpleasantness. 280 * </p> 281 * 282 * @param sql If not null, try to find a connection that already has 283 * the specified SQL statement in its prepared statement cache. 284 * @param connectionFlags The connection request flags. 285 * @return The connection that was acquired, never null. 286 * 287 * @throws IllegalStateException if the pool has been closed. 288 * @throws SQLiteException if a database error occurs. 289 */ 290 public SQLiteConnection acquireConnection(String sql, int connectionFlags) { 291 return waitForConnection(sql, connectionFlags); 292 } 293 294 /** 295 * Releases a connection back to the pool. 296 * <p> 297 * It is ok to call this method after the pool has closed, to release 298 * connections that were still in use at the time of closure. 299 * </p> 300 * 301 * @param connection The connection to release. Must not be null. 302 * 303 * @throws IllegalStateException if the connection was not acquired 304 * from this pool or if it has already been released. 305 */ 306 public void releaseConnection(SQLiteConnection connection) { 307 synchronized (mLock) { 308 Boolean mustReconfigure = mAcquiredConnections.remove(connection); 309 if (mustReconfigure == null) { 310 throw new IllegalStateException("Cannot perform this operation " 311 + "because the specified connection was not acquired " 312 + "from this pool or has already been released."); 313 } 314 315 if (!mIsOpen) { 316 closeConnectionAndLogExceptionsLocked(connection); 317 } else if (connection.isPrimaryConnection()) { 318 assert mAvailablePrimaryConnection == null; 319 try { 320 if (mustReconfigure == Boolean.TRUE) { 321 connection.reconfigure(mConfiguration); // might throw 322 } 323 } catch (RuntimeException ex) { 324 Log.e(TAG, "Failed to reconfigure released primary connection, closing it: " 325 + connection, ex); 326 closeConnectionAndLogExceptionsLocked(connection); 327 connection = null; 328 } 329 if (connection != null) { 330 mAvailablePrimaryConnection = connection; 331 } 332 wakeConnectionWaitersLocked(); 333 } else if (mAvailableNonPrimaryConnections.size() >= 334 mConfiguration.maxConnectionPoolSize - 1) { 335 closeConnectionAndLogExceptionsLocked(connection); 336 } else { 337 try { 338 if (mustReconfigure == Boolean.TRUE) { 339 connection.reconfigure(mConfiguration); // might throw 340 } 341 } catch (RuntimeException ex) { 342 Log.e(TAG, "Failed to reconfigure released non-primary connection, " 343 + "closing it: " + connection, ex); 344 closeConnectionAndLogExceptionsLocked(connection); 345 connection = null; 346 } 347 if (connection != null) { 348 mAvailableNonPrimaryConnections.add(connection); 349 } 350 wakeConnectionWaitersLocked(); 351 } 352 } 353 } 354 355 /** 356 * Returns true if the session should yield the connection due to 357 * contention over available database connections. 358 * 359 * @param connection The connection owned by the session. 360 * @param connectionFlags The connection request flags. 361 * @return True if the session should yield its connection. 362 * 363 * @throws IllegalStateException if the connection was not acquired 364 * from this pool or if it has already been released. 365 */ 366 public boolean shouldYieldConnection(SQLiteConnection connection, int connectionFlags) { 367 synchronized (mLock) { 368 if (!mAcquiredConnections.containsKey(connection)) { 369 throw new IllegalStateException("Cannot perform this operation " 370 + "because the specified connection was not acquired " 371 + "from this pool or has already been released."); 372 } 373 374 if (!mIsOpen) { 375 return false; 376 } 377 378 return isSessionBlockingImportantConnectionWaitersLocked( 379 connection.isPrimaryConnection(), connectionFlags); 380 } 381 } 382 383 /** 384 * Collects statistics about database connection memory usage. 385 * 386 * @param dbStatsList The list to populate. 387 */ 388 public void collectDbStats(ArrayList<DbStats> dbStatsList) { 389 synchronized (mLock) { 390 if (mAvailablePrimaryConnection != null) { 391 mAvailablePrimaryConnection.collectDbStats(dbStatsList); 392 } 393 394 for (SQLiteConnection connection : mAvailableNonPrimaryConnections) { 395 connection.collectDbStats(dbStatsList); 396 } 397 398 for (SQLiteConnection connection : mAcquiredConnections.keySet()) { 399 connection.collectDbStatsUnsafe(dbStatsList); 400 } 401 } 402 } 403 404 // Might throw. 405 private SQLiteConnection openConnectionLocked(boolean primaryConnection) { 406 final int connectionId = mNextConnectionId++; 407 return SQLiteConnection.open(this, mConfiguration, 408 connectionId, primaryConnection); // might throw 409 } 410 411 void onConnectionLeaked() { 412 // This code is running inside of the SQLiteConnection finalizer. 413 // 414 // We don't know whether it is just the connection that has been finalized (and leaked) 415 // or whether the connection pool has also been or is about to be finalized. 416 // Consequently, it would be a bad idea to try to grab any locks or to 417 // do any significant work here. So we do the simplest possible thing and 418 // set a flag. waitForConnection() periodically checks this flag (when it 419 // times out) so that it can recover from leaked connections and wake 420 // itself or other threads up if necessary. 421 // 422 // You might still wonder why we don't try to do more to wake up the waiters 423 // immediately. First, as explained above, it would be hard to do safely 424 // unless we started an extra Thread to function as a reference queue. Second, 425 // this is never supposed to happen in normal operation. Third, there is no 426 // guarantee that the GC will actually detect the leak in a timely manner so 427 // it's not all that important that we recover from the leak in a timely manner 428 // either. Fourth, if a badly behaved application finds itself hung waiting for 429 // several seconds while waiting for a leaked connection to be detected and recreated, 430 // then perhaps its authors will have added incentive to fix the problem! 431 432 Log.w(TAG, "A SQLiteConnection object for database '" 433 + mConfiguration.label + "' was leaked! Please fix your application " 434 + "to end transactions in progress properly and to close the database " 435 + "when it is no longer needed."); 436 437 mConnectionLeaked.set(true); 438 } 439 440 // Can't throw. 441 private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) { 442 try { 443 connection.close(); // might throw 444 } catch (RuntimeException ex) { 445 Log.e(TAG, "Failed to close connection, its fate is now in the hands " 446 + "of the merciful GC: " + connection, ex); 447 } 448 } 449 450 // Can't throw. 451 private void reconfigureAllConnectionsLocked() { 452 boolean wake = false; 453 if (mAvailablePrimaryConnection != null) { 454 try { 455 mAvailablePrimaryConnection.reconfigure(mConfiguration); // might throw 456 } catch (RuntimeException ex) { 457 Log.e(TAG, "Failed to reconfigure available primary connection, closing it: " 458 + mAvailablePrimaryConnection, ex); 459 closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection); 460 mAvailablePrimaryConnection = null; 461 wake = true; 462 } 463 } 464 465 int count = mAvailableNonPrimaryConnections.size(); 466 for (int i = 0; i < count; i++) { 467 final SQLiteConnection connection = mAvailableNonPrimaryConnections.get(i); 468 try { 469 connection.reconfigure(mConfiguration); // might throw 470 } catch (RuntimeException ex) { 471 Log.e(TAG, "Failed to reconfigure available non-primary connection, closing it: " 472 + connection, ex); 473 closeConnectionAndLogExceptionsLocked(connection); 474 mAvailableNonPrimaryConnections.remove(i--); 475 count -= 1; 476 wake = true; 477 } 478 } 479 480 if (!mAcquiredConnections.isEmpty()) { 481 ArrayList<SQLiteConnection> keysToUpdate = new ArrayList<SQLiteConnection>( 482 mAcquiredConnections.size()); 483 for (Map.Entry<SQLiteConnection, Boolean> entry : mAcquiredConnections.entrySet()) { 484 if (entry.getValue() != Boolean.TRUE) { 485 keysToUpdate.add(entry.getKey()); 486 } 487 } 488 final int updateCount = keysToUpdate.size(); 489 for (int i = 0; i < updateCount; i++) { 490 mAcquiredConnections.put(keysToUpdate.get(i), Boolean.TRUE); 491 } 492 } 493 494 if (wake) { 495 wakeConnectionWaitersLocked(); 496 } 497 } 498 499 // Might throw. 500 private SQLiteConnection waitForConnection(String sql, int connectionFlags) { 501 final boolean wantPrimaryConnection = 502 (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0; 503 504 final ConnectionWaiter waiter; 505 synchronized (mLock) { 506 throwIfClosedLocked(); 507 508 // Try to acquire a connection. 509 SQLiteConnection connection = null; 510 if (!wantPrimaryConnection) { 511 connection = tryAcquireNonPrimaryConnectionLocked( 512 sql, connectionFlags); // might throw 513 } 514 if (connection == null) { 515 connection = tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw 516 } 517 if (connection != null) { 518 return connection; 519 } 520 521 // No connections available. Enqueue a waiter in priority order. 522 final int priority = getPriority(connectionFlags); 523 final long startTime = SystemClock.uptimeMillis(); 524 waiter = obtainConnectionWaiterLocked(Thread.currentThread(), startTime, 525 priority, wantPrimaryConnection, sql, connectionFlags); 526 ConnectionWaiter predecessor = null; 527 ConnectionWaiter successor = mConnectionWaiterQueue; 528 while (successor != null) { 529 if (priority > successor.mPriority) { 530 waiter.mNext = successor; 531 break; 532 } 533 predecessor = successor; 534 successor = successor.mNext; 535 } 536 if (predecessor != null) { 537 predecessor.mNext = waiter; 538 } else { 539 mConnectionWaiterQueue = waiter; 540 } 541 } 542 543 // Park the thread until a connection is assigned or the pool is closed. 544 // Rethrow an exception from the wait, if we got one. 545 long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS; 546 long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis; 547 for (;;) { 548 // Detect and recover from connection leaks. 549 if (mConnectionLeaked.compareAndSet(true, false)) { 550 wakeConnectionWaitersLocked(); 551 } 552 553 // Wait to be unparked (may already have happened), a timeout, or interruption. 554 LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L); 555 556 // Clear the interrupted flag, just in case. 557 Thread.interrupted(); 558 559 // Check whether we are done waiting yet. 560 synchronized (mLock) { 561 throwIfClosedLocked(); 562 563 SQLiteConnection connection = waiter.mAssignedConnection; 564 if (connection != null) { 565 recycleConnectionWaiterLocked(waiter); 566 return connection; 567 } 568 569 RuntimeException ex = waiter.mException; 570 if (ex != null) { 571 recycleConnectionWaiterLocked(waiter); 572 throw ex; // rethrow! 573 } 574 575 final long now = SystemClock.uptimeMillis(); 576 if (now < nextBusyTimeoutTime) { 577 busyTimeoutMillis = now - nextBusyTimeoutTime; 578 } else { 579 logConnectionPoolBusyLocked(now - waiter.mStartTime, connectionFlags); 580 busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS; 581 nextBusyTimeoutTime = now + busyTimeoutMillis; 582 } 583 } 584 } 585 } 586 587 // Can't throw. 588 private void logConnectionPoolBusyLocked(long waitMillis, int connectionFlags) { 589 final Thread thread = Thread.currentThread(); 590 StringBuilder msg = new StringBuilder(); 591 msg.append("The connection pool for database '").append(mConfiguration.label); 592 msg.append("' has been unable to grant a connection to thread "); 593 msg.append(thread.getId()).append(" (").append(thread.getName()).append(") "); 594 msg.append("with flags 0x").append(Integer.toHexString(connectionFlags)); 595 msg.append(" for ").append(waitMillis * 0.001f).append(" seconds.\n"); 596 597 ArrayList<String> requests = new ArrayList<String>(); 598 int activeConnections = 0; 599 int idleConnections = 0; 600 if (!mAcquiredConnections.isEmpty()) { 601 for (Map.Entry<SQLiteConnection, Boolean> entry : mAcquiredConnections.entrySet()) { 602 final SQLiteConnection connection = entry.getKey(); 603 String description = connection.describeCurrentOperationUnsafe(); 604 if (description != null) { 605 requests.add(description); 606 activeConnections += 1; 607 } else { 608 idleConnections += 1; 609 } 610 } 611 } 612 int availableConnections = mAvailableNonPrimaryConnections.size(); 613 if (mAvailablePrimaryConnection != null) { 614 availableConnections += 1; 615 } 616 617 msg.append("Connections: ").append(activeConnections).append(" active, "); 618 msg.append(idleConnections).append(" idle, "); 619 msg.append(availableConnections).append(" available.\n"); 620 621 if (!requests.isEmpty()) { 622 msg.append("\nRequests in progress:\n"); 623 for (String request : requests) { 624 msg.append(" ").append(request).append("\n"); 625 } 626 } 627 628 Log.w(TAG, msg.toString()); 629 } 630 631 // Can't throw. 632 private void wakeConnectionWaitersLocked() { 633 // Unpark all waiters that have requests that we can fulfill. 634 // This method is designed to not throw runtime exceptions, although we might send 635 // a waiter an exception for it to rethrow. 636 ConnectionWaiter predecessor = null; 637 ConnectionWaiter waiter = mConnectionWaiterQueue; 638 boolean primaryConnectionNotAvailable = false; 639 boolean nonPrimaryConnectionNotAvailable = false; 640 while (waiter != null) { 641 boolean unpark = false; 642 if (!mIsOpen) { 643 unpark = true; 644 } else { 645 try { 646 SQLiteConnection connection = null; 647 if (!waiter.mWantPrimaryConnection && !nonPrimaryConnectionNotAvailable) { 648 connection = tryAcquireNonPrimaryConnectionLocked( 649 waiter.mSql, waiter.mConnectionFlags); // might throw 650 if (connection == null) { 651 nonPrimaryConnectionNotAvailable = true; 652 } 653 } 654 if (connection == null && !primaryConnectionNotAvailable) { 655 connection = tryAcquirePrimaryConnectionLocked( 656 waiter.mConnectionFlags); // might throw 657 if (connection == null) { 658 primaryConnectionNotAvailable = true; 659 } 660 } 661 if (connection != null) { 662 waiter.mAssignedConnection = connection; 663 unpark = true; 664 } else if (nonPrimaryConnectionNotAvailable && primaryConnectionNotAvailable) { 665 // There are no connections available and the pool is still open. 666 // We cannot fulfill any more connection requests, so stop here. 667 break; 668 } 669 } catch (RuntimeException ex) { 670 // Let the waiter handle the exception from acquiring a connection. 671 waiter.mException = ex; 672 unpark = true; 673 } 674 } 675 676 final ConnectionWaiter successor = waiter.mNext; 677 if (unpark) { 678 if (predecessor != null) { 679 predecessor.mNext = successor; 680 } else { 681 mConnectionWaiterQueue = successor; 682 } 683 waiter.mNext = null; 684 685 LockSupport.unpark(waiter.mThread); 686 } else { 687 predecessor = waiter; 688 } 689 waiter = successor; 690 } 691 } 692 693 // Might throw. 694 private SQLiteConnection tryAcquirePrimaryConnectionLocked(int connectionFlags) { 695 // If the primary connection is available, acquire it now. 696 SQLiteConnection connection = mAvailablePrimaryConnection; 697 if (connection != null) { 698 mAvailablePrimaryConnection = null; 699 finishAcquireConnectionLocked(connection, connectionFlags); // might throw 700 return connection; 701 } 702 703 // Make sure that the primary connection actually exists and has just been acquired. 704 for (SQLiteConnection acquiredConnection : mAcquiredConnections.keySet()) { 705 if (acquiredConnection.isPrimaryConnection()) { 706 return null; 707 } 708 } 709 710 // Uhoh. No primary connection! Either this is the first time we asked 711 // for it, or maybe it leaked? 712 connection = openConnectionLocked(true /*primaryConnection*/); // might throw 713 finishAcquireConnectionLocked(connection, connectionFlags); // might throw 714 return connection; 715 } 716 717 // Might throw. 718 private SQLiteConnection tryAcquireNonPrimaryConnectionLocked( 719 String sql, int connectionFlags) { 720 // Try to acquire the next connection in the queue. 721 SQLiteConnection connection; 722 final int availableCount = mAvailableNonPrimaryConnections.size(); 723 if (availableCount > 1 && sql != null) { 724 // If we have a choice, then prefer a connection that has the 725 // prepared statement in its cache. 726 for (int i = 0; i < availableCount; i++) { 727 connection = mAvailableNonPrimaryConnections.get(i); 728 if (connection.isPreparedStatementInCache(sql)) { 729 mAvailableNonPrimaryConnections.remove(i); 730 finishAcquireConnectionLocked(connection, connectionFlags); // might throw 731 return connection; 732 } 733 } 734 } 735 if (availableCount > 0) { 736 // Otherwise, just grab the next one. 737 connection = mAvailableNonPrimaryConnections.remove(availableCount - 1); 738 finishAcquireConnectionLocked(connection, connectionFlags); // might throw 739 return connection; 740 } 741 742 // Expand the pool if needed. 743 int openConnections = mAcquiredConnections.size(); 744 if (mAvailablePrimaryConnection != null) { 745 openConnections += 1; 746 } 747 if (openConnections >= mConfiguration.maxConnectionPoolSize) { 748 return null; 749 } 750 connection = openConnectionLocked(false /*primaryConnection*/); // might throw 751 finishAcquireConnectionLocked(connection, connectionFlags); // might throw 752 return connection; 753 } 754 755 // Might throw. 756 private void finishAcquireConnectionLocked(SQLiteConnection connection, int connectionFlags) { 757 try { 758 final boolean readOnly = (connectionFlags & CONNECTION_FLAG_READ_ONLY) != 0; 759 connection.setOnlyAllowReadOnlyOperations(readOnly); 760 761 mAcquiredConnections.put(connection, Boolean.FALSE); 762 } catch (RuntimeException ex) { 763 Log.e(TAG, "Failed to prepare acquired connection for session, closing it: " 764 + connection +", connectionFlags=" + connectionFlags); 765 closeConnectionAndLogExceptionsLocked(connection); 766 throw ex; // rethrow! 767 } 768 } 769 770 private boolean isSessionBlockingImportantConnectionWaitersLocked( 771 boolean holdingPrimaryConnection, int connectionFlags) { 772 ConnectionWaiter waiter = mConnectionWaiterQueue; 773 if (waiter != null) { 774 final int priority = getPriority(connectionFlags); 775 do { 776 // Only worry about blocked connections that have same or lower priority. 777 if (priority > waiter.mPriority) { 778 break; 779 } 780 781 // If we are holding the primary connection then we are blocking the waiter. 782 // Likewise, if we are holding a non-primary connection and the waiter 783 // would accept a non-primary connection, then we are blocking the waier. 784 if (holdingPrimaryConnection || !waiter.mWantPrimaryConnection) { 785 return true; 786 } 787 788 waiter = waiter.mNext; 789 } while (waiter != null); 790 } 791 return false; 792 } 793 794 private static int getPriority(int connectionFlags) { 795 return (connectionFlags & CONNECTION_FLAG_INTERACTIVE) != 0 ? 1 : 0; 796 } 797 798 private void throwIfClosedLocked() { 799 if (!mIsOpen) { 800 throw new IllegalStateException("Cannot perform this operation " 801 + "because the connection pool have been closed."); 802 } 803 } 804 805 private ConnectionWaiter obtainConnectionWaiterLocked(Thread thread, long startTime, 806 int priority, boolean wantPrimaryConnection, String sql, int connectionFlags) { 807 ConnectionWaiter waiter = mConnectionWaiterPool; 808 if (waiter != null) { 809 mConnectionWaiterPool = waiter.mNext; 810 waiter.mNext = null; 811 } else { 812 waiter = new ConnectionWaiter(); 813 } 814 waiter.mThread = thread; 815 waiter.mStartTime = startTime; 816 waiter.mPriority = priority; 817 waiter.mWantPrimaryConnection = wantPrimaryConnection; 818 waiter.mSql = sql; 819 waiter.mConnectionFlags = connectionFlags; 820 return waiter; 821 } 822 823 private void recycleConnectionWaiterLocked(ConnectionWaiter waiter) { 824 waiter.mNext = mConnectionWaiterPool; 825 waiter.mThread = null; 826 waiter.mSql = null; 827 waiter.mAssignedConnection = null; 828 waiter.mException = null; 829 mConnectionWaiterPool = waiter; 830 } 831 832 /** 833 * Dumps debugging information about this connection pool. 834 * 835 * @param printer The printer to receive the dump, not null. 836 * @param verbose True to dump more verbose information. 837 */ 838 public void dump(Printer printer, boolean verbose) { 839 Printer indentedPrinter = PrefixPrinter.create(printer, " "); 840 synchronized (mLock) { 841 printer.println("Connection pool for " + mConfiguration.path + ":"); 842 printer.println(" Open: " + mIsOpen); 843 printer.println(" Max connections: " + mConfiguration.maxConnectionPoolSize); 844 845 printer.println(" Available primary connection:"); 846 if (mAvailablePrimaryConnection != null) { 847 mAvailablePrimaryConnection.dump(indentedPrinter, verbose); 848 } else { 849 indentedPrinter.println("<none>"); 850 } 851 852 printer.println(" Available non-primary connections:"); 853 if (!mAvailableNonPrimaryConnections.isEmpty()) { 854 final int count = mAvailableNonPrimaryConnections.size(); 855 for (int i = 0; i < count; i++) { 856 mAvailableNonPrimaryConnections.get(i).dump(indentedPrinter, verbose); 857 } 858 } else { 859 indentedPrinter.println("<none>"); 860 } 861 862 printer.println(" Acquired connections:"); 863 if (!mAcquiredConnections.isEmpty()) { 864 for (Map.Entry<SQLiteConnection, Boolean> entry : 865 mAcquiredConnections.entrySet()) { 866 final SQLiteConnection connection = entry.getKey(); 867 connection.dumpUnsafe(indentedPrinter, verbose); 868 indentedPrinter.println(" Pending reconfiguration: " + entry.getValue()); 869 } 870 } else { 871 indentedPrinter.println("<none>"); 872 } 873 874 printer.println(" Connection waiters:"); 875 if (mConnectionWaiterQueue != null) { 876 int i = 0; 877 final long now = SystemClock.uptimeMillis(); 878 for (ConnectionWaiter waiter = mConnectionWaiterQueue; waiter != null; 879 waiter = waiter.mNext, i++) { 880 indentedPrinter.println(i + ": waited for " 881 + ((now - waiter.mStartTime) * 0.001f) 882 + " ms - thread=" + waiter.mThread 883 + ", priority=" + waiter.mPriority 884 + ", sql='" + waiter.mSql + "'"); 885 } 886 } else { 887 indentedPrinter.println("<none>"); 888 } 889 } 890 } 891 892 @Override 893 public String toString() { 894 return "SQLiteConnectionPool: " + mConfiguration.path; 895 } 896 897 private static final class ConnectionWaiter { 898 public ConnectionWaiter mNext; 899 public Thread mThread; 900 public long mStartTime; 901 public int mPriority; 902 public boolean mWantPrimaryConnection; 903 public String mSql; 904 public int mConnectionFlags; 905 public SQLiteConnection mAssignedConnection; 906 public RuntimeException mException; 907 } 908} 909