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 
SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration)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
finalize()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      */
open(SQLiteDatabaseConfiguration configuration)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
open()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      */
close()229     public void close() {
230         dispose(false);
231     }
232 
dispose(boolean finalized)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      */
reconfigure(SQLiteDatabaseConfiguration configuration)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      */
acquireConnection(String sql, int connectionFlags, CancellationSignal cancellationSignal)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      */
releaseConnection(SQLiteConnection connection)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")
recycleConnectionLocked(SQLiteConnection connection, AcquiredConnectionStatus status)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      */
shouldYieldConnection(SQLiteConnection connection, int connectionFlags)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      */
collectDbStats(ArrayList<DbStats> dbStatsList)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.
openConnectionLocked(SQLiteDatabaseConfiguration configuration, boolean primaryConnection)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 
onConnectionLeaked()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 
onStatementExecuted(long executionTimeMs)536     void onStatementExecuted(long executionTimeMs) {
537         mTotalExecutionTimeCounter.addAndGet(executionTimeMs);
538     }
539 
540     // Can't throw.
541     @GuardedBy("mLock")
closeAvailableConnectionsAndLogExceptionsLocked()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")
closeAvailableConnectionLocked(int connectionId)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")
closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked()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      */
closeAvailableNonPrimaryConnectionsAndLogExceptions()587     void closeAvailableNonPrimaryConnectionsAndLogExceptions() {
588         synchronized (mLock) {
589             closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
590         }
591     }
592 
593     // Can't throw.
594     @GuardedBy("mLock")
closeExcessConnectionsAndLogExceptionsLocked()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")
closeConnectionAndLogExceptionsLocked(SQLiteConnection connection)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.
discardAcquiredConnectionsLocked()619     private void discardAcquiredConnectionsLocked() {
620         markAcquiredConnectionsLocked(AcquiredConnectionStatus.DISCARD);
621     }
622 
623     // Can't throw.
624     @GuardedBy("mLock")
reconfigureAllConnectionsLocked()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.
markAcquiredConnectionsLocked(AcquiredConnectionStatus status)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.
waitForConnection(String sql, int connectionFlags, CancellationSignal cancellationSignal)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")
cancelConnectionWaiterLocked(ConnectionWaiter waiter)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.
logConnectionPoolBusyLocked(long waitMillis, int connectionFlags)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")
wakeConnectionWaitersLocked()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")
tryAcquirePrimaryConnectionLocked(int connectionFlags)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")
tryAcquireNonPrimaryConnectionLocked( String sql, int connectionFlags)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")
finishAcquireConnectionLocked(SQLiteConnection connection, int connectionFlags)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 
isSessionBlockingImportantConnectionWaitersLocked( boolean holdingPrimaryConnection, int connectionFlags)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 
getPriority(int connectionFlags)1033     private static int getPriority(int connectionFlags) {
1034         return (connectionFlags & CONNECTION_FLAG_INTERACTIVE) != 0 ? 1 : 0;
1035     }
1036 
setMaxConnectionPoolSizeLocked()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
setupIdleConnectionHandler(Looper looper, long timeoutMs)1055     public void setupIdleConnectionHandler(Looper looper, long timeoutMs) {
1056         synchronized (mLock) {
1057             mIdleConnectionHandler = new IdleConnectionHandler(looper, timeoutMs);
1058         }
1059     }
1060 
disableIdleConnectionHandler()1061     void disableIdleConnectionHandler() {
1062         synchronized (mLock) {
1063             mIdleConnectionHandler = null;
1064         }
1065     }
1066 
throwIfClosedLocked()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 
obtainConnectionWaiterLocked(Thread thread, long startTime, int priority, boolean wantPrimaryConnection, String sql, int connectionFlags)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 
recycleConnectionWaiterLocked(ConnectionWaiter waiter)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      */
dump(Printer printer, boolean verbose)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
toString()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 
IdleConnectionHandler(Looper looper, long timeout)1202         IdleConnectionHandler(Looper looper, long timeout) {
1203             super(looper);
1204             mTimeout = timeout;
1205         }
1206 
1207         @Override
handleMessage(Message msg)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 
connectionReleased(SQLiteConnection con)1223         void connectionReleased(SQLiteConnection con) {
1224             sendEmptyMessageDelayed(con.getConnectionId(), mTimeout);
1225         }
1226 
connectionAcquired(SQLiteConnection con)1227         void connectionAcquired(SQLiteConnection con) {
1228             // Remove any pending close operations
1229             removeMessages(con.getConnectionId());
1230         }
1231 
connectionClosed(SQLiteConnection con)1232         void connectionClosed(SQLiteConnection con) {
1233             removeMessages(con.getConnectionId());
1234         }
1235     }
1236 }
1237