Skip to content

Commit

Permalink
feature: support async plugin.stop implementation
Browse files Browse the repository at this point in the history
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().
  • Loading branch information
tkurki committed Oct 6, 2024
1 parent 31b4a2e commit df59acf
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 14 deletions.
4 changes: 3 additions & 1 deletion docs/src/develop/plugins/server_plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
33 changes: 20 additions & 13 deletions src/interfaces/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ module.exports = (theApp: any) => {
}
}

function stopPlugin(plugin: PluginInfo) {
function stopPlugin(plugin: PluginInfo): Promise<any> {
debug('Stopping plugin ' + plugin.name)
onStopHandlers[plugin.id].forEach((f: () => void) => {
try {
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
)
})
}
})
}
Expand Down Expand Up @@ -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)
}
})
})
})

Expand Down

0 comments on commit df59acf

Please sign in to comment.