mirror of https://github.com/M66B/FairEmail.git
228 lines
8.0 KiB
Kotlin
228 lines
8.0 KiB
Kotlin
/*
|
|
* 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 androidx.annotation.GuardedBy
|
|
import androidx.annotation.VisibleForTesting
|
|
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.
|
|
*
|
|
* @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
|
|
*/
|
|
internal class AutoCloser(
|
|
autoCloseTimeoutAmount: Long,
|
|
autoCloseTimeUnit: TimeUnit,
|
|
autoCloseExecutor: Executor
|
|
) {
|
|
lateinit var delegateOpenHelper: SupportSQLiteOpenHelper
|
|
private val handler = Handler(Looper.getMainLooper())
|
|
|
|
internal var onAutoCloseCallback: Runnable? = null
|
|
|
|
private val lock = Any()
|
|
|
|
private var autoCloseTimeoutInMs: Long = autoCloseTimeUnit.toMillis(autoCloseTimeoutAmount)
|
|
|
|
private val executor: Executor = autoCloseExecutor
|
|
|
|
@GuardedBy("lock")
|
|
internal var refCount = 0
|
|
|
|
@GuardedBy("lock")
|
|
internal var lastDecrementRefCountTimeStamp = SystemClock.uptimeMillis()
|
|
|
|
// The unwrapped SupportSqliteDatabase
|
|
@GuardedBy("lock")
|
|
internal var delegateDatabase: SupportSQLiteDatabase? = null
|
|
|
|
private var manuallyClosed = false
|
|
|
|
private val executeAutoCloser = Runnable { executor.execute(autoCloser) }
|
|
|
|
private val autoCloser = Runnable {
|
|
synchronized(lock) {
|
|
if (SystemClock.uptimeMillis() - lastDecrementRefCountTimeStamp
|
|
< autoCloseTimeoutInMs
|
|
) {
|
|
// 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@Runnable
|
|
}
|
|
if (refCount != 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@Runnable
|
|
}
|
|
onAutoCloseCallback?.run() ?: error(
|
|
"onAutoCloseCallback is null but it should" +
|
|
" have been set before use. Please file a bug " +
|
|
"against Room at: $autoCloseBug"
|
|
)
|
|
|
|
delegateDatabase?.let {
|
|
if (it.isOpen) {
|
|
it.close()
|
|
}
|
|
}
|
|
delegateDatabase = null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
fun init(delegateOpenHelper: SupportSQLiteOpenHelper) {
|
|
this.delegateOpenHelper = 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.
|
|
*/
|
|
fun <V> executeRefCountingFunction(block: (SupportSQLiteDatabase) -> V): V =
|
|
try {
|
|
block(incrementCountAndEnsureDbIsOpen())
|
|
} finally {
|
|
decrementCountAndScheduleClose()
|
|
}
|
|
|
|
/**
|
|
* Confirms that autoCloser is no longer running and confirms that delegateDatabase is set
|
|
* and open. delegateDatabase 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.
|
|
*/
|
|
fun incrementCountAndEnsureDbIsOpen(): SupportSQLiteDatabase {
|
|
// TODO(rohitsat): avoid synchronized(lock) 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(lock) {
|
|
|
|
// If there is a scheduled autoclose operation, we should remove it from the handler.
|
|
handler.removeCallbacks(executeAutoCloser)
|
|
refCount++
|
|
check(!manuallyClosed) { "Attempting to open already closed database." }
|
|
delegateDatabase?.let {
|
|
if (it.isOpen) {
|
|
return it
|
|
}
|
|
}
|
|
return delegateOpenHelper.writableDatabase.also { delegateDatabase = it }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
fun decrementCountAndScheduleClose() {
|
|
// TODO(rohitsat): avoid synchronized(lock) when possible
|
|
synchronized(lock) {
|
|
check(refCount > 0) {
|
|
"ref count is 0 or lower but we're supposed to decrement"
|
|
}
|
|
// decrement refCount
|
|
refCount--
|
|
|
|
// if refcount is zero, schedule close operation
|
|
if (refCount == 0) {
|
|
if (delegateDatabase == null) {
|
|
// No db to close, this can happen due to exceptions when creating db...
|
|
return
|
|
}
|
|
handler.postDelayed(executeAutoCloser, autoCloseTimeoutInMs)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Close the database if it is still active.
|
|
*
|
|
* @throws IOException if an exception is encountered when closing the underlying db.
|
|
*/
|
|
@Throws(IOException::class)
|
|
fun closeDatabaseIfOpen() {
|
|
synchronized(lock) {
|
|
manuallyClosed = true
|
|
delegateDatabase?.close()
|
|
delegateDatabase = 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
|
|
*/
|
|
val isActive: Boolean
|
|
get() = !manuallyClosed
|
|
|
|
/**
|
|
* Returns the current ref count for this auto closer. This is only visible for testing.
|
|
*
|
|
* @return current ref count
|
|
*/
|
|
@get:VisibleForTesting
|
|
internal val refCountForTest: Int
|
|
get() {
|
|
synchronized(lock) { return refCount }
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
fun setAutoCloseCallback(onAutoClose: Runnable) {
|
|
onAutoCloseCallback = onAutoClose
|
|
}
|
|
|
|
companion object {
|
|
const val autoCloseBug = "https://issuetracker.google.com/issues/new?component=" +
|
|
"413107&template=1096568"
|
|
}
|
|
}
|