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