FLUID-4884: Implement integrated and portable system for debugging and browsing framework activities operating on IoC trees

Metadata

Source
FLUID-4884
Type
Improvement
Priority
Major
Status
Open
Resolution
N/A
Assignee
N/A
Reporter
Antranig Basman
Created
2013-01-10T23:12:40.236-0500
Updated
2021-07-29T01:42:33.332-0400
Versions
N/A
Fixed Versions
  1. 6.0
Component
  1. IoC System

Description

The more sophisticated and powerful the IoC framework becomes, the harder it becomes to tell what it is doing. The value proposition behind an increasingly declarative idiom for expressing code clearly points to its tendency to facilitate the creation of an ecosystem of sophisticated browsing, debugging, and authoring tools, which we expect to have their power enhanced by removing the need for them to parse and interpret raw JavaScript code.

However, we actually need to build such a system, and the more work we defer to the framework, the more urgent the need becomes. Some initial description of the work was done by offering it as a 2012 GSoC project, described here - http://wiki.fluidproject.org/display/fluid/Google+Summer+of+Code+2012+with+the+Fluid+Project - but unfortunately this project was not taken up.

With our increased focus on authoring tools over the next few years with various projects (FLOE, Prosperity4all, etc.) we need to start formalising our ideas for design in this area and start work on the various primitives (e.g. browsers for configuration and other material expressed as JSON structures, dedicated graph widgets for visualising code structure) that are required and common across all these authoring scenarios. This JIRA and probably over time some dedicated wiki areas will be used as an area for collecting our design and implementation thoughts for this problem.

Attachments

Comments

  • Antranig Basman commented 2013-01-11T01:43:20.699-0500

    The process of diagnosing FLUID-4879 proves a useful and typical case study for debugging the framework. The user presented with a case of event injection where an instance of an event in a subcomponent appeared to be firing correctly, but what should have been the same instance injected into a supercomponent did not fire. An earlier version of the same situation with the same behaviour could be diagnosed "by eye" to be resulting from overwriting the injected event definition with a conflicting one from other configuration. However, the version held in the FLUID-4879 case couldn't immediately be explained - although given the result of the debugging investigation, a suitably intelligent observer could presumably have deduced it also, since in fact no direct framework bug was uncovered. However, the purpose of the framework is to be transparent and clear, and not require magical intelligence in order to diagnose the results expected from suitable or unsuitable configuration....

    So what do we do in practice, when discovering that the result from some IoC configuration is not what our intuition is telling us it should be? Right now we have little alternative but to debug into the framework code itself. In the FLUID-4879 case, the starting point would be the expression "{comp1}.comp2.events.onReady" held in the top component's entry for the "onReady" event. We "happen to know" that the parsing of this expression will pass through, in the first instance, the "stack fetcher" which today is held in FluidIoC.js, round about line 184. These 3 lines currently read

    184 var fetcher = function(parsed) {
    185 var context = parsed.context;
    186 var foundComponent;

    By line 186 we expect that the "context" has been resolved - and this so happens to be the only line of configuration in the system where the context is equal to "comp1". So we set a conditional breakpoint on line 186 to trap on "context === 'comp1'". This gets us into the right ballpark of timing and relevant logic. We expect no real problems to emerge with the component itself to be resolved, and so we step along to line 219 of this function which contains the fluid.get call

    219 return fluid.get(foundComponent, parsed.path, {strategies: fetchStrategies});

    Verifying that all is ok at ths point, we then proceed to the further thing that we "happen to know", that the crucial framework function which is being exercised is the extremely basic "ginger instantiation" which the current framework guarantees for components like "comp2". That is, seeing that comp2 references a nonexistent property, AND that there is a component definition which matches this path segment, we then proceed to instantiate it immediately. This requires us to know that the "ginger strategy" will be the one that is eventually invoked as part of this fluid.get action, held right now at line 90 of FluidIoc.js. Setting an unconditional breakpoint at line 93 verifies that we then reach this point reasonably directly:

    90 function makeGingerStrategy(instantiator, that, thatStack) {
    91 return function(component, thisSeg) {
    92 var atval = component[thisSeg];
    93 if (atval === undefined) {

    Stepping along, we see that the correct case is triggered, the 3rd branch which reads as follows:

    110 if (fluid.get(component, fluid.path("options", "components", thisSeg, "type"))) {
    111 fluid.initDependent(component, thisSeg);
    112 atval = component[thisSeg];
    113 }

    on line 111 we now expect the early creation of component "comp2" to be triggered by the failed reference of the first path segment of the original expression "{comp1}.comp2.events.onReady"

    Breaking on line 111 we see that the component is indeed constructed correctly, but huge labyrinths lie on both sides for the unwary - stepping IN, the very involved workflow of fluid.initDependent that involves various invocations of the "framework noise" functions such as fluid.tryCatch and fluid.describeActivity which are necessary for various stability and debuggability purposes, and stepping OUT, we find ourselves deep within the logic of fluid.get operating its lists of strategies. A careful investigator would think up a suitable conditional breakpoint to set on line 112 in order to become ensnared - something similar to "thisSeg === 'comp2'" perhaps but something stronger might be required to avoid a false triggering.

    Having verified that the expression is resolved correctly to the 2nd path segment, we then wonder whether it is being delivered correctly to the parent component. This requires further "magical knowledge", in particular that this is done by the function "initEvents" in the core framework, right now at Fluid.js line 975. We have:

    984 event = fluid.event.resolveEvent(that, eventKey, eventSpec);
    as well as
    989 if (event) {
    990 that.events[eventKey] = event;
    991 }

    In practice we debugged into fluid.event.resolveEvent itself, and found that everything was correctly resolved - although there was the alarming phenomenon that a "phantom event firer" was seen to be ALREADY in place for the onReady event, before the correct one was resolved and injected in its place - the "composite event firer" resolved from comp2. This might in some situations be the cause of a further bug if it could be observed, but in practice, so far, it doesn't seem to be, although this should be tracked down in due course.

    It is at this point that we looked back at the the console trace and thought about it in more detail, having verified that the correct event firer was indeed being injected up to the top component. This showed us the following trace

    Thu Jan 10 2013 13:03:08 GMT-0700 (Mountain Standard Time): Firing event [composite] onReady of component with typename fluid.tests.comp2 and id 2qoz3cj9-9 to list of 2 listeners
    Thu Jan 10 2013 13:03:08 GMT-0700 (Mountain Standard Time): ===== comp2's onReady listener
    Thu Jan 10 2013 13:03:08 GMT-0700 (Mountain Standard Time): ===== comp4's onReady listener

    Thinking about this more clearly, having eliminated one possible cause of the problem, the reason becomes clear - the event we were seeking to listen to has ALREADY FIRED by the point any of this resolution is occuring. Had we been suitably insightful, we could have detected this at the very first breakpoint we set, the one in the stackFetcher - but it is hard to always pick the real cause of an error as the most probable first idea. But the long chain of detection we went through shows the incredibly high costs of tracking such issues once one has taken a single false turn at the beginning.

    What kind of system could make these kinds of problems easy and transparent to track down? The next comment begins some speculations.

  • Antranig Basman commented 2013-01-11T01:54:43.508-0500

    An imagined UI contains (at least) two elements, described loosely as a "timeline" widget and an associated "tree display" widget. The timeline holds, potentially, every significant event in the stream of IoC actions (for example, component creation, event firing, listener attachment, model change, component attachment, etc.) but also contains a number of filtering widgets which in practice slim down the set to a small selection of relevant ones. Users are familiar with such interfaces from, say, the email filtering panel in Thunderbird, or, say, Chrome's "history filter". As well as allowing simple selection by event type and "location" (defined, perhaps, using 'IoCSS' expressions a la FLUID-4873) it might also allow complex combinations of assertions to narrow down to an exact event.

    This timeline contains a "scrubber" control similar to that seen in YouTube videos, etc. that allows the user to slide backwards and forwards through the event sequence. As they do this scrubbing, the associated "tree preview" animates to show a rough approximation of the state of the IoC tree at the time the event occurred. Since we don't expect to be able to completely snapshot the state of the whole tree at every event, this preview will be necessarily approximate - it will make correct guesses about gross structure, such as which components are instantiated and attached or not, but may be wrong about details such as the state of models or event firing - these will be fished directly out of their equivalents in the CURRENT state of the tree, and highlighted in a particular way to indicate that they are current values rather than historical ones.

    These two controls together should be sufficient to diagnose a large variety of IoC issues, especially as the "event transcript" structure which is being scrubbed through in the timeline will also contain some "contemporary values" which should help to deal with cases where "current values" have been used in the preview. This should also help users' intuition in walking directly through the process of tree construction and being able to directly perceive what has happened, broadly, in what order.

    We imagine this to be a kind of "popup panel" with many of the same UI aspects as the existing UIOptions panel - as well as being invokable through a UI similar to the "DOM browser" which appears in almost all contemporary browsers - something like a "magnifying glass" icon can be clicked on which puts the cursor into a particular mode, that when it hovers over DOM elements in the browser, information is displayed about them in a separate panel. In our case, when a DOM node is reached which was touched by an IoC-driven instantiation of a "viewComponent", all the relevant UI, including access to the "timeline" and "tree preview" widgets can be accessed directly from this node.

    The next comment holds a transcript from a chat with Colin describing a more ambitious scheme for enhancing this UI with the possibility to declare "IoC breakpoints" and sketches of mechanisms by which this could be achieved:

  • Antranig Basman commented 2013-01-11T01:56:18.006-0500

    (21:20:03) AntranigBasman@googlemail.com/Gaim: In the ultimate case, we would be able to set some kind of "IoC breakpoints"
    (21:20:15) AntranigBasman@googlemail.com/Gaim: But this would require some kind of very detailed access to the platform's native debugger
    (21:20:37) AntranigBasman@googlemail.com/Gaim: The IoC breakpoint specification would be converted into the equivalent literal breakpoint targetted at the framework code
    (21:20:56) AntranigBasman@googlemail.com/Gaim: We could certainly do this, with respect to something like the Chromedev wire debugging protocol, for example
    (21:21:04) AntranigBasman@googlemail.com/Gaim: Which would take care of node and also Chrome environments
    (21:21:58) AntranigBasman@googlemail.com/Gaim: We would create a kind of MAP describing the purpose of the framework code in some other kind of JSON dialect
    (21:22:02) AntranigBasman@googlemail.com/Gaim: THat would be quite bizarre!
    (21:22:10) AntranigBasman@googlemail.com/Gaim: As well as a pretty wacky kind of "documentation"
    (21:22:37) AntranigBasman@googlemail.com/Gaim: But it would encode things such as "Here IS the point at which subcomponent creation is initiated" or "Here IS the point at which the context portion of an EL expression is parsed" etc.
    (21:25:43) Clark Kent: That would be really amazing
    (21:26:09) AntranigBasman@googlemail.com/Gaim: The Chromedev wire protocol is pretty clear about how to encode such things
    (21:26:16) Clark Kent: is it?
    (21:26:26) Clark Kent: what does that look like?
    (21:26:37) AntranigBasman@googlemail.com/Gaim: So the "IoC Breakpoint to literal conditional JS breakpoint" mapper would actually be relatively simple
    (21:26:44) AntranigBasman@googlemail.com/Gaim: Just another kind of model transformer : P
    (21:29:05) AntranigBasman@googlemail.com/Gaim: Here is a bit of it
    (21:29:06) AntranigBasman@googlemail.com/Gaim: http://code.google.com/p/chromedevtools/wiki/ChromeDevToolsProtocol
    (21:29:16) AntranigBasman@googlemail.com/Gaim: A protocol of little JSON messages
    (21:29:25) AntranigBasman@googlemail.com/Gaim: Need to find all the rest of the detail
    (21:30:44) AntranigBasman@googlemail.com/Gaim: This is a full description, but of the wrong protocol
    (21:30:45) AntranigBasman@googlemail.com/Gaim: http://wiki.eclipse.org/JSDT/Debug/Rhino/Rhino_Debug_Wire_Protocol
    (21:30:50) AntranigBasman@googlemail.com/Gaim: But I think the real one isn't too different
    (21:31:24) Clark Kent: So the application generates these messages and sends them via the wire protocol to the debugger?
    (21:31:28) Clark Kent: So the workflow is roughly:
    (21:31:46) AntranigBasman@googlemail.com/Gaim: There is a "setbreakpoint" message that allows you to synthesize a breakpoint on the other side
    (21:31:49) Clark Kent: 1. Infusion instruments itself, recording events within the instantiation lifecycle of a component tree
    (21:32:02) Clark Kent: 2. This is transformed into WebKit debugger messages
    (21:32:06) Clark Kent: Ok
    (21:32:13) AntranigBasman@googlemail.com/Gaim: Yes, step 1 is right
    (21:32:20) Clark Kent: "synthesize a breakpoint"
    (21:32:23) Clark Kent: tell me about that
    (21:32:29) AntranigBasman@googlemail.com/Gaim: Step 2 I think is 2. A UI is generated which produces a browsable view of these events
    (21:32:54) AntranigBasman@googlemail.com/Gaim: 3. The UI exposes operations which allows you to set conditional breakpoints relative to this high-level sequence and then re-run the instantiation
    (21:33:27) AntranigBasman@googlemail.com/Gaim: 4. These conditional breakpoints are translated into literal language-level breakpoints that will stop at the appropriate point by stopping at the corresponding point in the framework code
    (21:33:52) AntranigBasman@googlemail.com/Gaim: 5. when the instantiation is rerun, the user gets to the see the exact state of the instantiating tree that he could only see an approximate view of in step 3
    (21:34:47) Clark Kent: ok
    (21:34:55) Clark Kent: I still struggle in one place
    (21:35:04) Clark Kent: There's this wire protocol...
    (21:35:14) Clark Kent: So the debugger starts up, listens on a port
    (21:35:36) Clark Kent: and then, I guess, we'd have a Node app providing the infrastructure for communicating with this socket
    (21:35:47) AntranigBasman@googlemail.com/Gaim: Yes.... I guess this is where something like our PCP work comes in
    (21:35:59) Clark Kent: yes, that makes sense
    (21:36:07) AntranigBasman@googlemail.com/Gaim: Since this could be helpful for delivering a non-crap user experience of interacting with a 3rd-party web-based tool
    (21:36:40) AntranigBasman@googlemail.com/Gaim: Whatever UI we deem appropriate for the PCP "native floating panel" will no doubt be appropriate for our "IoC framework inspector panel" too
    (21:37:21) AntranigBasman@googlemail.com/Gaim: And just as we can deliver an in-browser version of the panel, we might be able to do that for some selected browsers too
    (21:37:43) AntranigBasman@googlemail.com/Gaim: Say, for example, that FF doesn't expose any wire protocol at all, but instead exposes some kind of "bus" in the browser that is only accessible from browser extensions or the like
    (21:37:56) AntranigBasman@googlemail.com/Gaim: God knows how their native debugger is accessed, but I assume it's accessed somehow
    (21:38:08) Clark Kent: I'm fairly certain they also have a wire protocol
    (21:38:08) AntranigBasman@googlemail.com/Gaim: And similarly, I guess that Firebug is utterly inaccessible to anything in the outside world
    (21:38:16) Clark Kent: since I've used it to debug Android apps
    (21:38:48) AntranigBasman@googlemail.com/Gaim: Oh yes, I remember there is a kind of Firebug API
    (21:38:56) AntranigBasman@googlemail.com/Gaim: But I think it is too poor to allow you to synthesize breakpoints
    (21:38:57) Clark Kent: yes, even a plugin API
    (21:38:58) Clark Kent: https://wiki.mozilla.org/Remote_Debugging_Protocol
    (21:39:07) Clark Kent: That's the protocol for their new debugger, I think
    (21:39:18) AntranigBasman@googlemail.com/Gaim: cool
    (21:40:02) AntranigBasman@googlemail.com/Gaim: I see you can indeed set breakpoints with this
    (21:40:07) AntranigBasman@googlemail.com/Gaim: Although they don't say how to set conditional ones
    (21:40:56) Clark Kent: perhaps their new debugger doesn't yet support conditional breakpoints 😛
    (21:41:13) AntranigBasman@googlemail.com/Gaim: This is really much more elaborate than the other Chromelike protocols since there seems to be all this faff involving threads and actors
    (21:41:26) AntranigBasman@googlemail.com/Gaim: Every packet has these blasted "to" and "from" fields....
    (21:41:40) AntranigBasman@googlemail.com/Gaim: No doubt we will thank them for it in the end : P

  • Justin Obara commented 2013-01-15T07:31:17.854-0500

    Decapod-IoC.jpg is an example at an attempt to hand draw one of the IoC structures in Decapod. It includes the order of component creation and communication of events.

  • Antranig Basman commented 2015-04-05T16:35:09.137-0400

    Example of new-style DEBUGGING PROBES used to hunt problems during FLUID-5249 global instantiator work:

    fluid.registerProbes({
    previewCreate: {
    target: "{fluid.prefs.preview}.listeners.onCreate",
    func: "fluid.log",
    args: ["CREATED PREVIEW COMPONENT", "{that}"]
    },
    previewCreateEnd: {
    target: "{fluid.prefs.preview}.listeners.onCreate",
    func: "fluid.log",
    priority: "last",
    args: ["END OF ONCREATE for PREVIEW COMPONENT", "{that}"]
    },
    prefsReadyStart: {
    target: "{fluid.prefs.prefsEditor}.listeners.onReady",
    func: "fluid.log",
    priority: "first",
    args: ["START of ONREADY for PREFSEDITOR", "{that}"]
    },
    prefsReadyEnd: {
    target: "{fluid.prefs.prefsEditor}.listeners.onReady",
    func: "fluid.log",
    priority: "last",
    args: ["END of ONREADY for PREFSEDITOR", "{that}"]
    },
    previewReady: {
    target: "{fluid.prefs.preview}.listeners.onReady",
    func: "fluid.log",
    args: ["START of ONREADY for PREVIEW", "{that}"]
    },
    previewReadyEnd: {
    target: "{fluid.prefs.preview}.listeners.onReady",
    func: "fluid.log",
    priority: "last",
    args: ["END OF ONREADY for PREVIEW COMPONENT", "{that}"]
    },
    });

    The next really vital feature is the ability to gain a summary of the options distributions which have affected a component