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