Add locking around storage controller interactions.

This commit is contained in:
Greyson Parrelli
2026-07-22 12:38:56 -04:00
committed by Michelle Tang
parent 8b1c2bf2af
commit dfe6ae812d
@@ -19,6 +19,8 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
@@ -110,6 +112,13 @@ import kotlin.time.Duration.Companion.seconds
*/
class AppRegistrationStorageController(private val context: Context) : StorageController {
/**
* Serializes access to the in-progress registration data blob. Updates are read-modify-write, and concurrent
* writers (e.g. a flow-state save racing an account-data write) would otherwise lose one of the updates. Reads
* take the lock too, since a write deletes the previous blob after swapping the URI.
*/
private val inProgressDataLock = Mutex()
/**
* Restarts the process-wide network stack after account data is applied. Overridable only so tests can avoid
* touching the real, suite-shared network module; production must never replace it.
@@ -155,8 +164,10 @@ class AppRegistrationStorageController(private val context: Context) : StorageCo
}
override suspend fun clearAllData() = withContext(Dispatchers.IO) {
SignalStore.registration.inProgressRegistrationDataBlobUri?.toUri()?.let { AppDependencies.blobs.delete(context, it) }
SignalStore.registration.inProgressRegistrationDataBlobUri = null
inProgressDataLock.withLock {
SignalStore.registration.inProgressRegistrationDataBlobUri?.toUri()?.let { AppDependencies.blobs.delete(context, it) }
SignalStore.registration.inProgressRegistrationDataBlobUri = null
}
// Best-effort cleanup of the legacy plaintext file written by older builds.
File(context.cacheDir, TEMP_PROTO_FILENAME).takeIf { it.exists() }?.delete()
@@ -202,8 +213,22 @@ class AppRegistrationStorageController(private val context: Context) : StorageCo
}
override suspend fun readInProgressRegistrationData(): RegistrationData = withContext(Dispatchers.IO) {
val uri = SignalStore.registration.inProgressRegistrationDataBlobUri?.toUri() ?: return@withContext RegistrationData()
try {
inProgressDataLock.withLock {
readInProgressRegistrationDataInternal()
}
}
override suspend fun updateInProgressRegistrationData(updater: RegistrationData.Builder.() -> Unit) = withContext(Dispatchers.IO) {
inProgressDataLock.withLock {
val current = readInProgressRegistrationDataInternal()
val updated = current.newBuilder().apply(updater).build()
writeRegistrationData(updated)
}
}
private fun readInProgressRegistrationDataInternal(): RegistrationData {
val uri = SignalStore.registration.inProgressRegistrationDataBlobUri?.toUri() ?: return RegistrationData()
return try {
AppDependencies.blobs.getStream(context, uri).use { RegistrationData.ADAPTER.decode(it) }
} catch (e: Exception) {
Log.w(TAG, "Failed to read/decode in-progress registration data, returning empty.", e)
@@ -211,12 +236,6 @@ class AppRegistrationStorageController(private val context: Context) : StorageCo
}
}
override suspend fun updateInProgressRegistrationData(updater: RegistrationData.Builder.() -> Unit) = withContext(Dispatchers.IO) {
val current = readInProgressRegistrationData()
val updated = current.newBuilder().apply(updater).build()
writeRegistrationData(updated)
}
override suspend fun commitRegistrationData() = withContext(Dispatchers.IO) {
val data = readInProgressRegistrationData()
val accountData = data.accountData