fix #4421: ui: guard setProxy against races of slow vs fast requests

Some UI components use `Ext.data.Store.setProxy` to change their
associated API endpoint URL in reaction to user input. One example is
`BackupView`, which calls `setProxy` when the user switches from
listing backups on storage A to listing backups on storage B. However,
if A is slow, the UI may receive the response for A *after* the
response for B. It will then display the contents of A as if they were
the contents of B, resulting in a UI inconsistency.

The reason is that `Ext.data.Store` still processes the slow response
for A, even though it is obsolete. This patch overrides the
responsible callback of `Ext.data.Store` to only process responses
belonging to the currently active proxy object. This should rule out
similar race conditions in all components that use the `setProxy` API.
In the above example, the patch results in the response for A being
ignored.

Ignored responses are logged to the browser console.

Note that this patch only concerns components that use `setProxy` for
changing API endpoints. Other components (e.g. those using
`proxy.setURL` for the same purpose) may be open to similar race
conditions.

Link: https://lists.proxmox.com/pipermail/pve-devel/2023-March/056062.html
Signed-off-by: Friedrich Weber <f.weber@proxmox.com>
This commit is contained in:
Friedrich Weber 2023-03-06 15:03:14 +01:00 committed by Thomas Lamprecht
parent c16785f66a
commit 51083ee54a

View File

@ -1451,3 +1451,18 @@ Ext.define('Proxmox.Async', {
return new Promise((resolve, _reject) => setTimeout(resolve, millis));
},
});
Ext.override(Ext.data.Store, {
// If the store's proxy is changed while it is waiting for an AJAX
// response, `onProxyLoad` will still be called for the outdated response.
// To avoid displaying inconsistent information, only process responses
// belonging to the current proxy.
onProxyLoad: function(operation) {
let me = this;
if (operation.getProxy() === me.getProxy()) {
me.callParent(arguments);
} else {
console.log(`ignored outdated response: ${operation.getRequest().getUrl()}`);
}
},
});