Skip to content

Commit de6d515

Browse files
committed
fix(model-datastructure): ensure OTBranch doesn't return partial transactions
When OTBranch.operationsAndTree was called while a write transaction was still active, it returned the possibly incomplete list of operations that may not even be commited, if an exception occurred.
1 parent d3ebcf1 commit de6d515

File tree

1 file changed

+44
-14
lines changed
  • model-datastructure/src/commonMain/kotlin/org/modelix/model/operations

1 file changed

+44
-14
lines changed

model-datastructure/src/commonMain/kotlin/org/modelix/model/operations/OTBranch.kt

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,47 @@ class OTBranch(
3030
private val idGenerator: IIdGenerator,
3131
private val store: IDeserializingKeyValueStore,
3232
) : IBranch {
33-
private var operations: MutableList<IAppliedOperation> = ArrayList()
34-
private var treeForOperations: ITree = branch.computeRead { branch.transaction.tree }
35-
private val operationsLock = Any()
33+
private var currentOperations: MutableList<IAppliedOperation> = ArrayList()
34+
private val completedChanges: MutableList<Pair<List<IAppliedOperation>, ITree>> = ArrayList()
3635
private val id: String = branch.getId()
3736

3837
fun operationApplied(op: IAppliedOperation) {
39-
runSynchronized(operationsLock) {
40-
operations.add(op)
41-
treeForOperations = transaction.tree
42-
}
38+
check(canWrite()) { "Only allowed inside a write transaction" }
39+
currentOperations.add(op)
4340
}
4441

4542
override fun getId(): String {
4643
return id
4744
}
4845

46+
@Deprecated("renamed to getPendingChanges()", ReplaceWith("getPendingChanges()"))
4947
val operationsAndTree: Pair<List<IAppliedOperation>, ITree>
5048
get() {
51-
runSynchronized(operationsLock) {
52-
val newOperations: List<IAppliedOperation> = operations
53-
operations = ArrayList()
54-
return Pair(newOperations, treeForOperations)
49+
return runSynchronized(completedChanges) {
50+
val result = when (completedChanges.size) {
51+
0 -> emptyList<IAppliedOperation>() to computeReadT { it.tree }
52+
1 -> completedChanges[0]
53+
else -> completedChanges.flatMap { it.first } to completedChanges.last().second
54+
}
55+
completedChanges.clear()
56+
result
57+
}
58+
}
59+
60+
/**
61+
* @return the operations applied to the branch since the last call of this function and the resulting ITree.
62+
*/
63+
fun getPendingChanges(): Pair<List<IAppliedOperation>, ITree> {
64+
return runSynchronized(completedChanges) {
65+
val result = when (completedChanges.size) {
66+
0 -> emptyList<IAppliedOperation>() to computeReadT { it.tree }
67+
1 -> completedChanges[0]
68+
else -> completedChanges.flatMap { it.first } to completedChanges.last().second
5569
}
70+
completedChanges.clear()
71+
result
5672
}
73+
}
5774

5875
override fun addListener(l: IBranchListener) {
5976
branch.addListener(l)
@@ -78,7 +95,21 @@ class OTBranch(
7895

7996
override fun <T> computeWrite(computable: () -> T): T {
8097
checkNotEDT()
81-
return branch.computeWrite(computable)
98+
return if (canWrite()) {
99+
branch.computeWrite(computable)
100+
} else {
101+
branch.computeWriteT { t ->
102+
try {
103+
val result = computable()
104+
runSynchronized(completedChanges) {
105+
completedChanges += currentOperations to t.tree
106+
}
107+
result
108+
} finally {
109+
currentOperations = ArrayList()
110+
}
111+
}
112+
}
82113
}
83114

84115
override val readTransaction: IReadTransaction
@@ -96,8 +127,7 @@ class OTBranch(
96127
}
97128

98129
override fun runWrite(runnable: () -> Unit) {
99-
checkNotEDT()
100-
branch.runWrite(runnable)
130+
computeWrite(runnable)
101131
}
102132

103133
fun wrap(t: ITransaction): ITransaction {

0 commit comments

Comments
 (0)