diff --git a/src/Helper.spec.js b/src/Helper.spec.js index 21bc5918..fd86706c 100644 --- a/src/Helper.spec.js +++ b/src/Helper.spec.js @@ -61,6 +61,13 @@ async function applyRandomTransactions (users, objects, transactions, numberOfTr await users[0].connector.flushAll() } +async function garbageCollectAllUsers (users) { + for (var i in users) { + await users[i].db.garbageCollect() + await users[i].db.garbageCollect() + } +} + async function compareAllUsers(users){//eslint-disable-line var s1, s2, ds1, ds2, allDels1, allDels2 var db1 = [] @@ -81,6 +88,8 @@ async function compareAllUsers(users){//eslint-disable-line }) } await users[0].connector.flushAll() + await garbageCollectAllUsers(users) + await wait(200) for (var uid = 0; uid < users.length; uid++) { var u = users[uid] // compare deleted ops against deleteStore @@ -99,9 +108,8 @@ async function compareAllUsers(users){//eslint-disable-line var d = ds[j] for (var i = 0; i < d.len; i++) { var o = yield* this.getOperation([d.id[0], d.id[1] + i]) - if (o != null) { - expect(o.deleted).toBeTruthy() - } + // gc'd or deleted + expect(o == null || o.deleted).toBeTruthy() } } }) diff --git a/src/OperationStore.js b/src/OperationStore.js index d00cf3a6..11bd32aa 100644 --- a/src/OperationStore.js +++ b/src/OperationStore.js @@ -68,6 +68,7 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars this.gcTimeout = opts.gcTimeout || 5000 var os = this function garbageCollect () { + var def = Promise.defer() os.requestTransaction(function * () { for (var i in os.gc2) { var oid = os.gc2[i] @@ -87,7 +88,9 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars if (os.gcTimeout > 0) { os.gcInterval = setTimeout(garbageCollect, os.gcTimeout) } + def.resolve() }) + return def.promise } this.garbageCollect = garbageCollect if (this.gcTimeout > 0) { @@ -125,8 +128,12 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars apply (ops) { for (var key in ops) { var o = ops[key] - var required = Struct[o.struct].requiredOps(o) - this.whenOperationsExist(required, o) + if (!o.gc) { + var required = Struct[o.struct].requiredOps(o) + this.whenOperationsExist(required, o) + } else { + throw new Error("Must not receive gc'd ops!") + } } } // op is executed as soon as every operation requested is available. @@ -200,6 +207,7 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars var state = yield* this.getState(op.id[0]) if (op.id[1] === state.clock) { state.clock++ + yield* this.checkDeleteStoreForState(state) yield* this.setState(state) yield* Struct[op.struct].execute.call(this, op) yield* this.addOperation(op) diff --git a/src/OperationStores/Memory.js b/src/OperationStores/Memory.js index 1127aa87..7caf4b6a 100644 --- a/src/OperationStores/Memory.js +++ b/src/OperationStores/Memory.js @@ -125,6 +125,12 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars this.os = store.os this.ds = store.ds } + * checkDeleteStoreForState (state) { + var n = this.ds.findNodeWithUpperBound([state.user, state.clock]) + if (n !== null && n.val.id[0] === state.user) { + state.clock = Math.max(state.clock, n.val.id[1] + n.val.len) + } + } * getDeleteSet (id) { return this.ds.toDeleteSet(id) } @@ -196,14 +202,16 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars var endPos = endState.clock this.os.iterate([user, startPos], [user, endPos], function (op) {// eslint-disable-line - ops.push(Struct[op.struct].encode(op)) + if (!op.gc) { + ops.push(Struct[op.struct].encode(op)) + } }) } var res = [] for (var op of ops) { res.push(yield* this.makeOperationReady(startSS, op)) var state = startSS[op.id[0]] || 0 - if (state === op.id[1]) { + if (state === op.id[1] || true) { startSS[op.id[0]] = state + 1 } else { throw new Error('Unexpected operation!') @@ -218,8 +226,8 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars var clock while (o.right != null) { // while unknown, go to the right - clock = ss[o.right[0]] - if (clock != null && o.right[1] < clock) { + clock = ss[o.right[0]] || 0 + if (o.right[1] < clock && !o.gc) { break } o = yield* this.getOperation(o.right) @@ -227,8 +235,8 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars op.right = o.right while (o.left != null) { // while unknown, go to the right - clock = ss[o.left[0]] - if (clock != null && o.left[1] < clock) { + clock = ss[o.left[0]] || 0 + if (o.left[1] < clock && !o.gc) { break } o = yield* this.getOperation(o.left) diff --git a/src/Struct.js b/src/Struct.js index ad5f22bc..204ceb9c 100644 --- a/src/Struct.js +++ b/src/Struct.js @@ -25,11 +25,12 @@ var Struct = { return op }, requiredOps: function (op) { - return [op.target] + return [] // [op.target] }, execute: function * (op) { + console.log('Delete', op, console.trace()) var target = yield* this.getOperation(op.target) - if (!target.deleted) { + if (target != null && !target.deleted) { target.deleted = true if (target.left !== null && (yield* this.getOperation(target.left)).deleted) { this.store.addToGarbageCollector(target.id) @@ -44,13 +45,19 @@ var Struct = { } } yield* this.setOperation(target) - this.ds.delete(target.id) var t = this.store.initializedTypes[JSON.stringify(target.parent)] if (t != null) { yield* t._changed(this, copyObject(op)) } } - + if (target == null || !target.deleted) { + this.ds.delete(op.target) + var state = yield* this.getState(op.target[0]) + if (state === op.target[1]) { + yield* this.checkDeleteStoreForState(state) + yield* this.setState(state) + } + } } }, Insert: { @@ -65,7 +72,7 @@ var Struct = { } */ encode: function (op) { - /* + /* bad idea, right? var e = { id: op.id, left: op.left, diff --git a/src/Types/Array.spec.js b/src/Types/Array.spec.js index 7444d248..03eb4164 100644 --- a/src/Types/Array.spec.js +++ b/src/Types/Array.spec.js @@ -1,17 +1,17 @@ /* global createUsers, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactions */ /* eslint-env browser,jasmine */ -var numberOfYArrayTests = 100 +var numberOfYArrayTests = 5 describe('Array Type', function () { var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000 beforeEach(async function (done) { - await createUsers(this, 5) + await createUsers(this, 2) y1 = (yconfig1 = this.users[0]).root y2 = (yconfig2 = this.users[1]).root - y3 = (yconfig3 = this.users[2]).root + // y3 = (yconfig3 = this.users[2]).root flushAll = this.users[0].connector.flushAll done() }) @@ -192,12 +192,12 @@ describe('Array Type', function () { await wait() l3 = await y3.get('Array') await flushAll() - yconfig1.db.garbageCollect() - yconfig1.db.garbageCollect() + await garbageCollectAllUsers(this.users) yconfig1.db.logTable() expect(l1.toArray()).toEqual(l2.toArray()) expect(l2.toArray()).toEqual(l3.toArray()) expect(l2.toArray()).toEqual([]) + await compareAllUsers(this.users) done() }) }) @@ -240,6 +240,9 @@ describe('Array Type', function () { done() }) it(`succeed after ${numberOfYArrayTests} actions`, async function (done) { + for (var u of this.users) { + u.connector.debug = true + } await applyRandomTransactions(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests) await flushAll() await compareArrayValues(this.arrays) diff --git a/src/y.js b/src/y.js index 9ff86034..86d2e827 100644 --- a/src/y.js +++ b/src/y.js @@ -35,7 +35,9 @@ class YConfig { // eslint-disable-line no-unused-vars disconnect () { this.connector.disconnect() } - reconnect () { + async reconnect () { + await this.db.garbageCollect() + await this.db.garbageCollect() this.connector.reconnect() } destroy () {