Skip to content

Commit 0807b9d

Browse files
committed
refactor(state): Centralize write barrier & mutex in SafeBoxStateManager
1 parent 4e2247b commit 0807b9d

File tree

2 files changed

+43
-47
lines changed

2 files changed

+43
-47
lines changed

safebox/src/main/java/com/harrytmthy/safebox/SafeBox.kt

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,8 @@ import com.harrytmthy.safebox.storage.Bytes
3838
import com.harrytmthy.safebox.storage.SafeBoxBlobStore
3939
import com.harrytmthy.safebox.strategy.ValueFallbackStrategy
4040
import com.harrytmthy.safebox.strategy.ValueFallbackStrategy.WARN
41-
import kotlinx.coroutines.CompletableDeferred
4241
import kotlinx.coroutines.CoroutineDispatcher
4342
import kotlinx.coroutines.Dispatchers
44-
import kotlinx.coroutines.sync.Mutex
45-
import kotlinx.coroutines.sync.withLock
4643
import java.util.concurrent.ConcurrentHashMap
4744
import java.util.concurrent.CopyOnWriteArrayList
4845
import java.util.concurrent.atomic.AtomicBoolean
@@ -85,10 +82,6 @@ public class SafeBox private constructor(
8582

8683
private val listeners = CopyOnWriteArrayList<OnSharedPreferenceChangeListener>()
8784

88-
private val writeMutex = Mutex()
89-
90-
private val writeBarrier = AtomicReference(CompletableDeferred<Unit>().apply { complete(Unit) })
91-
9285
private val scanScheduled = AtomicBoolean(false)
9386

9487
private val delegate = object : Delegate {
@@ -101,21 +94,8 @@ public class SafeBox private constructor(
10194
entries.clear() // Prevents stale mutations on reused editor instance
10295
updateEntries(entriesToWrite, cleared)
10396
}
104-
val currentWriteBarrier = CompletableDeferred<Unit>()
105-
val previousWriteBarrier = writeBarrier.getAndSet(currentWriteBarrier)
10697
return stateManager.launchCommitWithWritingState {
107-
try {
108-
previousWriteBarrier.await()
109-
writeMutex.withLock {
110-
applyChanges(entriesToWrite, cleared)
111-
}
112-
true
113-
} catch (e: Exception) {
114-
Log.e("SafeBox", "Failed to commit changes.", e)
115-
false
116-
} finally {
117-
currentWriteBarrier.complete(Unit)
118-
}
98+
applyChanges(entriesToWrite, cleared)
11999
}
120100
}
121101

@@ -125,19 +105,8 @@ public class SafeBox private constructor(
125105
entries.clear() // Prevents stale mutations on reused editor instance
126106
updateEntries(entriesToWrite, cleared)
127107
}
128-
val currentWriteBarrier = CompletableDeferred<Unit>()
129-
val previousWriteBarrier = writeBarrier.getAndSet(currentWriteBarrier)
130108
stateManager.launchApplyWithWritingState {
131-
try {
132-
previousWriteBarrier.await()
133-
writeMutex.withLock {
134-
applyChanges(entriesToWrite, cleared)
135-
}
136-
} catch (e: Exception) {
137-
Log.e("SafeBox", "Failed to commit changes.", e)
138-
} finally {
139-
currentWriteBarrier.complete(Unit)
140-
}
109+
applyChanges(entriesToWrite, cleared)
141110
}
142111
}
143112

@@ -334,11 +303,8 @@ public class SafeBox private constructor(
334303
if (!scanScheduled.compareAndSet(false, true)) {
335304
return
336305
}
337-
val currentWriteBarrier = CompletableDeferred<Unit>()
338-
val previousWriteBarrier = writeBarrier.getAndSet(currentWriteBarrier)
339306
stateManager.launchApplyWithWritingState {
340307
try {
341-
previousWriteBarrier.await()
342308
val deadKeys = ArrayList<Bytes>()
343309
for ((encryptedKey, encryptedValue) in entries) {
344310
if (valueCipherProvider.tryDecrypt(encryptedValue) == null) {
@@ -347,12 +313,9 @@ public class SafeBox private constructor(
347313
}
348314
}
349315
if (deadKeys.isNotEmpty()) {
350-
writeMutex.withLock {
351-
blobStore.delete(*deadKeys.toTypedArray())
352-
}
316+
blobStore.delete(*deadKeys.toTypedArray())
353317
}
354318
} finally {
355-
currentWriteBarrier.complete(Unit)
356319
scanScheduled.set(false)
357320
}
358321
}

safebox/src/main/java/com/harrytmthy/safebox/state/SafeBoxStateManager.kt

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.harrytmthy.safebox.state
1818

19+
import android.util.Log
1920
import com.harrytmthy.safebox.SafeBox
2021
import com.harrytmthy.safebox.extensions.safeBoxScope
2122
import com.harrytmthy.safebox.state.SafeBoxState.IDLE
@@ -25,7 +26,10 @@ import kotlinx.coroutines.CompletableDeferred
2526
import kotlinx.coroutines.CoroutineDispatcher
2627
import kotlinx.coroutines.launch
2728
import kotlinx.coroutines.runBlocking
29+
import kotlinx.coroutines.sync.Mutex
30+
import kotlinx.coroutines.sync.withLock
2831
import java.util.concurrent.atomic.AtomicInteger
32+
import java.util.concurrent.atomic.AtomicReference
2933

3034
/**
3135
* Manages the lifecycle state of a [SafeBox] instance and coordinates concurrent read/write access.
@@ -52,6 +56,10 @@ internal class SafeBoxStateManager(
5256

5357
private val initialReadCompleted = CompletableDeferred<Unit>()
5458

59+
private val writeBarrier = AtomicReference(CompletableDeferred<Unit>().apply { complete(Unit) })
60+
61+
private val writeMutex = Mutex()
62+
5563
fun setStateListener(stateListener: SafeBoxStateListener?) {
5664
this.stateListener = stateListener
5765
}
@@ -70,24 +78,49 @@ internal class SafeBoxStateManager(
7078
}
7179
}
7280

73-
inline fun launchCommitWithWritingState(crossinline block: suspend () -> Boolean): Boolean =
74-
runBlocking {
75-
withStateTransition(block)
81+
inline fun launchCommitWithWritingState(crossinline block: suspend () -> Unit): Boolean {
82+
val currentWriteBarrier = CompletableDeferred<Unit>()
83+
val previousWriteBarrier = writeBarrier.getAndSet(currentWriteBarrier)
84+
return runBlocking {
85+
try {
86+
withStateTransition(previousWriteBarrier, block)
87+
true
88+
} catch (e: Exception) {
89+
Log.e("SafeBox", "Failed to commit changes.", e)
90+
false
91+
} finally {
92+
currentWriteBarrier.complete(Unit)
93+
}
7694
}
95+
}
7796

7897
inline fun launchApplyWithWritingState(crossinline block: suspend () -> Unit) {
98+
val currentWriteBarrier = CompletableDeferred<Unit>()
99+
val previousWriteBarrier = writeBarrier.getAndSet(currentWriteBarrier)
79100
safeBoxScope.launch(ioDispatcher) {
80-
withStateTransition(block)
101+
try {
102+
withStateTransition(previousWriteBarrier, block)
103+
} catch (e: Exception) {
104+
Log.e("SafeBox", "Failed to commit changes.", e)
105+
} finally {
106+
currentWriteBarrier.complete(Unit)
107+
}
81108
}
82109
}
83110

84-
private suspend inline fun <T> withStateTransition(crossinline block: suspend () -> T): T {
111+
private suspend inline fun withStateTransition(
112+
previousWriteBarrier: CompletableDeferred<Unit>,
113+
crossinline block: suspend () -> Unit,
114+
) {
85115
initialReadCompleted.await()
86116
if (concurrentWriteCount.incrementAndGet() == 1) {
87117
updateState(WRITING)
88118
}
89-
return try {
90-
block()
119+
try {
120+
previousWriteBarrier.await()
121+
writeMutex.withLock {
122+
block()
123+
}
91124
} finally {
92125
if (concurrentWriteCount.decrementAndGet() == 0) {
93126
updateState(IDLE)

0 commit comments

Comments
 (0)