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