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 { getStruct } from '../Util/structReferences.js'
import BinaryDecoder from '../Util/Binary/Decoder.js' import BinaryDecoder from '../Util/Binary/Decoder.js'
import { logID } from './messageToString.js' import { logID } from './messageToString.js'
import GC from '../Struct/GC.js';
class MissingEntry { class MissingEntry {
constructor (decoder, missing, struct) { constructor (decoder, missing, struct) {
@ -24,7 +25,14 @@ function _integrateRemoteStructHelper (y, struct) {
if (y.ss.getState(id.user) > id.clock) { if (y.ss.getState(id.user) > id.clock) {
return 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) let msu = y._missingStructs.get(id.user)
if (msu != null) { if (msu != null) {
let clock = id.clock 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 !== 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 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 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 rightD.len -= d
} }
} }

View File

@ -2,6 +2,7 @@ import Tree from '../Util/Tree.js'
import RootID from '../Util/ID/RootID.js' import RootID from '../Util/ID/RootID.js'
import { getStruct } from '../Util/structReferences.js' import { getStruct } from '../Util/structReferences.js'
import { logID } from '../MessageHandler/messageToString.js' import { logID } from '../MessageHandler/messageToString.js'
import GC from '../Struct/GC.js'
export default class OperationStore extends Tree { export default class OperationStore extends Tree {
constructor (y) { constructor (y) {
@ -11,17 +12,25 @@ export default class OperationStore extends Tree {
logTable () { logTable () {
const items = [] const items = []
this.iterate(null, null, function (item) { this.iterate(null, null, function (item) {
items.push({ if (item.constructor === GC) {
id: logID(item), items.push({
origin: logID(item._origin === null ? null : item._origin._lastId), id: logID(item),
left: logID(item._left === null ? null : item._left._lastId), content: item._length,
right: logID(item._right), deleted: 'GC'
right_origin: logID(item._right_origin), })
parent: logID(item._parent), } else {
parentSub: item._parentSub, items.push({
deleted: item._deleted, id: logID(item),
content: JSON.stringify(item._content) 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) 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 { export default class GC {
constructor () { constructor () {
this._id = null this._id = null
@ -9,7 +13,38 @@ export default class GC {
return true 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 * @private
*/ */
_fromBinary (y, decoder) { _fromBinary (y, decoder) {
this._id = decoder.readID() const id = decoder.readID()
this._id = id
this._length = decoder.readVarUint() 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) {} _gcChildren (y) {}
_gc (y) { _gc (y) {
y.ds.mark(this._id, this._length, true)
const gc = new GC() const gc = new GC()
gc._id = this._id gc._id = this._id
gc._length = this._length gc._length = this._length
y.os.delete(this._id) y.os.delete(this._id)
let n = y.os.put(gc) gc._integrate(y)
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)
}
} }
/** /**
@ -511,9 +495,19 @@ export default class Item {
} }
} else if (this._parent === null) { } else if (this._parent === null) {
if (this._origin !== 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) { } 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) { if (info & 0b1000) {

View File

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