SQLiteConnectionPool.java revision 47847f3f4dcf2a0dbea0bc0e4f02528e21d37a88
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 dalvik.system.CloseGuard;
20
21import android.content.CancellationSignal;
22import android.content.OperationCanceledException;
23import android.database.sqlite.SQLiteDebug.DbStats;
24import android.os.SystemClock;
25import android.util.Log;
26import android.util.PrefixPrinter;
27import android.util.Printer;
28
29import java.io.Closeable;
30import java.util.ArrayList;
31import java.util.Map;
32import java.util.WeakHashMap;
33import java.util.concurrent.atomic.AtomicBoolean;
34import 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 */
72public 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
148    private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) {
149        mConfiguration = new SQLiteDatabaseConfiguration(configuration);
150        setMaxConnectionPoolSizeLocked();
151    }
152
153    @Override
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     */
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
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     */
204    public void close() {
205        dispose(false);
206    }
207
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     */
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            if (mConfiguration.openFlags != configuration.openFlags) {
281                // If we are changing open flags and WAL mode at the same time, then
282                // we have no choice but to close the primary connection beforehand
283                // because there can only be one connection open when we change WAL mode.
284                if (walModeChanged) {
285                    closeAvailableConnectionsAndLogExceptionsLocked();
286                }
287
288                // Try to reopen the primary connection using the new open flags then
289                // close and discard all existing connections.
290                // This might throw if the database is corrupt or cannot be opened in
291                // the new mode in which case existing connections will remain untouched.
292                SQLiteConnection newPrimaryConnection = openConnectionLocked(configuration,
293                        true /*primaryConnection*/); // might throw
294
295                closeAvailableConnectionsAndLogExceptionsLocked();
296                discardAcquiredConnectionsLocked();
297
298                mAvailablePrimaryConnection = newPrimaryConnection;
299                mConfiguration.updateParametersFrom(configuration);
300                setMaxConnectionPoolSizeLocked();
301            } else {
302                // Reconfigure the database connections in place.
303                mConfiguration.updateParametersFrom(configuration);
304                setMaxConnectionPoolSizeLocked();
305
306                closeExcessConnectionsAndLogExceptionsLocked();
307                reconfigureAllConnectionsLocked();
308            }
309
310            wakeConnectionWaitersLocked();
311        }
312    }
313
314    /**
315     * Acquires a connection from the pool.
316     * <p>
317     * The caller must call {@link #releaseConnection} to release the connection
318     * back to the pool when it is finished.  Failure to do so will result
319     * in much unpleasantness.
320     * </p>
321     *
322     * @param sql If not null, try to find a connection that already has
323     * the specified SQL statement in its prepared statement cache.
324     * @param connectionFlags The connection request flags.
325     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
326     * @return The connection that was acquired, never null.
327     *
328     * @throws IllegalStateException if the pool has been closed.
329     * @throws SQLiteException if a database error occurs.
330     * @throws OperationCanceledException if the operation was canceled.
331     */
332    public SQLiteConnection acquireConnection(String sql, int connectionFlags,
333            CancellationSignal cancellationSignal) {
334        return waitForConnection(sql, connectionFlags, cancellationSignal);
335    }
336
337    /**
338     * Releases a connection back to the pool.
339     * <p>
340     * It is ok to call this method after the pool has closed, to release
341     * connections that were still in use at the time of closure.
342     * </p>
343     *
344     * @param connection The connection to release.  Must not be null.
345     *
346     * @throws IllegalStateException if the connection was not acquired
347     * from this pool or if it has already been released.
348     */
349    public void releaseConnection(SQLiteConnection connection) {
350        synchronized (mLock) {
351            AcquiredConnectionStatus status = mAcquiredConnections.remove(connection);
352            if (status == null) {
353                throw new IllegalStateException("Cannot perform this operation "
354                        + "because the specified connection was not acquired "
355                        + "from this pool or has already been released.");
356            }
357
358            if (!mIsOpen) {
359                closeConnectionAndLogExceptionsLocked(connection);
360            } else if (connection.isPrimaryConnection()) {
361                if (recycleConnectionLocked(connection, status)) {
362                    assert mAvailablePrimaryConnection == null;
363                    mAvailablePrimaryConnection = connection;
364                }
365                wakeConnectionWaitersLocked();
366            } else if (mAvailableNonPrimaryConnections.size() >= mMaxConnectionPoolSize - 1) {
367                closeConnectionAndLogExceptionsLocked(connection);
368            } else {
369                if (recycleConnectionLocked(connection, status)) {
370                    mAvailableNonPrimaryConnections.add(connection);
371                }
372                wakeConnectionWaitersLocked();
373            }
374        }
375    }
376
377    // Can't throw.
378    private boolean recycleConnectionLocked(SQLiteConnection connection,
379            AcquiredConnectionStatus status) {
380        if (status == AcquiredConnectionStatus.RECONFIGURE) {
381            try {
382                connection.reconfigure(mConfiguration); // might throw
383            } catch (RuntimeException ex) {
384                Log.e(TAG, "Failed to reconfigure released connection, closing it: "
385                        + connection, ex);
386                status = AcquiredConnectionStatus.DISCARD;
387            }
388        }
389        if (status == AcquiredConnectionStatus.DISCARD) {
390            closeConnectionAndLogExceptionsLocked(connection);
391            return false;
392        }
393        return true;
394    }
395
396    /**
397     * Returns true if the session should yield the connection due to
398     * contention over available database connections.
399     *
400     * @param connection The connection owned by the session.
401     * @param connectionFlags The connection request flags.
402     * @return True if the session should yield its connection.
403     *
404     * @throws IllegalStateException if the connection was not acquired
405     * from this pool or if it has already been released.
406     */
407    public boolean shouldYieldConnection(SQLiteConnection connection, int connectionFlags) {
408        synchronized (mLock) {
409            if (!mAcquiredConnections.containsKey(connection)) {
410                throw new IllegalStateException("Cannot perform this operation "
411                        + "because the specified connection was not acquired "
412                        + "from this pool or has already been released.");
413            }
414
415            if (!mIsOpen) {
416                return false;
417            }
418
419            return isSessionBlockingImportantConnectionWaitersLocked(
420                    connection.isPrimaryConnection(), connectionFlags);
421        }
422    }
423
424    /**
425     * Collects statistics about database connection memory usage.
426     *
427     * @param dbStatsList The list to populate.
428     */
429    public void collectDbStats(ArrayList<DbStats> dbStatsList) {
430        synchronized (mLock) {
431            if (mAvailablePrimaryConnection != null) {
432                mAvailablePrimaryConnection.collectDbStats(dbStatsList);
433            }
434
435            for (SQLiteConnection connection : mAvailableNonPrimaryConnections) {
436                connection.collectDbStats(dbStatsList);
437            }
438
439            for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
440                connection.collectDbStatsUnsafe(dbStatsList);
441            }
442        }
443    }
444
445    // Might throw.
446    private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
447            boolean primaryConnection) {
448        final int connectionId = mNextConnectionId++;
449        return SQLiteConnection.open(this, configuration,
450                connectionId, primaryConnection); // might throw
451    }
452
453    void onConnectionLeaked() {
454        // This code is running inside of the SQLiteConnection finalizer.
455        //
456        // We don't know whether it is just the connection that has been finalized (and leaked)
457        // or whether the connection pool has also been or is about to be finalized.
458        // Consequently, it would be a bad idea to try to grab any locks or to
459        // do any significant work here.  So we do the simplest possible thing and
460        // set a flag.  waitForConnection() periodically checks this flag (when it
461        // times out) so that it can recover from leaked connections and wake
462        // itself or other threads up if necessary.
463        //
464        // You might still wonder why we don't try to do more to wake up the waiters
465        // immediately.  First, as explained above, it would be hard to do safely
466        // unless we started an extra Thread to function as a reference queue.  Second,
467        // this is never supposed to happen in normal operation.  Third, there is no
468        // guarantee that the GC will actually detect the leak in a timely manner so
469        // it's not all that important that we recover from the leak in a timely manner
470        // either.  Fourth, if a badly behaved application finds itself hung waiting for
471        // several seconds while waiting for a leaked connection to be detected and recreated,
472        // then perhaps its authors will have added incentive to fix the problem!
473
474        Log.w(TAG, "A SQLiteConnection object for database '"
475                + mConfiguration.label + "' was leaked!  Please fix your application "
476                + "to end transactions in progress properly and to close the database "
477                + "when it is no longer needed.");
478
479        mConnectionLeaked.set(true);
480    }
481
482    // Can't throw.
483    private void closeAvailableConnectionsAndLogExceptionsLocked() {
484        closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
485
486        if (mAvailablePrimaryConnection != null) {
487            closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
488            mAvailablePrimaryConnection = null;
489        }
490    }
491
492    // Can't throw.
493    private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() {
494        final int count = mAvailableNonPrimaryConnections.size();
495        for (int i = 0; i < count; i++) {
496            closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
497        }
498        mAvailableNonPrimaryConnections.clear();
499    }
500
501    // Can't throw.
502    private void closeExcessConnectionsAndLogExceptionsLocked() {
503        int availableCount = mAvailableNonPrimaryConnections.size();
504        while (availableCount-- > mMaxConnectionPoolSize - 1) {
505            SQLiteConnection connection =
506                    mAvailableNonPrimaryConnections.remove(availableCount);
507            closeConnectionAndLogExceptionsLocked(connection);
508        }
509    }
510
511    // Can't throw.
512    private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
513        try {
514            connection.close(); // might throw
515        } catch (RuntimeException ex) {
516            Log.e(TAG, "Failed to close connection, its fate is now in the hands "
517                    + "of the merciful GC: " + connection, ex);
518        }
519    }
520
521    // Can't throw.
522    private void discardAcquiredConnectionsLocked() {
523        markAcquiredConnectionsLocked(AcquiredConnectionStatus.DISCARD);
524    }
525
526    // Can't throw.
527    private void reconfigureAllConnectionsLocked() {
528        if (mAvailablePrimaryConnection != null) {
529            try {
530                mAvailablePrimaryConnection.reconfigure(mConfiguration); // might throw
531            } catch (RuntimeException ex) {
532                Log.e(TAG, "Failed to reconfigure available primary connection, closing it: "
533                        + mAvailablePrimaryConnection, ex);
534                closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
535                mAvailablePrimaryConnection = null;
536            }
537        }
538
539        int count = mAvailableNonPrimaryConnections.size();
540        for (int i = 0; i < count; i++) {
541            final SQLiteConnection connection = mAvailableNonPrimaryConnections.get(i);
542            try {
543                connection.reconfigure(mConfiguration); // might throw
544            } catch (RuntimeException ex) {
545                Log.e(TAG, "Failed to reconfigure available non-primary connection, closing it: "
546                        + connection, ex);
547                closeConnectionAndLogExceptionsLocked(connection);
548                mAvailableNonPrimaryConnections.remove(i--);
549                count -= 1;
550            }
551        }
552
553        markAcquiredConnectionsLocked(AcquiredConnectionStatus.RECONFIGURE);
554    }
555
556    // Can't throw.
557    private void markAcquiredConnectionsLocked(AcquiredConnectionStatus status) {
558        if (!mAcquiredConnections.isEmpty()) {
559            ArrayList<SQLiteConnection> keysToUpdate = new ArrayList<SQLiteConnection>(
560                    mAcquiredConnections.size());
561            for (Map.Entry<SQLiteConnection, AcquiredConnectionStatus> entry
562                    : mAcquiredConnections.entrySet()) {
563                AcquiredConnectionStatus oldStatus = entry.getValue();
564                if (status != oldStatus
565                        && oldStatus != AcquiredConnectionStatus.DISCARD) {
566                    keysToUpdate.add(entry.getKey());
567                }
568            }
569            final int updateCount = keysToUpdate.size();
570            for (int i = 0; i < updateCount; i++) {
571                mAcquiredConnections.put(keysToUpdate.get(i), status);
572            }
573        }
574    }
575
576    // Might throw.
577    private SQLiteConnection waitForConnection(String sql, int connectionFlags,
578            CancellationSignal cancellationSignal) {
579        final boolean wantPrimaryConnection =
580                (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;
581
582        final ConnectionWaiter waiter;
583        synchronized (mLock) {
584            throwIfClosedLocked();
585
586            // Abort if canceled.
587            if (cancellationSignal != null) {
588                cancellationSignal.throwIfCanceled();
589            }
590
591            // Try to acquire a connection.
592            SQLiteConnection connection = null;
593            if (!wantPrimaryConnection) {
594                connection = tryAcquireNonPrimaryConnectionLocked(
595                        sql, connectionFlags); // might throw
596            }
597            if (connection == null) {
598                connection = tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw
599            }
600            if (connection != null) {
601                return connection;
602            }
603
604            // No connections available.  Enqueue a waiter in priority order.
605            final int priority = getPriority(connectionFlags);
606            final long startTime = SystemClock.uptimeMillis();
607            waiter = obtainConnectionWaiterLocked(Thread.currentThread(), startTime,
608                    priority, wantPrimaryConnection, sql, connectionFlags);
609            ConnectionWaiter predecessor = null;
610            ConnectionWaiter successor = mConnectionWaiterQueue;
611            while (successor != null) {
612                if (priority > successor.mPriority) {
613                    waiter.mNext = successor;
614                    break;
615                }
616                predecessor = successor;
617                successor = successor.mNext;
618            }
619            if (predecessor != null) {
620                predecessor.mNext = waiter;
621            } else {
622                mConnectionWaiterQueue = waiter;
623            }
624
625            if (cancellationSignal != null) {
626                final int nonce = waiter.mNonce;
627                cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
628                    @Override
629                    public void onCancel() {
630                        synchronized (mLock) {
631                            cancelConnectionWaiterLocked(waiter, nonce);
632                        }
633                    }
634                });
635            }
636        }
637
638        // Park the thread until a connection is assigned or the pool is closed.
639        // Rethrow an exception from the wait, if we got one.
640        long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
641        long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis;
642        for (;;) {
643            // Detect and recover from connection leaks.
644            if (mConnectionLeaked.compareAndSet(true, false)) {
645                synchronized (mLock) {
646                    wakeConnectionWaitersLocked();
647                }
648            }
649
650            // Wait to be unparked (may already have happened), a timeout, or interruption.
651            LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L);
652
653            // Clear the interrupted flag, just in case.
654            Thread.interrupted();
655
656            // Check whether we are done waiting yet.
657            synchronized (mLock) {
658                throwIfClosedLocked();
659
660                final SQLiteConnection connection = waiter.mAssignedConnection;
661                final RuntimeException ex = waiter.mException;
662                if (connection != null || ex != null) {
663                    if (cancellationSignal != null) {
664                        cancellationSignal.setOnCancelListener(null);
665                    }
666                    recycleConnectionWaiterLocked(waiter);
667                    if (connection != null) {
668                        return connection;
669                    }
670                    throw ex; // rethrow!
671                }
672
673                final long now = SystemClock.uptimeMillis();
674                if (now < nextBusyTimeoutTime) {
675                    busyTimeoutMillis = now - nextBusyTimeoutTime;
676                } else {
677                    logConnectionPoolBusyLocked(now - waiter.mStartTime, connectionFlags);
678                    busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
679                    nextBusyTimeoutTime = now + busyTimeoutMillis;
680                }
681            }
682        }
683    }
684
685    // Can't throw.
686    private void cancelConnectionWaiterLocked(ConnectionWaiter waiter, int nonce) {
687        if (waiter.mNonce != nonce) {
688            // Waiter already removed and recycled.
689            return;
690        }
691
692        if (waiter.mAssignedConnection != null || waiter.mException != null) {
693            // Waiter is done waiting but has not woken up yet.
694            return;
695        }
696
697        // Waiter must still be waiting.  Dequeue it.
698        ConnectionWaiter predecessor = null;
699        ConnectionWaiter current = mConnectionWaiterQueue;
700        while (current != waiter) {
701            assert current != null;
702            predecessor = current;
703            current = current.mNext;
704        }
705        if (predecessor != null) {
706            predecessor.mNext = waiter.mNext;
707        } else {
708            mConnectionWaiterQueue = waiter.mNext;
709        }
710
711        // Send the waiter an exception and unpark it.
712        waiter.mException = new OperationCanceledException();
713        LockSupport.unpark(waiter.mThread);
714
715        // Check whether removing this waiter will enable other waiters to make progress.
716        wakeConnectionWaitersLocked();
717    }
718
719    // Can't throw.
720    private void logConnectionPoolBusyLocked(long waitMillis, int connectionFlags) {
721        final Thread thread = Thread.currentThread();
722        StringBuilder msg = new StringBuilder();
723        msg.append("The connection pool for database '").append(mConfiguration.label);
724        msg.append("' has been unable to grant a connection to thread ");
725        msg.append(thread.getId()).append(" (").append(thread.getName()).append(") ");
726        msg.append("with flags 0x").append(Integer.toHexString(connectionFlags));
727        msg.append(" for ").append(waitMillis * 0.001f).append(" seconds.\n");
728
729        ArrayList<String> requests = new ArrayList<String>();
730        int activeConnections = 0;
731        int idleConnections = 0;
732        if (!mAcquiredConnections.isEmpty()) {
733            for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
734                String description = connection.describeCurrentOperationUnsafe();
735                if (description != null) {
736                    requests.add(description);
737                    activeConnections += 1;
738                } else {
739                    idleConnections += 1;
740                }
741            }
742        }
743        int availableConnections = mAvailableNonPrimaryConnections.size();
744        if (mAvailablePrimaryConnection != null) {
745            availableConnections += 1;
746        }
747
748        msg.append("Connections: ").append(activeConnections).append(" active, ");
749        msg.append(idleConnections).append(" idle, ");
750        msg.append(availableConnections).append(" available.\n");
751
752        if (!requests.isEmpty()) {
753            msg.append("\nRequests in progress:\n");
754            for (String request : requests) {
755                msg.append("  ").append(request).append("\n");
756            }
757        }
758
759        Log.w(TAG, msg.toString());
760    }
761
762    // Can't throw.
763    private void wakeConnectionWaitersLocked() {
764        // Unpark all waiters that have requests that we can fulfill.
765        // This method is designed to not throw runtime exceptions, although we might send
766        // a waiter an exception for it to rethrow.
767        ConnectionWaiter predecessor = null;
768        ConnectionWaiter waiter = mConnectionWaiterQueue;
769        boolean primaryConnectionNotAvailable = false;
770        boolean nonPrimaryConnectionNotAvailable = false;
771        while (waiter != null) {
772            boolean unpark = false;
773            if (!mIsOpen) {
774                unpark = true;
775            } else {
776                try {
777                    SQLiteConnection connection = null;
778                    if (!waiter.mWantPrimaryConnection && !nonPrimaryConnectionNotAvailable) {
779                        connection = tryAcquireNonPrimaryConnectionLocked(
780                                waiter.mSql, waiter.mConnectionFlags); // might throw
781                        if (connection == null) {
782                            nonPrimaryConnectionNotAvailable = true;
783                        }
784                    }
785                    if (connection == null && !primaryConnectionNotAvailable) {
786                        connection = tryAcquirePrimaryConnectionLocked(
787                                waiter.mConnectionFlags); // might throw
788                        if (connection == null) {
789                            primaryConnectionNotAvailable = true;
790                        }
791                    }
792                    if (connection != null) {
793                        waiter.mAssignedConnection = connection;
794                        unpark = true;
795                    } else if (nonPrimaryConnectionNotAvailable && primaryConnectionNotAvailable) {
796                        // There are no connections available and the pool is still open.
797                        // We cannot fulfill any more connection requests, so stop here.
798                        break;
799                    }
800                } catch (RuntimeException ex) {
801                    // Let the waiter handle the exception from acquiring a connection.
802                    waiter.mException = ex;
803                    unpark = true;
804                }
805            }
806
807            final ConnectionWaiter successor = waiter.mNext;
808            if (unpark) {
809                if (predecessor != null) {
810                    predecessor.mNext = successor;
811                } else {
812                    mConnectionWaiterQueue = successor;
813                }
814                waiter.mNext = null;
815
816                LockSupport.unpark(waiter.mThread);
817            } else {
818                predecessor = waiter;
819            }
820            waiter = successor;
821        }
822    }
823
824    // Might throw.
825    private SQLiteConnection tryAcquirePrimaryConnectionLocked(int connectionFlags) {
826        // If the primary connection is available, acquire it now.
827        SQLiteConnection connection = mAvailablePrimaryConnection;
828        if (connection != null) {
829            mAvailablePrimaryConnection = null;
830            finishAcquireConnectionLocked(connection, connectionFlags); // might throw
831            return connection;
832        }
833
834        // Make sure that the primary connection actually exists and has just been acquired.
835        for (SQLiteConnection acquiredConnection : mAcquiredConnections.keySet()) {
836            if (acquiredConnection.isPrimaryConnection()) {
837                return null;
838            }
839        }
840
841        // Uhoh.  No primary connection!  Either this is the first time we asked
842        // for it, or maybe it leaked?
843        connection = openConnectionLocked(mConfiguration,
844                true /*primaryConnection*/); // might throw
845        finishAcquireConnectionLocked(connection, connectionFlags); // might throw
846        return connection;
847    }
848
849    // Might throw.
850    private SQLiteConnection tryAcquireNonPrimaryConnectionLocked(
851            String sql, int connectionFlags) {
852        // Try to acquire the next connection in the queue.
853        SQLiteConnection connection;
854        final int availableCount = mAvailableNonPrimaryConnections.size();
855        if (availableCount > 1 && sql != null) {
856            // If we have a choice, then prefer a connection that has the
857            // prepared statement in its cache.
858            for (int i = 0; i < availableCount; i++) {
859                connection = mAvailableNonPrimaryConnections.get(i);
860                if (connection.isPreparedStatementInCache(sql)) {
861                    mAvailableNonPrimaryConnections.remove(i);
862                    finishAcquireConnectionLocked(connection, connectionFlags); // might throw
863                    return connection;
864                }
865            }
866        }
867        if (availableCount > 0) {
868            // Otherwise, just grab the next one.
869            connection = mAvailableNonPrimaryConnections.remove(availableCount - 1);
870            finishAcquireConnectionLocked(connection, connectionFlags); // might throw
871            return connection;
872        }
873
874        // Expand the pool if needed.
875        int openConnections = mAcquiredConnections.size();
876        if (mAvailablePrimaryConnection != null) {
877            openConnections += 1;
878        }
879        if (openConnections >= mMaxConnectionPoolSize) {
880            return null;
881        }
882        connection = openConnectionLocked(mConfiguration,
883                false /*primaryConnection*/); // might throw
884        finishAcquireConnectionLocked(connection, connectionFlags); // might throw
885        return connection;
886    }
887
888    // Might throw.
889    private void finishAcquireConnectionLocked(SQLiteConnection connection, int connectionFlags) {
890        try {
891            final boolean readOnly = (connectionFlags & CONNECTION_FLAG_READ_ONLY) != 0;
892            connection.setOnlyAllowReadOnlyOperations(readOnly);
893
894            mAcquiredConnections.put(connection, AcquiredConnectionStatus.NORMAL);
895        } catch (RuntimeException ex) {
896            Log.e(TAG, "Failed to prepare acquired connection for session, closing it: "
897                    + connection +", connectionFlags=" + connectionFlags);
898            closeConnectionAndLogExceptionsLocked(connection);
899            throw ex; // rethrow!
900        }
901    }
902
903    private boolean isSessionBlockingImportantConnectionWaitersLocked(
904            boolean holdingPrimaryConnection, int connectionFlags) {
905        ConnectionWaiter waiter = mConnectionWaiterQueue;
906        if (waiter != null) {
907            final int priority = getPriority(connectionFlags);
908            do {
909                // Only worry about blocked connections that have same or lower priority.
910                if (priority > waiter.mPriority) {
911                    break;
912                }
913
914                // If we are holding the primary connection then we are blocking the waiter.
915                // Likewise, if we are holding a non-primary connection and the waiter
916                // would accept a non-primary connection, then we are blocking the waier.
917                if (holdingPrimaryConnection || !waiter.mWantPrimaryConnection) {
918                    return true;
919                }
920
921                waiter = waiter.mNext;
922            } while (waiter != null);
923        }
924        return false;
925    }
926
927    private static int getPriority(int connectionFlags) {
928        return (connectionFlags & CONNECTION_FLAG_INTERACTIVE) != 0 ? 1 : 0;
929    }
930
931    private void setMaxConnectionPoolSizeLocked() {
932        if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
933            mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
934        } else {
935            // TODO: We don't actually need to restrict the connection pool size to 1
936            // for non-WAL databases.  There might be reasons to use connection pooling
937            // with other journal modes.  For now, enabling connection pooling and
938            // using WAL are the same thing in the API.
939            mMaxConnectionPoolSize = 1;
940        }
941    }
942
943    private void throwIfClosedLocked() {
944        if (!mIsOpen) {
945            throw new IllegalStateException("Cannot perform this operation "
946                    + "because the connection pool has been closed.");
947        }
948    }
949
950    private ConnectionWaiter obtainConnectionWaiterLocked(Thread thread, long startTime,
951            int priority, boolean wantPrimaryConnection, String sql, int connectionFlags) {
952        ConnectionWaiter waiter = mConnectionWaiterPool;
953        if (waiter != null) {
954            mConnectionWaiterPool = waiter.mNext;
955            waiter.mNext = null;
956        } else {
957            waiter = new ConnectionWaiter();
958        }
959        waiter.mThread = thread;
960        waiter.mStartTime = startTime;
961        waiter.mPriority = priority;
962        waiter.mWantPrimaryConnection = wantPrimaryConnection;
963        waiter.mSql = sql;
964        waiter.mConnectionFlags = connectionFlags;
965        return waiter;
966    }
967
968    private void recycleConnectionWaiterLocked(ConnectionWaiter waiter) {
969        waiter.mNext = mConnectionWaiterPool;
970        waiter.mThread = null;
971        waiter.mSql = null;
972        waiter.mAssignedConnection = null;
973        waiter.mException = null;
974        waiter.mNonce += 1;
975        mConnectionWaiterPool = waiter;
976    }
977
978    /**
979     * Dumps debugging information about this connection pool.
980     *
981     * @param printer The printer to receive the dump, not null.
982     * @param verbose True to dump more verbose information.
983     */
984    public void dump(Printer printer, boolean verbose) {
985        Printer indentedPrinter = PrefixPrinter.create(printer, "    ");
986        synchronized (mLock) {
987            printer.println("Connection pool for " + mConfiguration.path + ":");
988            printer.println("  Open: " + mIsOpen);
989            printer.println("  Max connections: " + mMaxConnectionPoolSize);
990
991            printer.println("  Available primary connection:");
992            if (mAvailablePrimaryConnection != null) {
993                mAvailablePrimaryConnection.dump(indentedPrinter, verbose);
994            } else {
995                indentedPrinter.println("<none>");
996            }
997
998            printer.println("  Available non-primary connections:");
999            if (!mAvailableNonPrimaryConnections.isEmpty()) {
1000                final int count = mAvailableNonPrimaryConnections.size();
1001                for (int i = 0; i < count; i++) {
1002                    mAvailableNonPrimaryConnections.get(i).dump(indentedPrinter, verbose);
1003                }
1004            } else {
1005                indentedPrinter.println("<none>");
1006            }
1007
1008            printer.println("  Acquired connections:");
1009            if (!mAcquiredConnections.isEmpty()) {
1010                for (Map.Entry<SQLiteConnection, AcquiredConnectionStatus> entry :
1011                        mAcquiredConnections.entrySet()) {
1012                    final SQLiteConnection connection = entry.getKey();
1013                    connection.dumpUnsafe(indentedPrinter, verbose);
1014                    indentedPrinter.println("  Status: " + entry.getValue());
1015                }
1016            } else {
1017                indentedPrinter.println("<none>");
1018            }
1019
1020            printer.println("  Connection waiters:");
1021            if (mConnectionWaiterQueue != null) {
1022                int i = 0;
1023                final long now = SystemClock.uptimeMillis();
1024                for (ConnectionWaiter waiter = mConnectionWaiterQueue; waiter != null;
1025                        waiter = waiter.mNext, i++) {
1026                    indentedPrinter.println(i + ": waited for "
1027                            + ((now - waiter.mStartTime) * 0.001f)
1028                            + " ms - thread=" + waiter.mThread
1029                            + ", priority=" + waiter.mPriority
1030                            + ", sql='" + waiter.mSql + "'");
1031                }
1032            } else {
1033                indentedPrinter.println("<none>");
1034            }
1035        }
1036    }
1037
1038    @Override
1039    public String toString() {
1040        return "SQLiteConnectionPool: " + mConfiguration.path;
1041    }
1042
1043    private static final class ConnectionWaiter {
1044        public ConnectionWaiter mNext;
1045        public Thread mThread;
1046        public long mStartTime;
1047        public int mPriority;
1048        public boolean mWantPrimaryConnection;
1049        public String mSql;
1050        public int mConnectionFlags;
1051        public SQLiteConnection mAssignedConnection;
1052        public RuntimeException mException;
1053        public int mNonce;
1054    }
1055}
1056