fixed late join issues when gc is turned off
This commit is contained in:
parent
aadef59934
commit
9c4074e3e3
@ -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())
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user