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