Flows/subflows must preinitialise their context objects

Fixes #2513

If a node inside a subflow accessed its context object in its
constructor, the subflow-instance flow context would not yet
have been created. This would cause a place holder context
to get created on its behalf, but that place holder doesn't
have its parent set properly. This then breaks the usage
of $parent inside such a subflow.

This fix has changed it so flows (and subflows) create their
flow context as part of their initial creation. That ensures
it exists when individual nodes from the subflow are created,
allowing them to safely access their context.

This has also fixed a related issue where any attempt to use
$parent to access beyond the root parent would seemingly hang
as the callback was never being called. This would cause
messages to get stuck in flows. The fix ensures the callback
is used in the root context objects and undefined is returned.
This commit is contained in:
Nick O'Leary
2020-03-27 23:47:12 +00:00
parent 4304d44851
commit 84771f5864
4 changed files with 191 additions and 27 deletions

View File

@@ -420,15 +420,39 @@ function createRootContext() {
Object.defineProperties(obj, {
get: {
value: function(key, storage, callback) {
if (!callback && typeof storage === 'function') {
callback = storage;
storage = undefined;
}
if (callback) {
callback()
return;
}
return undefined;
}
},
set: {
value: function(key, value, storage, callback) {
if (!callback && typeof storage === 'function') {
callback = storage;
storage = undefined;
}
if (callback) {
callback()
return
}
}
},
keys: {
value: function(storage, callback) {
if (!callback && typeof storage === 'function') {
callback = storage;
storage = undefined;
}
if (callback) {
callback();
return;
}
return undefined;
}
}
@@ -436,25 +460,48 @@ function createRootContext() {
return obj;
}
function getContext(localId,flowId,parent) {
var contextId = localId;
/**
* Get a flow-level context object.
* @param {string} flowId [description]
* @param {string} parentFlowId the id of the parent flow. undefined
* @return {object}} the context object
*/
function getFlowContext(flowId,parentFlowId) {
if (contexts.hasOwnProperty(flowId)) {
return contexts[flowId];
}
var parentContext = contexts[parentFlowId];
if (!parentContext) {
parentContext = createRootContext();
contexts[parentFlowId] = parentContext;
// throw new Error("Flow "+flowId+" is missing parent context "+parentFlowId);
}
var newContext = createContext(flowId,undefined,parentContext);
contexts[flowId] = newContext;
return newContext;
}
function getContext(nodeId, flowId) {
var contextId = nodeId;
if (flowId) {
contextId = localId+":"+flowId;
contextId = nodeId+":"+flowId;
}
if (contexts.hasOwnProperty(contextId)) {
return contexts[contextId];
}
var newContext = createContext(contextId,undefined,parent);
var newContext = createContext(contextId);
if (flowId) {
var node = flows.get(flowId);
var parent = undefined;
if (node && node.type.startsWith("subflow:")) {
parent = node.context().flow;
var flowContext = contexts[flowId];
if (!flowContext) {
// This is most likely due to a unit test for a node which doesn't
// initialise the flow properly.
// To keep things working, initialise the missing context.
// This *does not happen* in normal node-red operation
flowContext = createContext(flowId,undefined,createRootContext());
contexts[flowId] = flowContext;
}
else {
parent = createRootContext();
}
var flowContext = getContext(flowId,undefined,parent);
Object.defineProperty(newContext, 'flow', {
value: flowContext
});
@@ -466,6 +513,39 @@ function getContext(localId,flowId,parent) {
return newContext;
}
//
// function getContext(localId,flowId,parent) {
// var contextId = localId;
// if (flowId) {
// contextId = localId+":"+flowId;
// }
// console.log("getContext",localId,flowId,"known?",contexts.hasOwnProperty(contextId));
// if (contexts.hasOwnProperty(contextId)) {
// return contexts[contextId];
// }
// var newContext = createContext(contextId,undefined,parent);
// if (flowId) {
// var node = flows.get(flowId);
// console.log("flows,get",flowId,node&&node.type)
// var parent = undefined;
// if (node && node.type.startsWith("subflow:")) {
// parent = node.context().flow;
// }
// else {
// parent = createRootContext();
// }
// var flowContext = getContext(flowId,undefined,parent);
// Object.defineProperty(newContext, 'flow', {
// value: flowContext
// });
// }
// Object.defineProperty(newContext, 'global', {
// value: contexts['global']
// })
// contexts[contextId] = newContext;
// return newContext;
// }
function deleteContext(id,flowId) {
if(!hasConfiguredStore){
// only delete context if there's no configured storage.
@@ -517,6 +597,7 @@ module.exports = {
load: load,
listStores: listStores,
get: getContext,
getFlowContext:getFlowContext,
delete: deleteContext,
clean: clean,
close: close

View File

@@ -18,6 +18,7 @@ var clone = require("clone");
var redUtil = require("@node-red/util").util;
var flowUtil = require("./util");
var events = require("../../events");
const context = require('../context');
var Subflow;
var Log;
@@ -54,6 +55,8 @@ class Flow {
this.catchNodes = [];
this.statusNodes = [];
this.path = this.id;
// Ensure a context exists for this flow
this.context = context.getFlowContext(this.id,this.parent.id);
}
/**

View File

@@ -16,7 +16,7 @@
const clone = require("clone");
const Flow = require('./Flow').Flow;
const context = require('../context');
const util = require("util");
const redUtil = require("@node-red/util").util;
@@ -227,6 +227,10 @@ class Subflow extends Flow {
this.node.on("input", function(msg) { this.send(msg);});
this.node.on("close", function() { this.status({}); })
this.node.status = status => this.parent.handleStatus(this.node,status);
// Create a context instance
console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z)
this._context = context.get(this._alias||this.id,this.z);
this.node._updateWires = this.node.updateWires;