mirror of https://github.com/node-red/node-red.git
Add optional timeout to exec node
(both exec and spawn modes) and add test for it (both exec and spawn) also extra test for trigger node.pull/911/head
parent
4ad540412a
commit
bd59398cab
|
@ -31,7 +31,11 @@
|
|||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-useSpawn" placeholder="spawn" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-useSpawn" style="width: 70%;"><span data-i18n="exec.spawn"></span></label>
|
||||
<label for="node-input-useSpawn" style="width:70%;"><span data-i18n="exec.spawn"></span></label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-timer"><i class="fa fa-clock-o"></i> <span data-i18n="exec.label.timeout"></span></label>
|
||||
<input type="text" id="node-input-timer" style="width:50px; text-align:end;" data-i18n="[placeholder]exec.label.timeoutplace"> seconds
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
|
@ -59,6 +63,7 @@
|
|||
addpay: {value:true},
|
||||
append: {value:""},
|
||||
useSpawn: {value:""},
|
||||
timer: {value:""},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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({});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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"} ];
|
||||
|
|
Loading…
Reference in New Issue