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'))
})
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, ["test"])
gulp.watch(files.test, ['build'])

View File

@ -99,7 +99,9 @@ async function compareAllUsers(users){//eslint-disable-line
var d = ds[j]
for (var i = 0; i < d.len; 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++) {
promises.push(Y({
db: {
name: 'Memory'
name: 'Memory',
gcTimeout: -1
},
connector: {
name: 'Test',

View File

@ -39,7 +39,7 @@ class AbstractTransaction { // eslint-disable-line no-unused-vars
}
class AbstractOperationStore { // eslint-disable-line no-unused-vars
constructor (y) {
constructor (y, opts) {
this.y = y
// E.g. this.listenersById[id] : Array<Listener>
this.listenersById = {}
@ -62,6 +62,44 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
this.initializedTypes = {}
this.whenUserIdSetListener = null
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) {
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
var t = this.initializedTypes[JSON.stringify(op.parent)]
if (t != null) {
if (t != null && !op.deleted) {
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
constructor (y, opts) {
super(y)
super(y, opts)
if (opts == null) {
opts = {}
}

View File

@ -6,7 +6,7 @@ if (typeof window !== 'undefined') {
describe('IndexedDB', function () {
var ob
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) {

View File

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

View File

@ -31,6 +31,18 @@ var Struct = {
var target = yield* this.getOperation(op.target)
if (!target.deleted) {
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)
this.ds.delete(target.id)
var t = this.store.initializedTypes[JSON.stringify(target.parent)]

View File

@ -179,6 +179,27 @@ describe('Array Type', function () {
await wait(50)
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 () {
var randomArrayTransactions = [

View File

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

View File

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