diff --git a/lib/backend.js b/lib/backend.js index 7a3cd41cc..7676daaa0 100644 --- a/lib/backend.js +++ b/lib/backend.js @@ -57,6 +57,11 @@ function Backend(options) { function(error, context) { logger.error(error); }; + + var backend = this; + this._dbGetOps = util.deduplicateRequests(function() { + backend.db.getOps.apply(backend.db, arguments); + }); } module.exports = Backend; emitter.mixin(Backend); @@ -324,7 +329,7 @@ Backend.prototype._getSanitizedOps = function(agent, projection, collection, id, var backend = this; if (!opsOptions) opsOptions = {}; if (agent) opsOptions.agentCustom = agent.custom; - backend.db.getOps(collection, id, from, to, opsOptions, function(err, ops) { + this._dbGetOps(collection, id, from, to, opsOptions, function(err, ops) { if (err) return callback(err); backend._sanitizeOps(agent, projection, collection, id, ops, function(err) { if (err) return callback(err); diff --git a/lib/util.js b/lib/util.js index 0d613ed58..f5ad1889f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -98,6 +98,32 @@ exports.clone = function(obj) { return (obj === undefined) ? undefined : JSON.parse(JSON.stringify(obj)); }; +exports.deduplicateRequests = function(fn) { + var callbacksByArgs = {}; + return function() { + var args = Array.prototype.slice.call(arguments); + var callback = args.pop(); + var argString = JSON.stringify(args); + + var callbacks = exports.digOrCreate(callbacksByArgs, argString, function() { + return []; + }); + callbacks.push(callback); + + if (callbacks.length > 1) return; + + args.push(function() { + while (callbacks.length) { + var cb = callbacks.shift(); + cb.apply(null, arguments); + } + delete callbacksByArgs[argString]; + }); + + fn.apply(null, args); + }; +}; + var objectProtoPropNames = Object.create(null); Object.getOwnPropertyNames(Object.prototype).forEach(function(prop) { if (prop !== '__proto__') { diff --git a/test/backend.js b/test/backend.js index e0af12565..883bc4105 100644 --- a/test/backend.js +++ b/test/backend.js @@ -92,6 +92,23 @@ describe('Backend', function() { done(); }); }); + + it('deduplicates concurrent requests', function(done) { + var getOps = sinon.spy(backend.db, 'getOps'); + var count = 0; + var callback = function(error, ops) { + if (error) return done(error); + expect(ops).to.have.length(2); + expect(ops[0].create.data).to.eql({title: '1984'}); + expect(ops[1].op).to.eql([{p: ['author'], oi: 'George Orwell'}]); + count++; + expect(getOps).to.have.been.calledOnce; + if (count === 2) done(); + }; + + backend.getOps(agent, 'books', '1984', 0, null, callback); + backend.getOps(agent, 'books', '1984', 0, null, callback); + }); }); describe('getOpsBulk', function() {