diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index 19ef1622dd..cc7a2da5b5 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -165,19 +165,35 @@ async function fireStaticHook(hook, hookList, params) { if (hookFn.constructor && hookFn.constructor.name !== 'AsyncFunction') { hookFn = util.promisify(hookFn); } + try { // eslint-disable-next-line - await hookFn(params); + await timeout(hookFn(params), 5000, 'timeout'); } catch (err) { - winston.error(`[plugins] Error executing '${hook}' in plugin '${hookObj.id}'\n${err.stack}`); - if (!noErrorHooks.includes(hook)) { - throw err; + if (err && err.message === 'timeout') { + winston.warn(`[plugins] Callback timed out, hook '${hook}' in plugin '${hookObj.id}'`); + } else { + winston.error(`[plugins] Error executing '${hook}' in plugin '${hookObj.id}'\n${err.stack}`); + if (!noErrorHooks.includes(hook)) { + throw err; + } } } } } } +// https://advancedweb.hu/how-to-add-timeout-to-a-promise-in-javascript/ +const timeout = (prom, time, error) => { + let timer; + return Promise.race([ + prom, + new Promise((resolve, reject) => { + timer = setTimeout(reject, time, new Error(error)); + }), + ]).finally(() => clearTimeout(timer)); +}; + async function fireResponseHook(hook, hookList, params) { if (!Array.isArray(hookList) || !hookList.length) { return; diff --git a/test/plugins.js b/test/plugins.js index f0728ff7e1..bf4a600ff8 100644 --- a/test/plugins.js +++ b/test/plugins.js @@ -135,6 +135,21 @@ describe('Plugins', () => { }); }); + it('should register and timeout a static hook returning a promise but takes too long', (done) => { + async function method(data) { + assert.equal(data.bar, 'test'); + return new Promise((resolve) => { + setTimeout(resolve, 6000); + }); + } + plugins.hooks.register('test-plugin', { hook: 'static:test.hook', method: method }); + plugins.hooks.fire('static:test.hook', { bar: 'test' }, (err) => { + assert.ifError(err); + plugins.hooks.unregister('test-plugin', 'static:test.hook', method); + done(); + }); + }); + it('should get plugin data from nbbpm', (done) => { plugins.get('nodebb-plugin-markdown', (err, data) => { assert.ifError(err);