Sub-flows. I am missing something in how they work

Sorry, but this has me stumped.

I have/had a bunch of nodes which were painful for me to maintain and so I made a subflow of them.

Heaps easier to roll out updates to them. Rather than having to edit them all, I just edit the subflow and all is sweet.

Thing is it isn't.

I have most of them devices using the subflow to determine their condition, and most of them are content. Showing "online-responding" every pulse. (About 20 seconds)

Alas one of the devices decides to (now and then) become cantankerous every now and then and every other pulse, shows "booting" condition. Every alternative to that it shows as "online-responding".

So something is not happening in there to cause that.

If I re-wire the actual nodes into the flow, this behaviour stops.

But they are the same nodes as those in the subflow.

Any ideas what I can do to try and work out what is going on?

There are 2 other devices connected using the same subflow and they are happy as. No "booting" signal every alternate pulse.

(And typically: Now that I posted this question it has decided to behave itself.)

I don't want to just dump the flow here. It is complicated and you need to understand what it is doing.

I can write out what happens and post it, but I thought it would be polite to first ask for ideas on how to see what is going on where it is only happening with one (of three) devices connected and all are using the same subflow.

Are you using flow or global context inside the sub flow? I.e. you could have concurrency issues)

Are you processing object sent in (i.e. perhaps you have a reference object being modified asynchronously)

Have you added debug nodes before and after to capture the input and output and verified the node isn't behaving accordingly (i.e. "shit in, shit out" scenario)

Well. . . . . .

I went about making the subflow the wrong way. In that:

rather than make the subflow to do something I was doing it all with nodes.

When they all worked, I copied them and pasted them in the new location. Set a couple of things differently and it worked.

This was (but not originally) painful if I found a bug. I had to go through the process of editing all the nodes.

Going through the code in one (of 3) function nodes I have this line.
var debug = flow.get(device+'_Button_Debug') || false; // if active: true
Though not directly responsible for the problem, it uses flow.get from when it was not a subflow.

Will this cause problems? (New ones to which I haven't even got yet)

Most of the stuff is all just context.get/set( ) stuff. Though I have to admit, that is a flow.get( ) example.

Otherwise all variables are context.get/set( )

Just did a check. There is only the one flow.get( ) used. That is for debugging - as was indicated in the code.

So really it is all just simple context.get/set( ) stuff.

(I feel like I am repeating myself. Not intended.)

So I think I am meaning that I have kind of passed the shit in/shit out scenario because if it was true, none of the other two devices would work.

Also as I mentioned: It is intermittent. It was happening when I posted and as I was posting, it stopped. That is to say I was starting to re-wire the nodes to the "real" nodes (as opposed to the subflow) and it started to behave itself.

Reading a flag from flow context for debug is fine.

It's when you store a value in flow/global in a subflow, all instances of the subflow are doing the same thing. Potentially at the same time, interfering with each other.

As for node context.set/get, I have to be honest here - I'm not certain how subflow nodes are instansiated.

In my mind, context store is kinda like a class property - in that each instance had its own unique variable. But a node in a sub flow - I'm just not certain. My guess is context storage on a node inside a subflow is unique to each instance (and therefore safe) however I'm not 100% certain.
Without nick or Dave to quickly answer, we could perform a little test could help us understand.

Create a subflow with 1 function node...

Subflow...
In --> function-->out

var time = context.get("time");
if(!time){
  time=Date.now();
  context.set("time", time);
}
msg.payload = time;
return msg;

Then put 2 instances of the subflow on a flow...

  • Inject-subflow-debug
  • Inject-subflow-debug

If you click the 1st inject then wait a second then press the 2nd inject, do you get the same timestamp or a different timestamp?

A different timestamp means node context in a subflow is it's own variable and therefore safe (IE multiple subflow instances won't interfere with each other)

Only 1 output. :slight_smile:

I'll spare you the screen shot.

So that looks good for that.

context.get/set( ) is local to that instance of the iteration of the subflow.

As it is so intermittent I won't lose too much sleep on it.

I probably stuffed up somewhere. As I usually do.

Just to test the idea, this is what I know about the topic:

The nodes in a subflow can use flow context, but it is scoped to the nodes in the subflow, not the flow the instance of the subflow is on.

If you are on 0.20, then the nodes in the subflow can access the parent flow context. If you have a flow context variable called foo then the nodes in a subflow can use $parent.foo to access it.

I made this flow:
(Well, sub flow actually)

[{"id":"d03cee5c.debbf","type":"subflow","name":"Subflow 2","info":"","in":[{"x":220,"y":100,"wires":[{"id":"b6a05627.03a81"}]}],"out":[{"x":560,"y":100,"wires":[{"id":"b6a05627.03a81","port":0}]}]},{"id":"b6a05627.03a81","type":"function","z":"d03cee5c.debbf","name":"","func":"var x = flow.get($parent.test);\nmsg.payload = x;\nreturn msg;","outputs":1,"noerr":0,"x":360,"y":100,"wires":[[]]}]

That is a sub-flow.

I set this up:

[{"id":"d03cee5c.debbf","type":"subflow","name":"Subflow 2","info":"","in":[{"x":220,"y":100,"wires":[{"id":"b6a05627.03a81"}]}],"out":[{"x":560,"y":100,"wires":[{"id":"b6a05627.03a81","port":0}]}]},{"id":"b6a05627.03a81","type":"function","z":"d03cee5c.debbf","name":"","func":"var x = flow.get($parent.test);\nmsg.payload = x;\nreturn msg;","outputs":1,"noerr":0,"x":360,"y":100,"wires":[[]]},{"id":"9ea5acd.d1ad25","type":"debug","z":"accbdb61.d75918","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":580,"y":760,"wires":[]},{"id":"69a06ac9.813d24","type":"inject","z":"accbdb61.d75918","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":760,"wires":[["f6961980.dfd6d"]]},{"id":"f6961980.dfd6d","type":"subflow:d03cee5c.debbf","z":"accbdb61.d75918","name":"","x":340,"y":760,"wires":[["9ea5acd.d1ad25"]]},{"id":"26a16337.fdde04","type":"inject","z":"accbdb61.d75918","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":620,"wires":[["37c662d1.dc05ae"]]},{"id":"37c662d1.dc05ae","type":"change","z":"accbdb61.d75918","name":"","rules":[{"t":"set","p":"test","pt":"flow","to":"ONE","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":620,"wires":[[]]},{"id":"f43ae422.a4c","type":"change","z":"accbdb61.d75918","name":"","rules":[{"t":"set","p":"test","pt":"flow","to":"TWO","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":670,"wires":[[]]},{"id":"79938c3d.d2cf7c","type":"inject","z":"accbdb61.d75918","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":670,"wires":[["f43ae422.a4c"]]}]

I don't get to see "ONE" or "TWO" come out of the debug node if I set it with either of the top inject nodes.

But I can see the flow context variable if I look at it with the context data option.

So I'm missing something here.

The same seems valid for flow.get/set (just modify Steves time example and try). But changing it to global you will see they will operate on the same variable that is not kept inside each instance of the
subflows, i.e. it is instead in the global context

(Idiot hat on)

Sorry. I'm not getting what you mean.

To break it down to the node in the subflow, this is what I have - NOT working:

node.warn("Message received");  // for the sake of knowing the message arrived
var x = flow.get($parent.test);
msg.payload = x;
return msg;

Though I think line 2 is the problem.

So the flow context (from the "parent" flow) I want to get is called test.

I did it as explained from Nick (other thread a while ago), but I am (obviously) missing something.

For the sake of my learning can you show me what that code needs to be?

What they are trying to say is that if you EXPLICITLY name the variable/context (GLobal or Flow) then each time the subflow runs it will only access that named instance - regardless of where you call it from - this would be desireable if the subflow was not called concurrently by many flows.

On the other hand if you were to call it concurrently from many flows then you will want each instance of the subflow variables to be instantiated and unique to that calling iteration until the parent process dies - otherwise they will be trampling all over each other

Craig

I appreciate the reply Craig, but I am still not getting it.

I have (again keeping it in line with what is already said) in the flow I set a flow variable test to something. (String)

At any time the sub-flow is called and needs to get the value of test from the parent flow.

But I am still not seeing a working way of doing this.

I think the brain is really not working just now. :frowning:

Just to be clearer, that is something I did not respond to, I do not know how you can grab flow data from the parent. Only thing I noticed during my tests is that you can get global data in the same way as usual but context and flow data stays local in the sub flow

No problems. I'm not saying you had to answer.

I'm just confused that I have read how to do it, but what is written doesn't work, or: I am doing something wrong.

Just looking for what the correct way of doing it is.

Not sure where this comes from, but I can imagine that a subflow is not part of the flow it is living in (it's in its' own little world), in terms of context.

Instead of trying to read the flow variable in the subflow, why not just inject it into the subflow node with a payload property ? You have to inject payloads anyway.

I see Nick is replying, but just to set the records straight:

Where I got the information

1 Like

Assuming to you want to access the flow context variable called test in the flow containing your subflow instance node, then

var x = flow.get($parent.test);

Should be

var x = flow.get("$parent.test")
3 Likes

You need quotes around the text otherwise it thinks $parent2.test is a local variable it needs to evaluate and already not the value of a variable.

[{"id":"a11d54.726bd2b","type":"subflow","name":"Subflow 1","info":"","in":[{"x":60,"y":80,"wires":[{"id":"63e10fae.b1efd"}]}],"out":[{"x":320,"y":80,"wires":[{"id":"63e10fae.b1efd","port":0}]}],"status":{"x":320,"y":160,"wires":[{"id":"63e10fae.b1efd","port":0}]}},{"id":"63e10fae.b1efd","type":"function","z":"a11d54.726bd2b","name":"","func":"node.warn(\"Message received\");  // for the sake of knowing the message arrived\nmsg.payload = flow.get(\"$parent.test\");\nreturn msg;","outputs":1,"noerr":0,"x":190,"y":80,"wires":[[]]},{"id":"37170f03.e7cad","type":"inject","z":"5051cb93.cf0d34","name":"","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":560,"wires":[["d1b2515d.89157"]]},{"id":"d1b2515d.89157","type":"change","z":"5051cb93.cf0d34","name":"","rules":[{"t":"set","p":"test","pt":"flow","to":"From parent 1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":250,"y":560,"wires":[["ea05925a.2f409"]]},{"id":"ea05925a.2f409","type":"subflow:a11d54.726bd2b","z":"5051cb93.cf0d34","name":"","env":[],"x":380,"y":620,"wires":[["6b0da688.4a4178"]]},{"id":"6b0da688.4a4178","type":"debug","z":"5051cb93.cf0d34","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":530,"y":620,"wires":[]},{"id":"e5896147.211da","type":"change","z":"5051cb93.cf0d34","name":"","rules":[{"t":"set","p":"test","pt":"flow","to":"From parent 2","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":250,"y":700,"wires":[["ea05925a.2f409"]]},{"id":"8d4e3fd3.745dc","type":"inject","z":"5051cb93.cf0d34","name":"","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":700,"wires":[["e5896147.211da"]]}]

Thanks @knolleary and @dceejay.

With that resolved, how would I do what was shown in this real line:
var debug = flow.get(device+'_Button_Debug') || false;

device is resolved earlier from a local context.get( ).

My resolution to it is:
var debug = flow.get("$parent.device"+'_Button_Debug' ) || false;

correct?

I notice that how to use flow context in a subflow is documented in
https://nodered.org/docs/user-guide/context
but not in
https://nodered.org/docs/user-guide/editor/workspace/subflows

2 Likes

I think

var debug = flow.get(`$parent.${device}_Button_Debug`) || false;

or

var debug = flow.get("$parent." + device + "_Button_Debug") || false;

if you are a Luddite.