checking out new gc approach

This commit is contained in:
Kevin Jahns 2015-09-25 16:00:20 +02:00
parent 5e4c56af29
commit 183f30878e
8 changed files with 114 additions and 58 deletions

View File

@ -22,6 +22,7 @@ class AbstractConnector {
}
this.role = opts.role
this.connections = {}
this.isSynced = false
this.userEventListeners = []
this.whenSyncedListeners = []
this.currentSyncTarget = null
@ -97,7 +98,7 @@ class AbstractConnector {
true otherwise
*/
findNextSyncTarget () {
if (this.currentSyncTarget != null) {
if (this.currentSyncTarget != null || this.isSynced) {
return // "The current sync has not finished!"
}
@ -118,9 +119,7 @@ class AbstractConnector {
deleteSet: yield* this.getDeleteSet()
})
})
}
// This user synced with at least one user, set the state to synced (TODO: does this suffice?)
if (!this.isSynced) {
} else {
this.isSynced = true
for (var f of this.whenSyncedListeners) {
f()
@ -141,26 +140,31 @@ class AbstractConnector {
return
}
if (this.debug) {
console.log(`${sender} -> me: ${m.type}`, m);// eslint-disable-line
console.log(`${sender} -> ${this.userId}: ${m.type}`, m);// eslint-disable-line
}
if (m.type === 'sync step 1') {
// TODO: make transaction, stream the ops
let conn = this
this.y.db.requestTransaction(function *() {
var ops = yield* this.getOperations(m.stateSet)
var currentStateSet = yield* this.getStateSet()
var dels = yield* this.getOpsFromDeleteSet(m.deleteSet)
for (var d in dels) {
yield* Y.Struct.Delete.delete.call(this, dels[d].target)
}
if (dels.length > 0) {
this.store.apply(dels)
// broadcast missing dels from syncing client
/* TODO: solve this better?
this.store.y.connector.broadcast({
type: 'update',
ops: dels
})
*/
}
var ops = yield* this.getOperations(m.stateSet)
conn.send(sender, {
type: 'sync step 2',
os: ops,
stateSet: yield* this.getStateSet(),
stateSet: currentStateSet,
deleteSet: yield* this.getDeleteSet()
})
if (this.forwardToSyncingClients) {
@ -185,16 +189,18 @@ class AbstractConnector {
var broadcastHB = !this.broadcastedHB
this.broadcastedHB = true
this.y.db.requestTransaction(function *() {
var ops = yield* this.getOperations(m.stateSet)
var dels = yield* this.getOpsFromDeleteSet(m.deleteSet)
this.store.apply(dels)
for (var d in dels) {
yield* Y.Struct.Delete.delete.call(this, dels[d].target)
}
var ops = yield* this.getOperations(m.stateSet)
this.store.apply(m.os)
if (ops.length > 0) {
m = {
type: 'update',
ops: ops
}
if (!broadcastHB) {
if (!broadcastHB || true) { // TODO: no broadcast?
conn.send(sender, m)
} else {
// broadcast only once!

View File

@ -71,8 +71,8 @@ g.applyRandomTransactions = async(function * applyRandomTransactions (users, obj
var f = getRandom(transactions)
f(root)
}
function applyTransactions () {
for (var i = 0; i < numberOfTransactions / 2 + 1; i++) {
function applyTransactions (relAmount) {
for (var i = 0; i < numberOfTransactions * relAmount + 1; i++) {
var r = Math.random()
if (r >= 0.9) {
// 10% chance to flush
@ -83,16 +83,16 @@ g.applyRandomTransactions = async(function * applyRandomTransactions (users, obj
wait()
}
}
applyTransactions()
applyTransactions()
/* TODO: call applyTransactions here..
applyTransactions(0.5)
yield users[0].connector.flushAll()
yield users[0].connector.flushAll()
users[0].disconnect()
yield wait()
applyTransactions()
applyTransactions(0.5)
yield users[0].connector.flushAll()
// TODO: gc here????
yield wait(100)
users[0].reconnect()
*/
yield wait()
yield users[0].connector.flushAll()
})
@ -104,7 +104,7 @@ g.garbageCollectAllUsers = async(function * garbageCollectAllUsers (users) {
}
})
g.compareAllUsers = async(function * compareAllUsers (users) { //eslint-disable-line
g.compareAllUsers = async(function * compareAllUsers (users) {
var s1, s2 // state sets
var ds1, ds2 // delete sets
var allDels1, allDels2 // all deletions
@ -224,14 +224,12 @@ function async (makeGenerator) {
return handle(generator.throw(err))
})
}
// this may throw errors here, but its ok since this is used only for debugging
return handle(generator.next())
/* try {
try {
return handle(generator.next())
} catch (ex) {
generator.throw(ex) // TODO: check this out
// return Promise.reject(ex)
}*/
}
}
}
g.async = async

View File

@ -122,7 +122,7 @@ Y.AbstractTransaction = AbstractTransaction
* destroy()
- destroy the database
*/
class AbstractOperationStore { // eslint-disable-line no-unused-vars
class AbstractOperationStore {
constructor (y, opts) {
this.y = y
// E.g. this.listenersById[id] : Array<Listener>
@ -148,7 +148,7 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
this.waitingOperations = new Y.utils.RBTree()
this.gc1 = [] // first stage
this.gc2 = [] // second stage -> after that, kill it
this.gc2 = [] // second stage -> after that, remove the op
this.gcTimeout = opts.gcTimeout || 5000
var os = this
function garbageCollect () {
@ -197,10 +197,35 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
garbageCollect()
}
}
addToGarbageCollector (op) {
if (op.gc == null) {
/*
Try to add to GC.
TODO: rename this function
Only gc when
* creator of op is online
* left & right defined and both are from the same creator as op
returns true iff op was added to GC
*/
addToGarbageCollector (op, left, right) {
if (
op.gc == null &&
op.deleted === true &&
this.y.connector.isSynced &&
(this.y.connector.connections[op.id[0]] != null || op.id[0] === this.y.connector.userId) &&
left != null &&
right != null &&
left.deleted &&
right.deleted &&
left.id[0] === op.id[0] &&
right.id[0] === op.id[0]
) {
op.gc = true
this.gc1.push(op.id)
return true
} else {
return false
}
}
removeFromGarbageCollector (op) {

View File

@ -175,7 +175,7 @@ Y.Memory = (function () {
return stateVector
}
* getStateSet () {
return this.ss
return Y.utils.copyObject(this.ss)
}
* getOperations (startSS) {
// TODO: use bounds here!
@ -237,7 +237,7 @@ Y.Memory = (function () {
return op
}
}
class OperationStore extends Y.AbstractOperationStore { // eslint-disable-line no-undef
class OperationStore extends Y.AbstractOperationStore {
constructor (y, opts) {
super(y, opts)
this.os = new Y.utils.RBTree()

View File

@ -46,34 +46,51 @@ var Struct = {
*/
delete: function * (targetId) {
var target = yield* this.getOperation(targetId)
if (target != null && !target.deleted) {
target.deleted = true
if (target.left != null && (yield* this.getOperation(target.left)).deleted) {
// left is defined & the left op is already deleted.
// => Then this may get gc'd
this.store.addToGarbageCollector(target)
}
if (target.right != null) {
var right = yield* this.getOperation(target.right)
if (right.deleted && right.gc == null) {
this.store.addToGarbageCollector(right)
yield* this.setOperation(right)
}
}
yield* this.setOperation(target)
var t = this.store.initializedTypes[JSON.stringify(target.parent)]
if (t != null) {
yield* t._changed(this, {
struct: 'Delete',
target: targetId
})
if (target == null || !target.deleted) {
this.ds.delete(targetId)
var state = yield* this.getState(targetId[0])
if (state.clock === targetId[1]) {
yield* this.checkDeleteStoreForState(state)
yield* this.setState(state)
}
}
this.ds.delete(targetId)
var state = yield* this.getState(targetId[0])
if (state.clock === targetId[1]) {
yield* this.checkDeleteStoreForState(state)
yield* this.setState(state)
if (target != null && target.gc == null) {
if (!target.deleted) {
// set deleted & notify type
target.deleted = true
var type = this.store.initializedTypes[JSON.stringify(target.parent)]
if (type != null) {
yield* type._changed(this, {
struct: 'Delete',
target: targetId
})
}
}
var left = target.left != null ? yield* this.getOperation(target.left) : null
var right = target.right != null ? yield* this.getOperation(target.right) : null
this.store.addToGarbageCollector(target, left, right)
// set here because it was deleted and/or gc'd
yield* this.setOperation(target)
if (
left != null &&
left.left != null &&
this.store.addToGarbageCollector(left, yield* this.getOperation(left.left), target)
) {
yield* this.setOperation(left)
}
if (
right != null &&
right.right != null &&
this.store.addToGarbageCollector(right, target, yield* this.getOperation(right.right))
) {
yield* this.setOperation(right)
}
}
},
execute: function * (op) {
@ -215,6 +232,12 @@ var Struct = {
left = yield* this.getOperation(op.left)
op.right = left.right
left.right = op.id
// if left exists, and it is supposed to be gc'd. Remove it from the gc
if (left.gc != null) {
this.store.removeFromGarbageCollector(left)
}
yield* this.setOperation(left)
} else {
op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start
@ -228,7 +251,6 @@ var Struct = {
if (right.gc != null) {
this.store.removeFromGarbageCollector(right)
}
yield* this.setOperation(right)
}

View File

@ -2,7 +2,7 @@
/* eslint-env browser,jasmine */
var numberOfYArrayTests = 100
var repeatArrayTests = 1
var repeatArrayTests = 3
describe('Array Type', function () {
var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll
@ -245,6 +245,8 @@ describe('Array Type', function () {
yield applyRandomTransactions(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests)
yield flushAll()
yield compareArrayValues(this.arrays)
// yield compareAllUsers(this.users)
done()
}))
})

View File

@ -1,7 +1,7 @@
/* global createUsers, Y, compareAllUsers, getRandomNumber, applyRandomTransactions, async, describeManyTimes */
/* eslint-env browser,jasmine */
var numberOfYMapTests = 150
var numberOfYMapTests = 70
var repeatMapTeasts = 1
describe('Map Type', function () {

View File

@ -36,12 +36,15 @@ class YConfig {
this.connector.disconnect()
}
reconnect () {
this.connector.reconnect()
/* TODO: maybe do this..
Promise.all([
this.db.garbageCollect(),
this.db.garbageCollect()
]).then(() => {
this.connector.reconnect()
})
*/
}
destroy () {
this.connector.disconnect()