Fix potential thread-safety issue when changing backends

This commit is contained in:
Torsten Grote 2024-10-11 10:36:27 -03:00
parent 3d9eca0d40
commit 2957678465
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
5 changed files with 36 additions and 12 deletions

View file

@ -25,7 +25,10 @@ class BackendManager(
backendFactory: BackendFactory, backendFactory: BackendFactory,
) { ) {
@Volatile
private var mBackend: Backend? private var mBackend: Backend?
@Volatile
private var mBackendProperties: BackendProperties<*>? private var mBackendProperties: BackendProperties<*>?
val backend: Backend val backend: Backend
@ -81,6 +84,8 @@ class BackendManager(
* IMPORTANT: Do no call this while current plugins are being used, * IMPORTANT: Do no call this while current plugins are being used,
* e.g. while backup/restore operation is still running. * e.g. while backup/restore operation is still running.
*/ */
@WorkerThread
@Synchronized
fun <T> changePlugins( fun <T> changePlugins(
backend: Backend, backend: Backend,
storageProperties: BackendProperties<T>, storageProperties: BackendProperties<T>,

View file

@ -90,6 +90,7 @@ internal class SafHandler(
return false return false
} }
@WorkerThread
fun setPlugin(safProperties: SafProperties) { fun setPlugin(safProperties: SafProperties) {
backendManager.changePlugins( backendManager.changePlugins(
backend = backendFactory.createSafBackend(safProperties), backend = backendFactory.createSafBackend(safProperties),

View file

@ -87,6 +87,7 @@ internal class WebDavHandler(
settingsManager.saveWebDavConfig(properties.config) settingsManager.saveWebDavConfig(properties.config)
} }
@WorkerThread
fun setPlugin(properties: WebDavProperties, backend: Backend) { fun setPlugin(properties: WebDavProperties, backend: Backend) {
backendManager.changePlugins( backendManager.changePlugins(
backend = backend, backend = backend,

View file

@ -7,6 +7,7 @@ package com.stevesoltys.seedvault.repo
import android.content.Context import android.content.Context
import android.content.Context.MODE_APPEND import android.content.Context.MODE_APPEND
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.MemoryLogger import com.stevesoltys.seedvault.MemoryLogger
import com.stevesoltys.seedvault.proto.Snapshot import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.proto.Snapshot.Blob import com.stevesoltys.seedvault.proto.Snapshot.Blob
@ -90,6 +91,7 @@ class BlobCache(
* * changing to a different backup to prevent usage of blobs that don't exist there * * changing to a different backup to prevent usage of blobs that don't exist there
* * uploading a new snapshot to prevent the persistent cache from growing indefinitely * * uploading a new snapshot to prevent the persistent cache from growing indefinitely
*/ */
@WorkerThread
fun clearLocalCache() { fun clearLocalCache() {
log.info { "Clearing local cache..." } log.info { "Clearing local cache..." }
context.deleteFile(CACHE_FILE_NAME) context.deleteFile(CACHE_FILE_NAME)

View file

@ -10,6 +10,7 @@ import android.app.backup.IBackupManager
import android.app.job.JobInfo import android.app.job.JobInfo
import android.os.UserHandle import android.os.UserHandle
import android.util.Log import android.util.Log
import androidx.annotation.UiThread
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
@ -23,6 +24,7 @@ import com.stevesoltys.seedvault.worker.AppBackupWorker
import com.stevesoltys.seedvault.worker.BackupRequester.Companion.requestFilesAndAppBackup import com.stevesoltys.seedvault.worker.BackupRequester.Companion.requestFilesAndAppBackup
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.backup.BackupJobService import org.calyxos.backup.storage.backup.BackupJobService
import org.calyxos.seedvault.core.backends.Backend import org.calyxos.seedvault.core.backends.Backend
@ -46,9 +48,14 @@ internal class BackupStorageViewModel(
override val isRestoreOperation = false override val isRestoreOperation = false
@UiThread
override fun onSafUriSet(safProperties: SafProperties) { override fun onSafUriSet(safProperties: SafProperties) {
safHandler.save(safProperties) safHandler.save(safProperties)
viewModelScope.launch {
withContext(Dispatchers.IO) {
safHandler.setPlugin(safProperties) safHandler.setPlugin(safProperties)
}
withContext(Dispatchers.Main) { // UiThread
if (safProperties.isUsb) { if (safProperties.isUsb) {
// disable storage backup if new storage is on USB // disable storage backup if new storage is on USB
cancelBackupWorkers() cancelBackupWorkers()
@ -59,13 +66,21 @@ internal class BackupStorageViewModel(
} }
onStorageLocationSet(safProperties.isUsb) onStorageLocationSet(safProperties.isUsb)
} }
}
}
override fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend) { override fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend) {
webdavHandler.save(properties) webdavHandler.save(properties)
viewModelScope.launch {
withContext(Dispatchers.IO) {
webdavHandler.setPlugin(properties, backend) webdavHandler.setPlugin(properties, backend)
}
withContext(Dispatchers.Main) {
scheduleBackupWorkers() scheduleBackupWorkers()
onStorageLocationSet(isUsb = false) onStorageLocationSet(isUsb = false)
} }
}
}
private fun onStorageLocationSet(isUsb: Boolean) { private fun onStorageLocationSet(isUsb: Boolean) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {