From 06e7caab2dd4a737e2251fadbae35653d262c495 Mon Sep 17 00:00:00 2001
From: Kevin Jahns <kevin.jahns@rwth-aachen.de>
Date: Sun, 26 Jul 2015 16:03:13 +0000
Subject: [PATCH] gc implementation

---
 src/Helper.spec.js            | 14 +++++++++++---
 src/OperationStore.js         | 12 ++++++++++--
 src/OperationStores/Memory.js | 20 ++++++++++++++------
 src/Struct.js                 | 17 ++++++++++++-----
 src/Types/Array.spec.js       | 13 ++++++++-----
 src/y.js                      |  4 +++-
 6 files changed, 58 insertions(+), 22 deletions(-)

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 () {
-- 
GitLab