Merge "Added setIdleConnectionTimeout method" into oc-mr1-dev

This commit is contained in:
Fyodor Kupolov
2017-07-31 23:56:02 +00:00
committed by Android (Google) Code Review
11 changed files with 178 additions and 45 deletions

View File

@@ -11956,6 +11956,7 @@ package android.database.sqlite {
public static final class SQLiteDatabase.OpenParams {
method public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory();
method public android.database.DatabaseErrorHandler getErrorHandler();
method public long getIdleConnectionTimeout();
method public int getLookasideSlotCount();
method public int getLookasideSlotSize();
method public int getOpenFlags();
@@ -11969,6 +11970,7 @@ package android.database.sqlite {
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder removeOpenFlags(int);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setErrorHandler(android.database.DatabaseErrorHandler);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setIdleConnectionTimeout(long);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setLookasideConfig(int, int);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setOpenFlags(int);
}
@@ -12026,6 +12028,7 @@ package android.database.sqlite {
method public void onDowngrade(android.database.sqlite.SQLiteDatabase, int, int);
method public void onOpen(android.database.sqlite.SQLiteDatabase);
method public abstract void onUpgrade(android.database.sqlite.SQLiteDatabase, int, int);
method public void setIdleConnectionTimeout(long);
method public void setLookasideConfig(int, int);
method public void setWriteAheadLoggingEnabled(boolean);
}

View File

@@ -12752,6 +12752,7 @@ package android.database.sqlite {
public static final class SQLiteDatabase.OpenParams {
method public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory();
method public android.database.DatabaseErrorHandler getErrorHandler();
method public long getIdleConnectionTimeout();
method public int getLookasideSlotCount();
method public int getLookasideSlotSize();
method public int getOpenFlags();
@@ -12765,6 +12766,7 @@ package android.database.sqlite {
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder removeOpenFlags(int);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setErrorHandler(android.database.DatabaseErrorHandler);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setIdleConnectionTimeout(long);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setLookasideConfig(int, int);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setOpenFlags(int);
}
@@ -12822,6 +12824,7 @@ package android.database.sqlite {
method public void onDowngrade(android.database.sqlite.SQLiteDatabase, int, int);
method public void onOpen(android.database.sqlite.SQLiteDatabase);
method public abstract void onUpgrade(android.database.sqlite.SQLiteDatabase, int, int);
method public void setIdleConnectionTimeout(long);
method public void setLookasideConfig(int, int);
method public void setWriteAheadLoggingEnabled(boolean);
}

View File

@@ -12000,6 +12000,7 @@ package android.database.sqlite {
public static final class SQLiteDatabase.OpenParams {
method public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory();
method public android.database.DatabaseErrorHandler getErrorHandler();
method public long getIdleConnectionTimeout();
method public int getLookasideSlotCount();
method public int getLookasideSlotSize();
method public int getOpenFlags();
@@ -12013,6 +12014,7 @@ package android.database.sqlite {
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder removeOpenFlags(int);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setErrorHandler(android.database.DatabaseErrorHandler);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setIdleConnectionTimeout(long);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setLookasideConfig(int, int);
method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setOpenFlags(int);
}
@@ -12095,6 +12097,7 @@ package android.database.sqlite {
method public void onDowngrade(android.database.sqlite.SQLiteDatabase, int, int);
method public void onOpen(android.database.sqlite.SQLiteDatabase);
method public abstract void onUpgrade(android.database.sqlite.SQLiteDatabase, int, int);
method public void setIdleConnectionTimeout(long);
method public void setLookasideConfig(int, int);
method public void setWriteAheadLoggingEnabled(boolean);
}

View File

@@ -16,7 +16,6 @@
package android.database.sqlite;
import android.app.ActivityManager;
import android.database.sqlite.SQLiteDebug.DbStats;
import android.os.CancellationSignal;
import android.os.Handler;
@@ -24,7 +23,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.OperationCanceledException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
import android.util.PrefixPrinter;
import android.util.Printer;
@@ -84,15 +82,6 @@ public final class SQLiteConnectionPool implements Closeable {
// and logging a message about the connection pool being busy.
private static final long CONNECTION_POOL_BUSY_MILLIS = 30 * 1000; // 30 seconds
// TODO b/63398887 Move to SQLiteGlobal
private static final long IDLE_CONNECTION_CLOSE_DELAY_MILLIS = SystemProperties
.getInt("persist.debug.sqlite.idle_connection_close_delay", 30000);
// TODO b/63398887 STOPSHIP.
// Temporarily enabled for testing across a broader set of dogfood devices.
private static final boolean CLOSE_IDLE_CONNECTIONS = SystemProperties
.getBoolean("persist.debug.sqlite.close_idle_connections", true);
private final CloseGuard mCloseGuard = CloseGuard.get();
private final Object mLock = new Object();
@@ -167,16 +156,12 @@ public final class SQLiteConnectionPool implements Closeable {
private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) {
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
// Disable lookaside allocator on low-RAM devices
if (ActivityManager.isLowRamDeviceStatic()) {
mConfiguration.lookasideSlotCount = 0;
mConfiguration.lookasideSlotSize = 0;
}
setMaxConnectionPoolSizeLocked();
// Do not close idle connections for in-memory databases
if (CLOSE_IDLE_CONNECTIONS && !configuration.isInMemoryDb()) {
setupIdleConnectionHandler(Looper.getMainLooper(), IDLE_CONNECTION_CLOSE_DELAY_MILLIS);
// 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);
}
}
@@ -214,6 +199,12 @@ public final class SQLiteConnectionPool implements Closeable {
// 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;
@@ -1023,12 +1014,12 @@ public final class SQLiteConnectionPool implements Closeable {
}
/**
* Set up the handler based on the provided looper and delay.
* Set up the handler based on the provided looper and timeout.
*/
@VisibleForTesting
public void setupIdleConnectionHandler(Looper looper, long delayMs) {
public void setupIdleConnectionHandler(Looper looper, long timeoutMs) {
synchronized (mLock) {
mIdleConnectionHandler = new IdleConnectionHandler(looper, delayMs);
mIdleConnectionHandler = new IdleConnectionHandler(looper, timeoutMs);
}
}
@@ -1089,6 +1080,10 @@ public final class SQLiteConnectionPool implements Closeable {
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);
@@ -1155,11 +1150,11 @@ public final class SQLiteConnectionPool implements Closeable {
}
private class IdleConnectionHandler extends Handler {
private final long mDelay;
private final long mTimeout;
IdleConnectionHandler(Looper looper, long delay) {
IdleConnectionHandler(Looper looper, long timeout) {
super(looper);
mDelay = delay;
mTimeout = timeout;
}
@Override
@@ -1172,14 +1167,14 @@ public final class SQLiteConnectionPool implements Closeable {
if (closeAvailableConnectionLocked(msg.what)) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Closed idle connection " + mConfiguration.label + " " + msg.what
+ " after " + mDelay);
+ " after " + mTimeout);
}
}
}
}
void connectionReleased(SQLiteConnection con) {
sendEmptyMessageDelayed(con.getConnectionId(), mDelay);
sendEmptyMessageDelayed(con.getConnectionId(), mTimeout);
}
void connectionAcquired(SQLiteConnection con) {

View File

@@ -20,6 +20,7 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseErrorHandler;
@@ -30,6 +31,7 @@ import android.database.sqlite.SQLiteDebug.DbStats;
import android.os.CancellationSignal;
import android.os.Looper;
import android.os.OperationCanceledException;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -77,21 +79,21 @@ public final class SQLiteDatabase extends SQLiteClosable {
private static final int EVENT_DB_CORRUPT = 75004;
// TODO b/63398887 STOPSHIP.
// Temporarily enabled for testing across a broader set of dogfood devices.
private static final boolean DEBUG_CLOSE_IDLE_CONNECTIONS = SystemProperties
.getBoolean("persist.debug.sqlite.close_idle_connections", true);
// Stores reference to all databases opened in the current process.
// (The referent Object is not used at this time.)
// INVARIANT: Guarded by sActiveDatabases.
private static WeakHashMap<SQLiteDatabase, Object> sActiveDatabases =
new WeakHashMap<SQLiteDatabase, Object>();
private static WeakHashMap<SQLiteDatabase, Object> sActiveDatabases = new WeakHashMap<>();
// Thread-local for database sessions that belong to this database.
// Each thread has its own database session.
// INVARIANT: Immutable.
private final ThreadLocal<SQLiteSession> mThreadSession = new ThreadLocal<SQLiteSession>() {
@Override
protected SQLiteSession initialValue() {
return createSession();
}
};
private final ThreadLocal<SQLiteSession> mThreadSession = ThreadLocal
.withInitial(this::createSession);
// The optional factory to use when creating new Cursors. May be null.
// INVARIANT: Immutable.
@@ -261,12 +263,29 @@ public final class SQLiteDatabase extends SQLiteClosable {
private SQLiteDatabase(final String path, final int openFlags,
CursorFactory cursorFactory, DatabaseErrorHandler errorHandler,
int lookasideSlotSize, int lookasideSlotCount) {
int lookasideSlotSize, int lookasideSlotCount, long idleConnectionTimeoutMs) {
mCursorFactory = cursorFactory;
mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();
mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags);
mConfigurationLocked.lookasideSlotSize = lookasideSlotSize;
mConfigurationLocked.lookasideSlotCount = lookasideSlotCount;
// Disable lookaside allocator on low-RAM devices
if (ActivityManager.isLowRamDeviceStatic()) {
mConfigurationLocked.lookasideSlotCount = 0;
mConfigurationLocked.lookasideSlotSize = 0;
}
long effectiveTimeoutMs = Long.MAX_VALUE;
// Never close idle connections for in-memory databases
if (!mConfigurationLocked.isInMemoryDb()) {
// First, check app-specific value. Otherwise use defaults
// -1 in idleConnectionTimeoutMs indicates unset value
if (idleConnectionTimeoutMs >= 0) {
effectiveTimeoutMs = idleConnectionTimeoutMs;
} else if (DEBUG_CLOSE_IDLE_CONNECTIONS) {
effectiveTimeoutMs = SQLiteGlobal.getIdleConnectionTimeout();
}
}
mConfigurationLocked.idleConnectionTimeoutMs = effectiveTimeoutMs;
}
@Override
@@ -694,7 +713,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
Preconditions.checkArgument(openParams != null, "OpenParams cannot be null");
SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
openParams.mCursorFactory, openParams.mErrorHandler,
openParams.mLookasideSlotSize, openParams.mLookasideSlotCount);
openParams.mLookasideSlotSize, openParams.mLookasideSlotCount,
openParams.mIdleConnectionTimeout);
db.open();
return db;
}
@@ -720,7 +740,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
*/
public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory,
@DatabaseOpenFlags int flags, @Nullable DatabaseErrorHandler errorHandler) {
SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1);
SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1, -1);
db.open();
return db;
}
@@ -2267,14 +2287,17 @@ public final class SQLiteDatabase extends SQLiteClosable {
private final DatabaseErrorHandler mErrorHandler;
private final int mLookasideSlotSize;
private final int mLookasideSlotCount;
private long mIdleConnectionTimeout;
private OpenParams(int openFlags, CursorFactory cursorFactory,
DatabaseErrorHandler errorHandler, int lookasideSlotSize, int lookasideSlotCount) {
DatabaseErrorHandler errorHandler, int lookasideSlotSize, int lookasideSlotCount,
long idleConnectionTimeout) {
mOpenFlags = openFlags;
mCursorFactory = cursorFactory;
mErrorHandler = errorHandler;
mLookasideSlotSize = lookasideSlotSize;
mLookasideSlotCount = lookasideSlotCount;
mIdleConnectionTimeout = idleConnectionTimeout;
}
/**
@@ -2329,6 +2352,17 @@ public final class SQLiteDatabase extends SQLiteClosable {
return mErrorHandler;
}
/**
* Returns maximum number of milliseconds that SQLite connection is allowed to be idle
* before it is closed and removed from the pool.
* <p>If the value isn't set, the timeout defaults to the system wide timeout
*
* @return timeout in milliseconds or -1 if the value wasn't set.
*/
public long getIdleConnectionTimeout() {
return mIdleConnectionTimeout;
}
/**
* Creates a new instance of builder {@link Builder#Builder(OpenParams) initialized} with
* {@code this} parameters.
@@ -2345,6 +2379,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
public static final class Builder {
private int mLookasideSlotSize = -1;
private int mLookasideSlotCount = -1;
private long mIdleConnectionTimeout = -1;
private int mOpenFlags;
private CursorFactory mCursorFactory;
private DatabaseErrorHandler mErrorHandler;
@@ -2473,6 +2508,22 @@ public final class SQLiteDatabase extends SQLiteClosable {
return this;
}
/**
* Sets the maximum number of milliseconds that SQLite connection is allowed to be idle
* before it is closed and removed from the pool.
*
* @param idleConnectionTimeoutMs timeout in milliseconds. Use {@link Long#MAX_VALUE}
* to allow unlimited idle connections.
*/
@NonNull
public Builder setIdleConnectionTimeout(
@IntRange(from = 0) long idleConnectionTimeoutMs) {
Preconditions.checkArgument(idleConnectionTimeoutMs >= 0,
"idle connection timeout cannot be negative");
mIdleConnectionTimeout = idleConnectionTimeoutMs;
return this;
}
/**
* Creates an instance of {@link OpenParams} with the options that were previously set
* on this builder
@@ -2480,7 +2531,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
@NonNull
public OpenParams build() {
return new OpenParams(mOpenFlags, mCursorFactory, mErrorHandler, mLookasideSlotSize,
mLookasideSlotCount);
mLookasideSlotCount, mIdleConnectionTimeout);
}
}
}

View File

@@ -94,14 +94,21 @@ public final class SQLiteDatabaseConfiguration {
*
* <p>If negative, the default lookaside configuration will be used
*/
public int lookasideSlotSize;
public int lookasideSlotSize = -1;
/**
* The total number of lookaside memory slots per database connection
*
* <p>If negative, the default lookaside configuration will be used
*/
public int lookasideSlotCount;
public int lookasideSlotCount = -1;
/**
* The number of milliseconds that SQLite connection is allowed to be idle before it
* is closed and removed from the pool.
* <p>By default, idle connections are not closed
*/
public long idleConnectionTimeoutMs = Long.MAX_VALUE;
/**
* Creates a database configuration with the required parameters for opening a
@@ -122,8 +129,6 @@ public final class SQLiteDatabaseConfiguration {
// Set default values for optional parameters.
maxSqlCacheSize = 25;
locale = Locale.getDefault();
lookasideSlotSize = -1;
lookasideSlotCount = -1;
}
/**
@@ -164,6 +169,7 @@ public final class SQLiteDatabaseConfiguration {
customFunctions.addAll(other.customFunctions);
lookasideSlotSize = other.lookasideSlotSize;
lookasideSlotCount = other.lookasideSlotCount;
idleConnectionTimeoutMs = other.idleConnectionTimeoutMs;
}
/**

View File

@@ -124,4 +124,15 @@ public final class SQLiteGlobal {
com.android.internal.R.integer.db_connection_pool_size));
return Math.max(2, value);
}
/**
* The default number of milliseconds that SQLite connection is allowed to be idle before it
* is closed and removed from the pool.
*/
public static int getIdleConnectionTimeout() {
return SystemProperties.getInt("debug.sqlite.idle_connection_timeout",
Resources.getSystem().getInteger(
com.android.internal.R.integer.db_default_idle_connection_timeout));
}
}

View File

@@ -194,6 +194,26 @@ public abstract class SQLiteOpenHelper {
}
}
/**
* Sets the maximum number of milliseconds that SQLite connection is allowed to be idle
* before it is closed and removed from the pool.
*
* <p>This method should be called from the constructor of the subclass,
* before opening the database
*
* @param idleConnectionTimeoutMs timeout in milliseconds. Use {@link Long#MAX_VALUE} value
* to allow unlimited idle connections.
*/
public void setIdleConnectionTimeout(@IntRange(from = 0) final long idleConnectionTimeoutMs) {
synchronized (this) {
if (mDatabase != null && mDatabase.isOpen()) {
throw new IllegalStateException(
"Connection timeout setting cannot be changed after opening the database");
}
mOpenParamsBuilder.setIdleConnectionTimeout(idleConnectionTimeoutMs);
}
}
/**
* Create and/or open a database that will be used for reading and writing.
* The first time this is called, the database will be opened and

View File

@@ -1673,6 +1673,10 @@
The size of the WAL file is also constrained by 'db_journal_size_limit'. -->
<integer name="db_wal_autocheckpoint">100</integer>
<!-- The number of milliseconds that SQLite connection is allowed to be idle before it
is closed and removed from the pool -->
<integer name="db_default_idle_connection_timeout">30000</integer>
<!-- Max space (in MB) allocated to DownloadManager to store the downloaded
files if they are to be stored in DownloadManager's data dir,
which typically is /data/data/com.android.providers.downloads/files -->

View File

@@ -428,6 +428,7 @@
<java-symbol type="integer" name="db_connection_pool_size" />
<java-symbol type="integer" name="db_journal_size_limit" />
<java-symbol type="integer" name="db_wal_autocheckpoint" />
<java-symbol type="integer" name="db_default_idle_connection_timeout" />
<java-symbol type="integer" name="config_soundEffectVolumeDb" />
<java-symbol type="integer" name="config_lockSoundVolumeDb" />
<java-symbol type="integer" name="config_multiuserMaximumUsers" />

View File

@@ -25,6 +25,8 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteException;
import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.UiDevice;
import android.test.AndroidTestCase;
import android.test.PerformanceTestCase;
import android.test.suitebuilder.annotation.LargeTest;
@@ -1185,4 +1187,38 @@ public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceT
fail("unexpected");
}
}
@MediumTest
public void testCloseIdleConnection() throws Exception {
mDatabase.close();
SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
.setIdleConnectionTimeout(1000).build();
mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile.getPath(), params);
// Wait a bit and check that connection is still open
Thread.sleep(100);
String output = executeShellCommand("dumpsys dbinfo " + getContext().getPackageName());
assertTrue("Connection #0 should be open. Output: " + output,
output.contains("Connection #0:"));
// Now cause idle timeout and check that connection is closed
Thread.sleep(1000);
output = executeShellCommand("dumpsys dbinfo " + getContext().getPackageName());
assertFalse("Connection #0 should be closed. Output: " + output,
output.contains("Connection #0:"));
}
@SmallTest
public void testSetIdleConnectionTimeoutValidation() throws Exception {
try {
new SQLiteDatabase.OpenParams.Builder().setIdleConnectionTimeout(-1).build();
fail("Negative timeout should be rejected");
} catch (IllegalArgumentException expected) {
}
}
private String executeShellCommand(String cmd) throws Exception {
return UiDevice.getInstance(
InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
}
}