refactoring the tarnsition functions

This commit is contained in:
Kevin Jahns 2015-10-13 21:40:36 +02:00
parent d6e1cd42a2
commit 541a93d152
9 changed files with 563 additions and 537 deletions

View File

@ -18,7 +18,7 @@ g.g = g
g.YConcurrency_TestingMode = true
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000
g.describeManyTimes = function describeManyTimes (times, name, f) {
for (var i = 0; i < times; i++) {
@ -36,7 +36,7 @@ function wait (t) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve()
}, t * 2)
}, t)
})
}
g.wait = wait
@ -141,7 +141,7 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
s1 = yield* this.getStateSet()
ds1 = yield* this.getDeleteSet()
allDels1 = []
this.ds.iterate(null, null, function (d) {
yield* this.ds.iterate(this, null, null, function * (d) {
allDels1.push(d)
})
}
@ -149,7 +149,7 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
s2 = yield* this.getStateSet()
ds2 = yield* this.getDeleteSet()
allDels2 = []
this.ds.iterate(null, null, function (d) {
yield* this.ds.iterate(this, null, null, function * (d) {
allDels2.push(d)
})
}
@ -158,16 +158,16 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
for (var uid = 0; uid < users.length; uid++) {
var u = users[uid]
// compare deleted ops against deleteStore
u.db.os.iterate(null, null, function (o) {
if (o.deleted === true) {
expect(u.db.ds.isDeleted(o.id)).toBeTruthy()
}
})
// compare deleteStore against deleted ops
u.db.requestTransaction(function * () {
// compare deleted ops against deleteStore
yield* this.os.iterate(this, null, null, function * (o) {
if (o.deleted === true) {
expect(yield* this.isDeleted(o.id)).toBeTruthy()
}
})
// compare deleteStore against deleted ops
var ds = []
u.db.ds.iterate(null, null, function (d) {
yield* this.ds.iterate(this, null, null, function * (d) {
ds.push(d)
})
for (var j in ds) {
@ -186,25 +186,30 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
// compare allDels tree
yield wait()
if (s1 == null) {
u.db.requestTransaction(t1)
yield wait()
u.db.os.iterate(null, null, function (o) {
o = Y.utils.copyObject(o)
delete o.origin
db1.push(o)
u.db.requestTransaction(function * () {
yield* t1.call(this)
yield* this.os.iterate(this, null, null, function * (o) {
o = Y.utils.copyObject(o)
delete o.origin
db1.push(o)
})
})
yield wait()
} else {
u.db.requestTransaction(t2)
yield wait()
expect(s1).toEqual(s2)
expect(allDels1).toEqual(allDels2) // inner structure
expect(ds1).toEqual(ds2) // exported structure
var count = 0
u.db.os.iterate(null, null, function (o) {
o = Y.utils.copyObject(o)
delete o.origin
expect(db1[count++]).toEqual(o)
// TODO: make requestTransaction return a promise..
u.db.requestTransaction(function * () {
yield* t2.call(this)
expect(s1).toEqual(s2)
expect(allDels1).toEqual(allDels2) // inner structure
expect(ds1).toEqual(ds2) // exported structure
var count = 0
yield* this.os.iterate(this, null, null, function * (o) {
o = Y.utils.copyObject(o)
delete o.origin
expect(db1[count++]).toEqual(o)
})
})
yield wait()
}
}
})
@ -263,15 +268,16 @@ function async (makeGenerator) {
}
g.async = async
function logUsers (self) {
var logUsers = async(function * logUsers (self) {
if (self.constructor === Array) {
self = {users: self}
}
console.log('User 1: ', self.users[0].connector.userId, "=============================================") // eslint-disable-line
self.users[0].db.logTable() // eslint-disable-line
yield self.users[0].db.logTable() // eslint-disable-line
console.log('User 2: ', self.users[1].connector.userId, "=============================================") // eslint-disable-line
self.users[1].db.logTable() // eslint-disable-line
yield self.users[1].db.logTable() // eslint-disable-line
console.log('User 3: ', self.users[2].connector.userId, "=============================================") // eslint-disable-line
self.users[2].db.logTable() // eslint-disable-line
}
yield self.users[2].db.logTable() // eslint-disable-line
})
g.logUsers = logUsers

View File

@ -25,7 +25,6 @@
],
"userY": ...
}
* isDeleted(id)
* getOpsFromDeleteSet(ds) -- TODO: just call this.deleteOperation(id) here
- get a set of deletions that need to be applied in order to get to
achieve the state of the supplied ds
@ -78,6 +77,9 @@
class AbstractTransaction {
constructor (store) {
this.store = store
this.ss = store.ss
this.os = store.os
this.ds = store.ds
}
/*
Get a type based on the id of its model.
@ -138,7 +140,7 @@ class AbstractTransaction {
var target = yield* this.getOperation(targetId)
if (target == null || !target.deleted) {
this.ds.markDeleted(targetId)
yield* this.markDeleted(targetId)
}
if (target != null && target.gc == null) {
@ -193,6 +195,87 @@ class AbstractTransaction {
}
}
}
/*
Mark an operation as deleted&gc'd
*/
* markGarbageCollected (id) {
// this.mem.push(["gc", id]);
var n = yield* this.markDeleted(id)
if (!n.val.gc) {
if (n.val.id[1] < id[1]) {
// un-extend left
var newlen = n.val.len - (id[1] - n.val.id[1])
n.val.len -= newlen
n = yield this.ds.add({id: id, len: newlen, gc: false})
}
// get prev&next before adding a new operation
var prev = n.prev()
var next = n.next()
if (id[1] < n.val.id[1] + n.val.len - 1) {
// un-extend right
yield this.ds.add({id: [id[0], id[1] + 1], len: n.val.len - 1, gc: false})
n.val.len = 1
}
// set gc'd
n.val.gc = true
// can extend left?
if (
prev != null &&
prev.val.gc &&
Y.utils.compareIds([prev.val.id[0], prev.val.id[1] + prev.val.len], n.val.id)
) {
prev.val.len += n.val.len
yield this.ds.delete(n.val.id)
n = prev
}
// can extend right?
if (
next != null &&
next.val.gc &&
Y.utils.compareIds([n.val.id[0], n.val.id[1] + n.val.len], next.val.id)
) {
n.val.len += next.val.len
yield this.ds.delete(next.val.id)
}
}
}
/*
Mark an operation as deleted.
returns the delete node
*/
* markDeleted (id) {
// this.mem.push(["del", id]);
var n = yield this.ds.findNodeWithUpperBound(id)
if (n != null && n.val.id[0] === id[0]) {
if (n.val.id[1] <= id[1] && id[1] < n.val.id[1] + n.val.len) {
// already deleted
return n
} else if (n.val.id[1] + n.val.len === id[1] && !n.val.gc) {
// can extend existing deletion
n.val.len++
} else {
// cannot extend left
n = yield this.ds.add({id: id, len: 1, gc: false})
}
} else {
// cannot extend left
n = yield this.ds.add({id: id, len: 1, gc: false})
}
// can extend right?
var next = n.next()
if (
next !== null &&
Y.utils.compareIds([n.val.id[0], n.val.id[1] + n.val.len], next.val.id) &&
!next.val.gc
) {
n.val.len = n.val.len + next.val.len
yield this.ds.delete(next.val.id)
return yield this.ds.findNode(n.val.id)
} else {
return n
}
}
/*
Really remove an op and all its effects.
The complicated case here is the Insert operation:
@ -212,7 +295,7 @@ class AbstractTransaction {
// then set the state
yield* this.setState(state)
}
this.ds.markGarbageCollected(id)
yield* this.markGarbageCollected(id)
// if op exists, then clean that mess up..
var o = yield* this.getOperation(id)
@ -292,6 +375,248 @@ class AbstractTransaction {
yield* this.removeOperation(o.id)
}
}
* checkDeleteStoreForState (state) {
var n = yield this.ds.findNodeWithUpperBound([state.user, state.clock])
if (n !== null && n.val.id[0] === state.user && n.val.gc) {
state.clock = Math.max(state.clock, n.val.id[1] + n.val.len)
}
}
/*
apply a delete set in order to get
the state of the supplied ds
*/
* applyDeleteSet (ds) {
var deletions = []
function createDeletions (user, start, len, gc) {
for (var c = start; c < start + len; c++) {
deletions.push([user, c, gc])
}
}
for (var user in ds) {
var dv = ds[user]
var pos = 0
var d = dv[pos]
yield* this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function * (n) {
// cases:
// 1. d deletes something to the right of n
// => go to next n (break)
// 2. d deletes something to the left of n
// => create deletions
// => reset d accordingly
// *)=> if d doesn't delete anything anymore, go to next d (continue)
// 3. not 2) and d deletes something that also n deletes
// => reset d so that it doesn't contain n's deletion
// *)=> if d does not delete anything anymore, go to next d (continue)
while (d != null) {
var diff = 0 // describe the diff of length in 1) and 2)
if (n.id[1] + n.len <= d[0]) {
// 1)
break
} else if (d[0] < n.id[1]) {
// 2)
// 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])
} 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])
}
}
if (d[1] <= diff) {
// d doesn't delete anything anymore
d = dv[++pos]
} else {
d[0] = d[0] + diff // reset pos
d[1] = d[1] - diff // reset length
}
}
})
// for the rest.. just apply it
for (; pos < dv.length; pos++) {
d = dv[pos]
createDeletions(user, d[0], d[1], d[2])
}
}
for (var i in deletions) {
var del = deletions[i]
var id = [del[0], del[1]]
// always try to delete..
yield* this.deleteOperation(id)
if (del[2]) {
// gc
yield* this.garbageCollectOperation(id)
}
}
}
* isGarbageCollected (id) {
var n = yield this.ds.findNodeWithUpperBound(id)
return n !== null && n.val.id[0] === id[0] && id[1] < n.val.id[1] + n.val.len && n.val.gc
}
/*
A DeleteSet (ds) describes all the deleted ops in the OS
*/
* getDeleteSet () {
var ds = {}
yield* this.ds.iterate(this, null, null, function * (n) {
var user = n.id[0]
var counter = n.id[1]
var len = n.len
var gc = n.gc
var dv = ds[user]
if (dv === void 0) {
dv = []
ds[user] = dv
}
dv.push([counter, len, gc])
})
return ds
}
* isDeleted (id) {
var n = yield this.ds.findNodeWithUpperBound(id)
return n !== null && n.val.id[0] === id[0] && id[1] < n.val.id[1] + n.val.len
}
* setOperation (op) {
// TODO: you can remove this step! probs..
var n = yield this.os.findNode(op.id)
n.val = op
return op
}
* addOperation (op) {
var n = yield this.os.add(op)
return function () {
if (n != null) {
n = n.next()
return n != null ? n.val : null
} else {
return null
}
}
}
* getOperation (id) {
return yield this.os.find(id)
}
* removeOperation (id) {
yield this.os.delete(id)
}
* setState (state) {
this.ss[state.user] = state.clock
}
* getState (user) {
var clock = this.ss[user]
if (clock == null) {
clock = 0
}
return {
user: user,
clock: clock
}
}
* getStateVector () {
var stateVector = []
for (var user in this.ss) {
var clock = this.ss[user]
stateVector.push({
user: user,
clock: clock
})
}
return stateVector
}
* getStateSet () {
return Y.utils.copyObject(this.ss)
}
* getOperations (startSS) {
// TODO: use bounds here!
if (startSS == null) {
startSS = {}
}
var ops = []
var endSV = yield* this.getStateVector()
for (var endState of endSV) {
var user = endState.user
if (user === '_') {
continue
}
var startPos = startSS[user] || 0
var endPos = endState.clock
yield* this.os.iterate(this, [user, startPos], [user, endPos], function * (op) {
ops.push(op)
})
}
var res = []
for (var op of ops) {
res.push(yield* this.makeOperationReady(startSS, op))
}
return res
}
/*
Here, we make op executable for the receiving user.
Notes:
startSS: denotes to the SV that the remote user sent
currSS: denotes to the state vector that the user should have if he
applies all already sent operations (increases is each step)
We face several problems:
* Execute op as is won't work because ops depend on each other
-> find a way so that they do not anymore
* When changing left, must not go more to the left than the origin
* When changing right, you have to consider that other ops may have op
as their origin, this means that you must not set one of these ops
as the new right (interdependencies of ops)
* can't just go to the right until you find the first known operation,
With currSS
-> interdependency of ops is a problem
With startSS
-> leads to inconsistencies when two users join at the same time.
Then the position depends on the order of execution -> error!
Solution:
-> re-create originial situation
-> set op.left = op.origin (which never changes)
-> set op.right
to the first operation that is known (according to startSS)
or to the first operation that has an origin that is not to the
right of op.
-> Enforces unique execution order -> happy user
Improvements: TODO
* Could set left to origin, or the first known operation
(startSS or currSS.. ?)
-> Could be necessary when I turn GC again.
-> Is a bad(ish) idea because it requires more computation
*/
* makeOperationReady (startSS, op) {
op = Y.Struct[op.struct].encode(op)
op = Y.utils.copyObject(op)
var o = op
var ids = [op.id]
// search for the new op.right
// it is either the first known op (according to startSS)
// or the o that has no origin to the right of op
// (this is why we use the ids array)
while (o.right != null) {
var right = yield* this.getOperation(o.right)
if (o.right[1] < (startSS[o.right[0]] || 0) || !ids.some(function (id) {
return Y.utils.compareIds(id, right.origin)
})) {
break
}
ids.push(o.right)
o = right
}
op.right = o.right
op.left = op.origin
return op
}
}
Y.AbstractTransaction = AbstractTransaction
@ -374,14 +699,14 @@ class AbstractOperationStore {
})
})
}
garbageCollectAfterSync () {
var os = this.os
var self = this
os.iterate(null, null, function (op) {
if (op.deleted && op.left != null) {
var left = os.find(op.left)
self.addToGarbageCollector(op, left)
}
* garbageCollectAfterSync () {
this.requestTransaction(function * () {
yield* this.os.iterate(this, null, null, function * (op) {
if (op.deleted && op.left != null) {
var left = yield this.os.find(op.left)
this.store.addToGarbageCollector(op, left)
}
})
})
}
/*
@ -530,13 +855,13 @@ class AbstractOperationStore {
* tryExecute (op) {
if (op.struct === 'Delete') {
yield* Y.Struct.Delete.execute.call(this, op)
} else if ((yield* this.getOperation(op.id)) == null && !this.store.ds.isGarbageCollected(op.id)) {
} else if ((yield* this.getOperation(op.id)) == null && !(yield* this.isGarbageCollected(op.id))) {
yield* Y.Struct[op.struct].execute.call(this, op)
var next = yield* this.addOperation(op)
yield* this.store.operationAdded(this, op, next)
// Delete if DS says this is actually deleted
if (this.store.ds.isDeleted(op.id)) {
if (yield* this.isDeleted(op.id)) {
yield* Y.Struct['Delete'].execute.call(this, {struct: 'Delete', target: op.id})
}
}

View File

@ -6,350 +6,12 @@ class DeleteStore extends Y.utils.RBTree {
super()
this.mem = []
}
isDeleted (id) {
var n = this.findNodeWithUpperBound(id)
return n !== null && n.val.id[0] === id[0] && id[1] < n.val.id[1] + n.val.len
}
isGarbageCollected (id) {
var n = this.findNodeWithUpperBound(id)
return n !== null && n.val.id[0] === id[0] && id[1] < n.val.id[1] + n.val.len && n.val.gc
}
/*
Mark an operation as deleted&gc'd
returns the delete node
*/
markGarbageCollected (id) {
// this.mem.push(["gc", id]);
var n = this.markDeleted(id)
if (!n.val.gc) {
if (n.val.id[1] < id[1]) {
// un-extend left
var newlen = n.val.len - (id[1] - n.val.id[1])
n.val.len -= newlen
n = this.add({id: id, len: newlen, gc: false})
}
// get prev&next before adding a new operation
var prev = n.prev()
var next = n.next()
if (id[1] < n.val.id[1] + n.val.len - 1) {
// un-extend right
this.add({id: [id[0], id[1] + 1], len: n.val.len - 1, gc: false})
n.val.len = 1
}
// set gc'd
n.val.gc = true
// can extend left?
if (
prev != null &&
prev.val.gc &&
Y.utils.compareIds([prev.val.id[0], prev.val.id[1] + prev.val.len], n.val.id)
) {
prev.val.len += n.val.len
super.delete(n.val.id)
n = prev
}
// can extend right?
if (
next != null &&
next.val.gc &&
Y.utils.compareIds([n.val.id[0], n.val.id[1] + n.val.len], next.val.id)
) {
n.val.len += next.val.len
super.delete(next.val.id)
}
}
}
/*
Mark an operation as deleted.
returns the delete node
*/
markDeleted (id) {
// this.mem.push(["del", id]);
var n = this.findNodeWithUpperBound(id)
if (n != null && n.val.id[0] === id[0]) {
if (n.val.id[1] <= id[1] && id[1] < n.val.id[1] + n.val.len) {
// already deleted
return n
} else if (n.val.id[1] + n.val.len === id[1] && !n.val.gc) {
// can extend existing deletion
n.val.len++
} else {
// cannot extend left
n = this.add({id: id, len: 1, gc: false})
}
} else {
// cannot extend left
n = this.add({id: id, len: 1, gc: false})
}
// can extend right?
var next = n.next()
if (
next !== null &&
Y.utils.compareIds([n.val.id[0], n.val.id[1] + n.val.len], next.val.id) &&
!next.val.gc
) {
n.val.len = n.val.len + next.val.len
super.delete(next.val.id)
return this.findNode(n.val.id)
} else {
return n
}
}
/*
A DeleteSet (ds) describes all the deleted ops in the OS
*/
toDeleteSet () {
var ds = {}
this.iterate(null, null, function (n) {
var user = n.id[0]
var counter = n.id[1]
var len = n.len
var gc = n.gc
var dv = ds[user]
if (dv === void 0) {
dv = []
ds[user] = dv
}
dv.push([counter, len, gc])
})
return ds
}
}
Y.utils.DeleteStore = DeleteStore
Y.Memory = (function () {
class Transaction extends Y.AbstractTransaction {
constructor (store) {
super(store)
this.ss = store.ss
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 && n.val.gc) {
state.clock = Math.max(state.clock, n.val.id[1] + n.val.len)
}
}
* getDeleteSet (id) {
return this.ds.toDeleteSet(id)
}
/*
apply a delete set in order to get
the state of the supplied ds
*/
* applyDeleteSet (ds) {
var deletions = []
function createDeletions (user, start, len, gc) {
for (var c = start; c < start + len; c++) {
deletions.push([user, c, gc])
}
}
for (var user in ds) {
var dv = ds[user]
var pos = 0
var d = dv[pos]
this.ds.iterate([user, 0], [user, Number.MAX_VALUE], function (n) {
// cases:
// 1. d deletes something to the right of n
// => go to next n (break)
// 2. d deletes something to the left of n
// => create deletions
// => reset d accordingly
// *)=> if d doesn't delete anything anymore, go to next d (continue)
// 3. not 2) and d deletes something that also n deletes
// => reset d so that it doesn't contain n's deletion
// *)=> if d does not delete anything anymore, go to next d (continue)
while (d != null) {
var diff = 0 // describe the diff of length in 1) and 2)
if (n.id[1] + n.len <= d[0]) {
// 1)
break
} else if (d[0] < n.id[1]) {
// 2)
// 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])
} 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])
}
}
if (d[1] <= diff) {
// d doesn't delete anything anymore
d = dv[++pos]
} else {
d[0] = d[0] + diff // reset pos
d[1] = d[1] - diff // reset length
}
}
})
// for the rest.. just apply it
for (; pos < dv.length; pos++) {
d = dv[pos]
createDeletions(user, d[0], d[1], d[2])
}
}
for (var i in deletions) {
var del = deletions[i]
var id = [del[0], del[1]]
// always try to delete..
yield* this.deleteOperation(id)
if (del[2]) {
// gc
yield* this.garbageCollectOperation(id)
}
}
}
* isDeleted (id) {
return this.ds.isDeleted(id)
}
* setOperation (op) {
// TODO: you can remove this step! probs..
var n = this.os.findNode(op.id)
n.val = op
return op
}
* addOperation (op) {
var n = this.os.add(op)
return function () {
if (n != null) {
n = n.next()
return n != null ? n.val : null
} else {
return null
}
}
}
* getOperation (id) {
return this.os.find(id)
}
* removeOperation (id) {
this.os.delete(id)
}
* setState (state) {
this.ss[state.user] = state.clock
}
* getState (user) {
var clock = this.ss[user]
if (clock == null) {
clock = 0
}
return {
user: user,
clock: clock
}
}
* getStateVector () {
var stateVector = []
for (var user in this.ss) {
var clock = this.ss[user]
stateVector.push({
user: user,
clock: clock
})
}
return stateVector
}
* getStateSet () {
return Y.utils.copyObject(this.ss)
}
* getOperations (startSS) {
// TODO: use bounds here!
if (startSS == null) {
startSS = {}
}
var ops = []
var endSV = yield* this.getStateVector()
for (var endState of endSV) {
var user = endState.user
if (user === '_') {
continue
}
var startPos = startSS[user] || 0
var endPos = endState.clock
this.os.iterate([user, startPos], [user, endPos], function (op) {
ops.push(op)
})
}
var res = []
for (var op of ops) {
res.push(yield* this.makeOperationReady(startSS, op))
}
return res
}
/*
Here, we make op executable for the receiving user.
Notes:
startSS: denotes to the SV that the remote user sent
currSS: denotes to the state vector that the user should have if he
applies all already sent operations (increases is each step)
We face several problems:
* Execute op as is won't work because ops depend on each other
-> find a way so that they do not anymore
* When changing left, must not go more to the left than the origin
* When changing right, you have to consider that other ops may have op
as their origin, this means that you must not set one of these ops
as the new right (interdependencies of ops)
* can't just go to the right until you find the first known operation,
With currSS
-> interdependency of ops is a problem
With startSS
-> leads to inconsistencies when two users join at the same time.
Then the position depends on the order of execution -> error!
Solution:
-> re-create originial situation
-> set op.left = op.origin (which never changes)
-> set op.right
to the first operation that is known (according to startSS)
or to the first operation that has an origin that is not to the
right of op.
-> Enforces unique execution order -> happy user
Improvements: TODO
* Could set left to origin, or the first known operation
(startSS or currSS.. ?)
-> Could be necessary when I turn GC again.
-> Is a bad(ish) idea because it requires more computation
*/
* makeOperationReady (startSS, op) {
op = Y.Struct[op.struct].encode(op)
op = Y.utils.copyObject(op)
var o = op
var ids = [op.id]
// search for the new op.right
// it is either the first known op (according to startSS)
// or the o that has no origin to the right of op
// (this is why we use the ids array)
while (o.right != null) {
var right = yield* this.getOperation(o.right)
if (o.right[1] < (startSS[o.right[0]] || 0) || !ids.some(function (id) {
return Y.utils.compareIds(id, right.origin)
})) {
break
}
ids.push(o.right)
o = right
}
op.right = o.right
op.left = op.origin
return op
}
}
class OperationStore extends Y.AbstractOperationStore {
constructor (y, opts) {
@ -361,44 +23,45 @@ Y.Memory = (function () {
this.ds = new DeleteStore()
}
logTable () {
console.log('User: ', this.y.connector.userId, "=============================================") // eslint-disable-line
console.log("State Set (SS):", this.ss) // eslint-disable-line
console.log("Operation Store (OS):") // eslint-disable-line
this.os.logTable() // eslint-disable-line
console.log("Deletion Store (DS):") //eslint-disable-line
this.ds.logTable() // eslint-disable-line
var self = this
return new Promise(function (resolve) {
self.requestTransaction(function * () {
console.log('User: ', this.store.y.connector.userId, "==============================") // eslint-disable-line
console.log("State Set (SS):", this.ss) // eslint-disable-line
console.log("Operation Store (OS):") // eslint-disable-line
yield* this.os.logTable() // eslint-disable-line
console.log("Deletion Store (DS):") //eslint-disable-line
yield* this.ds.logTable() // eslint-disable-line
resolve()
}, true)
})
}
requestTransaction (_makeGen, requestNow) {
if (requestNow == null) { requestNow = false }
requestTransaction (_makeGen, callImmediately) {
if (!this.transactionInProgress) {
this.transactionInProgress = true
var transact = (xxxx) => {
var transact = () => {
var makeGen = _makeGen
while (makeGen != null) {
var t = new Transaction(this)
var gen = makeGen.call(t)
var res = gen.next()
while (!res.done) {
if (res.value === 'transaction') {
res = gen.next(t)
} else {
throw new Error("You must not yield this type. (Maybe you meant to use 'yield*'?)")
}
res = gen.next(res.value)
}
makeGen = this.waitingTransactions.shift()
}
this.transactionInProgress = false
}
if (!requestNow) {
setTimeout(transact, 0)
} else {
if (callImmediately) {
transact()
} else {
setTimeout(transact, 0)
}
} else {
this.waitingTransactions.push(_makeGen)
}
}
* destroy () { // eslint-disable-line
* destroy () {
super.destroy()
delete this.os
}

View File

@ -3,127 +3,146 @@
describe('Memory', function () {
describe('DeleteStore', function () {
var ds
var store
beforeEach(function () {
ds = new Y.utils.DeleteStore()
})
it('Deleted operation is deleted', function () {
ds.markDeleted(['u1', 10])
expect(ds.isDeleted(['u1', 10])).toBeTruthy()
expect(ds.toDeleteSet()).toEqual({'u1': [[10, 1, false]]})
})
it('Deleted operation extends other deleted operation', function () {
ds.markDeleted(['u1', 10])
ds.markDeleted(['u1', 11])
expect(ds.isDeleted(['u1', 10])).toBeTruthy()
expect(ds.isDeleted(['u1', 11])).toBeTruthy()
expect(ds.toDeleteSet()).toEqual({'u1': [[10, 2, false]]})
})
it('Deleted operation extends other deleted operation', function () {
ds.markDeleted(['0', 3])
ds.markDeleted(['0', 4])
ds.markDeleted(['0', 2])
expect(ds.toDeleteSet()).toEqual({'0': [[2, 3, false]]})
})
it('Debug #1', function () {
ds.markDeleted(['166', 0])
ds.markDeleted(['166', 2])
ds.markDeleted(['166', 0])
ds.markDeleted(['166', 2])
ds.markGarbageCollected(['166', 2])
ds.markDeleted(['166', 1])
ds.markDeleted(['166', 3])
ds.markGarbageCollected(['166', 3])
ds.markDeleted(['166', 0])
expect(ds.toDeleteSet()).toEqual({'166': [[0, 2, false], [2, 2, true]]})
})
it('Debug #2', function () {
ds.markDeleted(['293', 0])
ds.markDeleted(['291', 2])
ds.markDeleted(['291', 2])
ds.markGarbageCollected(['293', 0])
ds.markDeleted(['293', 1])
ds.markGarbageCollected(['291', 2])
expect(ds.toDeleteSet()).toEqual({'291': [[2, 1, true]], '293': [[0, 1, true], [1, 1, false]]})
})
it('Debug #3', function () {
ds.markDeleted(['581', 0])
ds.markDeleted(['581', 1])
ds.markDeleted(['580', 0])
ds.markDeleted(['580', 0])
ds.markGarbageCollected(['581', 0])
ds.markDeleted(['581', 2])
ds.markDeleted(['580', 1])
ds.markDeleted(['580', 2])
ds.markDeleted(['580', 1])
ds.markDeleted(['580', 2])
ds.markGarbageCollected(['581', 2])
ds.markGarbageCollected(['581', 1])
ds.markGarbageCollected(['580', 1])
expect(ds.toDeleteSet()).toEqual({'580': [[0, 1, false], [1, 1, true], [2, 1, false]], '581': [[0, 3, true]]})
})
it('Debug #4', function () {
ds.markDeleted(['544', 0])
ds.markDeleted(['543', 2])
ds.markDeleted(['544', 0])
ds.markDeleted(['543', 2])
ds.markGarbageCollected(['544', 0])
ds.markDeleted(['545', 1])
ds.markDeleted(['543', 4])
ds.markDeleted(['543', 3])
ds.markDeleted(['544', 1])
ds.markDeleted(['544', 2])
ds.markDeleted(['544', 1])
ds.markDeleted(['544', 2])
ds.markGarbageCollected(['543', 2])
ds.markGarbageCollected(['543', 4])
ds.markGarbageCollected(['544', 2])
ds.markGarbageCollected(['543', 3])
expect(ds.toDeleteSet()).toEqual({'543': [[2, 3, true]], '544': [[0, 1, true], [1, 1, false], [2, 1, true]], '545': [[1, 1, false]]})
})
it('Debug #5', async(function * (done) {
var store = new Y.Memory(null, {
store = new Y.Memory(null, {
name: 'Memory',
gcTimeout: -1
})
})
it('Deleted operation is deleted', async(function * (done) {
store.requestTransaction(function * () {
yield* this.markDeleted(['u1', 10])
expect(yield* this.isDeleted(['u1', 10])).toBeTruthy()
expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 1, false]]})
done()
})
}))
it('Deleted operation extends other deleted operation', async(function * (done) {
store.requestTransaction(function * () {
yield* this.markDeleted(['u1', 10])
yield* this.markDeleted(['u1', 11])
expect(yield* this.isDeleted(['u1', 10])).toBeTruthy()
expect(yield* this.isDeleted(['u1', 11])).toBeTruthy()
expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 2, false]]})
done()
})
}))
it('Deleted operation extends other deleted operation', async(function * (done) {
store.requestTransaction(function * () {
yield* this.markDeleted(['0', 3])
yield* this.markDeleted(['0', 4])
yield* this.markDeleted(['0', 2])
expect(yield* this.getDeleteSet()).toEqual({'0': [[2, 3, false]]})
done()
})
}))
it('Debug #1', async(function * (done) {
store.requestTransaction(function * () {
yield* this.markDeleted(['166', 0])
yield* this.markDeleted(['166', 2])
yield* this.markDeleted(['166', 0])
yield* this.markDeleted(['166', 2])
yield* this.markGarbageCollected(['166', 2])
yield* this.markDeleted(['166', 1])
yield* this.markDeleted(['166', 3])
yield* this.markGarbageCollected(['166', 3])
yield* this.markDeleted(['166', 0])
expect(yield* this.getDeleteSet()).toEqual({'166': [[0, 2, false], [2, 2, true]]})
done()
})
}))
it('Debug #2', async(function * (done) {
store.requestTransaction(function * () {
yield* this.markDeleted(['293', 0])
yield* this.markDeleted(['291', 2])
yield* this.markDeleted(['291', 2])
yield* this.markGarbageCollected(['293', 0])
yield* this.markDeleted(['293', 1])
yield* this.markGarbageCollected(['291', 2])
expect(yield* this.getDeleteSet()).toEqual({'291': [[2, 1, true]], '293': [[0, 1, true], [1, 1, false]]})
done()
})
}))
it('Debug #3', async(function * (done) {
store.requestTransaction(function * () {
yield* this.markDeleted(['581', 0])
yield* this.markDeleted(['581', 1])
yield* this.markDeleted(['580', 0])
yield* this.markDeleted(['580', 0])
yield* this.markGarbageCollected(['581', 0])
yield* this.markDeleted(['581', 2])
yield* this.markDeleted(['580', 1])
yield* this.markDeleted(['580', 2])
yield* this.markDeleted(['580', 1])
yield* this.markDeleted(['580', 2])
yield* this.markGarbageCollected(['581', 2])
yield* this.markGarbageCollected(['581', 1])
yield* this.markGarbageCollected(['580', 1])
expect(yield* this.getDeleteSet()).toEqual({'580': [[0, 1, false], [1, 1, true], [2, 1, false]], '581': [[0, 3, true]]})
done()
})
}))
it('Debug #4', async(function * (done) {
store.requestTransaction(function * () {
yield* this.markDeleted(['544', 0])
yield* this.markDeleted(['543', 2])
yield* this.markDeleted(['544', 0])
yield* this.markDeleted(['543', 2])
yield* this.markGarbageCollected(['544', 0])
yield* this.markDeleted(['545', 1])
yield* this.markDeleted(['543', 4])
yield* this.markDeleted(['543', 3])
yield* this.markDeleted(['544', 1])
yield* this.markDeleted(['544', 2])
yield* this.markDeleted(['544', 1])
yield* this.markDeleted(['544', 2])
yield* this.markGarbageCollected(['543', 2])
yield* this.markGarbageCollected(['543', 4])
yield* this.markGarbageCollected(['544', 2])
yield* this.markGarbageCollected(['543', 3])
expect(yield* this.getDeleteSet()).toEqual({'543': [[2, 3, true]], '544': [[0, 1, true], [1, 1, false], [2, 1, true]], '545': [[1, 1, false]]})
done()
})
}))
it('Debug #5', async(function * (done) {
store.requestTransaction(function * () {
yield* this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
expect(this.ds.toDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
expect(yield* this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
yield* this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 4, true]]})
expect(this.ds.toDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 4, true]]})
expect(yield* this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 4, true]]})
done()
})
}))
it('Debug #6', async(function * (done) {
var store = new Y.Memory(null, {
name: 'Memory',
gcTimeout: -1
})
store.requestTransaction(function * () {
yield* this.applyDeleteSet({'40': [[0, 3, false]]})
expect(this.ds.toDeleteSet()).toEqual({'40': [[0, 3, false]]})
expect(yield* this.getDeleteSet()).toEqual({'40': [[0, 3, false]]})
yield* this.applyDeleteSet({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
expect(this.ds.toDeleteSet()).toEqual({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
expect(yield* this.getDeleteSet()).toEqual({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
done()
})
}))
it('Debug #7', async(function * (done) {
store.requestTransaction(function * () {
yield* this.markDeleted(['9', 2])
yield* this.markDeleted(['11', 2])
yield* this.markDeleted(['11', 4])
yield* this.markDeleted(['11', 1])
yield* this.markDeleted(['9', 4])
yield* this.markDeleted(['10', 0])
yield* this.markGarbageCollected(['11', 2])
yield* this.markDeleted(['11', 2])
yield* this.markGarbageCollected(['11', 3])
yield* this.markDeleted(['11', 3])
yield* this.markDeleted(['11', 3])
yield* this.markDeleted(['9', 4])
yield* this.markDeleted(['10', 0])
yield* this.markGarbageCollected(['11', 1])
yield* this.markDeleted(['11', 1])
expect(yield* this.getDeleteSet()).toEqual({'9': [[2, 1, false], [4, 1, false]], '10': [[0, 1, false]], '11': [[1, 3, true], [4, 1, false]]})
done()
})
}))
it('Debug #7', function () {
ds.markDeleted(['9', 2])
ds.markDeleted(['11', 2])
ds.markDeleted(['11', 4])
ds.markDeleted(['11', 1])
ds.markDeleted(['9', 4])
ds.markDeleted(['10', 0])
ds.markGarbageCollected(['11', 2])
ds.markDeleted(['11', 2])
ds.markGarbageCollected(['11', 3])
ds.markDeleted(['11', 3])
ds.markDeleted(['11', 3])
ds.markDeleted(['9', 4])
ds.markDeleted(['10', 0])
ds.markGarbageCollected(['11', 1])
ds.markDeleted(['11', 1])
expect(ds.toDeleteSet()).toEqual({'9': [[2, 1, false], [4, 1, false]], '10': [[0, 1, false]], '11': [[1, 3, true], [4, 1, false]]})
})
})
})

View File

@ -186,15 +186,15 @@ class RBTree {
}
}
}
iterate (from, to, f) {
* iterate (t, from, to, f) {
var o = this.findNodeWithLowerBound(from)
while (o !== null && (to === null || Y.utils.smaller(o.val.id, to) || Y.utils.compareIds(o.val.id, to))) {
f(o.val)
yield* f.call(t, o.val)
o = o.next()
}
return true
}
logTable (from, to, filter) {
* logTable (from, to, filter) {
if (filter == null) {
filter = function () {
return true
@ -203,7 +203,7 @@ class RBTree {
if (from == null) { from = null }
if (to == null) { to = null }
var os = []
this.iterate(from, to, function (o) {
yield* this.iterate(this, from, to, function * (o) {
if (filter(o)) {
var o_ = {}
for (var key in o) {

View File

@ -51,7 +51,11 @@ function itRootNodeIsBlack (tree) {
describe('RedBlack Tree', function () {
beforeEach(function () {
this.tree = new Y.utils.RBTree()
this.memory = new Y.Memory(null, {
name: 'Memory',
gcTimeout: -1
})
this.tree = this.memory.os
})
it('can add&retrieve 5 elements', function () {
this.tree.add({val: 'four', id: [4]})
@ -144,48 +148,57 @@ describe('RedBlack Tree', function () {
itBlackHeightOfSubTreesAreEqual(tree)
it('iterating over a tree with lower bound yields the right amount of results', function () {
it('iterating over a tree with lower bound yields the right amount of results', function (done) {
var lowerBound = elements[Math.floor(Math.random() * elements.length)]
var expectedResults = elements.filter(function (e, pos) {
return (Y.utils.smaller(lowerBound, e) || Y.utils.compareIds(e, lowerBound)) && elements.indexOf(e) === pos
}).length
var actualResults = 0
tree.iterate(lowerBound, null, function (val) {
expect(val).not.toBeUndefined()
actualResults++
this.memory.requestTransaction(function * () {
yield* tree.iterate(this, lowerBound, null, function * (val) {
expect(val).not.toBeUndefined()
actualResults++
})
expect(expectedResults).toEqual(actualResults)
done()
})
expect(expectedResults).toEqual(actualResults)
})
it('iterating over a tree without bounds yield the right amount of results', function () {
it('iterating over a tree without bounds yield the right amount of results', function (done) {
var lowerBound = null
var expectedResults = elements.filter(function (e, pos) {
return elements.indexOf(e) === pos
}).length
var actualResults = 0
tree.iterate(lowerBound, null, function (val) {
expect(val).not.toBeUndefined()
actualResults++
this.memory.requestTransaction(function * () {
yield* tree.iterate(this, lowerBound, null, function * (val) {
expect(val).not.toBeUndefined()
actualResults++
})
expect(expectedResults).toEqual(actualResults)
done()
})
expect(expectedResults).toEqual(actualResults)
})
it('iterating over a tree with upper bound yields the right amount of results', function () {
it('iterating over a tree with upper bound yields the right amount of results', function (done) {
var upperBound = elements[Math.floor(Math.random() * elements.length)]
var expectedResults = elements.filter(function (e, pos) {
return (Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) && elements.indexOf(e) === pos
}).length
var actualResults = 0
tree.iterate(null, upperBound, function (val) {
expect(val).not.toBeUndefined()
actualResults++
this.memory.requestTransaction(function * () {
yield* tree.iterate(this, null, upperBound, function * (val) {
expect(val).not.toBeUndefined()
actualResults++
})
expect(expectedResults).toEqual(actualResults)
done()
})
expect(expectedResults).toEqual(actualResults)
})
it('iterating over a tree with upper and lower bounds yield the right amount of results', function () {
it('iterating over a tree with upper and lower bounds yield the right amount of results', function (done) {
var b1 = elements[Math.floor(Math.random() * elements.length)]
var b2 = elements[Math.floor(Math.random() * elements.length)]
var upperBound, lowerBound
@ -201,11 +214,14 @@ describe('RedBlack Tree', function () {
(Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) && elements.indexOf(e) === pos
}).length
var actualResults = 0
tree.iterate(lowerBound, upperBound, function (val) {
expect(val).not.toBeUndefined()
actualResults++
this.memory.requestTransaction(function * () {
yield* tree.iterate(this, lowerBound, upperBound, function * (val) {
expect(val).not.toBeUndefined()
actualResults++
})
expect(expectedResults).toEqual(actualResults)
done()
})
expect(expectedResults).toEqual(actualResults)
})
})
})

View File

@ -1,7 +1,7 @@
/* global createUsers, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, garbageCollectAllUsers, describeManyTimes */
/* eslint-env browser,jasmine */
var numberOfYArrayTests = 100
var numberOfYArrayTests = 10
var repeatArrayTests = 1
describe('Array Type', function () {

View File

@ -1,7 +1,7 @@
/* global createUsers, Y, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, describeManyTimes */
/* eslint-env browser,jasmine */
var numberOfYMapTests = 100
var numberOfYMapTests = 10
var repeatMapTeasts = 1
describe('Map Type', function () {

View File

@ -43,9 +43,6 @@ class YConfig {
this.db.destroy()
this.connector = null
this.db = null
this.transact = function () {
throw new Error('Remember?, you destroyed this type ;)')
}
}
}