diff --git a/nodes/core/core/75-exec.html b/nodes/core/core/75-exec.html
index 735ed984e..02c9b0f5f 100644
--- a/nodes/core/core/75-exec.html
+++ b/nodes/core/core/75-exec.html
@@ -31,7 +31,11 @@
-
+
+
+
+
+ seconds
@@ -59,6 +63,7 @@
addpay: {value:true},
append: {value:""},
useSpawn: {value:""},
+ timer: {value:""},
name: {value:""}
},
inputs:1,
diff --git a/nodes/core/core/75-exec.js b/nodes/core/core/75-exec.js
index 97a1a3b63..da03bf61a 100644
--- a/nodes/core/core/75-exec.js
+++ b/nodes/core/core/75-exec.js
@@ -27,55 +27,64 @@ module.exports = function(RED) {
this.addpay = n.addpay;
this.append = (n.append || "").trim();
this.useSpawn = n.useSpawn;
+ this.timer = Number(n.timer || 0)*1000;
this.activeProcesses = {};
+ var cleanup = function(p) {
+ //console.log("CLEANUP!!!",p);
+ node.activeProcesses[p].kill();
+ node.status({fill:"red",shape:"dot",text:"timeout"});
+ node.error("Exec node timeout");
+ }
+
var node = this;
this.on("input", function(msg) {
+ var child;
node.status({fill:"blue",shape:"dot",text:" "});
if (this.useSpawn === true) {
// make the extra args into an array
// then prepend with the msg.payload
-
var arg = node.cmd;
- if (node.addpay) {
- arg += " "+msg.payload;
- }
+ if (node.addpay) { arg += " "+msg.payload; }
arg += " "+node.append;
// slice whole line by spaces (trying to honour quotes);
arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g);
var cmd = arg.shift();
/* istanbul ignore else */
if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); }
- if (cmd.indexOf(" ") == -1) {
- var ex = spawn(cmd,arg);
- node.activeProcesses[ex.pid] = ex;
- ex.stdout.on('data', function (data) {
- //console.log('[exec] stdout: ' + data);
- if (isUtf8(data)) { msg.payload = data.toString(); }
- else { msg.payload = data; }
- node.send([msg,null,null]);
- });
- ex.stderr.on('data', function (data) {
- //console.log('[exec] stderr: ' + data);
- if (isUtf8(data)) { msg.payload = data.toString(); }
- else { msg.payload = new Buffer(data); }
- node.send([null,msg,null]);
- });
- ex.on('close', function (code) {
- //console.log('[exec] result: ' + code);
- delete node.activeProcesses[ex.pid];
- msg.payload = code;
- if (code === 0) { node.status({}); }
- else if (code < 0) { node.status({fill:"red",shape:"dot",text:"rc: "+code}); }
- else { node.status({fill:"yellow",shape:"dot",text:"rc: "+code}); }
- node.send([null,null,msg]);
- });
- ex.on('error', function (code) {
- delete node.activeProcesses[ex.pid];
- node.error(code,msg);
- });
+ child = spawn(cmd,arg);
+ if (node.timer !== 0) {
+ child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
}
- else { node.error(RED._("exec.spawnerr")); }
+ node.activeProcesses[child.pid] = child;
+ child.stdout.on('data', function (data) {
+ //console.log('[exec] stdout: ' + data);
+ if (isUtf8(data)) { msg.payload = data.toString(); }
+ else { msg.payload = data; }
+ node.send([msg,null,null]);
+ });
+ child.stderr.on('data', function (data) {
+ //console.log('[exec] stderr: ' + data);
+ if (isUtf8(data)) { msg.payload = data.toString(); }
+ else { msg.payload = new Buffer(data); }
+ node.send([null,msg,null]);
+ });
+ child.on('close', function (code) {
+ //console.log('[exec] result: ' + code);
+ delete node.activeProcesses[child.pid];
+ if (child.tout) { clearTimeout(child.tout); }
+ msg.payload = code;
+ if (code === 0) { node.status({}); }
+ if (code === null) { node.status({fill:"red",shape:"dot",text:"timeout"}); }
+ else if (code < 0) { node.status({fill:"red",shape:"dot",text:"rc: "+code}); }
+ else { node.status({fill:"yellow",shape:"dot",text:"rc: "+code}); }
+ node.send([null,null,msg]);
+ });
+ child.on('error', function (code) {
+ delete node.activeProcesses[child.pid];
+ if (child.tout) { clearTimeout(child.tout); }
+ node.error(code,msg);
+ });
}
else {
var cl = node.cmd;
@@ -83,13 +92,9 @@ module.exports = function(RED) {
if (node.append.trim() !== "") { cl += " "+node.append; }
/* istanbul ignore else */
if (RED.settings.verbose) { node.log(cl); }
- var child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) {
+ child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) {
msg.payload = new Buffer(stdout,"binary");
- try {
- if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
- } catch(e) {
- node.log(RED._("exec.badstdout"));
- }
+ if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
var msg2 = {payload:stderr};
var msg3 = null;
//console.log('[exec] stdout: ' + stdout);
@@ -100,9 +105,13 @@ module.exports = function(RED) {
}
node.status({});
node.send([msg,msg2,msg3]);
+ if (child.tout) { clearTimeout(child.tout); }
delete node.activeProcesses[child.pid];
});
child.on('error',function() {});
+ if (node.timer !== 0) {
+ child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
+ }
node.activeProcesses[child.pid] = child;
}
});
@@ -110,10 +119,12 @@ module.exports = function(RED) {
for (var pid in node.activeProcesses) {
/* istanbul ignore else */
if (node.activeProcesses.hasOwnProperty(pid)) {
+ if (node.activeProcesses[pid].tout) { clearTimeout(node.activeProcesses[pid].tout); }
node.activeProcesses[pid].kill();
}
}
node.activeProcesses = {};
+ node.status({});
});
}
RED.nodes.registerType("exec",ExecNode);
diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json
index 1847c382a..bfb930f0e 100644
--- a/nodes/core/locales/en-US/messages.json
+++ b/nodes/core/locales/en-US/messages.json
@@ -124,11 +124,11 @@
}
},
"exec": {
- "spawnerr": "Spawn command must be just the command - no spaces or extra parameters",
- "badstdout": "Bad STDOUT",
"label": {
"command": "Command",
- "append": "Append"
+ "append": "Append",
+ "timeout": "Timeout",
+ "timeoutplace": "optional"
},
"placeholder": {
"extraparams": "extra input parameters"
diff --git a/test/nodes/core/core/75-exec_spec.js b/test/nodes/core/core/75-exec_spec.js
index 9572a99c4..ca1fc3edb 100644
--- a/test/nodes/core/core/75-exec_spec.js
+++ b/test/nodes/core/core/75-exec_spec.js
@@ -41,6 +41,7 @@ describe('exec node', function() {
n1.should.have.property("cmd", "");
n1.should.have.property("append", "");
n1.should.have.property("addpay",true);
+ n1.should.have.property("timer",0);
done();
});
});
@@ -139,6 +140,33 @@ describe('exec node', function() {
n1.receive({});
});
});
+
+ it('should be able to timeout a long running command', function(done) {
+ var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"0.3"},
+ {id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
+
+ helper.load(execNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var n3 = helper.getNode("n3");
+ var n4 = helper.getNode("n4");
+ n4.on("input", function(msg) {
+ msg.should.have.property("payload");
+ msg.payload.should.have.property("killed",true);
+ //done();
+ });
+ setTimeout(function() {
+ var logEvents = helper.log().args.filter(function(evt) {
+ return evt[0].type == "exec";
+ });
+ logEvents.should.have.length(2);
+ logEvents[1][0].should.have.a.property('msg');
+ logEvents[1][0].msg.toString().should.startWith("Exec node timeout");
+ done();
+ },400);
+ n1.receive({});
+ });
+ });
});
describe('calling spawn', function() {
@@ -267,5 +295,31 @@ describe('exec node', function() {
});
});
+ it('should be able to timeout a long running command', function(done) {
+ var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"0.3", useSpawn:true},
+ {id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
+
+ helper.load(execNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var n3 = helper.getNode("n3");
+ var n4 = helper.getNode("n4");
+ n4.on("input", function(msg) {
+ msg.should.have.property("payload",null);
+ //done();
+ });
+ setTimeout(function() {
+ var logEvents = helper.log().args.filter(function(evt) {
+ return evt[0].type == "exec";
+ });
+ logEvents.should.have.length(2);
+ logEvents[1][0].should.have.a.property('msg');
+ logEvents[1][0].msg.toString().should.startWith("Exec node timeout");
+ done();
+ },400);
+ n1.receive({});
+ });
+ });
+
});
});
diff --git a/test/nodes/core/core/89-trigger_spec.js b/test/nodes/core/core/89-trigger_spec.js
index 3e1db2c9b..a333086ca 100644
--- a/test/nodes/core/core/89-trigger_spec.js
+++ b/test/nodes/core/core/89-trigger_spec.js
@@ -264,6 +264,35 @@ describe('trigger Node', function() {
});
});
+ it('should be able output the 2nd payload', function(done) {
+ var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"false", op1type:"nul", op2type:"payl", op1:"false", op2:"true", duration:200, wires:[["n2"]] },
+ {id:"n2", type:"helper"} ];
+ helper.load(triggerNode, flow, function() {
+ var n1 = helper.getNode("n1");
+ var n2 = helper.getNode("n2");
+ var c = 0;
+ n2.on("input", function(msg) {
+ if (c === 0) {
+ msg.should.have.a.property("payload", "Goodbye");
+ c += 1;
+ }
+ else {
+ msg.should.have.a.property("payload", "World");
+ (Date.now() - ss).should.be.greaterThan(380);
+ done();
+ }
+ });
+ var ss = Date.now();
+ n1.emit("input", {payload:"Hello"});
+ setTimeout( function() {
+ n1.emit("input", {payload:"Goodbye"});
+ },100);
+ setTimeout( function() {
+ n1.emit("input", {payload:"World"});
+ },400);
+ });
+ });
+
it('should be able to apply mustache templates to payloads', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"{{payload}}", op2:"{{topic}}", duration:50, wires:[["n2"]] },
{id:"n2", type:"helper"} ];