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