summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorAaron Bohy <aab@odoo.com>2021-04-15 13:53:13 +0000
committerAaron Bohy <aab@odoo.com>2021-04-23 12:46:12 +0000
commita52aa41d04330efb81090409ec7fbcbbedaca317 (patch)
tree74c20f4ea92141f3203beb325524cb19881627b3 /addons
parent1476c0f78281fb90e76db5eac193466b86e75a25 (diff)
[FIX] bus: prevent longpoll requests storm
Before this commit, it might happen that, in some situations, with several tabs opened, the CrossTabBus called the longpolling route repeatedly, thus slowing down the server, and freezing the webclient. The issue was tricky to reproduce. It was a race-condition that could occur when several tabs performed simultanous calls to addChannel, while being unloaded or becoming mastertab in the meantime (e.g. when opening/closing/refreshing several tabs simultaneously). This issue has been introduced by [1] which by mistake (probably) made each tab calling itself the localStorage to update the list of channels when it was notified that the list of channels in the localStorage just changed. So if several tabs had a slightly different list of channels at a given moment (e.g. at startup), it might happen that they in turn, undo what another tab just put in the localStorage, and thus produced an infinite loop of localStorage writes and longpolling request aborts/calls. The issue could be reproduced with the OCA module [2], which performs several addChannel at webclient startup. This commit restores this part of the code as it was initially written in [3]. Closes #69067 opw~2502799 maybe opw~2451865 as well [1] https://github.com/odoo/odoo/commit/6448420 [2] https://odoo-community.org/shop/product/web-notify-2670#attr=10773 [3] https://github.com/odoo/odoo/commit/38581f67236377daa767ca2216529a26b8708b00#diff-f6eccad21ae3543606ab8f97b8b097d015412caeaee2bf8cc928eb3ccabac9f5R149 closes odoo/odoo#69756 X-original-commit: 93439c75fee5280fbca451b4aa2fcd8c86f8b325 Signed-off-by: Géry Debongnie (ged) <ged@openerp.com> Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
Diffstat (limited to 'addons')
-rw-r--r--addons/bus/static/src/js/crosstab_bus.js4
-rw-r--r--addons/bus/static/tests/bus_tests.js77
2 files changed, 78 insertions, 3 deletions
diff --git a/addons/bus/static/src/js/crosstab_bus.js b/addons/bus/static/src/js/crosstab_bus.js
index fc6a9c3d900..bb2b2a00d50 100644
--- a/addons/bus/static/src/js/crosstab_bus.js
+++ b/addons/bus/static/src/js/crosstab_bus.js
@@ -327,9 +327,7 @@ var CrossTabBus = Longpolling.extend({
}
// update channels
else if (key === this._generateKey('channels')) {
- var channels = value;
- _.each(_.difference(this._channels, channels), this.deleteChannel.bind(this));
- _.each(_.difference(channels, this._channels), this.addChannel.bind(this));
+ this._channels = value;
}
// update options
else if (key === this._generateKey('options')) {
diff --git a/addons/bus/static/tests/bus_tests.js b/addons/bus/static/tests/bus_tests.js
index b13694a7afb..449e3ba2c93 100644
--- a/addons/bus/static/tests/bus_tests.js
+++ b/addons/bus/static/tests/bus_tests.js
@@ -2,6 +2,7 @@ odoo.define('web.bus_tests', function (require) {
"use strict";
var BusService = require('bus.BusService');
+var CrossTabBus = require('bus.CrossTab');
var AbstractStorageService = require('web.AbstractStorageService');
var RamStorage = require('web.RamStorage');
var testUtils = require('web.test_utils');
@@ -310,5 +311,81 @@ QUnit.module('Bus', {
parentMaster.destroy();
parentSlave.destroy();
});
+
+ QUnit.test('two tabs calling addChannel simultaneously', async function (assert) {
+ assert.expect(5);
+
+ let id = 1;
+ testUtils.patch(CrossTabBus, {
+ init: function () {
+ this._super.apply(this, arguments);
+ this.__tabId__ = id++;
+ },
+ addChannel: function (channel) {
+ assert.step('Tab ' + this.__tabId__ + ': addChannel ' + channel);
+ this._super.apply(this, arguments);
+ },
+ deleteChannel: function (channel) {
+ assert.step('Tab ' + this.__tabId__ + ': deleteChannel ' + channel);
+ this._super.apply(this, arguments);
+ },
+ });
+
+ let pollPromise;
+ const parentTab1 = new Widget();
+ await testUtils.addMockEnvironment(parentTab1, {
+ data: {},
+ services: {
+ local_storage: LocalStorageServiceMock,
+ },
+ mockRPC: function (route) {
+ if (route === '/longpolling/poll') {
+ pollPromise = testUtils.makeTestPromise();
+ pollPromise.abort = (function () {
+ this.reject({message: "XmlHttpRequestError abort"}, $.Event());
+ }).bind(pollPromise);
+ return pollPromise;
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+ const parentTab2 = new Widget();
+ await testUtils.addMockEnvironment(parentTab2, {
+ data: {},
+ services: {
+ local_storage: LocalStorageServiceMock,
+ },
+ mockRPC: function (route) {
+ if (route === '/longpolling/poll') {
+ pollPromise = testUtils.makeTestPromise();
+ pollPromise.abort = (function () {
+ this.reject({message: "XmlHttpRequestError abort"}, $.Event());
+ }).bind(pollPromise);
+ return pollPromise;
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ const tab1 = new CrossTabBus(parentTab1);
+ const tab2 = new CrossTabBus(parentTab2);
+
+ tab1.addChannel("alpha");
+ tab2.addChannel("alpha");
+ tab1.addChannel("beta");
+ tab2.addChannel("beta");
+
+ assert.verifySteps([
+ "Tab 1: addChannel alpha",
+ "Tab 2: addChannel alpha",
+ "Tab 1: addChannel beta",
+ "Tab 2: addChannel beta",
+ ]);
+
+ testUtils.unpatch(CrossTabBus);
+ parentTab1.destroy();
+ parentTab2.destroy();
+ });
});
+
});