0 Design: Node module lifecycle
Nick O'Leary edited this page 2016-10-21 10:30:25 +01:00

tldr; a node's runtime file (it's .js file) needs to have a way to provide a callback function the runtime can call when the node module is being removed

With the palette editor making it easier to add/remove nodes from the editor, a gap has been revealed in this structure. Specifically, there is no hook in there for when the runtime wants to remove the module. If it has created any http route handlers (for example), they will be left behind.

Existing node runtime structure

Currently a node's runtime component is provided as a node.js module. This is loaded into the runtime using the standard require function.

The node module is expected to have the basic structure:

module.exports = function(RED) {

}

The function it exports is called by the runtime, passing in the RED object as a reference to the runtime api.

The function can optionally return a promise object that will resolve once the node module has been properly initialised. This is used if the node needs to take any asynchronous actions before it can decide if it's runtime dependencies are fully satisfied - for example, checking the presence of an external system.

var when = require('when');
module.exports = function(RED) {
    return when.promise(function(resolve,reject) {
        // initialise node module
        // eventually either call resolve() if all is good,
        // or call reject("my error message") if all is not good
    });
}

Proposed changes

All of the following is optional - nodes will continue to work as written today

The exported function should now return a object that identifies additional configuration properties of the module. The only property this object may contain is the remove function - to be called when the module is being removed from the runtime.

module.exports = function(RED) {
    // Called when the module is loaded into the runtime.
    // Initialise any state etc and register node constructors

    return {
        remove: function() {
            // called when this module is being removed from the runtime
        }
    }
}

Alternatively, if a node needs to initialise asynchronously, it can return a promise (as before), that resolves to the config object:

module.exports = function(RED) {
    // Called when the module is loaded into the runtime.
    // Initialise any state etc and register node constructors

    return when.promise(function(resolve,reject) {
        resolve({
            remove: function() {
                // called when this module is being removed from the runtime
            }
        });
     });
}

Other considerations

There are a number of initialisation actions a node may take today, such as registering its node types and adding an HTTP route handlers, amongst others.

As part of this work, we need to ensure every action can be undone.

There are two possible approaches. One is to provide suitable functions under RED to undo any actions taken. That would allow actions to be undone at other times, not just unload.

The alternative is to wrap the functions provided by RED to automatically record what actions are taken, and then automatically tidy them up when needed. That makes it easier for a node as it doesn't have to do anything explicit - in fact it would help to tidy up existing nodes that don't adopt this new scheme.

The options are not mutually exclusive. At a minimum, there should be functions to undo any action taken (registerType being a possible exception). If the runtime also tidies up remaining resources, so much the better.

Action Function Reversed by
Register node RED.nodes.registerType Handled by the runtime as part of the unload process
Register http routes RED.httpAdmin.get/post/etc Add RED.httpAdmin.remove function to undo them
Register log handler RED.log.addHandler (done) Add RED.log.removeHandler