1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

adding timeout attribute to function node

- [x] New feature (non-breaking change which adds functionality)

Discussion here:
https://discourse.nodered.org/t/function-node-doesnt-have-timeout-feature/78483

## Proposed changes

Adding a timeout attribute to the function node, so an endless funciton doesnt break the node red server.

## Checklist

- [x] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
- [x] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [x] I have run `grunt` to verify the unit tests pass
- [x] I have added suitable unit tests to cover the new/changed functionality
This commit is contained in:
Kilian Hertel 2023-05-22 10:16:37 +02:00
parent 67dd7e30fa
commit 2253417459
6 changed files with 78 additions and 8 deletions

View File

@ -82,6 +82,11 @@
<input id="node-input-outputs" style="width: 60px;" value="1">
</div>
<div class="form-row">
<label for="node-input-timeout"><i class="fa fa-clock"></i> <span data-i18n="function.label.timeout"></span></label>
<input id="node-input-timeout" style="width: 60px;" value="1000">
</div>
<div class="form-row node-input-libs-row hide" style="margin-bottom: 0px;">
<label><i class="fa fa-cubes"></i> <span data-i18n="function.label.modules"></span></label>
</div>
@ -360,6 +365,7 @@
name: {value:"_DEFAULT_"},
func: {value:"\nreturn msg;"},
outputs: {value:1},
timeout:{value:0},
noerr: {value:0,required:true,
validate: function(v, opt) {
if (!v) {
@ -464,6 +470,26 @@
}
});
// 4294967295 is max in node.js timeout.
$( "#node-input-timeout" ).spinner({
min: 0,
max: 4294967294,
change: function(event, ui) {
var value = this.value;
if(value == ""){
value = 0;
}
else
{
value = parseInt(value);
}
value = isNaN(value) ? 1 : value;
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
if (value !== this.value) { $(this).spinner("value", value); }
}
});
var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs, offset) {
var editor = RED.editor.createEditor({
id: id,
@ -503,7 +529,7 @@
editor:this.editor, // the field name the main text body goes to
mode:"ace/mode/nrjavascript",
fields:[
'name', 'outputs',
'name', 'outputs', 'timeout',
{
name: 'initialize',
get: function() {

View File

@ -96,6 +96,13 @@ module.exports = function(RED) {
node.name = n.name;
node.func = n.func;
node.outputs = n.outputs;
node.timeout = n.timeout*1;
if(node.timeout>0){
node.timeoutOptions = {
timeout:node.timeout,
breakOnSigint:true
}
}
node.ini = n.initialize ? n.initialize.trim() : "";
node.fin = n.finalize ? n.finalize.trim() : "";
node.libs = n.libs || [];
@ -362,6 +369,10 @@ module.exports = function(RED) {
})(__initSend__);`;
iniOpt = createVMOpt(node, " setup");
iniScript = new vm.Script(iniText, iniOpt);
if(node.timeout>0){
iniOpt.timeout = node.timeout;
iniOpt.breakOnSigint = true;
}
}
node.script = vm.createScript(functionText, createVMOpt(node, ""));
if (node.fin && (node.fin !== "")) {
@ -385,6 +396,10 @@ module.exports = function(RED) {
})();`;
finOpt = createVMOpt(node, " cleanup");
finScript = new vm.Script(finText, finOpt);
if(node.timeout>0){
finOpt.timeout = node.timeout;
finOpt.breakOnSigint = true;
}
}
var promise = Promise.resolve();
if (iniScript) {
@ -396,9 +411,12 @@ module.exports = function(RED) {
var start = process.hrtime();
context.msg = msg;
context.__send__ = send;
context.__done__ = done;
node.script.runInContext(context);
context.__done__ = done;
var opts = {};
if (node.timeout>0){
opts = node.timeoutOptions;
}
node.script.runInContext(context,opts);
context.results.then(function(results) {
sendResults(node,send,msg._msgid,results,false);
if (handleNodeDoneCall) {

View File

@ -214,7 +214,8 @@
"initialize": "Start",
"finalize": "Stopp",
"outputs": "Ausgänge",
"modules": "Module"
"modules": "Module",
"timeout": "Timeout (ms)"
},
"text": {
"initialize": "// Der Code hier wird ausgeführt,\n// wenn der Node gestartet wird\n",

View File

@ -248,7 +248,8 @@
"initialize": "On Start",
"finalize": "On Stop",
"outputs": "Outputs",
"modules": "Modules"
"modules": "Modules",
"timeout": "Timeout (ms)"
},
"text": {
"initialize": "// Code added here will be run once\n// whenever the node is started.\n",

View File

@ -212,7 +212,8 @@
"function": "Функция",
"initialize": "Настройка",
"finalize": "Закрытие",
"outputs": "Выходы"
"outputs": "Выходы",
"timeout":"Время ожидания (мс)"
},
"text": {
"initialize": "// Добавленный здесь код будет исполняться\n// однократно при развертывании узла.\n",

View File

@ -1424,7 +1424,30 @@ describe('function node', function() {
});
});
it('should timeout if timeout is set', function(done) {
var flow = [{id:"n1",type:"function",wires:[["n2"]],timeout:"10",func:"while(1==1){};\nreturn msg;"}];
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
n1.receive({payload:"foo",topic: "bar"});
setTimeout(function() {
try {
helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "function";
});
logEvents.should.have.length(1);
var msg = logEvents[0][0];
msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('id', 'n1');
msg.should.have.property('type', 'function');
should.equal(msg.msg.message, 'Script execution timed out after 10ms');
done();
} catch(err) {
done(err);
}
},50);
});
});
describe("finalize function", function() {