node-red/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js

397 lines
14 KiB
JavaScript
Raw Normal View History

/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var when = require("when");
var clone = require("clone");
2019-01-16 17:27:19 +01:00
var Subflow;
2018-04-23 15:24:51 +02:00
var Log;
2018-08-17 23:10:54 +02:00
var redUtil = require("@node-red/util").util;
var flowUtil = require("./util");
2019-01-16 17:27:19 +01:00
var events = require("../../events");
var nodeCloseTimeout = 15000;
2019-01-11 15:53:21 +01:00
class Flow {
2019-01-16 17:27:19 +01:00
constructor(parent,globalFlow,flow) {
this.TYPE = 'flow';
this.parent = parent;
this.global = globalFlow;
2019-01-11 15:53:21 +01:00
if (typeof flow === 'undefined') {
2019-01-16 17:27:19 +01:00
this.flow = globalFlow;
this.isGlobalFlow = true;
2019-01-11 15:53:21 +01:00
} else {
this.flow = flow;
2019-01-16 17:27:19 +01:00
this.isGlobalFlow = false;
2019-01-11 15:53:21 +01:00
}
2019-01-16 17:27:19 +01:00
this.id = this.flow.id || "global";
2019-01-11 15:53:21 +01:00
this.activeNodes = {};
this.subflowInstanceNodes = {};
2019-01-16 17:27:19 +01:00
this.catchNodes = [];
this.statusNodes = [];
}
2019-01-16 17:27:19 +01:00
debug(msg) {
Log.log({
id: this.id||"global",
level: Log.DEBUG,
type:this.TYPE,
msg:msg
})
}
log(msg) {
Log.log({
id: this.id||"global",
level: Log.INFO,
type:this.TYPE,
msg:msg
})
}
trace(msg) {
Log.log({
id: this.id||"global",
level: Log.TRACE,
type:this.TYPE,
msg:msg
})
}
2019-01-11 15:53:21 +01:00
start(diff) {
2019-01-16 17:27:19 +01:00
this.trace("start "+this.TYPE);
var node;
2015-11-24 23:38:42 +01:00
var newNode;
var id;
2019-01-16 17:27:19 +01:00
this.catchNodes = [];
this.statusNodes = [];
2019-01-11 15:53:21 +01:00
var configNodes = Object.keys(this.flow.configs);
var configNodeAttempts = {};
while (configNodes.length > 0) {
id = configNodes.shift();
2019-01-11 15:53:21 +01:00
node = this.flow.configs[id];
if (!this.activeNodes[id]) {
var readyToCreate = true;
// This node doesn't exist.
// Check it doesn't reference another non-existent config node
for (var prop in node) {
2019-01-11 15:53:21 +01:00
if (node.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== '_users' && this.flow.configs[node[prop]]) {
if (!this.activeNodes[node[prop]]) {
// References a non-existent config node
// Add it to the back of the list to try again later
configNodes.push(id);
configNodeAttempts[id] = (configNodeAttempts[id]||0)+1;
if (configNodeAttempts[id] === 100) {
throw new Error("Circular config node dependency detected: "+id);
}
readyToCreate = false;
break;
}
}
}
if (readyToCreate) {
2019-01-16 17:27:19 +01:00
newNode = flowUtil.createNode(this,node);
2015-11-24 23:38:42 +01:00
if (newNode) {
2019-01-11 15:53:21 +01:00
this.activeNodes[id] = newNode;
2015-11-24 23:38:42 +01:00
}
}
}
2016-04-27 13:37:20 +02:00
}
2015-12-09 22:51:46 +01:00
if (diff && diff.rewired) {
for (var j=0;j<diff.rewired.length;j++) {
2019-01-11 15:53:21 +01:00
var rewireNode = this.activeNodes[diff.rewired[j]];
if (rewireNode) {
2019-01-11 15:53:21 +01:00
rewireNode.updateWires(this.flow.nodes[rewireNode.id].wires);
}
}
}
2019-01-11 15:53:21 +01:00
for (id in this.flow.nodes) {
if (this.flow.nodes.hasOwnProperty(id)) {
node = this.flow.nodes[id];
if (!node.subflow) {
2019-01-11 15:53:21 +01:00
if (!this.activeNodes[id]) {
2019-01-16 17:27:19 +01:00
newNode = flowUtil.createNode(this,node);
2015-11-24 23:38:42 +01:00
if (newNode) {
2019-01-11 15:53:21 +01:00
this.activeNodes[id] = newNode;
2015-11-24 23:38:42 +01:00
}
}
} else {
2019-01-11 15:53:21 +01:00
if (!this.subflowInstanceNodes[id]) {
try {
2019-01-11 15:53:21 +01:00
var subflowDefinition = this.flow.subflows[node.subflow]||this.global.subflows[node.subflow]
2019-01-16 17:27:19 +01:00
// console.log("NEED TO CREATE A SUBFLOW",id,node.subflow);
this.subflowInstanceNodes[id] = true;
var subflow = Subflow.create(
this,
this.global,
subflowDefinition,
node
);
subflow.start();
this.activeNodes[id] = subflow.node;
this.subflowInstanceNodes[id] = subflow;
// this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
// for (var i=0;i<nodes.length;i++) {
// if (nodes[i]) {
// this.activeNodes[nodes[i].id] = nodes[i];
// }
// }
} catch(err) {
console.log(err.stack)
}
}
}
}
}
2019-01-16 17:27:19 +01:00
var activeCount = Object.keys(this.activeNodes).length;
if (activeCount > 0) {
this.trace("------------------|--------------|-----------------");
this.trace(" id | type | alias");
this.trace("------------------|--------------|-----------------");
}
// Build the map of catch/status nodes.
2019-01-11 15:53:21 +01:00
for (id in this.activeNodes) {
if (this.activeNodes.hasOwnProperty(id)) {
node = this.activeNodes[id];
2019-01-16 17:27:19 +01:00
this.trace(" "+id.padEnd(16)+" | "+node.type.padEnd(12)+" | "+(node._alias||""));
if (node.type === "catch") {
2019-01-16 17:27:19 +01:00
this.catchNodes.push(node);
} else if (node.type === "status") {
2019-01-16 17:27:19 +01:00
this.statusNodes.push(node);
}
}
}
2019-01-16 17:27:19 +01:00
if (activeCount > 0) {
this.trace("------------------|--------------|-----------------");
}
// this.dump();
}
2019-01-11 15:53:21 +01:00
stop(stopList, removedList) {
return new Promise((resolve,reject) => {
2019-01-16 17:27:19 +01:00
this.trace("stop "+this.TYPE);
var i;
if (stopList) {
2019-01-16 17:27:19 +01:00
// for (i=0;i<stopList.length;i++) {
// if (this.subflowInstanceNodes[stopList[i]]) {
// console.log("NEED TO STOP A SUBFLOW",stopList[i]);
// // The first in the list is the instance node we already
// // know about
// // stopList = stopList.concat(this.subflowInstanceNodes[stopList[i]].slice(1))
// }
// }
} else {
2019-01-11 15:53:21 +01:00
stopList = Object.keys(this.activeNodes);
}
// Convert the list to a map to avoid multiple scans of the list
var removedMap = {};
removedList = removedList || [];
removedList.forEach(function(id) {
removedMap[id] = true;
});
var promises = [];
for (i=0;i<stopList.length;i++) {
2019-01-11 15:53:21 +01:00
var node = this.activeNodes[stopList[i]];
if (node) {
2019-01-11 15:53:21 +01:00
delete this.activeNodes[stopList[i]];
if (this.subflowInstanceNodes[stopList[i]]) {
2019-01-16 17:27:19 +01:00
try {
var subflow = this.subflowInstanceNodes[stopList[i]];
promises.push(this.stopNode(node,false).then(() => { subflow.stop() }));
} catch(err) {
node.error(err);
}
2019-01-11 15:53:21 +01:00
delete this.subflowInstanceNodes[stopList[i]];
2019-01-16 17:27:19 +01:00
} else {
try {
var removed = removedMap[stopList[i]];
promises.push(this.stopNode(node,removed));
} catch(err) {
node.error(err);
}
}
}
}
when.settle(promises).then(function(results) {
resolve();
});
});
}
2019-01-16 17:27:19 +01:00
stopNode(node,removed) {
return when.promise(function(resolve, reject) {
var start;
when.promise(function(resolve) {
Log.trace("Stopping node "+node.type+":"+node.id+(removed?" removed":""));
start = Date.now();
resolve(node.close(removed));
}).timeout(nodeCloseTimeout).then(function(){
var delta = Date.now() - start;
Log.trace("Stopped node "+node.type+":"+node.id+" ("+delta+"ms)" );
resolve(delta);
},function(err) {
var delta = Date.now() - start;
node.error(Log._("nodes.flows.stopping-error",{message:err}));
Log.debug(err.stack);
reject(err);
});
})
}
2019-01-11 15:53:21 +01:00
update(_global,_flow) {
this.global = _global;
this.flow = _flow;
}
2019-01-11 15:53:21 +01:00
getNode(id) {
2019-01-16 17:27:19 +01:00
// console.log('getNode',id,!!this.activeNodes[id])
if (!id) {
return undefined;
}
// console.log((new Error().stack).toString().split("\n").slice(1,3).join("\n"))
if ((this.flow.configs && this.flow.configs[id]) || (this.flow.nodes && this.flow.nodes[id])) {
// This is a node owned by this flow, so return whatever we have got
// During a stop/restart, activeNodes could be null for this id
return this.activeNodes[id];
} else if (this.activeNodes[id]) {
// TEMP: this is a subflow internal node within this flow
return this.activeNodes[id];
}
return this.parent.getNode(id);
}
2019-01-11 15:53:21 +01:00
getActiveNodes() {
return this.activeNodes;
}
2019-01-11 15:53:21 +01:00
handleStatus(node,statusMessage) {
2019-01-16 17:27:19 +01:00
events.emit("node-status",{
id: node.id,
status:statusMessage
});
var handled = false;
2019-01-16 17:27:19 +01:00
this.statusNodes.forEach(function(targetStatusNode) {
if (targetStatusNode.scope && targetStatusNode.scope.indexOf(node.id) === -1) {
return;
}
2019-01-16 17:27:19 +01:00
var message = {
status: {
text: "",
source: {
id: node.id,
type: node.type,
name: node.name
}
}
};
if (statusMessage.hasOwnProperty("text")) {
message.status.text = statusMessage.text.toString();
}
2019-01-16 17:27:19 +01:00
targetStatusNode.receive(message);
handled = true;
});
if (!handled) {
// // Nothing in this flow handled the status - pass it to the parent
this.parent.handleStatus(node,statusMessage);
}
}
2019-01-11 15:53:21 +01:00
handleError(node,logMessage,msg) {
2019-01-16 17:27:19 +01:00
// console.log("HE",logMessage);
var count = 1;
if (msg && msg.hasOwnProperty("error") && msg.error !== null) {
if (msg.error.hasOwnProperty("source") && msg.error.source !== null) {
if (msg.error.source.id === node.id) {
count = msg.error.source.count+1;
if (count === 10) {
node.warn(Log._("nodes.flow.error-loop"));
return false;
}
}
}
}
var handled = false;
2019-01-16 17:27:19 +01:00
this.catchNodes.forEach(function(targetCatchNode) {
if (targetCatchNode.scope && targetCatchNode.scope.indexOf(node.id) === -1) {
return;
}
2019-01-16 17:27:19 +01:00
var errorMessage;
if (msg) {
errorMessage = redUtil.cloneMessage(msg);
} else {
2019-01-16 17:27:19 +01:00
errorMessage = {};
}
2019-01-16 17:27:19 +01:00
if (errorMessage.hasOwnProperty("error")) {
errorMessage._error = errorMessage.error;
}
2019-01-16 17:27:19 +01:00
errorMessage.error = {
message: logMessage.toString(),
source: {
id: node.id,
type: node.type,
name: node.name,
count: count
}
2019-01-16 17:27:19 +01:00
};
if (logMessage.hasOwnProperty('stack')) {
errorMessage.error.stack = logMessage.stack;
}
2019-01-16 17:27:19 +01:00
targetCatchNode.receive(errorMessage);
handled = true;
});
if (!handled) {
// Nothing in this flow handled the error - pass it to the parent
handled = this.parent.handleError(node,logMessage);
}
2019-01-16 17:27:19 +01:00
return handled;
}
2019-01-16 17:27:19 +01:00
dump() {
console.log("==================")
console.log(this.TYPE, this.id);
for (var id in this.activeNodes) {
if (this.activeNodes.hasOwnProperty(id)) {
var node = this.activeNodes[id];
console.log(" ",id.padEnd(16),node.type)
if (node.wires) {
console.log(" -> ",node.wires)
}
}
}
2019-01-16 17:27:19 +01:00
console.log("==================")
}
}
module.exports = {
2018-04-23 15:24:51 +02:00
init: function(runtime) {
nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000;
Log = runtime.log;
2019-01-16 17:27:19 +01:00
Subflow = require("./Subflow");
Subflow.init(runtime);
},
2019-01-16 17:27:19 +01:00
create: function(parent,global,conf) {
return new Flow(parent,global,conf);
},
Flow: Flow
}