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