gc implementation

This commit is contained in:
Kevin Jahns 2015-07-26 16:03:13 +00:00
parent c8ded24842
commit 06e7caab2d
6 changed files with 58 additions and 22 deletions

View File

@ -61,6 +61,13 @@ async function applyRandomTransactions (users, objects, transactions, numberOfTr
await users[0].connector.flushAll() 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 async function compareAllUsers(users){//eslint-disable-line
var s1, s2, ds1, ds2, allDels1, allDels2 var s1, s2, ds1, ds2, allDels1, allDels2
var db1 = [] var db1 = []
@ -81,6 +88,8 @@ async function compareAllUsers(users){//eslint-disable-line
}) })
} }
await users[0].connector.flushAll() await users[0].connector.flushAll()
await garbageCollectAllUsers(users)
await wait(200)
for (var uid = 0; uid < users.length; uid++) { for (var uid = 0; uid < users.length; uid++) {
var u = users[uid] var u = users[uid]
// compare deleted ops against deleteStore // compare deleted ops against deleteStore
@ -99,9 +108,8 @@ async function compareAllUsers(users){//eslint-disable-line
var d = ds[j] var d = ds[j]
for (var i = 0; i < d.len; i++) { for (var i = 0; i < d.len; i++) {
var o = yield* this.getOperation([d.id[0], d.id[1] + i]) var o = yield* this.getOperation([d.id[0], d.id[1] + i])
if (o != null) { // gc'd or deleted
expect(o.deleted).toBeTruthy() expect(o == null || o.deleted).toBeTruthy()
}
} }
} }
}) })

View File

@ -68,6 +68,7 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
this.gcTimeout = opts.gcTimeout || 5000 this.gcTimeout = opts.gcTimeout || 5000
var os = this var os = this
function garbageCollect () { function garbageCollect () {
var def = Promise.defer()
os.requestTransaction(function * () { os.requestTransaction(function * () {
for (var i in os.gc2) { for (var i in os.gc2) {
var oid = os.gc2[i] var oid = os.gc2[i]
@ -87,7 +88,9 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
if (os.gcTimeout > 0) { if (os.gcTimeout > 0) {
os.gcInterval = setTimeout(garbageCollect, os.gcTimeout) os.gcInterval = setTimeout(garbageCollect, os.gcTimeout)
} }
def.resolve()
}) })
return def.promise
} }
this.garbageCollect = garbageCollect this.garbageCollect = garbageCollect
if (this.gcTimeout > 0) { if (this.gcTimeout > 0) {
@ -125,8 +128,12 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
apply (ops) { apply (ops) {
for (var key in ops) { for (var key in ops) {
var o = ops[key] var o = ops[key]
var required = Struct[o.struct].requiredOps(o) if (!o.gc) {
this.whenOperationsExist(required, o) 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. // 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]) var state = yield* this.getState(op.id[0])
if (op.id[1] === state.clock) { if (op.id[1] === state.clock) {
state.clock++ state.clock++
yield* this.checkDeleteStoreForState(state)
yield* this.setState(state) yield* this.setState(state)
yield* Struct[op.struct].execute.call(this, op) yield* Struct[op.struct].execute.call(this, op)
yield* this.addOperation(op) yield* this.addOperation(op)

View File

@ -125,6 +125,12 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
this.os = store.os this.os = store.os
this.ds = store.ds 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) { * getDeleteSet (id) {
return this.ds.toDeleteSet(id) return this.ds.toDeleteSet(id)
} }
@ -196,14 +202,16 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
var endPos = endState.clock var endPos = endState.clock
this.os.iterate([user, startPos], [user, endPos], function (op) {// eslint-disable-line 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 = [] var res = []
for (var op of ops) { for (var op of ops) {
res.push(yield* this.makeOperationReady(startSS, op)) res.push(yield* this.makeOperationReady(startSS, op))
var state = startSS[op.id[0]] || 0 var state = startSS[op.id[0]] || 0
if (state === op.id[1]) { if (state === op.id[1] || true) {
startSS[op.id[0]] = state + 1 startSS[op.id[0]] = state + 1
} else { } else {
throw new Error('Unexpected operation!') throw new Error('Unexpected operation!')
@ -218,8 +226,8 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
var clock var clock
while (o.right != null) { while (o.right != null) {
// while unknown, go to the right // while unknown, go to the right
clock = ss[o.right[0]] clock = ss[o.right[0]] || 0
if (clock != null && o.right[1] < clock) { if (o.right[1] < clock && !o.gc) {
break break
} }
o = yield* this.getOperation(o.right) o = yield* this.getOperation(o.right)
@ -227,8 +235,8 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
op.right = o.right op.right = o.right
while (o.left != null) { while (o.left != null) {
// while unknown, go to the right // while unknown, go to the right
clock = ss[o.left[0]] clock = ss[o.left[0]] || 0
if (clock != null && o.left[1] < clock) { if (o.left[1] < clock && !o.gc) {
break break
} }
o = yield* this.getOperation(o.left) o = yield* this.getOperation(o.left)

View File

@ -25,11 +25,12 @@ var Struct = {
return op return op
}, },
requiredOps: function (op) { requiredOps: function (op) {
return [op.target] return [] // [op.target]
}, },
execute: function * (op) { execute: function * (op) {
console.log('Delete', op, console.trace())
var target = yield* this.getOperation(op.target) var target = yield* this.getOperation(op.target)
if (!target.deleted) { if (target != null && !target.deleted) {
target.deleted = true target.deleted = true
if (target.left !== null && (yield* this.getOperation(target.left)).deleted) { if (target.left !== null && (yield* this.getOperation(target.left)).deleted) {
this.store.addToGarbageCollector(target.id) this.store.addToGarbageCollector(target.id)
@ -44,13 +45,19 @@ var Struct = {
} }
} }
yield* this.setOperation(target) yield* this.setOperation(target)
this.ds.delete(target.id)
var t = this.store.initializedTypes[JSON.stringify(target.parent)] var t = this.store.initializedTypes[JSON.stringify(target.parent)]
if (t != null) { if (t != null) {
yield* t._changed(this, copyObject(op)) 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: { Insert: {
@ -65,7 +72,7 @@ var Struct = {
} }
*/ */
encode: function (op) { encode: function (op) {
/* /* bad idea, right?
var e = { var e = {
id: op.id, id: op.id,
left: op.left, left: op.left,

View File

@ -1,17 +1,17 @@
/* global createUsers, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactions */ /* global createUsers, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactions */
/* eslint-env browser,jasmine */ /* eslint-env browser,jasmine */
var numberOfYArrayTests = 100 var numberOfYArrayTests = 5
describe('Array Type', function () { describe('Array Type', function () {
var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000 jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000
beforeEach(async function (done) { beforeEach(async function (done) {
await createUsers(this, 5) await createUsers(this, 2)
y1 = (yconfig1 = this.users[0]).root y1 = (yconfig1 = this.users[0]).root
y2 = (yconfig2 = this.users[1]).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 flushAll = this.users[0].connector.flushAll
done() done()
}) })
@ -192,12 +192,12 @@ describe('Array Type', function () {
await wait() await wait()
l3 = await y3.get('Array') l3 = await y3.get('Array')
await flushAll() await flushAll()
yconfig1.db.garbageCollect() await garbageCollectAllUsers(this.users)
yconfig1.db.garbageCollect()
yconfig1.db.logTable() yconfig1.db.logTable()
expect(l1.toArray()).toEqual(l2.toArray()) expect(l1.toArray()).toEqual(l2.toArray())
expect(l2.toArray()).toEqual(l3.toArray()) expect(l2.toArray()).toEqual(l3.toArray())
expect(l2.toArray()).toEqual([]) expect(l2.toArray()).toEqual([])
await compareAllUsers(this.users)
done() done()
}) })
}) })
@ -240,6 +240,9 @@ describe('Array Type', function () {
done() done()
}) })
it(`succeed after ${numberOfYArrayTests} actions`, async 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 applyRandomTransactions(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests)
await flushAll() await flushAll()
await compareArrayValues(this.arrays) await compareArrayValues(this.arrays)

View File

@ -35,7 +35,9 @@ class YConfig { // eslint-disable-line no-unused-vars
disconnect () { disconnect () {
this.connector.disconnect() this.connector.disconnect()
} }
reconnect () { async reconnect () {
await this.db.garbageCollect()
await this.db.garbageCollect()
this.connector.reconnect() this.connector.reconnect()
} }
destroy () { destroy () {