started implementing the garbage collector

This commit is contained in:
Kevin Jahns 2015-07-26 03:13:13 +00:00
parent dae0f71cbc
commit c8ded24842
10 changed files with 106 additions and 21 deletions

View File

@ -133,7 +133,7 @@ gulp.task('build_jasmine_browser', function () {
.pipe(gulp.dest('build')) .pipe(gulp.dest('build'))
}) })
gulp.task('develop', ['build_jasmine_browser', 'build'], function () { gulp.task('develop', ['build_jasmine_browser'], function () {
gulp.watch(files.test, ['build_jasmine_browser']) gulp.watch(files.test, ['build_jasmine_browser'])
// gulp.watch(files.test, ["test"]) // gulp.watch(files.test, ["test"])
gulp.watch(files.test, ['build']) gulp.watch(files.test, ['build'])

View File

@ -99,7 +99,9 @@ async function compareAllUsers(users){//eslint-disable-line
var d = ds[j] var d = ds[j]
for (var i = 0; i < d.len; i++) { for (var i = 0; i < d.len; i++) {
var o = yield* this.getOperation([d.id[0], d.id[1] + i]) var o = yield* this.getOperation([d.id[0], d.id[1] + i])
expect(o.deleted).toBeTruthy() if (o != null) {
expect(o.deleted).toBeTruthy()
}
} }
} }
}) })
@ -139,7 +141,8 @@ async function createUsers(self, numberOfUsers) {//eslint-disable-line
for (var i = 0; i < numberOfUsers; i++) { for (var i = 0; i < numberOfUsers; i++) {
promises.push(Y({ promises.push(Y({
db: { db: {
name: 'Memory' name: 'Memory',
gcTimeout: -1
}, },
connector: { connector: {
name: 'Test', name: 'Test',

View File

@ -39,7 +39,7 @@ class AbstractTransaction { // eslint-disable-line no-unused-vars
} }
class AbstractOperationStore { // eslint-disable-line no-unused-vars class AbstractOperationStore { // eslint-disable-line no-unused-vars
constructor (y) { constructor (y, opts) {
this.y = y this.y = y
// E.g. this.listenersById[id] : Array<Listener> // E.g. this.listenersById[id] : Array<Listener>
this.listenersById = {} this.listenersById = {}
@ -62,6 +62,44 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
this.initializedTypes = {} this.initializedTypes = {}
this.whenUserIdSetListener = null this.whenUserIdSetListener = null
this.waitingOperations = new RBTree() this.waitingOperations = new RBTree()
this.gc1 = [] // first stage
this.gc2 = [] // second stage -> after that, kill it
this.gcTimeout = opts.gcTimeout || 5000
var os = this
function garbageCollect () {
os.requestTransaction(function * () {
for (var i in os.gc2) {
var oid = os.gc2[i]
var o = yield* this.getOperation(oid)
if (o.left != null) {
var left = yield* this.getOperation(o.left)
left.right = o.right
}
if (o.right != null) {
var right = yield* this.getOperation(o.right)
right.left = o.left
}
yield* this.removeOperation(o.id)
}
os.gc2 = os.gc1
os.gc1 = []
if (os.gcTimeout > 0) {
os.gcInterval = setTimeout(garbageCollect, os.gcTimeout)
}
})
}
this.garbageCollect = garbageCollect
if (this.gcTimeout > 0) {
garbageCollect()
}
}
addToGarbageCollector (op) {
this.gc1.push(op)
}
destroy () {
clearInterval(this.gcInterval)
this.gcInterval = null
} }
setUserId (userId) { setUserId (userId) {
this.userId = userId this.userId = userId
@ -201,7 +239,7 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
} }
// notify parent, if it has been initialized as a custom type // notify parent, if it has been initialized as a custom type
var t = this.initializedTypes[JSON.stringify(op.parent)] var t = this.initializedTypes[JSON.stringify(op.parent)]
if (t != null) { if (t != null && !op.deleted) {
yield* t._changed(transaction, copyObject(op)) yield* t._changed(transaction, copyObject(op))
} }
} }

View File

@ -81,7 +81,7 @@ Y.IndexedDB = (function () { // eslint-disable-line
} }
class OperationStore extends AbstractOperationStore { // eslint-disable-line no-undef class OperationStore extends AbstractOperationStore { // eslint-disable-line no-undef
constructor (y, opts) { constructor (y, opts) {
super(y) super(y, opts)
if (opts == null) { if (opts == null) {
opts = {} opts = {}
} }

View File

@ -6,7 +6,7 @@ if (typeof window !== 'undefined') {
describe('IndexedDB', function () { describe('IndexedDB', function () {
var ob var ob
beforeAll(function () { beforeAll(function () {
ob = new Y.IndexedDB(null, {namespace: 'Test'}) ob = new Y.IndexedDB(null, {namespace: 'Test', gcTimeout: -1})
}) })
it('can add and get operation', function (done) { it('can add and get operation', function (done) {

View File

@ -238,8 +238,8 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
} }
} }
class OperationStore extends AbstractOperationStore { // eslint-disable-line no-undef class OperationStore extends AbstractOperationStore { // eslint-disable-line no-undef
constructor (y) { constructor (y, opts) {
super(y) super(y, opts)
this.os = new RBTree() this.os = new RBTree()
this.ss = {} this.ss = {}
this.waitingTransactions = [] this.waitingTransactions = []
@ -249,10 +249,10 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
logTable () { logTable () {
this.os.logTable() this.os.logTable()
} }
requestTransaction (_makeGen) { requestTransaction (_makeGen, requestNow = false) {
if (!this.transactionInProgress) { if (!this.transactionInProgress) {
this.transactionInProgress = true this.transactionInProgress = true
setTimeout(() => { var transact = () => {
var makeGen = _makeGen var makeGen = _makeGen
while (makeGen != null) { while (makeGen != null) {
var t = new Transaction(this) var t = new Transaction(this)
@ -268,12 +268,18 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
makeGen = this.waitingTransactions.shift() makeGen = this.waitingTransactions.shift()
} }
this.transactionInProgress = false this.transactionInProgress = false
}, 0) }
if (!requestNow) {
setTimeout(transact, 0)
} else {
transact()
}
} else { } else {
this.waitingTransactions.push(_makeGen) this.waitingTransactions.push(_makeGen)
} }
} }
* removeDatabase () { // eslint-disable-line * destroy () { // eslint-disable-line
super.destroy()
delete this.os delete this.os
} }
} }

View File

@ -31,6 +31,18 @@ var Struct = {
var target = yield* this.getOperation(op.target) var target = yield* this.getOperation(op.target)
if (!target.deleted) { if (!target.deleted) {
target.deleted = true target.deleted = true
if (target.left !== null && (yield* this.getOperation(target.left)).deleted) {
this.store.addToGarbageCollector(target.id)
target.gc = true
}
if (target.right !== null) {
var right = yield* this.getOperation(target.right)
if (right.deleted && right.gc == null) {
this.store.addToGarbageCollector(right.id)
right.gc = true
yield* this.setOperation(right)
}
}
yield* this.setOperation(target) yield* this.setOperation(target)
this.ds.delete(target.id) this.ds.delete(target.id)
var t = this.store.initializedTypes[JSON.stringify(target.parent)] var t = this.store.initializedTypes[JSON.stringify(target.parent)]

View File

@ -179,6 +179,27 @@ describe('Array Type', function () {
await wait(50) await wait(50)
done() done()
}) })
it('garbage collects', async function (done) {
var l1, l2, l3
l1 = await y1.set('Array', Y.Array)
l1.insert(0, ['x', 'y', 'z'])
await flushAll()
yconfig1.disconnect()
l1.delete(0, 3)
l2 = await y2.get('Array')
await wait()
yconfig1.reconnect()
await wait()
l3 = await y3.get('Array')
await flushAll()
yconfig1.db.garbageCollect()
yconfig1.db.garbageCollect()
yconfig1.db.logTable()
expect(l1.toArray()).toEqual(l2.toArray())
expect(l2.toArray()).toEqual(l3.toArray())
expect(l2.toArray()).toEqual([])
done()
})
}) })
describe(`Random tests`, function () { describe(`Random tests`, function () {
var randomArrayTransactions = [ var randomArrayTransactions = [

View File

@ -34,10 +34,18 @@
if (op.left === null) { if (op.left === null) {
if (op.opContent != null) { if (op.opContent != null) {
delete this.contents[key] delete this.contents[key]
this.opContents[key] = op.opContent if (op.deleted) {
delete this.opContents[key]
} else {
this.opContents[key] = op.opContent
}
} else { } else {
delete this.opContents[key] delete this.opContents[key]
this.contents[key] = op.content if (op.deleted) {
delete this.contents[key]
} else {
this.contents[key] = op.content
}
} }
this.map[key] = op.id this.map[key] = op.id
var insertEvent = { var insertEvent = {
@ -54,11 +62,8 @@
} }
} else if (op.struct === 'Delete') { } else if (op.struct === 'Delete') {
if (compareIds(this.map[key], op.target)) { if (compareIds(this.map[key], op.target)) {
if (this.opContents[key] != null) { delete this.opContents[key]
delete this.opContents[key] delete this.contents[key]
} else {
delete this.contents[key]
}
var deleteEvent = { var deleteEvent = {
name: key, name: key,
object: this, object: this,

View File

@ -40,7 +40,7 @@ class YConfig { // eslint-disable-line no-unused-vars
} }
destroy () { destroy () {
this.connector.disconnect() this.connector.disconnect()
this.db.removeDatabase() this.db.destroy()
this.connector = null this.connector = null
this.db = null this.db = null
this.transact = function () { this.transact = function () {