mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
exec node can be killed on demand
This commit is contained in:
parent
1841fc18fa
commit
ada8e447cc
@ -55,8 +55,10 @@
|
|||||||
things like pipe the result to another command.</p>
|
things like pipe the result to another command.</p>
|
||||||
<p>Commands or parameters with spaces should be enclosed in quotes - <i>"This is a single parameter"</i></p>
|
<p>Commands or parameters with spaces should be enclosed in quotes - <i>"This is a single parameter"</i></p>
|
||||||
<p>If stdout is binary a <i>buffer</i> is returned - otherwise returns a <i>string</i>.</p>
|
<p>If stdout is binary a <i>buffer</i> is returned - otherwise returns a <i>string</i>.</p>
|
||||||
<p>The blue status icon will be visible while the node is active.</p>
|
<p>The blue status icon and PID will be visible while the node is active.</p>
|
||||||
<p>If running a Python app you may need to use the <code>-u</code> parameter to stop the output being buffered.</p>
|
<p>Sending <code>msg.kill</code> will kill a single active process. If there is more than one process running then
|
||||||
|
<code>msg.kill</code> must be set with the value of the PID to be killed.</p>
|
||||||
|
<p>Tip: If running a Python app you may need to use the <code>-u</code> parameter to stop the output being buffered.</p>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -73,6 +75,7 @@
|
|||||||
},
|
},
|
||||||
inputs:1,
|
inputs:1,
|
||||||
outputs:3,
|
outputs:3,
|
||||||
|
outputLabels: ["stdout","stderr","rc"],
|
||||||
icon: "arrow-in.png",
|
icon: "arrow-in.png",
|
||||||
align: "right",
|
align: "right",
|
||||||
label: function() {
|
label: function() {
|
||||||
|
@ -38,88 +38,103 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.on("input", function(msg) {
|
this.on("input", function(msg) {
|
||||||
var child;
|
if (msg.hasOwnProperty("kill")) {
|
||||||
node.status({fill:"blue",shape:"dot",text:" "});
|
if (node.activeProcesses.hasOwnProperty(msg.kill) ) {
|
||||||
if (this.useSpawn === true) {
|
node.activeProcesses[msg.kill].kill();
|
||||||
// make the extra args into an array
|
node.status({fill:"red",shape:"dot",text:"killed"});
|
||||||
// then prepend with the msg.payload
|
|
||||||
var arg = node.cmd;
|
|
||||||
if ((node.addpay === true) && msg.hasOwnProperty("payload")) { arg += " "+msg.payload; }
|
|
||||||
if (node.append.trim() !== "") { arg += " "+node.append; }
|
|
||||||
// slice whole line by spaces (trying to honour quotes);
|
|
||||||
arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g);
|
|
||||||
var cmd = arg.shift();
|
|
||||||
if (/^".*"$/.test(cmd)) { cmd = cmd.slice(1,-1); }
|
|
||||||
/* istanbul ignore else */
|
|
||||||
if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); }
|
|
||||||
child = spawn(cmd,arg);
|
|
||||||
var unknownCommand = (child.pid === undefined);
|
|
||||||
if (node.timer !== 0) {
|
|
||||||
child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
|
|
||||||
}
|
}
|
||||||
node.activeProcesses[child.pid] = child;
|
else {
|
||||||
child.stdout.on('data', function (data) {
|
if (Object.keys(node.activeProcesses).length === 1) {
|
||||||
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
node.activeProcesses[Object.keys(node.activeProcesses)[0]].kill();
|
||||||
// console.log('[exec] stdout: ' + data,child.pid);
|
node.status({fill:"red",shape:"dot",text:"killed"});
|
||||||
if (isUtf8(data)) { msg.payload = data.toString(); }
|
|
||||||
else { msg.payload = data; }
|
|
||||||
node.send([RED.util.cloneMessage(msg),null,null]);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
child.stderr.on('data', function (data) {
|
|
||||||
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
|
||||||
if (isUtf8(data)) { msg.payload = data.toString(); }
|
|
||||||
else { msg.payload = new Buffer(data); }
|
|
||||||
node.send([null,RED.util.cloneMessage(msg),null]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
child.on('close', function (code) {
|
|
||||||
if (unknownCommand || (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null)) {
|
|
||||||
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,RED.util.cloneMessage(msg)]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
child.on('error', function (code) {
|
|
||||||
if (child.tout) { clearTimeout(child.tout); }
|
|
||||||
delete node.activeProcesses[child.pid];
|
|
||||||
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
|
||||||
node.error(code,RED.util.cloneMessage(msg));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var cl = node.cmd;
|
var child;
|
||||||
if ((node.addpay === true) && msg.hasOwnProperty("payload")) { cl += " "+msg.payload; }
|
if (this.useSpawn === true) {
|
||||||
if (node.append.trim() !== "") { cl += " "+node.append; }
|
// make the extra args into an array
|
||||||
/* istanbul ignore else */
|
// then prepend with the msg.payload
|
||||||
if (RED.settings.verbose) { node.log(cl); }
|
var arg = node.cmd;
|
||||||
child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) {
|
if ((node.addpay === true) && msg.hasOwnProperty("payload")) { arg += " "+msg.payload; }
|
||||||
msg.payload = new Buffer(stdout,"binary");
|
if (node.append.trim() !== "") { arg += " "+node.append; }
|
||||||
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
|
// slice whole line by spaces (trying to honour quotes);
|
||||||
var msg2 = {payload:stderr};
|
arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g);
|
||||||
var msg3 = null;
|
var cmd = arg.shift();
|
||||||
//console.log('[exec] stdout: ' + stdout);
|
if (/^".*"$/.test(cmd)) { cmd = cmd.slice(1,-1); }
|
||||||
//console.log('[exec] stderr: ' + stderr);
|
/* istanbul ignore else */
|
||||||
if (error !== null) {
|
if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); }
|
||||||
msg3 = {payload:error};
|
child = spawn(cmd,arg);
|
||||||
//console.log('[exec] error: ' + error);
|
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
|
||||||
|
var unknownCommand = (child.pid === undefined);
|
||||||
|
if (node.timer !== 0) {
|
||||||
|
child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
|
||||||
}
|
}
|
||||||
node.status({});
|
node.activeProcesses[child.pid] = child;
|
||||||
node.send([msg,msg2,msg3]);
|
child.stdout.on('data', function (data) {
|
||||||
if (child.tout) { clearTimeout(child.tout); }
|
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
||||||
delete node.activeProcesses[child.pid];
|
// console.log('[exec] stdout: ' + data,child.pid);
|
||||||
});
|
if (isUtf8(data)) { msg.payload = data.toString(); }
|
||||||
child.on('error',function() {});
|
else { msg.payload = data; }
|
||||||
if (node.timer !== 0) {
|
node.send([RED.util.cloneMessage(msg),null,null]);
|
||||||
child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
|
}
|
||||||
|
});
|
||||||
|
child.stderr.on('data', function (data) {
|
||||||
|
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
||||||
|
if (isUtf8(data)) { msg.payload = data.toString(); }
|
||||||
|
else { msg.payload = new Buffer(data); }
|
||||||
|
node.send([null,RED.util.cloneMessage(msg),null]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
child.on('close', function (code) {
|
||||||
|
if (unknownCommand || (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null)) {
|
||||||
|
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,RED.util.cloneMessage(msg)]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
child.on('error', function (code) {
|
||||||
|
if (child.tout) { clearTimeout(child.tout); }
|
||||||
|
delete node.activeProcesses[child.pid];
|
||||||
|
if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) {
|
||||||
|
node.error(code,RED.util.cloneMessage(msg));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var cl = node.cmd;
|
||||||
|
if ((node.addpay === true) && msg.hasOwnProperty("payload")) { cl += " "+msg.payload; }
|
||||||
|
if (node.append.trim() !== "") { cl += " "+node.append; }
|
||||||
|
/* istanbul ignore else */
|
||||||
|
if (RED.settings.verbose) { node.log(cl); }
|
||||||
|
child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) {
|
||||||
|
msg.payload = new Buffer(stdout,"binary");
|
||||||
|
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
|
||||||
|
var msg2 = {payload:stderr};
|
||||||
|
var msg3 = null;
|
||||||
|
//console.log('[exec] stdout: ' + stdout);
|
||||||
|
//console.log('[exec] stderr: ' + stderr);
|
||||||
|
if (error !== null) {
|
||||||
|
msg3 = {payload:error};
|
||||||
|
//console.log('[exec] error: ' + error);
|
||||||
|
}
|
||||||
|
if (!msg3) { node.status({}); }
|
||||||
|
node.send([msg,msg2,msg3]);
|
||||||
|
if (child.tout) { clearTimeout(child.tout); }
|
||||||
|
delete node.activeProcesses[child.pid];
|
||||||
|
});
|
||||||
|
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
|
||||||
|
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;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.on('close',function() {
|
this.on('close',function() {
|
||||||
|
@ -58,42 +58,42 @@ describe('exec node', function() {
|
|||||||
arg3(null,arg1,arg1.toUpperCase());
|
arg3(null,arg1,arg1.toUpperCase());
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.load(execNode, flow, function() {
|
helper.load(execNode, flow, function() {
|
||||||
var n1 = helper.getNode("n1");
|
var n1 = helper.getNode("n1");
|
||||||
var n2 = helper.getNode("n2");
|
var n2 = helper.getNode("n2");
|
||||||
var n3 = helper.getNode("n3");
|
var n3 = helper.getNode("n3");
|
||||||
var n4 = helper.getNode("n4");
|
var n4 = helper.getNode("n4");
|
||||||
var received = 0;
|
var received = 0;
|
||||||
var messages = [null,null];
|
var messages = [null,null];
|
||||||
var completeTest = function() {
|
var completeTest = function() {
|
||||||
received++;
|
received++;
|
||||||
if (received < 2) {
|
if (received < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try{
|
try {
|
||||||
var msg = messages[0];
|
var msg = messages[0];
|
||||||
msg.should.have.property("payload");
|
msg.should.have.property("payload");
|
||||||
msg.payload.should.be.a.String();
|
msg.payload.should.be.a.String();
|
||||||
msg.payload.should.equal("echo");
|
msg.payload.should.equal("echo");
|
||||||
|
|
||||||
msg = messages[1];
|
msg = messages[1];
|
||||||
msg.should.have.property("payload");
|
msg.should.have.property("payload");
|
||||||
msg.payload.should.be.a.String,
|
msg.payload.should.be.a.String;
|
||||||
msg.payload.should.equal("ECHO");
|
msg.payload.should.equal("ECHO");
|
||||||
child_process.exec.restore();
|
child_process.exec.restore();
|
||||||
done();
|
done();
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
child_process.exec.restore();
|
child_process.exec.restore();
|
||||||
done(err);
|
done(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
n2.on("input", function(msg) {
|
n2.on("input", function(msg) {
|
||||||
messages[0] = msg;
|
messages[0] = msg;
|
||||||
completeTest();
|
completeTest();
|
||||||
});
|
});
|
||||||
n3.on("input", function(msg) {
|
n3.on("input", function(msg) {
|
||||||
messages[1] = msg;
|
messages[1] = msg;
|
||||||
completeTest();
|
completeTest();
|
||||||
});
|
});
|
||||||
n1.receive({payload:"and"});
|
n1.receive({payload:"and"});
|
||||||
});
|
});
|
||||||
@ -121,7 +121,7 @@ describe('exec node', function() {
|
|||||||
if (received < 2) {
|
if (received < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try{
|
try {
|
||||||
var msg = messages[0];
|
var msg = messages[0];
|
||||||
msg.should.have.property("payload");
|
msg.should.have.property("payload");
|
||||||
msg.payload.should.be.a.String();
|
msg.payload.should.be.a.String();
|
||||||
@ -210,6 +210,27 @@ describe('exec node', function() {
|
|||||||
n1.receive({});
|
n1.receive({});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to kill a long running command', function(done) {
|
||||||
|
var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"2"},
|
||||||
|
{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);
|
||||||
|
msg.payload.should.have.property("signal","SIGTERM");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
setTimeout(function() {
|
||||||
|
n1.receive({kill:true});
|
||||||
|
},150);
|
||||||
|
n1.receive({});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('calling spawn', function() {
|
describe('calling spawn', function() {
|
||||||
@ -297,7 +318,7 @@ describe('exec node', function() {
|
|||||||
if (received < 2) {
|
if (received < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try{
|
try {
|
||||||
var msg = messages[0];
|
var msg = messages[0];
|
||||||
msg.should.have.property("payload");
|
msg.should.have.property("payload");
|
||||||
msg.payload.should.be.a.String();
|
msg.payload.should.be.a.String();
|
||||||
@ -401,5 +422,26 @@ describe('exec node', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to kill a long running command', function(done) {
|
||||||
|
var flow = [{id:"n1",type:"exec",wires:[["n2"],["n3"],["n4"]],command:"sleep", addpay:false, append:"1", timer:"2"},
|
||||||
|
{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);
|
||||||
|
msg.payload.should.have.property("signal","SIGTERM");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
setTimeout(function() {
|
||||||
|
n1.receive({kill:true});
|
||||||
|
},150);
|
||||||
|
n1.receive({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user