fixed several random tests

This commit is contained in:
Kevin Jahns 2017-10-23 22:43:33 +02:00
parent 2b7d2ed1e6
commit d859fd68fe
13 changed files with 100 additions and 69 deletions

View File

@ -6,6 +6,7 @@ const bits8 = 0b11111111
export default class BinaryEncoder { export default class BinaryEncoder {
constructor () { constructor () {
// TODO: implement chained Uint8Array buffers instead of Array buffer
this.data = [] this.data = []
} }

View File

@ -88,6 +88,7 @@ export default class AbstractConnector {
isSynced: false, isSynced: false,
role: role, role: role,
processAfterAuth: [], processAfterAuth: [],
processAfterSync: [],
auth: auth || null, auth: auth || null,
receivedSyncStep2: false receivedSyncStep2: false
}) })
@ -122,17 +123,15 @@ export default class AbstractConnector {
} }
_fireIsSyncedListeners () { _fireIsSyncedListeners () {
setTimeout(() => { if (!this.isSynced) {
if (!this.isSynced) { this.isSynced = true
this.isSynced = true // It is safer to remove this!
// It is safer to remove this! // call whensynced listeners
// call whensynced listeners for (var f of this.whenSyncedListeners) {
for (var f of this.whenSyncedListeners) { f()
f()
}
this.whenSyncedListeners = []
} }
}, 0) this.whenSyncedListeners = []
}
} }
send (uid, buffer) { send (uid, buffer) {
@ -237,16 +236,16 @@ export default class AbstractConnector {
let messages = senderConn.processAfterAuth let messages = senderConn.processAfterAuth
senderConn.processAfterAuth = [] senderConn.processAfterAuth = []
return messages.reduce((p, m) => messages.forEach(m =>
p.then(() => this.computeMessage(m[0], m[1], m[2], m[3], m[4])) this.computeMessage(m[0], m[1], m[2], m[3], m[4])
, Promise.resolve()) )
}) })
} }
} }
if (skipAuth || senderConn.auth != null) { if ((skipAuth || senderConn.auth != null) && (messageType !== 'update' || senderConn.isSynced)) {
return this.computeMessage(messageType, senderConn, decoder, encoder, sender, skipAuth) this.computeMessage(messageType, senderConn, decoder, encoder, sender, skipAuth)
} else { } else {
senderConn.processAfterAuth.push([messageType, senderConn, decoder, encoder, sender, false]) senderConn.processAfterSync.push([messageType, senderConn, decoder, encoder, sender, false])
} }
} }
@ -270,9 +269,15 @@ export default class AbstractConnector {
_setSyncedWith (user) { _setSyncedWith (user) {
if (user != null) { if (user != null) {
this.connections.get(user).isSynced = true const userConn = this.connections.get(user)
userConn.isSynced = true
const messages = userConn.processAfterSync
userConn.processAfterSync = []
messages.forEach(m => {
this.computeMessage(m[0], m[1], m[2], m[3], m[4])
})
} }
let conns = Array.from(this.connections.values()) const conns = Array.from(this.connections.values())
if (conns.length > 0 && conns.every(u => u.isSynced)) { if (conns.length > 0 && conns.every(u => u.isSynced)) {
this._fireIsSyncedListeners() this._fireIsSyncedListeners()
} }

View File

@ -19,22 +19,27 @@ class MissingEntry {
function _integrateRemoteStructHelper (y, struct) { function _integrateRemoteStructHelper (y, struct) {
struct._integrate(y) struct._integrate(y)
if (struct.constructor !== Delete) { if (struct.constructor !== Delete) {
let msu = y._missingStructs.get(struct._id.user) const id = struct._id
let msu = y._missingStructs.get(id.user)
if (msu != null) { if (msu != null) {
let len = struct._length let clock = id.clock
for (let i = 0; i < len; i++) { const finalClock = clock + struct._length
if (msu.has(struct._id.clock + i)) { for (;clock < finalClock; clock++) {
let msuc = msu.get(struct._id.clock + i) const missingStructs = msu.get(clock)
msuc.forEach(missingDef => { if (missingStructs !== undefined) {
missingStructs.forEach(missingDef => {
missingDef.missing-- missingDef.missing--
if (missingDef.missing === 0) { if (missingDef.missing === 0) {
let missing = missingDef.struct._fromBinary(y, missingDef.decoder) const decoder = missingDef.decoder
let oldPos = decoder.pos
let missing = missingDef.struct._fromBinary(y, decoder)
decoder.pos = oldPos
if (missing.length === 0) { if (missing.length === 0) {
y._readyToIntegrate.push(missingDef.struct) y._readyToIntegrate.push(missingDef.struct)
} }
} }
}) })
msu.delete(struct._id.clock) msu.delete(clock)
} }
} }
} }
@ -57,10 +62,10 @@ export function stringifyStructs (y, decoder, strBuilder) {
export function integrateRemoteStructs (decoder, encoder, y) { export function integrateRemoteStructs (decoder, encoder, y) {
while (decoder.length !== decoder.pos) { while (decoder.length !== decoder.pos) {
let decoderPos = decoder.pos
let reference = decoder.readVarUint() let reference = decoder.readVarUint()
let Constr = getStruct(reference) let Constr = getStruct(reference)
let struct = new Constr() let struct = new Constr()
let decoderPos = decoder.pos
let missing = struct._fromBinary(y, decoder) let missing = struct._fromBinary(y, decoder)
if (missing.length === 0) { if (missing.length === 0) {
while (struct != null) { while (struct != null) {

View File

@ -16,11 +16,27 @@ export function splitHelper (y, a, b, diff) {
b._id = new ID(aID.user, aID.clock + diff) b._id = new ID(aID.user, aID.clock + diff)
b._origin = a b._origin = a
b._left = a b._left = a
b._right = a._right
if (b._right !== null) {
b._right._left = b
}
b._right_origin = a._right_origin
// do not set a._right_origin, as this will lead to problems when syncing
a._right = b a._right = b
a._right_origin = b
b._parent = a._parent b._parent = a._parent
b._parentSub = a._parentSub b._parentSub = a._parentSub
b._deleted = a._deleted b._deleted = a._deleted
// now search all relevant items to the right and update origin
// if origin is not it foundOrigins, we don't have to search any longer
let foundOrigins = new Set()
foundOrigins.add(a)
let o = b._right
while (o !== null && foundOrigins.has(o._origin)) {
if (o._origin === a) {
o._origin = b
}
o = o._right
}
y.os.put(b) y.os.put(b)
} }
@ -133,21 +149,25 @@ export default class Item {
// Note that conflictingItems is a subset of itemsBeforeOrigin // Note that conflictingItems is a subset of itemsBeforeOrigin
while (o !== null && o !== this._right) { while (o !== null && o !== this._right) {
itemsBeforeOrigin.add(o) itemsBeforeOrigin.add(o)
conflictingItems.add(o)
if (this._origin === o._origin) { if (this._origin === o._origin) {
// case 1 // case 1
if (o._id.user < this._id.user) { if (o._id.user < this._id.user) {
this._left = o this._left = o
conflictingItems = new Set() conflictingItems.clear()
} }
} else if (itemsBeforeOrigin.has(o)) { } else if (itemsBeforeOrigin.has(o._origin)) {
// case 2 // case 2
if (conflictingItems.has(o)) { if (!conflictingItems.has(o._origin)) {
this._left = o this._left = o
conflictingItems = new Set() conflictingItems.clear()
} }
} else { } else {
break break
} }
// TODO: try to use right_origin instead.
// Then you could basically omit conflictingItems!
// Note: you probably can't use right_origin in every case.. only when setting _left
o = o._right o = o._right
} }
// reconnect left/right + update parent map/start if necessary // reconnect left/right + update parent map/start if necessary
@ -193,9 +213,12 @@ export default class Item {
if (this._origin !== null) { if (this._origin !== null) {
info += 0b1 // origin is defined info += 0b1 // origin is defined
} }
// TODO: remove
/* no longer send _left
if (this._left !== this._origin) { if (this._left !== this._origin) {
info += 0b10 // do not copy origin to left info += 0b10 // do not copy origin to left
} }
*/
if (this._right_origin !== null) { if (this._right_origin !== null) {
info += 0b100 info += 0b100
} }
@ -236,24 +259,9 @@ export default class Item {
missing.push(originID) missing.push(originID)
} else { } else {
this._origin = origin this._origin = origin
}
}
}
// read left
if (info & 0b10) {
// left !== origin
const leftID = decoder.readID()
if (this._left === null) {
const left = y.os.getItemCleanEnd(leftID)
if (left === null) {
// use origin instead
this._left = this._origin this._left = this._origin
} else {
this._left = left
} }
} }
} else {
this._left = this._origin
} }
// read right // read right
if (info & 0b100) { if (info & 0b100) {

View File

@ -28,7 +28,9 @@ export default class ItemJSON extends Item {
} }
} }
_logString () { _logString () {
return `ItemJSON(id:${logID(this._id)},content:${JSON.stringify(this._content)},left:${logID(this._left)},origin:${logID(this._origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${logID(this._parentSub)})` const left = this._left !== null ? this._left._lastId : null
const origin = this._origin !== null ? this._origin._lastId : null
return `ItemJSON(id:${logID(this._id)},content:${JSON.stringify(this._content)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${logID(this._parentSub)})`
} }
_splitAt (y, diff) { _splitAt (y, diff) {
if (diff === 0) { if (diff === 0) {

View File

@ -19,7 +19,9 @@ export default class ItemString extends Item {
encoder.writeVarString(this._content) encoder.writeVarString(this._content)
} }
_logString () { _logString () {
return `ItemJSON(id:${logID(this._id)},content:${JSON.stringify(this._content)},left:${logID(this._left)},origin:${logID(this._origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${logID(this._parentSub)})` const left = this._left !== null ? this._left._lastId : null
const origin = this._origin !== null ? this._origin._lastId : null
return `ItemJSON(id:${logID(this._id)},content:${JSON.stringify(this._content)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${logID(this._parentSub)})`
} }
_splitAt (y, diff) { _splitAt (y, diff) {
if (diff === 0) { if (diff === 0) {

View File

@ -204,6 +204,8 @@ export default class YArray extends Type {
this.insertAfter(left, content) this.insertAfter(left, content)
} }
_logString () { _logString () {
return `YArray(id:${logID(this._id)},start:${logID(this._start)},left:${logID(this._left)},origin:${logID(this._origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${logID(this._parentSub)})` const left = this._left !== null ? this._left._lastId : null
const origin = this._origin !== null ? this._origin._lastId : null
return `YArray(id:${logID(this._id)},start:${logID(this._start)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${logID(this._parentSub)})`
} }
} }

View File

@ -83,6 +83,8 @@ export default class YMap extends Type {
} }
} }
_logString () { _logString () {
return `YMap(id:${logID(this._id)},mapSize:${this._map.size},left:${logID(this._left)},origin:${logID(this._origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${logID(this._parentSub)})` const left = this._left !== null ? this._left._lastId : null
const origin = this._origin !== null ? this._origin._lastId : null
return `YMap(id:${logID(this._id)},mapSize:${this._map.size},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${logID(this._parentSub)})`
} }
} }

View File

@ -52,6 +52,8 @@ export default class YText extends YArray {
}) })
} }
_logString () { _logString () {
return `YText(id:${logID(this._id)},start:${logID(this._start)},left:${logID(this._left)},origin:${logID(this._origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${logID(this._parentSub)})` const left = this._left !== null ? this._left._lastId : null
const origin = this._origin !== null ? this._origin._lastId : null
return `YText(id:${logID(this._id)},start:${logID(this._start)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${logID(this._parentSub)})`
} }
} }

View File

@ -153,6 +153,8 @@ export default class YXmlFragment extends YArray {
} }
} }
_logString () { _logString () {
return `YXml(id:${logID(this._id)},parent:${logID(this._parent)},parentSub:${this._parentSub})` const left = this._left !== null ? this._left._lastId : null
const origin = this._origin !== null ? this._origin._lastId : null
return `YXml(id:${logID(this._id)},left:${logID(left)},origin:${logID(origin)},right:${this._right},parent:${logID(this._parent)},parentSub:${this._parentSub})`
} }
} }

View File

@ -272,7 +272,7 @@ var arrayTransactions = [
] ]
test('y-array: Random tests (5)', async function randomArray5 (t) { test('y-array: Random tests (5)', async function randomArray5 (t) {
await applyRandomTests(t, arrayTransactions, 5) await applyRandomTests(t, arrayTransactions, 12)
}) })
test('y-array: Random tests (42)', async function randomArray42 (t) { test('y-array: Random tests (42)', async function randomArray42 (t) {

View File

@ -248,6 +248,8 @@ export function wait (t) {
export async function applyRandomTests (t, mods, iterations) { export async function applyRandomTests (t, mods, iterations) {
const chance = new Chance(t.getSeed() * 1000000000) const chance = new Chance(t.getSeed() * 1000000000)
// TODO: remove
console.info('seed: ' + t._seed)
var initInformation = await initArrays(t, { users: 5, chance: chance }) var initInformation = await initArrays(t, { users: 5, chance: chance })
let { users } = initInformation let { users } = initInformation
for (var i = 0; i < iterations; i++) { for (var i = 0; i < iterations; i++) {

View File

@ -119,22 +119,20 @@ export default function extendTestConnector (Y) {
}) })
} }
receiveMessage (sender, m) { receiveMessage (sender, m) {
setTimeout(() => { if (this.y.userID !== sender && this.connections.has(sender)) {
if (this.y.userID !== sender && this.connections.has(sender)) { var buffer = this.connections.get(sender).buffer
var buffer = this.connections.get(sender).buffer if (buffer == null) {
if (buffer == null) { buffer = this.connections.get(sender).buffer = []
buffer = this.connections.get(sender).buffer = []
}
buffer.push(m)
if (this.chance.bool({likelihood: 30})) {
// flush 1/2 with 30% chance
var flushLength = Math.round(buffer.length / 2)
buffer.splice(0, flushLength).forEach(m => {
super.receiveMessage(sender, m)
})
}
} }
}, 0) buffer.push(m)
if (this.chance.bool({likelihood: 30})) {
// flush 1/2 with 30% chance
var flushLength = Math.round(buffer.length / 2)
buffer.splice(0, flushLength).forEach(m => {
super.receiveMessage(sender, m)
})
}
}
} }
async _flushAll (flushUsers) { async _flushAll (flushUsers) {
if (flushUsers.some(u => u.connector.y.userID === this.y.userID)) { if (flushUsers.some(u => u.connector.y.userID === this.y.userID)) {