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