diff --git a/addons/web/static/src/js/core/concurrency.js b/addons/web/static/src/js/core/concurrency.js index 20247d1c483..6eeebe7ff58 100644 --- a/addons/web/static/src/js/core/concurrency.js +++ b/addons/web/static/src/js/core/concurrency.js @@ -201,6 +201,7 @@ return { Mutex: Class.extend({ init: function () { this.def = $.Deferred().resolve(); + this.unlockedDef = undefined; }, /** * Add a computation to the queue, it will be executed as soon as the @@ -210,14 +211,27 @@ return { * @returns {Deferred} */ exec: function (action) { + var self = this; var current = this.def; var next = this.def = $.Deferred(); + this.unlockedDef = this.unlockedDef || $.Deferred(); return current.then(function() { - return $.when(action()).always(function() { + return $.when(action()).always(function () { next.resolve(); + if (self.def.state() === 'resolved' && self.unlockedDef) { + self.unlockedDef.resolve(); + self.unlockedDef = undefined; + } }); }); - } + }, + /** + * @returns {Promise} resolved as soon as the Mutex is unlocked + * (directly if it is currently idle) + */ + getUnlockedDef: function () { + return $.when(this.unlockedDef); + }, }), /** * A MutexedDropPrevious is a primitive for serializing computations while diff --git a/addons/web/static/tests/core/concurrency_tests.js b/addons/web/static/tests/core/concurrency_tests.js index 5b1e4c505ac..516ce6a1b8f 100644 --- a/addons/web/static/tests/core/concurrency_tests.js +++ b/addons/web/static/tests/core/concurrency_tests.js @@ -87,6 +87,58 @@ QUnit.module('core', {}, function () { assert.strictEqual(p3.state(), "resolved"); }); + QUnit.test('mutex: getUnlockedDef checks', function (assert) { + assert.expect(5); + + var m = new concurrency.Mutex(); + + var def1 = $.Deferred(); + var def2 = $.Deferred(); + + assert.strictEqual(m.getUnlockedDef().state(), "resolved"); + + m.exec(function() { return def1; }); + + var unlockedDef = m.getUnlockedDef(); + + assert.strictEqual(unlockedDef.state(), "pending"); + + m.exec(function() { return def2; }); + + assert.strictEqual(unlockedDef.state(), "pending"); + + def1.resolve(); + + assert.strictEqual(unlockedDef.state(), "pending"); + + def2.resolve(); + + assert.strictEqual(unlockedDef.state(), "resolved"); + }); + + QUnit.test('DropPrevious: basic usecase', function (assert) { + assert.expect(5); + + var dp = new concurrency.DropPrevious(); + + var def1 = $.Deferred(); + var def2 = $.Deferred(); + + var r1 = dp.add(def1); + + assert.strictEqual(r1.state(), "pending"); + + var r2 = dp.add(def2); + + assert.strictEqual(r1.state(), "rejected"); + assert.strictEqual(r2.state(), "pending"); + + def2.resolve(); + + assert.strictEqual(r1.state(), "rejected"); + assert.strictEqual(r2.state(), "resolved"); + }); + QUnit.test('DropMisordered: resolve all correctly ordered, sync', function (assert) { assert.expect(1);