From df59acf62a4383b0727e2be2d179e407b7c705ea Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Tue, 1 Oct 2024 22:56:36 +0300 Subject: [PATCH] feature: support async plugin.stop implementation Fixes #1802 Add support for plugins returning a Promise from their stop implementation to allow async stop operations. In plugin restarts, mainly when changing configuration, we wait for the optional stop Promise to resolve before calling plugin.start(). --- docs/src/develop/plugins/server_plugin.md | 4 ++- src/interfaces/plugins.ts | 33 ++++++++++++++--------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/docs/src/develop/plugins/server_plugin.md b/docs/src/develop/plugins/server_plugin.md index 73309440e..4df2ba849 100644 --- a/docs/src/develop/plugins/server_plugin.md +++ b/docs/src/develop/plugins/server_plugin.md @@ -213,10 +213,12 @@ A plugin must return an object containing the following functions: - `start(settings, restartPlugin)`: This function is called when the plugin is enabled or when the server starts (and the plugin is enabled). The `settings` parameter contains the configuration data entered via the **Plugin Config** screen. `restartPlugin` is a function that can be called by the plugin to restart itself. - `stop()`: This function is called when the plugin is disabled or after configuration changes. Use this function to "clean up" the resources consumed by the plugin i.e. unsubscribe from streams, stop timers / loops and close devices. +If there are asynchronous operations in your plugin's stop implementation you should return a Promise that resolves +when stopping is complete. - `schema()`: A function that returns an object defining the schema of the plugin's configuration data. It is used by the server to generate the user interface in the **Plugin Config** screen. -_Note: When a plugin's configuration is changed the server will first call `stop()` to stop the plugin and then `start()` with the new configuration data._ +_Note: When a plugin's configuration is changed the server will first call `stop()` to stop the plugin and then `start()` with the new configuration data. Return a Promise from `stop` if needed so that `start` is not called before stopping is complete._ A plugin can also contain the following optional functions: diff --git a/src/interfaces/plugins.ts b/src/interfaces/plugins.ts index 3d58ecd6a..b99d4e4f1 100644 --- a/src/interfaces/plugins.ts +++ b/src/interfaces/plugins.ts @@ -418,7 +418,7 @@ module.exports = (theApp: any) => { } } - function stopPlugin(plugin: PluginInfo) { + function stopPlugin(plugin: PluginInfo): Promise { debug('Stopping plugin ' + plugin.name) onStopHandlers[plugin.id].forEach((f: () => void) => { try { @@ -428,9 +428,12 @@ module.exports = (theApp: any) => { } }) onStopHandlers[plugin.id] = [] - plugin.stop() - theApp.setPluginStatus(plugin.id, 'Stopped') - debug('Stopped plugin ' + plugin.name) + const result = Promise.resolve(plugin.stop()) + result.then(() => { + theApp.setPluginStatus(plugin.id, 'Stopped') + debug('Stopped plugin ' + plugin.name) + }) + return result } function setPluginStartedMessage(plugin: PluginInfo) { @@ -657,8 +660,11 @@ module.exports = (theApp: any) => { if (err) { console.error(err) } else { - stopPlugin(plugin) - doPluginStart(app, plugin, location, newConfiguration, restart) + stopPlugin(plugin).then(() => { + return Promise.resolve( + doPluginStart(app, plugin, location, newConfiguration, restart) + ) + }) } }) } @@ -713,13 +719,14 @@ module.exports = (theApp: any) => { return } res.json('Saved configuration for plugin ' + plugin.id) - stopPlugin(plugin) - const options = getPluginOptions(plugin.id) - plugin.enableLogging = options.enableLogging - plugin.enableDebug = options.enableDebug - if (options.enabled) { - doPluginStart(app, plugin, location, options.configuration, restart) - } + stopPlugin(plugin).then(() => { + const options = getPluginOptions(plugin.id) + plugin.enableLogging = options.enableLogging + plugin.enableDebug = options.enableDebug + if (options.enabled) { + doPluginStart(app, plugin, location, options.configuration, restart) + } + }) }) })