fix gc when item is deleted in observer call

This commit is contained in:
Kevin Jahns 2019-04-26 18:37:38 +02:00
parent a336cc167c
commit 8c5a06bbf8
12 changed files with 73 additions and 40 deletions

View File

@ -76,11 +76,17 @@
img[ychange_state='removed'] { img[ychange_state='removed'] {
padding: 2px; padding: 2px;
} }
.y-connect-btn {
position: absolute;
top: 20px;
right: 20px;
}
</style> </style>
</head> </head>
<body> <body>
<p>This example shows how to bind a YXmlFragment type to a <a href="http://prosemirror.net">Prosemirror</a> editor.</p> <button type="button" class="y-connect-btn">Disconnect</button>
<p>The content of this editor is shared with every client who visits this domain.</p> <p>This example shows how to bind a YXmlFragment to a <a href="http://prosemirror.net">Prosemirror</a> editor using <a href="https://github.com/y-js/y-prosemirror">y-prosemirror</a>.</p>
<p>The content of this editor is shared with every client that visits this domain.</p>
<div class="code-html"> <div class="code-html">
<div id="editor" style="margin-bottom: 23px"></div> <div id="editor" style="margin-bottom: 23px"></div>

View File

@ -22,4 +22,15 @@ const prosemirrorView = new EditorView(document.querySelector('#editor'), {
}) })
}) })
const connectBtn = document.querySelector('.y-connect-btn')
connectBtn.addEventListener('click', () => {
if (ydocument.wsconnected) {
ydocument.disconnect()
connectBtn.textContent = 'Connect'
} else {
ydocument.connect()
connectBtn.textContent = 'Disconnect'
}
})
window.example = { provider, ydocument, type, prosemirrorView } window.example = { provider, ydocument, type, prosemirrorView }

12
package-lock.json generated
View File

@ -3036,9 +3036,9 @@
} }
}, },
"lib0": { "lib0": {
"version": "0.0.1", "version": "0.0.2",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.0.1.tgz", "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.0.2.tgz",
"integrity": "sha512-k2BL//TczDqrl+GzTp8vUkZdyO/tJaMkN3O0OcqmfROvsmmBOYvdEGuwLacLl+c7AB6qFwmdh3cdgqg6YVPn2Q==" "integrity": "sha512-7bJgV2emHGRO5kpj66Gdc9SQKVfhDBLx0UIS/aU5P8R0179nRFHKDTYGvLlNloWbeUUARlqk3ndFIO4JbUy7Sw=="
}, },
"live-server": { "live-server": {
"version": "1.2.1", "version": "1.2.1",
@ -5578,6 +5578,12 @@
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
"dev": true "dev": true
}, },
"y-protocols": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-0.0.2.tgz",
"integrity": "sha512-ixAaywK7USrMX7h6H2PZ59rtNVZcfJCNO0+/gDhAV1Sizwxdt0T6wPm9RCxDJtY3pXNeWA8MgtBysMGkgGA5xA==",
"dev": true
},
"yallist": { "yallist": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",

View File

@ -55,9 +55,10 @@
}, },
"homepage": "http://y-js.org", "homepage": "http://y-js.org",
"dependencies": { "dependencies": {
"lib0": "0.0.1" "lib0": "0.0.2"
}, },
"devDependencies": { "devDependencies": {
"y-protocols": "0.0.2",
"codemirror": "^5.42.0", "codemirror": "^5.42.0",
"concurrently": "^3.6.1", "concurrently": "^3.6.1",
"jsdoc": "^3.5.5", "jsdoc": "^3.5.5",

View File

@ -421,13 +421,14 @@ export class AbstractItem extends AbstractStruct {
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {StructStore} store * @param {StructStore} store
* @param {boolean} parentGCd
* *
* @private * @private
*/ */
gc (transaction, store) { gc (transaction, store, parentGCd) {
this.delete(transaction) this.delete(transaction)
let r let r
if (this.parent._item !== null && this.parent._item.deleted) { if (parentGCd) {
r = new GC(this.id, this.length) r = new GC(this.id, this.length)
} else { } else {
r = new ItemDeleted(this.id, this.left, this.origin, this.right, this.rightOrigin, this.parent, this.parentSub, this.length) r = new ItemDeleted(this.id, this.left, this.origin, this.right, this.rightOrigin, this.parent, this.parentSub, this.length)

View File

@ -85,6 +85,19 @@ export class ItemDeleted extends AbstractItem {
} }
return false return false
} }
/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {boolean} parentGCd
*
* @private
*/
gc (transaction, store, parentGCd) {
if (parentGCd) {
super.gc(transaction, store, parentGCd)
}
}
/** /**
* @param {encoding.Encoder} encoder * @param {encoding.Encoder} encoder
* @param {number} offset * @param {number} offset

View File

@ -124,13 +124,13 @@ export class ItemType extends AbstractItem {
gcChildren (transaction, store) { gcChildren (transaction, store) {
let item = this.type._start let item = this.type._start
while (item !== null) { while (item !== null) {
item.gc(transaction, store) item.gc(transaction, store, true)
item = item.right item = item.right
} }
this.type._start = null this.type._start = null
this.type._map.forEach(item => { this.type._map.forEach(item => {
while (item !== null) { while (item !== null) {
item.gc(transaction, store) item.gc(transaction, store, true)
// @ts-ignore // @ts-ignore
item = item.left item = item.left
} }
@ -141,10 +141,11 @@ export class ItemType extends AbstractItem {
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {StructStore} store * @param {StructStore} store
* @param {boolean} parentGCd
*/ */
gc (transaction, store) { gc (transaction, store, parentGCd) {
this.gcChildren(transaction, store) this.gcChildren(transaction, store)
super.gc(transaction, store) super.gc(transaction, store, parentGCd)
} }
} }

View File

@ -10,7 +10,6 @@ import {
findIndexSS, findIndexSS,
callEventHandlerListeners, callEventHandlerListeners,
AbstractItem, AbstractItem,
ItemDeleted,
ID, AbstractType, AbstractStruct, YEvent, Y // eslint-disable-line ID, AbstractType, AbstractStruct, YEvent, Y // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@ -45,8 +44,9 @@ import * as math from 'lib0/math.js'
export class Transaction { export class Transaction {
/** /**
* @param {Y} y * @param {Y} y
* @param {any} origin
*/ */
constructor (y) { constructor (y, origin) {
/** /**
* The Yjs instance. * The Yjs instance.
* @type {Y} * @type {Y}
@ -90,6 +90,10 @@ export class Transaction {
* @private * @private
*/ */
this._mergeStructs = new Set() this._mergeStructs = new Set()
/**
* @type {any}
*/
this.origin = origin
} }
/** /**
* @type {encoding.Encoder|null} * @type {encoding.Encoder|null}
@ -124,22 +128,24 @@ export const nextID = transaction => {
* *
* @param {Y} y * @param {Y} y
* @param {function(Transaction):void} f * @param {function(Transaction):void} f
* @param {any} [origin]
* *
* @private * @private
* @function * @function
*/ */
export const transact = (y, f) => { export const transact = (y, f, origin = null) => {
const transactionCleanups = y._transactionCleanups const transactionCleanups = y._transactionCleanups
let initialCall = false let initialCall = false
if (y._transaction === null) { if (y._transaction === null) {
initialCall = true initialCall = true
y._transaction = new Transaction(y) y._transaction = new Transaction(y, origin)
transactionCleanups.push(y._transaction) transactionCleanups.push(y._transaction)
y.emit('beforeTransaction', [y._transaction, y]) y.emit('beforeTransaction', [y._transaction, y])
} }
try { try {
f(y._transaction) f(y._transaction)
} finally { } finally {
// @todo set after state here
if (initialCall && transactionCleanups[0] === y._transaction) { if (initialCall && transactionCleanups[0] === y._transaction) {
// The first transaction ended, now process observer calls. // The first transaction ended, now process observer calls.
// Observer call may create new transactions for which we need to call the observers and do cleanup. // Observer call may create new transactions for which we need to call the observers and do cleanup.
@ -185,13 +191,7 @@ export const transact = (y, f) => {
break break
} }
if (struct.deleted && struct instanceof AbstractItem) { if (struct.deleted && struct instanceof AbstractItem) {
if (struct.constructor !== ItemDeleted || (struct.parent._item !== null && struct.parent._item.deleted)) { struct.gc(transaction, store, false)
// check if we can GC
struct.gc(transaction, store)
} else {
// otherwise only gc children (if there are any)
struct.gcChildren(transaction, store)
}
} }
} }
} }

View File

@ -52,11 +52,12 @@ export class Y extends Observable {
* other peers. * other peers.
* *
* @param {function(Transaction):void} f The function that should be executed as a transaction * @param {function(Transaction):void} f The function that should be executed as a transaction
* @param {any} [origin] Origin of who started the transaction. Will be stored on transaction.origin
* *
* @public * @public
*/ */
transact (f) { transact (f, origin = null) {
transact(this, f) transact(this, f, origin)
} }
/** /**
* Define a shared data type. * Define a shared data type.

View File

@ -19,14 +19,14 @@ import * as syncProtocol from 'y-protocols/sync.js'
* @param {TestYInstance} y * @param {TestYInstance} y
*/ */
const afterTransaction = (transaction, y) => { const afterTransaction = (transaction, y) => {
y.mMux(() => { if (transaction.origin !== y.tc) {
const m = transaction.updateMessage const m = transaction.updateMessage
if (m !== null) { if (m !== null) {
const encoder = encoding.createEncoder() const encoder = encoding.createEncoder()
syncProtocol.writeUpdate(encoder, m) syncProtocol.writeUpdate(encoder, m)
broadcastMessage(y, encoding.toBuffer(encoder)) broadcastMessage(y, encoding.toBuffer(encoder))
} }
}) }
} }
/** /**
@ -59,11 +59,6 @@ export class TestYInstance extends Y.Y {
* @type {Map<TestYInstance, Array<ArrayBuffer>>} * @type {Map<TestYInstance, Array<ArrayBuffer>>}
*/ */
this.receiving = new Map() this.receiving = new Map()
/**
* Message mutex
* @type {Function}
*/
this.mMux = createMutex()
testConnector.allConns.add(this) testConnector.allConns.add(this)
// set up observe on local model // set up observe on local model
this.on('afterTransactionCleanup', afterTransaction) this.on('afterTransactionCleanup', afterTransaction)
@ -165,11 +160,9 @@ export class TestConnector {
return this.flushRandomMessage() return this.flushRandomMessage()
} }
const encoder = encoding.createEncoder() const encoder = encoding.createEncoder()
receiver.mMux(() => {
// console.log('receive (' + sender.userID + '->' + receiver.userID + '):\n', syncProtocol.stringifySyncMessage(decoding.createDecoder(m), receiver)) // console.log('receive (' + sender.userID + '->' + receiver.userID + '):\n', syncProtocol.stringifySyncMessage(decoding.createDecoder(m), receiver))
// do not publish data created when this function is executed (could be ss2 or update message) // do not publish data created when this function is executed (could be ss2 or update message)
syncProtocol.readSyncMessage(decoding.createDecoder(m), encoder, receiver) syncProtocol.readSyncMessage(decoding.createDecoder(m), encoder, receiver, receiver.tc)
})
if (encoding.length(encoder) > 0) { if (encoding.length(encoder) > 0) {
// send reply message // send reply message
sender._receive(encoding.toBuffer(encoder), receiver) sender._receive(encoding.toBuffer(encoder), receiver)

View File

@ -54,9 +54,9 @@
/* Experimental Options */ /* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"maxNodeModuleJsDepth": 5, "maxNodeModuleJsDepth": 0,
// "types": ["./src/utils/typedefs.js"] // "types": ["./src/utils/typedefs.js"]
}, },
"include": ["./src/**/*", "./tests/**/*"], "include": ["./src/**/*", "./tests/**/*"],
"exclude": ["../lib0/**/*", "node_modules"] "exclude": ["../lib0/**/*", "node_modules/**/*", "dist", "dist/**/*.js"]
} }