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