fixed several random tests
This commit is contained in:
parent
2b7d2ed1e6
commit
d859fd68fe
@ -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 = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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)})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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++) {
|
||||||
|
@ -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)) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user