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