[FIX] context: solve tricky concurrency issue

part of #330
This commit is contained in:
Géry Debongnie
2019-10-25 13:23:49 +02:00
parent 3c38bbc076
commit 690d8edf67
2 changed files with 25 additions and 10 deletions
+22 -7
View File
@@ -15,7 +15,7 @@ import { onWillUnmount } from "./hooks";
export class Context extends EventBus { export class Context extends EventBus {
state: any; state: any;
observer: Observer; observer: Observer;
id: number = 1; rev: number = 1;
// mapping from component id to last observed context id // mapping from component id to last observed context id
mapping: { [componentId: number]: number } = {}; mapping: { [componentId: number]: number } = {};
@@ -46,13 +46,13 @@ export class Context extends EventBus {
* with the same depth in parallel. * with the same depth in parallel.
*/ */
async __notifyComponents() { async __notifyComponents() {
const id = ++this.id; const rev = ++this.rev;
const subs = this.subscriptions.update || []; const subs = this.subscriptions.update || [];
for (let i = 0, iLen = subs.length; i < iLen; i++) { for (let i = 0, iLen = subs.length; i < iLen; i++) {
const sub = subs[i]; const sub = subs[i];
const shouldCallback = sub.owner ? sub.owner.__owl__.isMounted : true; const shouldCallback = sub.owner ? sub.owner.__owl__.isMounted : true;
if (shouldCallback) { if (shouldCallback) {
const render = sub.callback.call(sub.owner, id); const render = sub.callback.call(sub.owner, rev);
scheduler.flush(); scheduler.flush();
await render; await render;
} }
@@ -76,15 +76,30 @@ export function useContextWithCB(ctx: Context, component: Component<any, any>, m
if (id in mapping) { if (id in mapping) {
return ctx.state; return ctx.state;
} }
if (!__owl__.observer) {
__owl__.observer = new Observer();
__owl__.observer.notifyCB = component.render.bind(component);
}
const currentCB = __owl__.observer.notifyCB;
__owl__.observer.notifyCB = function () {
if (ctx.rev > mapping[id]) {
// in this case, the context has been updated since we were rendering
// last, and we do not need to render here with the observer. A
// rendering is coming anyway, with the correct props.
return;
}
currentCB();
}
mapping[id] = 0; mapping[id] = 0;
const renderFn = __owl__.renderFn; const renderFn = __owl__.renderFn;
__owl__.renderFn = function(comp, params) { __owl__.renderFn = function(comp, params) {
mapping[id] = ctx.id; mapping[id] = ctx.rev;
return renderFn(comp, params); return renderFn(comp, params);
}; };
ctx.on("update", component, async contextId => { ctx.on("update", component, async contextRev => {
if (mapping[id] < contextId) { if (mapping[id] < contextRev) {
mapping[id] = contextId; mapping[id] = contextRev;
await method(); await method();
} }
}); });
+3 -3
View File
@@ -174,7 +174,7 @@ describe("Context", () => {
expect(testContext.subscriptions.update.length).toBe(0); expect(testContext.subscriptions.update.length).toBe(0);
}); });
test.skip("concurrent renderings", async () => { test("concurrent renderings", async () => {
const testContext = new Context({ x: { n: 1 }, key: "x" }); const testContext = new Context({ x: { n: 1 }, key: "x" });
const def = makeDeferred(); const def = makeDeferred();
let stateC; let stateC;
@@ -207,7 +207,7 @@ describe("Context", () => {
expect(fixture.innerHTML).toBe("<div><p><span>1a</span></p></div>"); expect(fixture.innerHTML).toBe("<div><p><span>1a</span></p></div>");
testContext.state.key = "y"; testContext.state.key = "y";
testContext.state.y = 2; testContext.state.y = {n: 2};
delete testContext.state.x; delete testContext.state.x;
await nextTick(); await nextTick();
@@ -219,6 +219,6 @@ describe("Context", () => {
def.resolve(); def.resolve();
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div><p><span>1a</span></p></div>"); expect(fixture.innerHTML).toBe("<div><p><span>2b</span></p></div>");
}); });
}); });