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