diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.js b/packages/node_modules/@node-red/nodes/core/function/10-function.js index 71f6e15d6..0fcdd5877 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.js +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.js @@ -403,6 +403,8 @@ module.exports = function(RED) { if(node.timeout>0){ finOpt.timeout = node.timeout; finOpt.breakOnSigint = true; + } else if (RED.settings.globalFunctionTimeout > 0){ + finOpt.timeout = RED.settings.globalFunctionTimeout * 1000 } } var promise = Promise.resolve(); @@ -419,8 +421,14 @@ module.exports = function(RED) { var opts = {}; if (node.timeout>0){ opts = node.timeoutOptions; + } else if (RED.settings. globalFunctionTimeout > 0){ + opts.timeout = RED.settings. globalFunctionTimeout * 1000 + } + try { + node.script.runInContext(context,opts); + } catch (err) { + return done(err); } - node.script.runInContext(context,opts); context.results.then(function(results) { sendResults(node,send,msg._msgid,results,false); if (handleNodeDoneCall) { diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index 5c4ce0e94..269cac160 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -490,6 +490,7 @@ module.exports = { * - fileWorkingDirectory * - functionGlobalContext * - functionExternalModules + * - globalFunctionTimeout * - functionTimeout * - nodeMessageBufferMaxLength * - ui (for use with Node-RED Dashboard) @@ -516,7 +517,19 @@ module.exports = { /** Allow the Function node to load additional npm modules directly */ functionExternalModules: true, - /** Default timeout, in seconds, for the Function node. 0 means no timeout is applied */ + + /** + * The default timeout (in seconds) for all Function nodes. + * Individual nodes can set their own timeout value within their configuration. + */ + globalFunctionTimeout: 0, + + /** + * Default timeout, in seconds, for the Function node. 0 means no timeout is applied + * This value is applied when the node is first added to the workspace - any changes + * must then be made with the individual node configurations. + * To set a global timeout value, use `globalFunctionTimeout` + */ functionTimeout: 0, /** The following property can be used to set predefined values in Global Context. diff --git a/test/nodes/core/function/10-function_spec.js b/test/nodes/core/function/10-function_spec.js index 56c4ec976..6a04547f4 100644 --- a/test/nodes/core/function/10-function_spec.js +++ b/test/nodes/core/function/10-function_spec.js @@ -1451,7 +1451,7 @@ describe('function node', function() { }); }); - it('check if default function timeout settings are recognized', function (done) { + it('check if function timeout settings are recognized', function (done) { RED.settings.functionTimeout = 0.01; var flow = [{id: "n1",type: "function",timeout: RED.settings.functionTimeout,wires: [["n2"]],func: "while(1==1){};\nreturn msg;"}]; helper.load(functionNode, flow, function () { @@ -1479,6 +1479,65 @@ describe('function node', function() { }); }); + it('check if default function timeout settings are recognized', function (done) { + RED.settings.globalFunctionTimeout = 0.01; + var flow = [{id: "n1",type: "function",wires: [["n2"]],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(RED.settings.globalFunctionTimeout, 0.01); + should.equal(msg.msg.message, 'Script execution timed out after 10ms'); + delete RED.settings.globalFunctionTimeout; + done(); + } catch (err) { + done(err); + } + }, 500); + }); + }); + + it('check if functionTimeout has higher precedence over default function timeout setting', function (done) { + RED.settings.globalFunctionTimeout = 0.02; + RED.settings.functionTimeout = 0.01; + var flow = [{id: "n1",type: "function",timeout: RED.settings.functionTimeout,wires: [["n2"]],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(RED.settings.functionTimeout, 0.01); + should.equal(RED.settings.globalFunctionTimeout, 0.02); + should.equal(msg.msg.message, 'Script execution timed out after 10ms'); + delete RED.settings.functionTimeout; + delete RED.settings.globalFunctionTimeout; + done(); + } catch (err) { + done(err); + } + }, 500); + }); + }); + describe("finalize function", function() { it('should execute', function(done) {