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