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