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