fix most gc bugs - test suite running again

This commit is contained in:
Kevin Jahns 2018-04-19 18:28:25 +02:00
parent d915c8dd13
commit ef6eb08335
6 changed files with 109 additions and 44 deletions

View File

@ -1,6 +1,7 @@
import { getStruct } from '../Util/structReferences.js'
import BinaryDecoder from '../Util/Binary/Decoder.js'
import { logID } from './messageToString.js'
import GC from '../Struct/GC.js';
class MissingEntry {
constructor (decoder, missing, struct) {
@ -24,7 +25,14 @@ function _integrateRemoteStructHelper (y, struct) {
if (y.ss.getState(id.user) > id.clock) {
return
}
struct._integrate(y)
if (struct.constructor === GC || (struct._parent.constructor !== GC && struct._parent._deleted === false)) {
// Is either a GC or Item with an undeleted parent
// save to integrate
struct._integrate(y)
} else {
// Is an Item. parent was deleted.
struct._gc(y)
}
let msu = y._missingStructs.get(id.user)
if (msu != null) {
let clock = id.clock

View File

@ -52,7 +52,7 @@ export default class DeleteStore extends Tree {
if (rightD !== null && rightD._id.user === id.user) {
if (rightD._id.clock < id.clock + length && id.clock <= rightD._id.clock && id.clock + length < rightD._id.clock + rightD.len) { // we only consider the case where we resize the node
const d = id.clock + length - rightD._id.clock
rightD._id.clock += d
rightD._id = new ID(rightD._id.user, rightD._id.clock + d)
rightD.len -= d
}
}

View File

@ -2,6 +2,7 @@ import Tree from '../Util/Tree.js'
import RootID from '../Util/ID/RootID.js'
import { getStruct } from '../Util/structReferences.js'
import { logID } from '../MessageHandler/messageToString.js'
import GC from '../Struct/GC.js'
export default class OperationStore extends Tree {
constructor (y) {
@ -11,17 +12,25 @@ export default class OperationStore extends Tree {
logTable () {
const items = []
this.iterate(null, null, function (item) {
items.push({
id: logID(item),
origin: logID(item._origin === null ? null : item._origin._lastId),
left: logID(item._left === null ? null : item._left._lastId),
right: logID(item._right),
right_origin: logID(item._right_origin),
parent: logID(item._parent),
parentSub: item._parentSub,
deleted: item._deleted,
content: JSON.stringify(item._content)
})
if (item.constructor === GC) {
items.push({
id: logID(item),
content: item._length,
deleted: 'GC'
})
} else {
items.push({
id: logID(item),
origin: logID(item._origin === null ? null : item._origin._lastId),
left: logID(item._left === null ? null : item._left._lastId),
right: logID(item._right),
right_origin: logID(item._right_origin),
parent: logID(item._parent),
parentSub: item._parentSub,
deleted: item._deleted,
content: JSON.stringify(item._content)
})
}
})
console.table(items)
}

View File

@ -1,4 +1,8 @@
import { getStructReference } from '../Util/structReferences.js'
import { RootFakeUserID } from '../Util/ID/RootID.js'
import ID from '../Util/ID/ID.js'
// TODO should have the same base class as Item
export default class GC {
constructor () {
this._id = null
@ -9,7 +13,38 @@ export default class GC {
return true
}
integrate () {
_integrate (y) {
const id = this._id
const userState = y.ss.getState(id.user)
if (id.clock === userState) {
y.ss.setState(id.user, id.clock + this._length)
}
y.ds.mark(this._id, this._length, true)
let n = y.os.put(this)
const prev = n.prev().val
if (prev !== null && prev.constructor === GC && prev._id.user === n.val._id.user && prev._id.clock + prev._length === n.val._id.clock) {
// TODO: do merging for all items!
prev._length += n.val._length
y.os.delete(n.val._id)
n = prev
}
if (n.val) {
n = n.val
}
const next = y.os.findNext(n._id)
if (next !== null && next.constructor === GC && next._id.user === n._id.user && next._id.clock === n._id.clock + n._length) {
n._length += next._length
y.os.delete(next._id)
}
if (id.user !== RootFakeUserID) {
if (y.connector !== null && (y.connector._forwardAppliedStructs || id.user === y.userID)) {
y.connector.broadcastStruct(this)
}
if (y.persistence !== null) {
y.persistence.saveStruct(y, this)
}
}
}
/**
@ -37,8 +72,17 @@ export default class GC {
* @private
*/
_fromBinary (y, decoder) {
this._id = decoder.readID()
const id = decoder.readID()
this._id = id
this._length = decoder.readVarUint()
return []
}
const missing = []
if (y.ss.getState(id.user) < id.clock) {
missing.push(new ID(id.user, id.clock - 1))
}
return missing
}
_splitAt () {
return this
}
}

View File

@ -240,27 +240,11 @@ export default class Item {
_gcChildren (y) {}
_gc (y) {
y.ds.mark(this._id, this._length, true)
const gc = new GC()
gc._id = this._id
gc._length = this._length
y.os.delete(this._id)
let n = y.os.put(gc)
const prev = n.prev().val
if (prev !== null && prev.constructor === GC && prev._id.user === n.val._id.user && prev._id.clock + prev._length === n.val._id.clock) {
// TODO: do merging for all items!
prev._length += n.val._length
y.os.delete(n.val._id)
n = prev
}
if (n.val) {
n = n.val
}
const next = y.os.findNext(n._id)
if (next !== null && next.constructor === GC && next._id.user === n._id.user && next._id.clock === n._id.clock + n._length) {
n._length += next._length
y.os.delete(n._id)
}
gc._integrate(y)
}
/**
@ -511,9 +495,19 @@ export default class Item {
}
} else if (this._parent === null) {
if (this._origin !== null) {
this._parent = this._origin._parent
if (this._origin.constructor === GC) {
// if origin is a gc, set parent also gc'd
this._parent = this._origin
} else {
this._parent = this._origin._parent
}
} else if (this._right_origin !== null) {
this._parent = this._right_origin._parent
// if origin is a gc, set parent also gc'd
if (this._right_origin.constructor === GC) {
this._parent = this._right_origin
} else {
this._parent = this._right_origin._parent
}
}
}
if (info & 0b1000) {

View File

@ -8,6 +8,7 @@ import ItemJSON from '../src/Struct/ItemJSON.js'
import ItemString from '../src/Struct/ItemString.js'
import { defragmentItemContent } from '../src/Util/defragmentItemContent.js'
import Quill from 'quill'
import GC from '../src/Struct/GC.js';
export const Y = _Y
@ -110,13 +111,22 @@ export async function compareUsers (t, users) {
var data = {}
let ops = []
u.os.iterate(null, null, function (op) {
const json = {
id: op._id,
left: op._left === null ? null : op._left._lastId,
right: op._right === null ? null : op._right._id,
length: op._length,
deleted: op._deleted,
parent: op._parent._id
let json
if (op.constructor === GC) {
json = {
type: 'GC',
id: op._id,
length: op._length
}
} else {
json = {
id: op._id,
left: op._left === null ? null : op._left._lastId,
right: op._right === null ? null : op._right._id,
length: op._length,
deleted: op._deleted,
parent: op._parent._id
}
}
if (op instanceof ItemJSON || op instanceof ItemString) {
json.content = op._content
@ -186,7 +196,7 @@ export async function initArrays (t, opts) {
y.dom = dom
y.on('afterTransaction', function () {
for (let missing of y._missingStructs.values()) {
if (Array.from(missing.values()).length > 0) {
if (missing.size > 0) {
console.error(new Error('Test check in "afterTransaction": missing should be empty!'))
}
}