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