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