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