all tests working. Fixed an older bug: When gc an op I forgot to update the state. This only affected offline editing, and was very hard to catch in the past
This commit is contained in:
parent
bffd130b92
commit
895ec86ff6
@ -143,8 +143,8 @@ module.exports = function (Y/* :any */) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var conn = this
|
||||||
if (syncUser != null) {
|
if (syncUser != null) {
|
||||||
var conn = this
|
|
||||||
this.currentSyncTarget = syncUser
|
this.currentSyncTarget = syncUser
|
||||||
this.y.db.requestTransaction(function *() {
|
this.y.db.requestTransaction(function *() {
|
||||||
var stateSet = yield* this.getStateSet()
|
var stateSet = yield* this.getStateSet()
|
||||||
@ -157,14 +157,15 @@ module.exports = function (Y/* :any */) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.isSynced = true
|
|
||||||
// call when synced listeners
|
|
||||||
for (var f of this.whenSyncedListeners) {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
this.whenSyncedListeners = []
|
|
||||||
this.y.db.requestTransaction(function *() {
|
this.y.db.requestTransaction(function *() {
|
||||||
|
// it is crucial that isSynced is set at the time garbageCollectAfterSync is called
|
||||||
|
conn.isSynced = true
|
||||||
yield* this.garbageCollectAfterSync()
|
yield* this.garbageCollectAfterSync()
|
||||||
|
// call whensynced listeners
|
||||||
|
for (var f of conn.whenSyncedListeners) {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
conn.whenSyncedListeners = []
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,10 +75,14 @@ module.exports = function (Y /* :any */) {
|
|||||||
}
|
}
|
||||||
this.gc1 = [] // first stage
|
this.gc1 = [] // first stage
|
||||||
this.gc2 = [] // second stage -> after that, remove the op
|
this.gc2 = [] // second stage -> after that, remove the op
|
||||||
this.gcTimeout = opts.gcTimeout || 50000
|
this.gcTimeout = !opts.gcTimeout ? 50000 : opts.gcTimeoutś
|
||||||
function garbageCollect () {
|
function garbageCollect () {
|
||||||
return os.whenTransactionsFinished().then(function () {
|
return os.whenTransactionsFinished().then(function () {
|
||||||
if (os.gc1.length > 0 || os.gc2.length > 0) {
|
if (os.gc1.length > 0 || os.gc2.length > 0) {
|
||||||
|
if (!os.y.isConnected()) {
|
||||||
|
debugger
|
||||||
|
console.log('gc should be empty when disconnected!')
|
||||||
|
}
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
os.requestTransaction(function * () {
|
os.requestTransaction(function * () {
|
||||||
if (os.y.connector != null && os.y.connector.isSynced) {
|
if (os.y.connector != null && os.y.connector.isSynced) {
|
||||||
@ -346,15 +350,16 @@ module.exports = function (Y /* :any */) {
|
|||||||
// yield* this.store.operationAdded(this, op)
|
// yield* this.store.operationAdded(this, op)
|
||||||
} else {
|
} else {
|
||||||
// check if this op was defined
|
// check if this op was defined
|
||||||
var defined = yield* this.getOperation(op.id)
|
var defined = yield* this.getInsertion(op.id)
|
||||||
while (defined != null && defined.content != null) {
|
while (defined != null && defined.content != null) {
|
||||||
// check if this op has a longer content in the case it is defined
|
// check if this op has a longer content in the case it is defined
|
||||||
if (defined.content.length < op.content.length) {
|
if (defined.id[1] + defined.content.length < op.id[1] + op.content.length) {
|
||||||
op.content.splice(0, defined.content.length)
|
var overlapSize = defined.content.length - (op.id[1] - defined.id[1])
|
||||||
op.id = [op.id[0], op.id[1] + defined.content.length]
|
op.content.splice(0, overlapSize)
|
||||||
op.left = defined.id
|
op.id = [op.id[0], op.id[1] + overlapSize]
|
||||||
op.origin = defined.id
|
op.left = Y.utils.getLastId(defined)
|
||||||
defined = yield* this.getOperation(op.id)
|
op.origin = op.left
|
||||||
|
defined = yield* this.getOperation(op.id) // getOperation suffices here
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,6 @@ g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransaction
|
|||||||
g.applyRandomTransactionsWithGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
|
g.applyRandomTransactionsWithGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
|
||||||
yield* applyTransactions(1, numberOfTransactions, objects, users.slice(1), transactions)
|
yield* applyTransactions(1, numberOfTransactions, objects, users.slice(1), transactions)
|
||||||
yield Y.utils.globalRoom.flushAll()
|
yield Y.utils.globalRoom.flushAll()
|
||||||
yield g.garbageCollectAllUsers(users)
|
|
||||||
for (var u in users) {
|
for (var u in users) {
|
||||||
// TODO: here, we enforce that two users never sync at the same time with u[0]
|
// TODO: here, we enforce that two users never sync at the same time with u[0]
|
||||||
// enforce that in the connector itself!
|
// enforce that in the connector itself!
|
||||||
|
@ -205,7 +205,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
length = target.id[1] - targetId[1]
|
length = target.id[1] - targetId[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
if (!target.deleted) {
|
if (!target.deleted) {
|
||||||
callType = true
|
callType = true
|
||||||
@ -240,10 +240,10 @@ module.exports = function (Y/* :any */) {
|
|||||||
} else {
|
} else {
|
||||||
left = null
|
left = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// set here because it was deleted and/or gc'd
|
// set here because it was deleted and/or gc'd
|
||||||
yield* this.setOperation(target)
|
yield* this.setOperation(target)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Check if it is possible to add right to the gc.
|
Check if it is possible to add right to the gc.
|
||||||
Because this delete can't be responsible for left being gc'd,
|
Because this delete can't be responsible for left being gc'd,
|
||||||
@ -380,7 +380,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
// gc is stronger, so reduce length of n
|
// gc is stronger, so reduce length of n
|
||||||
n.len -= diff
|
n.len -= diff
|
||||||
if (diff >= next.len) {
|
if (diff >= next.len) {
|
||||||
// delete the missing range after next
|
// delete the missing range after next
|
||||||
diff = diff - next.len // missing range after next
|
diff = diff - next.len // missing range after next
|
||||||
if (diff > 0) {
|
if (diff > 0) {
|
||||||
yield* this.ds.put(n) // unneccessary? TODO!
|
yield* this.ds.put(n) // unneccessary? TODO!
|
||||||
@ -420,6 +420,9 @@ module.exports = function (Y/* :any */) {
|
|||||||
operations that can be gc'd and add them to the garbage collector.
|
operations that can be gc'd and add them to the garbage collector.
|
||||||
*/
|
*/
|
||||||
* garbageCollectAfterSync () {
|
* garbageCollectAfterSync () {
|
||||||
|
if (this.store.gc1.length > 0 || this.store.gc2.length > 0) {
|
||||||
|
console.warn('gc should be empty after sync')
|
||||||
|
}
|
||||||
yield* this.os.iterate(this, null, null, function * (op) {
|
yield* this.os.iterate(this, null, null, function * (op) {
|
||||||
if (op.gc) {
|
if (op.gc) {
|
||||||
delete op.gc
|
delete op.gc
|
||||||
@ -469,7 +472,9 @@ module.exports = function (Y/* :any */) {
|
|||||||
var o = yield* this.getOperation(id)
|
var o = yield* this.getOperation(id)
|
||||||
yield* this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd
|
yield* this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd
|
||||||
// if op exists, then clean that mess up..
|
// if op exists, then clean that mess up..
|
||||||
if (o != null) {
|
if (o == null) {
|
||||||
|
yield* this.updateState(id[0])
|
||||||
|
} else if (o != null) {
|
||||||
var deps = []
|
var deps = []
|
||||||
if (o.opContent != null) {
|
if (o.opContent != null) {
|
||||||
deps.push(o.opContent)
|
deps.push(o.opContent)
|
||||||
@ -647,9 +652,6 @@ module.exports = function (Y/* :any */) {
|
|||||||
*/
|
*/
|
||||||
* applyDeleteSet (ds) {
|
* applyDeleteSet (ds) {
|
||||||
var deletions = []
|
var deletions = []
|
||||||
function createDeletions (user, start, len, gc) {
|
|
||||||
deletions.push([user, start, len, gc])
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var user in ds) {
|
for (var user in ds) {
|
||||||
var dv = ds[user]
|
var dv = ds[user]
|
||||||
@ -676,14 +678,14 @@ module.exports = function (Y/* :any */) {
|
|||||||
// delete maximum the len of d
|
// delete maximum the len of d
|
||||||
// else delete as much as possible
|
// else delete as much as possible
|
||||||
diff = Math.min(n.id[1] - d[0], d[1])
|
diff = Math.min(n.id[1] - d[0], d[1])
|
||||||
createDeletions(user, d[0], diff, d[2])
|
deletions.push([user, d[0], diff, d[2]])
|
||||||
} else {
|
} else {
|
||||||
// 3)
|
// 3)
|
||||||
diff = n.id[1] + n.len - d[0] // never null (see 1)
|
diff = n.id[1] + n.len - d[0] // never null (see 1)
|
||||||
if (d[2] && !n.gc) {
|
if (d[2] && !n.gc) {
|
||||||
// d marks as gc'd but n does not
|
// d marks as gc'd but n does not
|
||||||
// then delete either way
|
// then delete either way
|
||||||
createDeletions(user, d[0], Math.min(diff, d[1]), d[2])
|
deletions.push([user, d[0], Math.min(diff, d[1]), d[2]])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (d[1] <= diff) {
|
if (d[1] <= diff) {
|
||||||
@ -698,57 +700,38 @@ module.exports = function (Y/* :any */) {
|
|||||||
// for the rest.. just apply it
|
// for the rest.. just apply it
|
||||||
for (; pos < dv.length; pos++) {
|
for (; pos < dv.length; pos++) {
|
||||||
d = dv[pos]
|
d = dv[pos]
|
||||||
createDeletions(user, d[0], d[1], d[2])
|
deletions.push([user, d[0], d[1], d[2]])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var i = 0; i < deletions.length; i++) {
|
for (var i = 0; i < deletions.length; i++) {
|
||||||
var del = deletions[i]
|
var del = deletions[i]
|
||||||
// always try to delete..
|
// always try to delete..
|
||||||
var state = yield* this.getState(del[0])
|
yield* this.deleteOperation([del[0], del[1]], del[2])
|
||||||
if (del[1] < state.clock) {
|
|
||||||
yield* this.deleteOperation([del[0], del[1]], del[2])
|
|
||||||
if (del[3]) {
|
|
||||||
// gc..
|
|
||||||
yield* this.markGarbageCollected([del[0], del[1]], del[2]) // always mark gc'd
|
|
||||||
// remove operation..
|
|
||||||
var counter = del[1] + del[2]
|
|
||||||
while (counter >= del[1]) {
|
|
||||||
var o = yield* this.os.findWithUpperBound([del[0], counter - 1])
|
|
||||||
if (o == null) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var oLen = o.content != null ? o.content.length : 1
|
|
||||||
if (o.id[0] !== del[0] || o.id[1] + oLen <= del[1]) {
|
|
||||||
// not in range
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (o.id[1] + oLen > del[1] + del[2]) {
|
|
||||||
// overlaps right
|
|
||||||
o = yield* this.getInsertionCleanEnd([del[0], del[1] + del[2] - 1])
|
|
||||||
}
|
|
||||||
if (o.id[1] < del[1]) {
|
|
||||||
// overlaps left
|
|
||||||
o = yield* this.getInsertionCleanStart([del[0], del[1]])
|
|
||||||
}
|
|
||||||
counter = o.id[1]
|
|
||||||
yield* this.garbageCollectOperation(o.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (del[3]) {
|
|
||||||
yield* this.markGarbageCollected([del[0], del[1]], del[2])
|
|
||||||
} else {
|
|
||||||
yield* this.markDeleted([del[0], del[1]], del[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (del[3]) {
|
if (del[3]) {
|
||||||
// check to increase the state of the respective user
|
// gc..
|
||||||
if (state.clock >= del[1] && state.clock < del[1] + del[2]) {
|
yield* this.markGarbageCollected([del[0], del[1]], del[2]) // always mark gc'd
|
||||||
state.clock = del[1] + del[2]
|
// remove operation..
|
||||||
// also check if more expected operations were gc'd
|
var counter = del[1] + del[2]
|
||||||
yield* this.checkDeleteStoreForState(state) // TODO: unneccessary?
|
while (counter >= del[1]) {
|
||||||
// then set the state
|
var o = yield* this.os.findWithUpperBound([del[0], counter - 1])
|
||||||
yield* this.setState(state)
|
if (o == null) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var oLen = o.content != null ? o.content.length : 1
|
||||||
|
if (o.id[0] !== del[0] || o.id[1] + oLen <= del[1]) {
|
||||||
|
// not in range
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (o.id[1] + oLen > del[1] + del[2]) {
|
||||||
|
// overlaps right
|
||||||
|
o = yield* this.getInsertionCleanEnd([del[0], del[1] + del[2] - 1])
|
||||||
|
}
|
||||||
|
if (o.id[1] < del[1]) {
|
||||||
|
// overlaps left
|
||||||
|
o = yield* this.getInsertionCleanStart([del[0], del[1]])
|
||||||
|
}
|
||||||
|
counter = o.id[1]
|
||||||
|
yield* this.garbageCollectOperation(o.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.store.forwardAppliedOperations) {
|
if (this.store.forwardAppliedOperations) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user