From 895ec86ff6242ff3f9d0c858cf43a705bd40ab59 Mon Sep 17 00:00:00 2001 From: Kevin Jahns <kevin.jahns@rwth-aachen.de> Date: Fri, 22 Apr 2016 21:21:23 +0100 Subject: [PATCH] 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 --- src/Connector.js | 15 ++++---- src/Database.js | 21 +++++++---- src/SpecHelper.js | 1 - src/Transaction.js | 93 +++++++++++++++++++--------------------------- 4 files changed, 59 insertions(+), 71 deletions(-) diff --git a/src/Connector.js b/src/Connector.js index a8fb9a40..37726bca 100644 --- a/src/Connector.js +++ b/src/Connector.js @@ -143,8 +143,8 @@ module.exports = function (Y/* :any */) { break } } + var conn = this if (syncUser != null) { - var conn = this this.currentSyncTarget = syncUser this.y.db.requestTransaction(function *() { var stateSet = yield* this.getStateSet() @@ -157,14 +157,15 @@ module.exports = function (Y/* :any */) { }) }) } else { - this.isSynced = true - // call when synced listeners - for (var f of this.whenSyncedListeners) { - f() - } - this.whenSyncedListeners = [] this.y.db.requestTransaction(function *() { + // it is crucial that isSynced is set at the time garbageCollectAfterSync is called + conn.isSynced = true yield* this.garbageCollectAfterSync() + // call whensynced listeners + for (var f of conn.whenSyncedListeners) { + f() + } + conn.whenSyncedListeners = [] }) } } diff --git a/src/Database.js b/src/Database.js index 964b7efa..2ff87f6b 100644 --- a/src/Database.js +++ b/src/Database.js @@ -75,10 +75,14 @@ module.exports = function (Y /* :any */) { } this.gc1 = [] // first stage this.gc2 = [] // second stage -> after that, remove the op - this.gcTimeout = opts.gcTimeout || 50000 + this.gcTimeout = !opts.gcTimeout ? 50000 : opts.gcTimeoutÅ› function garbageCollect () { return os.whenTransactionsFinished().then(function () { 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) => { os.requestTransaction(function * () { if (os.y.connector != null && os.y.connector.isSynced) { @@ -346,15 +350,16 @@ module.exports = function (Y /* :any */) { // yield* this.store.operationAdded(this, op) } else { // 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) { // check if this op has a longer content in the case it is defined - if (defined.content.length < op.content.length) { - op.content.splice(0, defined.content.length) - op.id = [op.id[0], op.id[1] + defined.content.length] - op.left = defined.id - op.origin = defined.id - defined = yield* this.getOperation(op.id) + if (defined.id[1] + defined.content.length < op.id[1] + op.content.length) { + var overlapSize = defined.content.length - (op.id[1] - defined.id[1]) + op.content.splice(0, overlapSize) + op.id = [op.id[0], op.id[1] + overlapSize] + op.left = Y.utils.getLastId(defined) + op.origin = op.left + defined = yield* this.getOperation(op.id) // getOperation suffices here } else { break } diff --git a/src/SpecHelper.js b/src/SpecHelper.js index 3c0ec688..0fe85a8f 100644 --- a/src/SpecHelper.js +++ b/src/SpecHelper.js @@ -123,7 +123,6 @@ g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransaction g.applyRandomTransactionsWithGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) { yield* applyTransactions(1, numberOfTransactions, objects, users.slice(1), transactions) yield Y.utils.globalRoom.flushAll() - yield g.garbageCollectAllUsers(users) for (var u in users) { // TODO: here, we enforce that two users never sync at the same time with u[0] // enforce that in the connector itself! diff --git a/src/Transaction.js b/src/Transaction.js index 37ce364e..4bf4eca4 100644 --- a/src/Transaction.js +++ b/src/Transaction.js @@ -205,7 +205,7 @@ module.exports = function (Y/* :any */) { } length = target.id[1] - targetId[1] } - + if (target != null) { if (!target.deleted) { callType = true @@ -240,10 +240,10 @@ module.exports = function (Y/* :any */) { } else { left = null } - + // set here because it was deleted and/or gc'd yield* this.setOperation(target) - + /* Check if it is possible to add right to the gc. 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 n.len -= diff if (diff >= next.len) { - // delete the missing range after next + // delete the missing range after next diff = diff - next.len // missing range after next if (diff > 0) { 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. */ * 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) { if (op.gc) { delete op.gc @@ -469,7 +472,9 @@ module.exports = function (Y/* :any */) { var o = yield* this.getOperation(id) 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 (o != null) { + if (o == null) { + yield* this.updateState(id[0]) + } else if (o != null) { var deps = [] if (o.opContent != null) { deps.push(o.opContent) @@ -647,9 +652,6 @@ module.exports = function (Y/* :any */) { */ * applyDeleteSet (ds) { var deletions = [] - function createDeletions (user, start, len, gc) { - deletions.push([user, start, len, gc]) - } for (var user in ds) { var dv = ds[user] @@ -676,14 +678,14 @@ module.exports = function (Y/* :any */) { // delete maximum the len of d // else delete as much as possible 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 { // 3) diff = n.id[1] + n.len - d[0] // never null (see 1) if (d[2] && !n.gc) { // d marks as gc'd but n does not // 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) { @@ -698,57 +700,38 @@ module.exports = function (Y/* :any */) { // for the rest.. just apply it for (; pos < dv.length; 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++) { var del = deletions[i] // always try to delete.. - var state = yield* this.getState(del[0]) - 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]) - } - } + yield* this.deleteOperation([del[0], del[1]], del[2]) if (del[3]) { - // check to increase the state of the respective user - if (state.clock >= del[1] && state.clock < del[1] + del[2]) { - state.clock = del[1] + del[2] - // also check if more expected operations were gc'd - yield* this.checkDeleteStoreForState(state) // TODO: unneccessary? - // then set the state - yield* this.setState(state) + // 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) } } if (this.store.forwardAppliedOperations) { -- GitLab