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