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