FLUID-6588: onResourcesLoaded event can fire before createOnEvent component can register its listener

Metadata

Source
FLUID-6588
Type
Bug
Priority
Major
Status
Reopened
Resolution
N/A
Assignee
Antranig Basman
Reporter
Antranig Basman
Created
2021-01-11T09:06:55.951-0500
Updated
2024-07-17T08:12:34.351-0400
Versions
N/A
Fixed Versions
N/A
Component
  1. Framework

Description

This bug affects all fairly recent versions of the framework (from the latter half of 2020 or so). In some situation, possibly when I/O resolves synchronously as a result of being cached by the browser from an earlier request, the onResourcesLoaded event of a ResourceLoader component can fire before fluid.bindDeferredComponent via fluid.concludeComponentObservation can register its listener. The following trace was obtained from Tony Atkins [RtF] recent update of fluid-handlebars which is in progress at https://github.com/fluid-project/fluid-handlebars/pull/38

bindDeferred instantiation for main of parent component { typeName: "fluid.tests.templateAware.testEnvironment id: 7oh44guo-77" gradeNames: ["fluid.test.testEnvironment","fluid.tests.templateAware.testEnvironment"]} at path testEnvironment-7oh44guo-77 for event {that}.events.createFixtures
testem.js:967 bindDeferred instantiation for contained of parent component { typeName: "fluid.tests.templateAware.testEnvironment id: 7oh44guo-77" gradeNames: ["fluid.test.testEnvironment","fluid.tests.templateAware.testEnvironment"]} at path testEnvironment-7oh44guo-77 for event {that}.events.createFixtures
testem.js:967 

firing onResourcesLoaded for component { typeName: "fluid.tests.templateAware.serverResourceAware id: 7oh44guo-124" gradeNames: ["fluid.resourceLoader","fluid.handlebars.serverResourceAware","fluid.binder","fluid.binder.bindOnDomChange","fluid.baseViewComponent","fluid.handlebars.templateAware","fluid.handlebars.templateAware.serverResourceAware","fluid.tests.handlebars.templateAware.serverResourceAware","fluid.tests.templateAware.serverResourceAware"]} at path testEnvironment-7oh44guo-77.main
testem.js:967 

Added bindDeferred listener to event {fluid.handlebars.serverResourceAware}.events.onResourcesLoaded of component { typeName: "fluid.tests.templateAware.contained id: 7oh44guo-125" gradeNames: ["fluid.resourceLoader","fluid.handlebars.serverResourceAware","fluid.binder","fluid.binder.bindOnDomChange","fluid.baseViewComponent","fluid.handlebars.templateAware","fluid.handlebars.templateAware.serverResourceAware","fluid.tests.handlebars.templateAware.serverResourceAware","fluid.tests.templateAware.serverResourceAware","fluid.tests.templateAware.contained"]} at path testEnvironment-7oh44guo-77.contained:  
fluid.event.firer
testem.js:967 

Added bindDeferred listener to event {fluid.handlebars.serverResourceAware}.events.onResourcesLoaded of component { typeName: "fluid.tests.templateAware.serverResourceAware id: 7oh44guo-124" gradeNames: ["fluid.resourceLoader","fluid.handlebars.serverResourceAware","fluid.binder","fluid.binder.bindOnDomChange","fluid.baseViewComponent","fluid.handlebars.templateAware","fluid.handlebars.templateAware.serverResourceAware","fluid.tests.handlebars.templateAware.serverResourceAware","fluid.tests.templateAware.serverResourceAware"]} at path testEnvironment-7oh44guo-77.main:  
fluid.event.firer
testem.js:967 

firing onResourcesLoaded for component { typeName: "fluid.tests.templateAware.contained id: 7oh44guo-125" gradeNames: ["fluid.resourceLoader","fluid.handlebars.serverResourceAware","fluid.binder","fluid.binder.bindOnDomChange","fluid.baseViewComponent","fluid.handlebars.templateAware","fluid.handlebars.templateAware.serverResourceAware","fluid.tests.handlebars.templateAware.serverResourceAware","fluid.tests.templateAware.serverResourceAware","fluid.tests.templateAware.contained"]} at path testEnvironment-7oh44guo-77.contained
testem.js:967 

bindDeferred instantiation for renderer of parent component { typeName: "fluid.tests.templateAware.contained id: 7oh44guo-125" gradeNames: ["fluid.resourceLoader","fluid.handlebars.serverResourceAware","fluid.binder","fluid.binder.bindOnDomChange","fluid.baseViewComponent","fluid.handlebars.templateAware","fluid.handlebars.templateAware.serverResourceAware","fluid.tests.handlebars.templateAware.serverResourceAware","fluid.tests.templateAware.serverResourceAware","fluid.tests.templateAware.contained"]} at path testEnvironment-7oh44guo-77.contained for event {fluid.handlebars.serverResourceAware}.events.onResourcesLoaded
testem.js:967 

containedRendered event triggered.

This should be impossible because of the rather conservative explicit deferral in fluid.fetchResources.checkCompletion

fluid.fetchResources.checkCompletion = function (resourceSpecs, resourceFetcher) {
        var incomplete = fluid.find_if(resourceSpecs, function (resourceSpec) {
            return !resourceSpec.promise.disposition;
        });
        if (!incomplete) {
            // Close over this since it might get re-initialised
            var completionPromise = resourceFetcher.completionPromise;
            // Always defer notification in an anti-Zalgo scheme to ease problems like FLUID-6202
            fluid.invokeLater(function () {
                if (!completionPromise.disposition) {
                    completionPromise.resolve(resourceSpecs);
                }
            });
        }
    };

but in any case we observe that concludeComponentObservation is probably an unhelpfully late time to bind these listeners - in the modern framework there are all kinds of events that can fire during construction.

Comments

  • Antranig Basman commented 2021-01-14T16:10:48.888-0500

    Diagnosis: The headline described situation is indeed the cause. The missing pieces of the puzzle are -
    i) The serverTemplateAware component issued bindings directly from its model into the resources area, which unwittingly enlisted itself into the "fast path" of resource fetching (a similar kind of "future punning" as when the Astea team managed to source a collection of dynamicComponents from a model field)
    ii) This had the effect of initiating the resource fetch extremely early and enlisting the entire set of co-instantiating components into a model transaction. However,
    iii) The timing of the onResourcesLoaded event was left as direct - regardless of the condition of the overall transaction, the event fired the moment that all the resources for any one component were complete. As a result, this then fired the event that was listened to by createOnEvent, which was not listened to since the component had not yet reached concludeComponentObservation.

    This was an unfortunate interaction between "old world" and "new world" semantics that is sort of unavoidable, and we will have to imagine that such "old world" code will cease to exist. In the meantime, faced with the choice of allowing createOnEvent components before onCreate, and special-casing this firing, we choose the latter - since a user who is part of "old world" semantics is always going to be surprised by trying to react to or trigger a component construction when the surrounding component tree is partially constructed.