1
0
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:
Dave Conway-Jones 2017-02-07 20:13:05 +00:00
parent 1841fc18fa
commit ada8e447cc
3 changed files with 175 additions and 115 deletions

View File

@ -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() {

View File

@ -38,8 +38,20 @@ module.exports = function(RED) {
} }
this.on("input", function(msg) { this.on("input", function(msg) {
if (msg.hasOwnProperty("kill")) {
if (node.activeProcesses.hasOwnProperty(msg.kill) ) {
node.activeProcesses[msg.kill].kill();
node.status({fill:"red",shape:"dot",text:"killed"});
}
else {
if (Object.keys(node.activeProcesses).length === 1) {
node.activeProcesses[Object.keys(node.activeProcesses)[0]].kill();
node.status({fill:"red",shape:"dot",text:"killed"});
}
}
}
else {
var child; var child;
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
@ -53,6 +65,7 @@ module.exports = function(RED) {
/* istanbul ignore else */ /* istanbul ignore else */
if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); } if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); }
child = spawn(cmd,arg); child = spawn(cmd,arg);
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
var unknownCommand = (child.pid === undefined); var unknownCommand = (child.pid === undefined);
if (node.timer !== 0) { if (node.timer !== 0) {
child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer); child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer);
@ -80,8 +93,8 @@ module.exports = function(RED) {
msg.payload = code; msg.payload = code;
if (code === 0) { node.status({}); } if (code === 0) { node.status({}); }
if (code === null) { node.status({fill:"red",shape:"dot",text:"timeout"}); } 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 if (code < 0) { node.status({fill:"red",shape:"dot",text:"rc:"+code}); }
else { node.status({fill:"yellow",shape:"dot",text:"rc: "+code}); } else { node.status({fill:"yellow",shape:"dot",text:"rc:"+code}); }
node.send([null,null,RED.util.cloneMessage(msg)]); node.send([null,null,RED.util.cloneMessage(msg)]);
} }
}); });
@ -110,17 +123,19 @@ module.exports = function(RED) {
msg3 = {payload:error}; msg3 = {payload:error};
//console.log('[exec] error: ' + error); //console.log('[exec] error: ' + error);
} }
node.status({}); if (!msg3) { node.status({}); }
node.send([msg,msg2,msg3]); node.send([msg,msg2,msg3]);
if (child.tout) { clearTimeout(child.tout); } if (child.tout) { clearTimeout(child.tout); }
delete node.activeProcesses[child.pid]; delete node.activeProcesses[child.pid];
}); });
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
child.on('error',function() {}); child.on('error',function() {});
if (node.timer !== 0) { if (node.timer !== 0) {
child.tout = setTimeout(function() { cleanup(child.pid); }, node.timer); 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() {
for (var pid in node.activeProcesses) { for (var pid in node.activeProcesses) {

View File

@ -70,7 +70,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();
@ -78,7 +78,7 @@ describe('exec node', function() {
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();
@ -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({});
});
});
}); });
}); });