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