fixed late join issues when gc is turned off

This commit is contained in:
Kevin Jahns 2015-10-11 02:55:46 +02:00
parent aadef59934
commit 9c4074e3e3
5 changed files with 85 additions and 66 deletions

View File

@ -120,7 +120,7 @@ gulp.task('build:test', function () {
if (!options.regenerator) { if (!options.regenerator) {
babelOptions.blacklist = 'regenerator' babelOptions.blacklist = 'regenerator'
} }
gulp.src('src/**/*.js') return gulp.src('src/**/*.js')
.pipe(sourcemaps.init()) .pipe(sourcemaps.init())
.pipe(babel(babelOptions)) .pipe(babel(babelOptions))
.pipe(sourcemaps.write()) .pipe(sourcemaps.write())

View File

@ -30,6 +30,7 @@ class AbstractConnector {
this.forwardToSyncingClients = opts.forwardToSyncingClients !== false this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
this.debug = opts.debug === true this.debug = opts.debug === true
this.broadcastedHB = false this.broadcastedHB = false
this.syncStep2 = Promise.resolve()
} }
reconnect () { reconnect () {
} }
@ -184,27 +185,34 @@ class AbstractConnector {
let conn = this let conn = this
var broadcastHB = !this.broadcastedHB var broadcastHB = !this.broadcastedHB
this.broadcastedHB = true this.broadcastedHB = true
this.y.db.requestTransaction(function * () { var db = this.y.db
this.syncStep2 = new Promise(function (resolve) {
db.requestTransaction(function * () {
yield* this.applyDeleteSet(m.deleteSet) yield* this.applyDeleteSet(m.deleteSet)
this.store.apply(m.os) this.store.apply(m.os)
}) db.requestTransaction(function * () {
this.y.db.requestTransaction(function * () {
var ops = yield* this.getOperations(m.stateSet) var ops = yield* this.getOperations(m.stateSet)
if (ops.length > 0) { if (ops.length > 0) {
m = { m = {
type: 'update', type: 'update',
ops: ops ops: ops
} }
if (!broadcastHB || true) { // TODO: consider to broadcast here.. if (!broadcastHB) { // TODO: consider to broadcast here..
conn.send(sender, m) conn.send(sender, m)
} else { } else {
// broadcast only once! // broadcast only once!
conn.broadcast(m) conn.broadcast(m)
} }
} }
resolve()
})
})
}) })
} else if (m.type === 'sync done') { } else if (m.type === 'sync done') {
this._setSyncedWith(sender) var self = this
this.syncStep2.then(function () {
self._setSyncedWith(sender)
})
} else if (m.type === 'update') { } else if (m.type === 'update') {
if (this.forwardToSyncingClients) { if (this.forwardToSyncingClients) {
for (var client of this.syncingClients) { for (var client of this.syncingClients) {

View File

@ -18,7 +18,7 @@ g.g = g
g.YConcurrency_TestingMode = true g.YConcurrency_TestingMode = true
jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000 jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000
g.describeManyTimes = function describeManyTimes (times, name, f) { g.describeManyTimes = function describeManyTimes (times, name, f) {
for (var i = 0; i < times; i++) { for (var i = 0; i < times; i++) {
@ -31,12 +31,12 @@ g.describeManyTimes = function describeManyTimes (times, name, f) {
*/ */
function wait (t) { function wait (t) {
if (t == null) { if (t == null) {
t = 5 t = 10
} }
return new Promise(function (resolve) { return new Promise(function (resolve) {
setTimeout(function () { setTimeout(function () {
resolve() resolve()
}, t) }, t * 2)
}) })
} }
g.wait = wait g.wait = wait
@ -110,10 +110,12 @@ g.applyRandomTransactions = async(function * applyRandomTransactions (users, obj
g.garbageCollectAllUsers = async(function * garbageCollectAllUsers (users) { g.garbageCollectAllUsers = async(function * garbageCollectAllUsers (users) {
return yield wait(100)// TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! return yield wait(100)// TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/*
for (var i in users) { for (var i in users) {
yield users[i].db.garbageCollect() yield users[i].db.garbageCollect()
yield users[i].db.garbageCollect() yield users[i].db.garbageCollect()
} }
*/
}) })
g.compareAllUsers = async(function * compareAllUsers (users) { g.compareAllUsers = async(function * compareAllUsers (users) {
@ -239,7 +241,7 @@ function async (makeGenerator) {
try { try {
return handle(generator.next()) return handle(generator.next())
} catch (ex) { } catch (ex) {
generator.throw(ex) // TODO: check this out generator.throw(ex)
// return Promise.reject(ex) // return Promise.reject(ex)
} }
} }

View File

@ -4,9 +4,6 @@
class DeleteStore extends Y.utils.RBTree { class DeleteStore extends Y.utils.RBTree {
constructor () { constructor () {
super() super()
// TODO: debugggg
this.mem = [];
this.memDS = [];
} }
isDeleted (id) { isDeleted (id) {
var n = this.findNodeWithUpperBound(id) var n = this.findNodeWithUpperBound(id)
@ -18,9 +15,7 @@ class DeleteStore extends Y.utils.RBTree {
returns the delete node returns the delete node
*/ */
markGarbageCollected (id) { markGarbageCollected (id) {
this.mem.push({"gc": id});
var n = this.markDeleted(id) var n = this.markDeleted(id)
this.mem.pop()
if (!n.val.gc) { if (!n.val.gc) {
if (n.val.id[1] < id[1]) { if (n.val.id[1] < id[1]) {
// un-extend left // un-extend left
@ -65,7 +60,6 @@ class DeleteStore extends Y.utils.RBTree {
returns the delete node returns the delete node
*/ */
markDeleted (id) { markDeleted (id) {
this.mem.push({"del": id});
var n = this.findNodeWithUpperBound(id) var n = this.findNodeWithUpperBound(id)
if (n != null && n.val.id[0] === id[0]) { 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) { if (n.val.id[1] <= id[1] && id[1] < n.val.id[1] + n.val.len) {
@ -125,8 +119,6 @@ Y.Memory = (function () {
this.ss = store.ss this.ss = store.ss
this.os = store.os this.os = store.os
this.ds = store.ds this.ds = store.ds
this.memDS = store.ds.memDS; // TODO: remove
} }
* checkDeleteStoreForState (state) { * checkDeleteStoreForState (state) {
var n = this.ds.findNodeWithUpperBound([state.user, state.clock]) var n = this.ds.findNodeWithUpperBound([state.user, state.clock])
@ -149,11 +141,6 @@ Y.Memory = (function () {
} }
} }
var memAction = {
before: yield* this.getDeleteSet(),
applied: JSON.parse(JSON.stringify(ds))
};
for (var user in ds) { for (var user in ds) {
var dv = ds[user] var dv = ds[user]
var pos = 0 var pos = 0
@ -215,8 +202,6 @@ Y.Memory = (function () {
yield* this.deleteOperation(id) yield* this.deleteOperation(id)
} }
} }
memAction.after = yield* this.getDeleteSet();
this.memDS.push(memAction);
} }
* isDeleted (id) { * isDeleted (id) {
return this.ds.isDeleted(id) return this.ds.isDeleted(id)
@ -294,43 +279,67 @@ Y.Memory = (function () {
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
if ((state === op.id[1]) || true) {
startSS[op.id[0]] = op.id[1] + 1
} else {
throw new Error('Unexpected operation!')
}
*/
} }
return res return res
} }
* makeOperationReady (ss, op) { /*
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.Struct[op.struct].encode(op)
// instead of ss, you could use currSS (a ss that increments when you add an operation)
op = Y.utils.copyObject(op) op = Y.utils.copyObject(op)
var o = 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) { while (o.right != null) {
// while unknown, go to the right var right = yield* this.getOperation(o.right)
if (o.right[1] < (ss[o.right[0]] || 0)) { // && !Y.utils.compareIds(op.id, o.origin) if (o.right[1] < (startSS[o.right[0]] || 0) || !ids.some(function (id) {
return Y.utils.compareIds(id, right.origin)
})) {
break break
} }
o = yield* this.getOperation(o.right) ids.push(o.right)
o = right
} }
// new right is known according to the ss
op.right = o.right op.right = o.right
/* op.left = op.origin
while (o.left != null) {
// while unknown, go to the right
if (o.left[1] < (ss[o.left[0]] || 0)) {
break
}
o = yield* this.getOperation(o.left)
}
// new left is known according to the ss
op.left = o.left
*/
return op return op
} }
} }

View File

@ -2,7 +2,7 @@
/* eslint-env browser,jasmine */ /* eslint-env browser,jasmine */
var numberOfYArrayTests = 10 var numberOfYArrayTests = 10
var repeatArrayTests = 1000 var repeatArrayTests = 1
describe('Array Type', function () { describe('Array Type', function () {
var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll