mirror of https://github.com/M66B/FairEmail.git
312 lines
11 KiB
Java
312 lines
11 KiB
Java
/*
|
|
* Copyright 2020 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 androidx.room;
|
|
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.SystemClock;
|
|
import android.util.Log;
|
|
|
|
import androidx.annotation.GuardedBy;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.VisibleForTesting;
|
|
import androidx.arch.core.util.Function;
|
|
import androidx.room.util.SneakyThrow;
|
|
import androidx.sqlite.db.SupportSQLiteDatabase;
|
|
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
|
|
|
import java.io.IOException;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
/**
|
|
* AutoCloser is responsible for automatically opening (using
|
|
* delegateOpenHelper) and closing (on a timer started when there are no remaining references) a
|
|
* SupportSqliteDatabase.
|
|
*
|
|
* It is important to ensure that the ref count is incremented when using a returned database.
|
|
*/
|
|
final class AutoCloser {
|
|
|
|
@Nullable
|
|
private SupportSQLiteOpenHelper mDelegateOpenHelper = null;
|
|
|
|
@NonNull
|
|
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
|
|
|
// Package private for access from mAutoCloser
|
|
@Nullable
|
|
Runnable mOnAutoCloseCallback = null;
|
|
|
|
// Package private for access from mAutoCloser
|
|
@NonNull
|
|
final Object mLock = new Object();
|
|
|
|
// Package private for access from mAutoCloser
|
|
final long mAutoCloseTimeoutInMs;
|
|
|
|
// Package private for access from mExecuteAutoCloser
|
|
@NonNull
|
|
final Executor mExecutor;
|
|
|
|
// Package private for access from mAutoCloser
|
|
@GuardedBy("mLock")
|
|
int mRefCount = 0;
|
|
|
|
// Package private for access from mAutoCloser
|
|
@GuardedBy("mLock")
|
|
long mLastDecrementRefCountTimeStamp = SystemClock.uptimeMillis();
|
|
|
|
// The unwrapped SupportSqliteDatabase
|
|
// Package private for access from mAutoCloser
|
|
@GuardedBy("mLock")
|
|
@Nullable
|
|
SupportSQLiteDatabase mDelegateDatabase;
|
|
|
|
private boolean mManuallyClosed = false;
|
|
|
|
private final Runnable mExecuteAutoCloser = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
mExecutor.execute(mAutoCloser);
|
|
}
|
|
};
|
|
|
|
// Package private for access from mExecuteAutoCloser
|
|
@NonNull
|
|
final Runnable mAutoCloser = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
synchronized (mLock) {
|
|
if (SystemClock.uptimeMillis() - mLastDecrementRefCountTimeStamp
|
|
< mAutoCloseTimeoutInMs) {
|
|
// An increment + decrement beat us to closing the db. We
|
|
// will not close the database, and there should be at least
|
|
// one more auto-close scheduled.
|
|
return;
|
|
}
|
|
|
|
if (mRefCount != 0) {
|
|
// An increment beat us to closing the db. We don't close the
|
|
// db, and another closer will be scheduled once the ref
|
|
// count is decremented.
|
|
return;
|
|
}
|
|
|
|
if (mOnAutoCloseCallback != null) {
|
|
mOnAutoCloseCallback.run();
|
|
} else {
|
|
throw new IllegalStateException("mOnAutoCloseCallback is null but it should"
|
|
+ " have been set before use. Please file a bug "
|
|
+ "against Room at: https://issuetracker.google"
|
|
+ ".com/issues/new?component=413107&template=1096568");
|
|
}
|
|
|
|
if (mDelegateDatabase != null && mDelegateDatabase.isOpen()) {
|
|
try {
|
|
mDelegateDatabase.close();
|
|
} catch (IOException e) {
|
|
SneakyThrow.reThrow(e);
|
|
}
|
|
mDelegateDatabase = null;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Construct an AutoCloser.
|
|
*
|
|
* @param autoCloseTimeoutAmount time for auto close timer
|
|
* @param autoCloseTimeUnit time unit for autoCloseTimeoutAmount
|
|
* @param autoCloseExecutor the executor on which the auto close operation will happen
|
|
*/
|
|
AutoCloser(long autoCloseTimeoutAmount,
|
|
@NonNull TimeUnit autoCloseTimeUnit,
|
|
@NonNull Executor autoCloseExecutor) {
|
|
mAutoCloseTimeoutInMs = autoCloseTimeUnit.toMillis(autoCloseTimeoutAmount);
|
|
mExecutor = autoCloseExecutor;
|
|
}
|
|
|
|
/**
|
|
* Since we need to construct the AutoCloser in the RoomDatabase.Builder, we need to set the
|
|
* delegateOpenHelper after construction.
|
|
*
|
|
* @param delegateOpenHelper the open helper that is used to create
|
|
* new SupportSqliteDatabases
|
|
*/
|
|
public void init(@NonNull SupportSQLiteOpenHelper delegateOpenHelper) {
|
|
if (mDelegateOpenHelper != null) {
|
|
Log.e(Room.LOG_TAG, "AutoCloser initialized multiple times. Please file a bug against"
|
|
+ " room at: https://issuetracker.google"
|
|
+ ".com/issues/new?component=413107&template=1096568");
|
|
return;
|
|
}
|
|
this.mDelegateOpenHelper = delegateOpenHelper;
|
|
}
|
|
|
|
/**
|
|
* Execute a ref counting function. The function will receive an unwrapped open database and
|
|
* this database will stay open until at least after function returns. If there are no more
|
|
* references in use for the db once function completes, an auto close operation will be
|
|
* scheduled.
|
|
*/
|
|
@Nullable
|
|
public <V> V executeRefCountingFunction(@NonNull Function<SupportSQLiteDatabase, V> function) {
|
|
try {
|
|
SupportSQLiteDatabase db = incrementCountAndEnsureDbIsOpen();
|
|
return function.apply(db);
|
|
} finally {
|
|
decrementCountAndScheduleClose();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Confirms that autoCloser is no longer running and confirms that mDelegateDatabase is set
|
|
* and open. mDelegateDatabase will not be auto closed until
|
|
* decrementRefCountAndScheduleClose is called. decrementRefCountAndScheduleClose must be
|
|
* called once for each call to incrementCountAndEnsureDbIsOpen.
|
|
*
|
|
* If this throws an exception, decrementCountAndScheduleClose must still be called!
|
|
*
|
|
* @return the *unwrapped* SupportSQLiteDatabase.
|
|
*/
|
|
@NonNull
|
|
public SupportSQLiteDatabase incrementCountAndEnsureDbIsOpen() {
|
|
//TODO(rohitsat): avoid synchronized(mLock) when possible. We should be able to avoid it
|
|
// when refCount is not hitting zero or if there is no auto close scheduled if we use
|
|
// Atomics.
|
|
synchronized (mLock) {
|
|
// If there is a scheduled autoclose operation, we should remove it from the handler.
|
|
mHandler.removeCallbacks(mExecuteAutoCloser);
|
|
|
|
mRefCount++;
|
|
|
|
if (mManuallyClosed) {
|
|
throw new IllegalStateException("Attempting to open already closed database.");
|
|
}
|
|
|
|
if (mDelegateDatabase != null && mDelegateDatabase.isOpen()) {
|
|
return mDelegateDatabase;
|
|
}
|
|
|
|
// Get the database while holding `mLock` so no other threads try to create it or
|
|
// destroy it.
|
|
if (mDelegateOpenHelper != null) {
|
|
mDelegateDatabase = mDelegateOpenHelper.getWritableDatabase();
|
|
} else {
|
|
throw new IllegalStateException("AutoCloser has not been initialized. Please file "
|
|
+ "a bug against Room at: "
|
|
+ "https://issuetracker.google.com/issues/new?component=413107&template=1096568");
|
|
}
|
|
|
|
return mDelegateDatabase;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decrements the ref count and schedules a close if there are no other references to the db.
|
|
* This must only be called after a corresponding incrementCountAndEnsureDbIsOpen call.
|
|
*/
|
|
public void decrementCountAndScheduleClose() {
|
|
//TODO(rohitsat): avoid synchronized(mLock) when possible
|
|
synchronized (mLock) {
|
|
if (mRefCount <= 0) {
|
|
throw new IllegalStateException("ref count is 0 or lower but we're supposed to "
|
|
+ "decrement");
|
|
}
|
|
|
|
// decrement refCount
|
|
mRefCount--;
|
|
|
|
// if refcount is zero, schedule close operation
|
|
if (mRefCount == 0) {
|
|
if (mDelegateDatabase == null) {
|
|
// No db to close, this can happen due to exceptions when creating db...
|
|
return;
|
|
}
|
|
mHandler.postDelayed(mExecuteAutoCloser, mAutoCloseTimeoutInMs);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the underlying database. This does not ensure that the database is open; the
|
|
* caller is responsible for ensuring that the database is open and the ref count is non-zero.
|
|
*
|
|
* This is primarily meant for use cases where we don't want to open the database (isOpen) or
|
|
* we know that the database is already open (KeepAliveCursor).
|
|
*/
|
|
@Nullable // Since the db might be closed
|
|
public SupportSQLiteDatabase getDelegateDatabase() {
|
|
synchronized (mLock) {
|
|
return mDelegateDatabase;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Close the database if it is still active.
|
|
*
|
|
* @throws IOException if an exception is encountered when closing the underlying db.
|
|
*/
|
|
public void closeDatabaseIfOpen() throws IOException {
|
|
synchronized (mLock) {
|
|
mManuallyClosed = true;
|
|
|
|
if (mDelegateDatabase != null) {
|
|
mDelegateDatabase.close();
|
|
}
|
|
mDelegateDatabase = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The auto closer is still active if the database has not been closed. This means that
|
|
* whether or not the underlying database is closed, when active we will re-open it on the
|
|
* next access.
|
|
*
|
|
* @return a boolean indicating whether the auto closer is still active
|
|
*/
|
|
public boolean isActive() {
|
|
return !mManuallyClosed;
|
|
}
|
|
|
|
/**
|
|
* Returns the current ref count for this auto closer. This is only visible for testing.
|
|
*
|
|
* @return current ref count
|
|
*/
|
|
@VisibleForTesting
|
|
public int getRefCountForTest() {
|
|
synchronized (mLock) {
|
|
return mRefCount;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets a callback that will be run every time the database is auto-closed. This callback
|
|
* needs to be lightweight since it is run while holding a lock.
|
|
*
|
|
* @param onAutoClose the callback to run
|
|
*/
|
|
public void setAutoCloseCallback(Runnable onAutoClose) {
|
|
mOnAutoCloseCallback = onAutoClose;
|
|
}
|
|
}
|