mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
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.
This commit is contained in:
parent
4ad540412a
commit
bd59398cab
@ -31,7 +31,11 @@
|
|||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label> </label>
|
<label> </label>
|
||||||
<input type="checkbox" id="node-input-useSpawn" placeholder="spawn" style="display: inline-block; width: auto; vertical-align: top;">
|
<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>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
<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},
|
addpay: {value:true},
|
||||||
append: {value:""},
|
append: {value:""},
|
||||||
useSpawn: {value:""},
|
useSpawn: {value:""},
|
||||||
|
timer: {value:""},
|
||||||
name: {value:""}
|
name: {value:""}
|
||||||
},
|
},
|
||||||
inputs:1,
|
inputs:1,
|
||||||
|
@ -27,55 +27,64 @@ module.exports = function(RED) {
|
|||||||
this.addpay = n.addpay;
|
this.addpay = n.addpay;
|
||||||
this.append = (n.append || "").trim();
|
this.append = (n.append || "").trim();
|
||||||
this.useSpawn = n.useSpawn;
|
this.useSpawn = n.useSpawn;
|
||||||
|
this.timer = Number(n.timer || 0)*1000;
|
||||||
this.activeProcesses = {};
|
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;
|
var node = this;
|
||||||
this.on("input", function(msg) {
|
this.on("input", function(msg) {
|
||||||
|
var child;
|
||||||
node.status({fill:"blue",shape:"dot",text:" "});
|
node.status({fill:"blue",shape:"dot",text:" "});
|
||||||
if (this.useSpawn === true) {
|
if (this.useSpawn === true) {
|
||||||
// make the extra args into an array
|
// make the extra args into an array
|
||||||
// then prepend with the msg.payload
|
// then prepend with the msg.payload
|
||||||
|
|
||||||
var arg = node.cmd;
|
var arg = node.cmd;
|
||||||
if (node.addpay) {
|
if (node.addpay) { arg += " "+msg.payload; }
|
||||||
arg += " "+msg.payload;
|
|
||||||
}
|
|
||||||
arg += " "+node.append;
|
arg += " "+node.append;
|
||||||
// slice whole line by spaces (trying to honour quotes);
|
// slice whole line by spaces (trying to honour quotes);
|
||||||
arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g);
|
arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g);
|
||||||
var cmd = arg.shift();
|
var cmd = arg.shift();
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); }
|
if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); }
|
||||||
if (cmd.indexOf(" ") == -1) {
|
child = spawn(cmd,arg);
|
||||||
var ex = spawn(cmd,arg);
|
if (node.timer !== 0) {
|
||||||
node.activeProcesses[ex.pid] = ex;
|
child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
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 {
|
else {
|
||||||
var cl = node.cmd;
|
var cl = node.cmd;
|
||||||
@ -83,13 +92,9 @@ module.exports = function(RED) {
|
|||||||
if (node.append.trim() !== "") { cl += " "+node.append; }
|
if (node.append.trim() !== "") { cl += " "+node.append; }
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (RED.settings.verbose) { node.log(cl); }
|
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");
|
msg.payload = new Buffer(stdout,"binary");
|
||||||
try {
|
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
|
||||||
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
|
|
||||||
} catch(e) {
|
|
||||||
node.log(RED._("exec.badstdout"));
|
|
||||||
}
|
|
||||||
var msg2 = {payload:stderr};
|
var msg2 = {payload:stderr};
|
||||||
var msg3 = null;
|
var msg3 = null;
|
||||||
//console.log('[exec] stdout: ' + stdout);
|
//console.log('[exec] stdout: ' + stdout);
|
||||||
@ -100,9 +105,13 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
node.status({});
|
node.status({});
|
||||||
node.send([msg,msg2,msg3]);
|
node.send([msg,msg2,msg3]);
|
||||||
|
if (child.tout) { clearTimeout(child.tout); }
|
||||||
delete node.activeProcesses[child.pid];
|
delete node.activeProcesses[child.pid];
|
||||||
});
|
});
|
||||||
child.on('error',function() {});
|
child.on('error',function() {});
|
||||||
|
if (node.timer !== 0) {
|
||||||
|
child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
|
||||||
|
}
|
||||||
node.activeProcesses[child.pid] = child;
|
node.activeProcesses[child.pid] = child;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -110,10 +119,12 @@ module.exports = function(RED) {
|
|||||||
for (var pid in node.activeProcesses) {
|
for (var pid in node.activeProcesses) {
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (node.activeProcesses.hasOwnProperty(pid)) {
|
if (node.activeProcesses.hasOwnProperty(pid)) {
|
||||||
|
if (node.activeProcesses[pid].tout) { clearTimeout(node.activeProcesses[pid].tout); }
|
||||||
node.activeProcesses[pid].kill();
|
node.activeProcesses[pid].kill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.activeProcesses = {};
|
node.activeProcesses = {};
|
||||||
|
node.status({});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("exec",ExecNode);
|
RED.nodes.registerType("exec",ExecNode);
|
||||||
|
@ -124,11 +124,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exec": {
|
"exec": {
|
||||||
"spawnerr": "Spawn command must be just the command - no spaces or extra parameters",
|
|
||||||
"badstdout": "Bad STDOUT",
|
|
||||||
"label": {
|
"label": {
|
||||||
"command": "Command",
|
"command": "Command",
|
||||||
"append": "Append"
|
"append": "Append",
|
||||||
|
"timeout": "Timeout",
|
||||||
|
"timeoutplace": "optional"
|
||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"extraparams": "extra input parameters"
|
"extraparams": "extra input parameters"
|
||||||
|
@ -41,6 +41,7 @@ describe('exec node', function() {
|
|||||||
n1.should.have.property("cmd", "");
|
n1.should.have.property("cmd", "");
|
||||||
n1.should.have.property("append", "");
|
n1.should.have.property("append", "");
|
||||||
n1.should.have.property("addpay",true);
|
n1.should.have.property("addpay",true);
|
||||||
|
n1.should.have.property("timer",0);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -139,6 +140,33 @@ describe('exec node', function() {
|
|||||||
n1.receive({});
|
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() {
|
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) {
|
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"]] },
|
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"{{payload}}", op2:"{{topic}}", duration:50, wires:[["n2"]] },
|
||||||
{id:"n2", type:"helper"} ];
|
{id:"n2", type:"helper"} ];
|
||||||
|
Loading…
Reference in New Issue
Block a user