From 14d59de2bddb19854c0bece81301120afb36f6a8 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Sat, 7 May 2016 13:16:48 +0100 Subject: [PATCH] improved awaitOps --- src/Database.js | 9 +---- src/SpecHelper.js | 19 ++++++++-- src/Utils.js | 95 +++++++++++++++++++++++++++++++++++------------ 3 files changed, 90 insertions(+), 33 deletions(-) diff --git a/src/Database.js b/src/Database.js index 7810c178..6b9ece78 100644 --- a/src/Database.js +++ b/src/Database.js @@ -479,14 +479,9 @@ module.exports = function (Y /* :any */) { this.waitingTransactions.push(makeGen) if (!this.transactionInProgress) { this.transactionInProgress = true - if (false || callImmediately) { // TODO: decide whether this is ok or not.. + setTimeout(() => { this.transact(this.getNextRequest()) - } else { - var self = this - setTimeout(function () { - self.transact(self.getNextRequest()) - }, 0) - } + }, 0) } } } diff --git a/src/SpecHelper.js b/src/SpecHelper.js index 47ce153f..99276089 100644 --- a/src/SpecHelper.js +++ b/src/SpecHelper.js @@ -88,8 +88,21 @@ g.getRandomString = getRandomString function * applyTransactions (relAmount, numberOfTransactions, objects, users, transactions, noReconnect) { for (var i = 0; i < numberOfTransactions * relAmount + 1; i++) { var r = Math.random() - if (r >= 0.5) { - // 50% chance to flush + if (r > 0.9) { + // 10% chance of toggling concurrent user interactions. + // There will be an artificial delay until ops can be executed by the type, + // therefore, operations of the database will be (pre)transformed until user operations arrive + yield (function simulateConcurrentUserInteractions (type) { + if (type.eventHandler.awaiting === 0 && type.eventHandler._debuggingAwaiting !== true) { + type.eventHandler.awaiting = 1 + type.eventHandler._debuggingAwaiting = true + } else { + // fixAwaitingInType will handle _debuggingAwaiting + return fixAwaitingInType(type) + } + })(getRandom(objects)) + } else if (r >= 0.5) { + // 40% chance to flush yield Y.utils.globalRoom.flushOne() // flushes for some user.. (not necessarily 0) } else if (noReconnect || r >= 0.05) { // 45% chance to create operation @@ -121,7 +134,7 @@ function fixAwaitingInType (type) { type.os.requestTransaction(function * () { if (type.eventHandler.awaiting > 0 && type.eventHandler._debuggingAwaiting === true) { type.eventHandler._debuggingAwaiting = false - yield* type.eventHandler.awaitedOps(this, 0) + yield* type.eventHandler.awaitOps(this, function * () { /* mock function */ }) } wait(50).then(type.os.whenTransactionsFinished()).then(wait(50)).then(resolve) }) diff --git a/src/Utils.js b/src/Utils.js index 6214fcb5..b8cf7a19 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -105,35 +105,84 @@ module.exports = function (Y /* : any*/) { this.awaiting++ ops.map(Y.utils.copyOperation).forEach(this.onevent) } - * awaitedOps (transaction, n) { - // remove awaited ops - this.waiting.splice(this.waiting.length - n) - // update all waiting ops - for (let i = 0; i < this.waiting.length; i++) { - var o = this.waiting[i] - if (o.struct === 'Insert') { - var _o = yield* transaction.getInsertion(o.id) - if (!Y.utils.compareIds(_o.id, o.id)) { - // o got extended - o.left = [o.id[0], o.id[1] - 1] - } else if (_o.left == null) { - o.left = null - } else { - // find next undeleted op - var left = yield* transaction.getInsertion(_o.left) - while (left.deleted != null) { - if (left.left != null) { - left = yield* transaction.getInsertion(left.left) - } else { - left = null + * awaitOps (transaction, f, args) { + function notSoSmartSort (array) { + // this function sorts insertions in a executable order + var result = [] + while (array.length > 0) { + for (var i = 0; i < array.length; i++) { + var independent = true + for (var j = 0; j < array.length; j++) { + if (Y.utils.matchesId(array[j], array[i].left)) { + // array[i] depends on array[j] + independent = false break } } - o.left = left != null ? Y.utils.getLastId(left) : null + if (independent) { + result.push(array.splice(i, 1)[0]) + i-- + } } } + return result + } + var before = this.waiting.length + // somehow create new operations + yield* f.apply(transaction, args) + // remove all appended ops / awaited ops + this.waiting.splice(before) + if (this.awaiting > 0) this.awaiting-- + // if there are no awaited ops anymore, we can update all waiting ops, and send execute them (if there are still no awaited ops) + if (this.awaiting === 0 && this.waiting.length > 0) { + // update all waiting ops + for (let i = 0; i < this.waiting.length; i++) { + var o = this.waiting[i] + if (o.struct === 'Insert') { + var _o = yield* transaction.getInsertion(o.id) + if (!Y.utils.compareIds(_o.id, o.id)) { + // o got extended + o.left = [o.id[0], o.id[1] - 1] + } else if (_o.left == null) { + o.left = null + } else { + // find next undeleted op + var left = yield* transaction.getInsertion(_o.left) + while (left.deleted != null) { + if (left.left != null) { + left = yield* transaction.getInsertion(left.left) + } else { + left = null + break + } + } + o.left = left != null ? Y.utils.getLastId(left) : null + } + } + } + // the previous stuff was async, so we have to check again! + // We also pull changes from the bindings, if there exists such a method, this could increase awaiting too + if (this._pullChanges != null) { + this._pullChanges() + } + if (this.awaiting === 0) { + // sort by type, execute inserts first + var ins = [] + var dels = [] + this.waiting.forEach(function (o) { + if (o.struct === 'Delete') { + dels.push(o) + } else { + ins.push(o) + } + }) + // put in executable order + ins = notSoSmartSort(ins) + ins.forEach(this.onevent) + dels.forEach(this.onevent) + this.waiting = [] + } } - this._tryCallEvents() } // TODO: Remove awaitedInserts and awaitedDeletes in favor of awaitedOps, as they are deprecated and do not always work // Do this in one of the coming releases that are breaking anyway