blob: 27c9ee544f30efd65954989d9fe6c924f68767c9 [file] [log] [blame]
Jeff Browne5360fb2011-10-31 17:48:13 -07001/*
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
Jeff Brown4c1241d2012-02-02 17:05:00 -080021import android.content.CancellationSignal;
Jeff Brown75ea64f2012-01-25 19:37:13 -080022import android.content.OperationCanceledException;
Jeff Browne5360fb2011-10-31 17:48:13 -070023import 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 boolean mIsOpen;
85 private int mNextConnectionId;
86
87 private ConnectionWaiter mConnectionWaiterPool;
88 private ConnectionWaiter mConnectionWaiterQueue;
89
90 // Strong references to all available connections.
91 private final ArrayList<SQLiteConnection> mAvailableNonPrimaryConnections =
92 new ArrayList<SQLiteConnection>();
93 private SQLiteConnection mAvailablePrimaryConnection;
94
Jeff Brown559d0642012-02-29 10:19:12 -080095 // Describes what should happen to an acquired connection when it is returned to the pool.
96 enum AcquiredConnectionStatus {
97 // The connection should be returned to the pool as usual.
98 NORMAL,
99
100 // The connection must be reconfigured before being returned.
101 RECONFIGURE,
102
103 // The connection must be closed and discarded.
104 DISCARD,
105 }
106
Jeff Browne5360fb2011-10-31 17:48:13 -0700107 // Weak references to all acquired connections. The associated value
Jeff Brown559d0642012-02-29 10:19:12 -0800108 // indicates whether the connection must be reconfigured before being
109 // returned to the available connection list or discarded.
Jeff Browne5360fb2011-10-31 17:48:13 -0700110 // For example, the prepared statement cache size may have changed and
Jeff Brown559d0642012-02-29 10:19:12 -0800111 // need to be updated in preparation for the next client.
112 private final WeakHashMap<SQLiteConnection, AcquiredConnectionStatus> mAcquiredConnections =
113 new WeakHashMap<SQLiteConnection, AcquiredConnectionStatus>();
Jeff Browne5360fb2011-10-31 17:48:13 -0700114
115 /**
116 * Connection flag: Read-only.
117 * <p>
118 * This flag indicates that the connection will only be used to
119 * perform read-only operations.
120 * </p>
121 */
122 public static final int CONNECTION_FLAG_READ_ONLY = 1 << 0;
123
124 /**
125 * Connection flag: Primary connection affinity.
126 * <p>
127 * This flag indicates that the primary connection is required.
128 * This flag helps support legacy applications that expect most data modifying
129 * operations to be serialized by locking the primary database connection.
130 * Setting this flag essentially implements the old "db lock" concept by preventing
131 * an operation from being performed until it can obtain exclusive access to
132 * the primary connection.
133 * </p>
134 */
135 public static final int CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY = 1 << 1;
136
137 /**
138 * Connection flag: Connection is being used interactively.
139 * <p>
140 * This flag indicates that the connection is needed by the UI thread.
141 * The connection pool can use this flag to elevate the priority
142 * of the database connection request.
143 * </p>
144 */
145 public static final int CONNECTION_FLAG_INTERACTIVE = 1 << 2;
146
147 private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) {
148 mConfiguration = new SQLiteDatabaseConfiguration(configuration);
149 }
150
151 @Override
152 protected void finalize() throws Throwable {
153 try {
154 dispose(true);
155 } finally {
156 super.finalize();
157 }
158 }
159
160 /**
161 * Opens a connection pool for the specified database.
162 *
163 * @param configuration The database configuration.
164 * @return The connection pool.
165 *
166 * @throws SQLiteException if a database error occurs.
167 */
168 public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) {
169 if (configuration == null) {
170 throw new IllegalArgumentException("configuration must not be null.");
171 }
172
173 // Create the pool.
174 SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration);
175 pool.open(); // might throw
176 return pool;
177 }
178
179 // Might throw
180 private void open() {
181 // Open the primary connection.
182 // This might throw if the database is corrupt.
Jeff Brown559d0642012-02-29 10:19:12 -0800183 mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
Jeff Browne5360fb2011-10-31 17:48:13 -0700184 true /*primaryConnection*/); // might throw
185
186 // Mark the pool as being open for business.
187 mIsOpen = true;
188 mCloseGuard.open("close");
189 }
190
191 /**
192 * Closes the connection pool.
193 * <p>
194 * When the connection pool is closed, it will refuse all further requests
195 * to acquire connections. All connections that are currently available in
196 * the pool are closed immediately. Any connections that are still in use
197 * will be closed as soon as they are returned to the pool.
198 * </p>
199 *
200 * @throws IllegalStateException if the pool has been closed.
201 */
202 public void close() {
203 dispose(false);
204 }
205
206 private void dispose(boolean finalized) {
207 if (mCloseGuard != null) {
208 if (finalized) {
209 mCloseGuard.warnIfOpen();
210 }
211 mCloseGuard.close();
212 }
213
214 if (!finalized) {
215 // Close all connections. We don't need (or want) to do this
216 // when finalized because we don't know what state the connections
217 // themselves will be in. The finalizer is really just here for CloseGuard.
218 // The connections will take care of themselves when their own finalizers run.
219 synchronized (mLock) {
220 throwIfClosedLocked();
221
222 mIsOpen = false;
223
Jeff Brown559d0642012-02-29 10:19:12 -0800224 closeAvailableConnectionsAndLogExceptionsLocked();
Jeff Browne5360fb2011-10-31 17:48:13 -0700225
226 final int pendingCount = mAcquiredConnections.size();
227 if (pendingCount != 0) {
228 Log.i(TAG, "The connection pool for " + mConfiguration.label
229 + " has been closed but there are still "
230 + pendingCount + " connections in use. They will be closed "
231 + "as they are released back to the pool.");
232 }
233
234 wakeConnectionWaitersLocked();
235 }
236 }
237 }
238
239 /**
240 * Reconfigures the database configuration of the connection pool and all of its
241 * connections.
242 * <p>
243 * Configuration changes are propagated down to connections immediately if
244 * they are available or as soon as they are released. This includes changes
245 * that affect the size of the pool.
246 * </p>
247 *
248 * @param configuration The new configuration.
249 *
250 * @throws IllegalStateException if the pool has been closed.
251 */
252 public void reconfigure(SQLiteDatabaseConfiguration configuration) {
253 if (configuration == null) {
254 throw new IllegalArgumentException("configuration must not be null.");
255 }
256
257 synchronized (mLock) {
258 throwIfClosedLocked();
259
Jeff Browne67ca422012-03-21 17:24:05 -0700260 boolean restrictToOneConnection = false;
Jeff Brownd67c8c62012-03-22 14:15:01 -0700261 if (mConfiguration.walEnabled != configuration.walEnabled) {
Jeff Browne67ca422012-03-21 17:24:05 -0700262 // WAL mode can only be changed if there are no acquired connections
263 // because we need to close all but the primary connection first.
264 if (!mAcquiredConnections.isEmpty()) {
265 throw new IllegalStateException("Write Ahead Logging (WAL) mode cannot "
266 + "be enabled or disabled while there are transactions in "
267 + "progress. Finish all transactions and release all active "
268 + "database connections first.");
269 }
270
271 // Close all non-primary connections. This should happen immediately
272 // because none of them are in use.
273 closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
274 assert mAvailableNonPrimaryConnections.isEmpty();
275
276 restrictToOneConnection = true;
277 }
278
Jeff Brown559d0642012-02-29 10:19:12 -0800279 if (mConfiguration.openFlags != configuration.openFlags) {
Jeff Browne67ca422012-03-21 17:24:05 -0700280 // If we are changing open flags and WAL mode at the same time, then
281 // we have no choice but to close the primary connection beforehand
282 // because there can only be one connection open when we change WAL mode.
283 if (restrictToOneConnection) {
284 closeAvailableConnectionsAndLogExceptionsLocked();
285 }
286
Jeff Brown559d0642012-02-29 10:19:12 -0800287 // Try to reopen the primary connection using the new open flags then
288 // close and discard all existing connections.
289 // This might throw if the database is corrupt or cannot be opened in
290 // the new mode in which case existing connections will remain untouched.
291 SQLiteConnection newPrimaryConnection = openConnectionLocked(configuration,
292 true /*primaryConnection*/); // might throw
Jeff Browne5360fb2011-10-31 17:48:13 -0700293
Jeff Brown559d0642012-02-29 10:19:12 -0800294 closeAvailableConnectionsAndLogExceptionsLocked();
295 discardAcquiredConnectionsLocked();
296
297 mAvailablePrimaryConnection = newPrimaryConnection;
298 mConfiguration.updateParametersFrom(configuration);
299 } else {
300 // Reconfigure the database connections in place.
301 mConfiguration.updateParametersFrom(configuration);
302
303 closeExcessConnectionsAndLogExceptionsLocked();
304 reconfigureAllConnectionsLocked();
Jeff Browne5360fb2011-10-31 17:48:13 -0700305 }
306
Jeff Browne5360fb2011-10-31 17:48:13 -0700307 wakeConnectionWaitersLocked();
308 }
309 }
310
311 /**
312 * Acquires a connection from the pool.
313 * <p>
314 * The caller must call {@link #releaseConnection} to release the connection
315 * back to the pool when it is finished. Failure to do so will result
316 * in much unpleasantness.
317 * </p>
318 *
319 * @param sql If not null, try to find a connection that already has
320 * the specified SQL statement in its prepared statement cache.
321 * @param connectionFlags The connection request flags.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800322 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700323 * @return The connection that was acquired, never null.
324 *
325 * @throws IllegalStateException if the pool has been closed.
326 * @throws SQLiteException if a database error occurs.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800327 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700328 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800329 public SQLiteConnection acquireConnection(String sql, int connectionFlags,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800330 CancellationSignal cancellationSignal) {
331 return waitForConnection(sql, connectionFlags, cancellationSignal);
Jeff Browne5360fb2011-10-31 17:48:13 -0700332 }
333
334 /**
335 * Releases a connection back to the pool.
336 * <p>
337 * It is ok to call this method after the pool has closed, to release
338 * connections that were still in use at the time of closure.
339 * </p>
340 *
341 * @param connection The connection to release. Must not be null.
342 *
343 * @throws IllegalStateException if the connection was not acquired
344 * from this pool or if it has already been released.
345 */
346 public void releaseConnection(SQLiteConnection connection) {
347 synchronized (mLock) {
Jeff Brown559d0642012-02-29 10:19:12 -0800348 AcquiredConnectionStatus status = mAcquiredConnections.remove(connection);
349 if (status == null) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700350 throw new IllegalStateException("Cannot perform this operation "
351 + "because the specified connection was not acquired "
352 + "from this pool or has already been released.");
353 }
354
355 if (!mIsOpen) {
356 closeConnectionAndLogExceptionsLocked(connection);
357 } else if (connection.isPrimaryConnection()) {
Jeff Brown559d0642012-02-29 10:19:12 -0800358 if (recycleConnectionLocked(connection, status)) {
359 assert mAvailablePrimaryConnection == null;
Jeff Browne5360fb2011-10-31 17:48:13 -0700360 mAvailablePrimaryConnection = connection;
361 }
362 wakeConnectionWaitersLocked();
363 } else if (mAvailableNonPrimaryConnections.size() >=
364 mConfiguration.maxConnectionPoolSize - 1) {
365 closeConnectionAndLogExceptionsLocked(connection);
366 } else {
Jeff Brown559d0642012-02-29 10:19:12 -0800367 if (recycleConnectionLocked(connection, status)) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700368 mAvailableNonPrimaryConnections.add(connection);
369 }
370 wakeConnectionWaitersLocked();
371 }
372 }
373 }
374
Jeff Brown559d0642012-02-29 10:19:12 -0800375 // Can't throw.
376 private boolean recycleConnectionLocked(SQLiteConnection connection,
377 AcquiredConnectionStatus status) {
378 if (status == AcquiredConnectionStatus.RECONFIGURE) {
379 try {
380 connection.reconfigure(mConfiguration); // might throw
381 } catch (RuntimeException ex) {
382 Log.e(TAG, "Failed to reconfigure released connection, closing it: "
383 + connection, ex);
384 status = AcquiredConnectionStatus.DISCARD;
385 }
386 }
387 if (status == AcquiredConnectionStatus.DISCARD) {
388 closeConnectionAndLogExceptionsLocked(connection);
389 return false;
390 }
391 return true;
392 }
393
Jeff Browne5360fb2011-10-31 17:48:13 -0700394 /**
395 * Returns true if the session should yield the connection due to
396 * contention over available database connections.
397 *
398 * @param connection The connection owned by the session.
399 * @param connectionFlags The connection request flags.
400 * @return True if the session should yield its connection.
401 *
402 * @throws IllegalStateException if the connection was not acquired
403 * from this pool or if it has already been released.
404 */
405 public boolean shouldYieldConnection(SQLiteConnection connection, int connectionFlags) {
406 synchronized (mLock) {
407 if (!mAcquiredConnections.containsKey(connection)) {
408 throw new IllegalStateException("Cannot perform this operation "
409 + "because the specified connection was not acquired "
410 + "from this pool or has already been released.");
411 }
412
413 if (!mIsOpen) {
414 return false;
415 }
416
417 return isSessionBlockingImportantConnectionWaitersLocked(
418 connection.isPrimaryConnection(), connectionFlags);
419 }
420 }
421
422 /**
423 * Collects statistics about database connection memory usage.
424 *
425 * @param dbStatsList The list to populate.
426 */
427 public void collectDbStats(ArrayList<DbStats> dbStatsList) {
428 synchronized (mLock) {
429 if (mAvailablePrimaryConnection != null) {
430 mAvailablePrimaryConnection.collectDbStats(dbStatsList);
431 }
432
433 for (SQLiteConnection connection : mAvailableNonPrimaryConnections) {
434 connection.collectDbStats(dbStatsList);
435 }
436
437 for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
438 connection.collectDbStatsUnsafe(dbStatsList);
439 }
440 }
441 }
442
443 // Might throw.
Jeff Brown559d0642012-02-29 10:19:12 -0800444 private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
445 boolean primaryConnection) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700446 final int connectionId = mNextConnectionId++;
Jeff Brown559d0642012-02-29 10:19:12 -0800447 return SQLiteConnection.open(this, configuration,
Jeff Browne5360fb2011-10-31 17:48:13 -0700448 connectionId, primaryConnection); // might throw
449 }
450
451 void onConnectionLeaked() {
452 // This code is running inside of the SQLiteConnection finalizer.
453 //
454 // We don't know whether it is just the connection that has been finalized (and leaked)
455 // or whether the connection pool has also been or is about to be finalized.
456 // Consequently, it would be a bad idea to try to grab any locks or to
457 // do any significant work here. So we do the simplest possible thing and
458 // set a flag. waitForConnection() periodically checks this flag (when it
459 // times out) so that it can recover from leaked connections and wake
460 // itself or other threads up if necessary.
461 //
462 // You might still wonder why we don't try to do more to wake up the waiters
463 // immediately. First, as explained above, it would be hard to do safely
464 // unless we started an extra Thread to function as a reference queue. Second,
465 // this is never supposed to happen in normal operation. Third, there is no
466 // guarantee that the GC will actually detect the leak in a timely manner so
467 // it's not all that important that we recover from the leak in a timely manner
468 // either. Fourth, if a badly behaved application finds itself hung waiting for
469 // several seconds while waiting for a leaked connection to be detected and recreated,
470 // then perhaps its authors will have added incentive to fix the problem!
471
472 Log.w(TAG, "A SQLiteConnection object for database '"
473 + mConfiguration.label + "' was leaked! Please fix your application "
474 + "to end transactions in progress properly and to close the database "
475 + "when it is no longer needed.");
476
477 mConnectionLeaked.set(true);
478 }
479
480 // Can't throw.
Jeff Brown559d0642012-02-29 10:19:12 -0800481 private void closeAvailableConnectionsAndLogExceptionsLocked() {
Jeff Browne67ca422012-03-21 17:24:05 -0700482 closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
Jeff Brown559d0642012-02-29 10:19:12 -0800483
484 if (mAvailablePrimaryConnection != null) {
485 closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
486 mAvailablePrimaryConnection = null;
487 }
488 }
489
490 // Can't throw.
Jeff Browne67ca422012-03-21 17:24:05 -0700491 private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() {
492 final int count = mAvailableNonPrimaryConnections.size();
493 for (int i = 0; i < count; i++) {
494 closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
495 }
496 mAvailableNonPrimaryConnections.clear();
497 }
498
499 // Can't throw.
Jeff Brown559d0642012-02-29 10:19:12 -0800500 private void closeExcessConnectionsAndLogExceptionsLocked() {
501 int availableCount = mAvailableNonPrimaryConnections.size();
502 while (availableCount-- > mConfiguration.maxConnectionPoolSize - 1) {
503 SQLiteConnection connection =
504 mAvailableNonPrimaryConnections.remove(availableCount);
505 closeConnectionAndLogExceptionsLocked(connection);
506 }
507 }
508
509 // Can't throw.
Jeff Browne5360fb2011-10-31 17:48:13 -0700510 private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
511 try {
512 connection.close(); // might throw
513 } catch (RuntimeException ex) {
514 Log.e(TAG, "Failed to close connection, its fate is now in the hands "
515 + "of the merciful GC: " + connection, ex);
516 }
517 }
518
519 // Can't throw.
Jeff Brown559d0642012-02-29 10:19:12 -0800520 private void discardAcquiredConnectionsLocked() {
521 markAcquiredConnectionsLocked(AcquiredConnectionStatus.DISCARD);
522 }
523
524 // Can't throw.
Jeff Browne5360fb2011-10-31 17:48:13 -0700525 private void reconfigureAllConnectionsLocked() {
Jeff Browne5360fb2011-10-31 17:48:13 -0700526 if (mAvailablePrimaryConnection != null) {
527 try {
528 mAvailablePrimaryConnection.reconfigure(mConfiguration); // might throw
529 } catch (RuntimeException ex) {
530 Log.e(TAG, "Failed to reconfigure available primary connection, closing it: "
531 + mAvailablePrimaryConnection, ex);
532 closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
533 mAvailablePrimaryConnection = null;
Jeff Browne5360fb2011-10-31 17:48:13 -0700534 }
535 }
536
537 int count = mAvailableNonPrimaryConnections.size();
538 for (int i = 0; i < count; i++) {
539 final SQLiteConnection connection = mAvailableNonPrimaryConnections.get(i);
540 try {
541 connection.reconfigure(mConfiguration); // might throw
542 } catch (RuntimeException ex) {
543 Log.e(TAG, "Failed to reconfigure available non-primary connection, closing it: "
544 + connection, ex);
545 closeConnectionAndLogExceptionsLocked(connection);
546 mAvailableNonPrimaryConnections.remove(i--);
547 count -= 1;
Jeff Browne5360fb2011-10-31 17:48:13 -0700548 }
549 }
550
Jeff Brown559d0642012-02-29 10:19:12 -0800551 markAcquiredConnectionsLocked(AcquiredConnectionStatus.RECONFIGURE);
552 }
553
554 // Can't throw.
555 private void markAcquiredConnectionsLocked(AcquiredConnectionStatus status) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700556 if (!mAcquiredConnections.isEmpty()) {
557 ArrayList<SQLiteConnection> keysToUpdate = new ArrayList<SQLiteConnection>(
558 mAcquiredConnections.size());
Jeff Brown559d0642012-02-29 10:19:12 -0800559 for (Map.Entry<SQLiteConnection, AcquiredConnectionStatus> entry
560 : mAcquiredConnections.entrySet()) {
561 AcquiredConnectionStatus oldStatus = entry.getValue();
562 if (status != oldStatus
563 && oldStatus != AcquiredConnectionStatus.DISCARD) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700564 keysToUpdate.add(entry.getKey());
565 }
566 }
567 final int updateCount = keysToUpdate.size();
568 for (int i = 0; i < updateCount; i++) {
Jeff Brown559d0642012-02-29 10:19:12 -0800569 mAcquiredConnections.put(keysToUpdate.get(i), status);
Jeff Browne5360fb2011-10-31 17:48:13 -0700570 }
571 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700572 }
573
574 // Might throw.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800575 private SQLiteConnection waitForConnection(String sql, int connectionFlags,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800576 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700577 final boolean wantPrimaryConnection =
578 (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;
579
580 final ConnectionWaiter waiter;
581 synchronized (mLock) {
582 throwIfClosedLocked();
583
Jeff Brown75ea64f2012-01-25 19:37:13 -0800584 // Abort if canceled.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800585 if (cancellationSignal != null) {
586 cancellationSignal.throwIfCanceled();
Jeff Brown75ea64f2012-01-25 19:37:13 -0800587 }
588
Jeff Browne5360fb2011-10-31 17:48:13 -0700589 // Try to acquire a connection.
590 SQLiteConnection connection = null;
591 if (!wantPrimaryConnection) {
592 connection = tryAcquireNonPrimaryConnectionLocked(
593 sql, connectionFlags); // might throw
594 }
595 if (connection == null) {
596 connection = tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw
597 }
598 if (connection != null) {
599 return connection;
600 }
601
602 // No connections available. Enqueue a waiter in priority order.
603 final int priority = getPriority(connectionFlags);
604 final long startTime = SystemClock.uptimeMillis();
605 waiter = obtainConnectionWaiterLocked(Thread.currentThread(), startTime,
606 priority, wantPrimaryConnection, sql, connectionFlags);
607 ConnectionWaiter predecessor = null;
608 ConnectionWaiter successor = mConnectionWaiterQueue;
609 while (successor != null) {
610 if (priority > successor.mPriority) {
611 waiter.mNext = successor;
612 break;
613 }
614 predecessor = successor;
615 successor = successor.mNext;
616 }
617 if (predecessor != null) {
618 predecessor.mNext = waiter;
619 } else {
620 mConnectionWaiterQueue = waiter;
621 }
Jeff Brown75ea64f2012-01-25 19:37:13 -0800622
Jeff Brown4c1241d2012-02-02 17:05:00 -0800623 if (cancellationSignal != null) {
Jeff Brown75ea64f2012-01-25 19:37:13 -0800624 final int nonce = waiter.mNonce;
Jeff Brown4c1241d2012-02-02 17:05:00 -0800625 cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
Jeff Brown75ea64f2012-01-25 19:37:13 -0800626 @Override
627 public void onCancel() {
628 synchronized (mLock) {
629 cancelConnectionWaiterLocked(waiter, nonce);
630 }
631 }
632 });
633 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700634 }
635
636 // Park the thread until a connection is assigned or the pool is closed.
637 // Rethrow an exception from the wait, if we got one.
638 long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
639 long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis;
640 for (;;) {
641 // Detect and recover from connection leaks.
642 if (mConnectionLeaked.compareAndSet(true, false)) {
Jeff Brown75ea64f2012-01-25 19:37:13 -0800643 synchronized (mLock) {
644 wakeConnectionWaitersLocked();
645 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700646 }
647
648 // Wait to be unparked (may already have happened), a timeout, or interruption.
649 LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L);
650
651 // Clear the interrupted flag, just in case.
652 Thread.interrupted();
653
654 // Check whether we are done waiting yet.
655 synchronized (mLock) {
656 throwIfClosedLocked();
657
Jeff Brown75ea64f2012-01-25 19:37:13 -0800658 final SQLiteConnection connection = waiter.mAssignedConnection;
659 final RuntimeException ex = waiter.mException;
660 if (connection != null || ex != null) {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800661 if (cancellationSignal != null) {
662 cancellationSignal.setOnCancelListener(null);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800663 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700664 recycleConnectionWaiterLocked(waiter);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800665 if (connection != null) {
666 return connection;
667 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700668 throw ex; // rethrow!
669 }
670
671 final long now = SystemClock.uptimeMillis();
672 if (now < nextBusyTimeoutTime) {
673 busyTimeoutMillis = now - nextBusyTimeoutTime;
674 } else {
675 logConnectionPoolBusyLocked(now - waiter.mStartTime, connectionFlags);
676 busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
677 nextBusyTimeoutTime = now + busyTimeoutMillis;
678 }
679 }
680 }
681 }
682
683 // Can't throw.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800684 private void cancelConnectionWaiterLocked(ConnectionWaiter waiter, int nonce) {
685 if (waiter.mNonce != nonce) {
686 // Waiter already removed and recycled.
687 return;
688 }
689
690 if (waiter.mAssignedConnection != null || waiter.mException != null) {
691 // Waiter is done waiting but has not woken up yet.
692 return;
693 }
694
695 // Waiter must still be waiting. Dequeue it.
696 ConnectionWaiter predecessor = null;
697 ConnectionWaiter current = mConnectionWaiterQueue;
698 while (current != waiter) {
699 assert current != null;
700 predecessor = current;
701 current = current.mNext;
702 }
703 if (predecessor != null) {
704 predecessor.mNext = waiter.mNext;
705 } else {
706 mConnectionWaiterQueue = waiter.mNext;
707 }
708
709 // Send the waiter an exception and unpark it.
710 waiter.mException = new OperationCanceledException();
711 LockSupport.unpark(waiter.mThread);
712
713 // Check whether removing this waiter will enable other waiters to make progress.
714 wakeConnectionWaitersLocked();
715 }
716
717 // Can't throw.
Jeff Browne5360fb2011-10-31 17:48:13 -0700718 private void logConnectionPoolBusyLocked(long waitMillis, int connectionFlags) {
719 final Thread thread = Thread.currentThread();
720 StringBuilder msg = new StringBuilder();
721 msg.append("The connection pool for database '").append(mConfiguration.label);
722 msg.append("' has been unable to grant a connection to thread ");
723 msg.append(thread.getId()).append(" (").append(thread.getName()).append(") ");
724 msg.append("with flags 0x").append(Integer.toHexString(connectionFlags));
725 msg.append(" for ").append(waitMillis * 0.001f).append(" seconds.\n");
726
727 ArrayList<String> requests = new ArrayList<String>();
728 int activeConnections = 0;
729 int idleConnections = 0;
730 if (!mAcquiredConnections.isEmpty()) {
Jeff Brown559d0642012-02-29 10:19:12 -0800731 for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700732 String description = connection.describeCurrentOperationUnsafe();
733 if (description != null) {
734 requests.add(description);
735 activeConnections += 1;
736 } else {
737 idleConnections += 1;
738 }
739 }
740 }
741 int availableConnections = mAvailableNonPrimaryConnections.size();
742 if (mAvailablePrimaryConnection != null) {
743 availableConnections += 1;
744 }
745
746 msg.append("Connections: ").append(activeConnections).append(" active, ");
747 msg.append(idleConnections).append(" idle, ");
748 msg.append(availableConnections).append(" available.\n");
749
750 if (!requests.isEmpty()) {
751 msg.append("\nRequests in progress:\n");
752 for (String request : requests) {
753 msg.append(" ").append(request).append("\n");
754 }
755 }
756
757 Log.w(TAG, msg.toString());
758 }
759
760 // Can't throw.
761 private void wakeConnectionWaitersLocked() {
762 // Unpark all waiters that have requests that we can fulfill.
763 // This method is designed to not throw runtime exceptions, although we might send
764 // a waiter an exception for it to rethrow.
765 ConnectionWaiter predecessor = null;
766 ConnectionWaiter waiter = mConnectionWaiterQueue;
767 boolean primaryConnectionNotAvailable = false;
768 boolean nonPrimaryConnectionNotAvailable = false;
769 while (waiter != null) {
770 boolean unpark = false;
771 if (!mIsOpen) {
772 unpark = true;
773 } else {
774 try {
775 SQLiteConnection connection = null;
776 if (!waiter.mWantPrimaryConnection && !nonPrimaryConnectionNotAvailable) {
777 connection = tryAcquireNonPrimaryConnectionLocked(
778 waiter.mSql, waiter.mConnectionFlags); // might throw
779 if (connection == null) {
780 nonPrimaryConnectionNotAvailable = true;
781 }
782 }
783 if (connection == null && !primaryConnectionNotAvailable) {
784 connection = tryAcquirePrimaryConnectionLocked(
785 waiter.mConnectionFlags); // might throw
786 if (connection == null) {
787 primaryConnectionNotAvailable = true;
788 }
789 }
790 if (connection != null) {
791 waiter.mAssignedConnection = connection;
792 unpark = true;
793 } else if (nonPrimaryConnectionNotAvailable && primaryConnectionNotAvailable) {
794 // There are no connections available and the pool is still open.
795 // We cannot fulfill any more connection requests, so stop here.
796 break;
797 }
798 } catch (RuntimeException ex) {
799 // Let the waiter handle the exception from acquiring a connection.
800 waiter.mException = ex;
801 unpark = true;
802 }
803 }
804
805 final ConnectionWaiter successor = waiter.mNext;
806 if (unpark) {
807 if (predecessor != null) {
808 predecessor.mNext = successor;
809 } else {
810 mConnectionWaiterQueue = successor;
811 }
812 waiter.mNext = null;
813
814 LockSupport.unpark(waiter.mThread);
815 } else {
816 predecessor = waiter;
817 }
818 waiter = successor;
819 }
820 }
821
822 // Might throw.
823 private SQLiteConnection tryAcquirePrimaryConnectionLocked(int connectionFlags) {
824 // If the primary connection is available, acquire it now.
825 SQLiteConnection connection = mAvailablePrimaryConnection;
826 if (connection != null) {
827 mAvailablePrimaryConnection = null;
828 finishAcquireConnectionLocked(connection, connectionFlags); // might throw
829 return connection;
830 }
831
832 // Make sure that the primary connection actually exists and has just been acquired.
833 for (SQLiteConnection acquiredConnection : mAcquiredConnections.keySet()) {
834 if (acquiredConnection.isPrimaryConnection()) {
835 return null;
836 }
837 }
838
839 // Uhoh. No primary connection! Either this is the first time we asked
840 // for it, or maybe it leaked?
Jeff Brown559d0642012-02-29 10:19:12 -0800841 connection = openConnectionLocked(mConfiguration,
842 true /*primaryConnection*/); // might throw
Jeff Browne5360fb2011-10-31 17:48:13 -0700843 finishAcquireConnectionLocked(connection, connectionFlags); // might throw
844 return connection;
845 }
846
847 // Might throw.
848 private SQLiteConnection tryAcquireNonPrimaryConnectionLocked(
849 String sql, int connectionFlags) {
850 // Try to acquire the next connection in the queue.
851 SQLiteConnection connection;
852 final int availableCount = mAvailableNonPrimaryConnections.size();
853 if (availableCount > 1 && sql != null) {
854 // If we have a choice, then prefer a connection that has the
855 // prepared statement in its cache.
856 for (int i = 0; i < availableCount; i++) {
857 connection = mAvailableNonPrimaryConnections.get(i);
858 if (connection.isPreparedStatementInCache(sql)) {
859 mAvailableNonPrimaryConnections.remove(i);
860 finishAcquireConnectionLocked(connection, connectionFlags); // might throw
861 return connection;
862 }
863 }
864 }
865 if (availableCount > 0) {
866 // Otherwise, just grab the next one.
867 connection = mAvailableNonPrimaryConnections.remove(availableCount - 1);
868 finishAcquireConnectionLocked(connection, connectionFlags); // might throw
869 return connection;
870 }
871
872 // Expand the pool if needed.
873 int openConnections = mAcquiredConnections.size();
874 if (mAvailablePrimaryConnection != null) {
875 openConnections += 1;
876 }
877 if (openConnections >= mConfiguration.maxConnectionPoolSize) {
878 return null;
879 }
Jeff Brown559d0642012-02-29 10:19:12 -0800880 connection = openConnectionLocked(mConfiguration,
881 false /*primaryConnection*/); // might throw
Jeff Browne5360fb2011-10-31 17:48:13 -0700882 finishAcquireConnectionLocked(connection, connectionFlags); // might throw
883 return connection;
884 }
885
886 // Might throw.
887 private void finishAcquireConnectionLocked(SQLiteConnection connection, int connectionFlags) {
888 try {
889 final boolean readOnly = (connectionFlags & CONNECTION_FLAG_READ_ONLY) != 0;
890 connection.setOnlyAllowReadOnlyOperations(readOnly);
891
Jeff Brown559d0642012-02-29 10:19:12 -0800892 mAcquiredConnections.put(connection, AcquiredConnectionStatus.NORMAL);
Jeff Browne5360fb2011-10-31 17:48:13 -0700893 } catch (RuntimeException ex) {
894 Log.e(TAG, "Failed to prepare acquired connection for session, closing it: "
895 + connection +", connectionFlags=" + connectionFlags);
896 closeConnectionAndLogExceptionsLocked(connection);
897 throw ex; // rethrow!
898 }
899 }
900
901 private boolean isSessionBlockingImportantConnectionWaitersLocked(
902 boolean holdingPrimaryConnection, int connectionFlags) {
903 ConnectionWaiter waiter = mConnectionWaiterQueue;
904 if (waiter != null) {
905 final int priority = getPriority(connectionFlags);
906 do {
907 // Only worry about blocked connections that have same or lower priority.
908 if (priority > waiter.mPriority) {
909 break;
910 }
911
912 // If we are holding the primary connection then we are blocking the waiter.
913 // Likewise, if we are holding a non-primary connection and the waiter
914 // would accept a non-primary connection, then we are blocking the waier.
915 if (holdingPrimaryConnection || !waiter.mWantPrimaryConnection) {
916 return true;
917 }
918
919 waiter = waiter.mNext;
920 } while (waiter != null);
921 }
922 return false;
923 }
924
925 private static int getPriority(int connectionFlags) {
926 return (connectionFlags & CONNECTION_FLAG_INTERACTIVE) != 0 ? 1 : 0;
927 }
928
929 private void throwIfClosedLocked() {
930 if (!mIsOpen) {
931 throw new IllegalStateException("Cannot perform this operation "
Jeff Brown559d0642012-02-29 10:19:12 -0800932 + "because the connection pool has been closed.");
Jeff Browne5360fb2011-10-31 17:48:13 -0700933 }
934 }
935
936 private ConnectionWaiter obtainConnectionWaiterLocked(Thread thread, long startTime,
937 int priority, boolean wantPrimaryConnection, String sql, int connectionFlags) {
938 ConnectionWaiter waiter = mConnectionWaiterPool;
939 if (waiter != null) {
940 mConnectionWaiterPool = waiter.mNext;
941 waiter.mNext = null;
942 } else {
943 waiter = new ConnectionWaiter();
944 }
945 waiter.mThread = thread;
946 waiter.mStartTime = startTime;
947 waiter.mPriority = priority;
948 waiter.mWantPrimaryConnection = wantPrimaryConnection;
949 waiter.mSql = sql;
950 waiter.mConnectionFlags = connectionFlags;
951 return waiter;
952 }
953
954 private void recycleConnectionWaiterLocked(ConnectionWaiter waiter) {
955 waiter.mNext = mConnectionWaiterPool;
956 waiter.mThread = null;
957 waiter.mSql = null;
958 waiter.mAssignedConnection = null;
959 waiter.mException = null;
Jeff Brown75ea64f2012-01-25 19:37:13 -0800960 waiter.mNonce += 1;
Jeff Browne5360fb2011-10-31 17:48:13 -0700961 mConnectionWaiterPool = waiter;
962 }
963
964 /**
965 * Dumps debugging information about this connection pool.
966 *
967 * @param printer The printer to receive the dump, not null.
Jeff Browna9be4152012-01-18 15:29:57 -0800968 * @param verbose True to dump more verbose information.
Jeff Browne5360fb2011-10-31 17:48:13 -0700969 */
Jeff Browna9be4152012-01-18 15:29:57 -0800970 public void dump(Printer printer, boolean verbose) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700971 Printer indentedPrinter = PrefixPrinter.create(printer, " ");
972 synchronized (mLock) {
973 printer.println("Connection pool for " + mConfiguration.path + ":");
974 printer.println(" Open: " + mIsOpen);
975 printer.println(" Max connections: " + mConfiguration.maxConnectionPoolSize);
976
977 printer.println(" Available primary connection:");
978 if (mAvailablePrimaryConnection != null) {
Jeff Browna9be4152012-01-18 15:29:57 -0800979 mAvailablePrimaryConnection.dump(indentedPrinter, verbose);
Jeff Browne5360fb2011-10-31 17:48:13 -0700980 } else {
981 indentedPrinter.println("<none>");
982 }
983
984 printer.println(" Available non-primary connections:");
985 if (!mAvailableNonPrimaryConnections.isEmpty()) {
986 final int count = mAvailableNonPrimaryConnections.size();
987 for (int i = 0; i < count; i++) {
Jeff Browna9be4152012-01-18 15:29:57 -0800988 mAvailableNonPrimaryConnections.get(i).dump(indentedPrinter, verbose);
Jeff Browne5360fb2011-10-31 17:48:13 -0700989 }
990 } else {
991 indentedPrinter.println("<none>");
992 }
993
994 printer.println(" Acquired connections:");
995 if (!mAcquiredConnections.isEmpty()) {
Jeff Brown559d0642012-02-29 10:19:12 -0800996 for (Map.Entry<SQLiteConnection, AcquiredConnectionStatus> entry :
Jeff Browne5360fb2011-10-31 17:48:13 -0700997 mAcquiredConnections.entrySet()) {
998 final SQLiteConnection connection = entry.getKey();
Jeff Browna9be4152012-01-18 15:29:57 -0800999 connection.dumpUnsafe(indentedPrinter, verbose);
Jeff Brown559d0642012-02-29 10:19:12 -08001000 indentedPrinter.println(" Status: " + entry.getValue());
Jeff Browne5360fb2011-10-31 17:48:13 -07001001 }
1002 } else {
1003 indentedPrinter.println("<none>");
1004 }
1005
1006 printer.println(" Connection waiters:");
1007 if (mConnectionWaiterQueue != null) {
1008 int i = 0;
1009 final long now = SystemClock.uptimeMillis();
1010 for (ConnectionWaiter waiter = mConnectionWaiterQueue; waiter != null;
1011 waiter = waiter.mNext, i++) {
1012 indentedPrinter.println(i + ": waited for "
1013 + ((now - waiter.mStartTime) * 0.001f)
1014 + " ms - thread=" + waiter.mThread
1015 + ", priority=" + waiter.mPriority
1016 + ", sql='" + waiter.mSql + "'");
1017 }
1018 } else {
1019 indentedPrinter.println("<none>");
1020 }
1021 }
1022 }
1023
1024 @Override
1025 public String toString() {
1026 return "SQLiteConnectionPool: " + mConfiguration.path;
1027 }
1028
1029 private static final class ConnectionWaiter {
1030 public ConnectionWaiter mNext;
1031 public Thread mThread;
1032 public long mStartTime;
1033 public int mPriority;
1034 public boolean mWantPrimaryConnection;
1035 public String mSql;
1036 public int mConnectionFlags;
1037 public SQLiteConnection mAssignedConnection;
1038 public RuntimeException mException;
Jeff Brown75ea64f2012-01-25 19:37:13 -08001039 public int mNonce;
Jeff Browne5360fb2011-10-31 17:48:13 -07001040 }
1041}