Node-RED custom node stop() method and orphaned event listeners
0 reputation · 27 Aug 2021, 17:54 UTC
0 reputation · 27 Aug 2021, 17:54 UTC
Node-RED utilizes a deployment mechanism that allows updating flow logic without a full process restart. During this cycle, the runtime invokes the stop method on existing nodes to clear timers, intervals, and event listeners before initializing the updated node versions.
While the context API handles state preservation across deployments, the responsibility for resource cleanup falls on the node implementation. If a custom node fails to explicitly clear asynchronous listeners or intervals within its stop function, these resources may persist in the Node.js event loop despite the node being removed from the active flow.
Given the runtime's reliance on the stop method for lifecycle management in version 3.x and 4.x, it is unclear how the system handles nodes that omit this method or encounter errors during its execution.
stop()?34775 reputation · 27 Aug 2021, 18:46 UTC
No — the Node-RED runtime does not provide a fallback mechanism that force-clears timers, intervals, or event listeners for custom nodes that omit stop(). If your node doesn't implement it, the runtime simply has nothing to call, and any asynchronous resources the node created stay alive in the Node.js event loop after the flow is redeployed.
If stop() exists but throws partway through, the runtime catches the exception, logs it, and continues the redeploy. The node is treated as stopped for flow-replacement purposes, but anything not yet cleaned up before the throw remains active. There is no second pass.
This is a structural limitation, not an oversight. Node-RED has no registry of which setInterval handles, EventEmitter subscriptions, or open sockets a given node created. Node.js itself doesn't tag timers or listeners with an owner, so the runtime cannot selectively remove them without risking killing resources belonging to other nodes. Cleanup is therefore delegated entirely to the node implementation — this is well-established behavior across the 3.x and 4.x lines.
Each redeploy instantiates fresh node objects, but orphaned timers and listeners from the previous instances keep firing. The practical consequences:
context() or RED objects, producing confusing behavior that looks like a context bug but is actually a lifecycle bug.Track every resource you create and release all of it in stop(), wrapped so one failure can't skip the rest:
function MyNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.interval = setInterval(() => { /* work */ }, 5000);
node.onMsg = (msg) => { /* ... */ };
node.on('input', node.onMsg);
node.on('close', function (removed, done) {
clearInterval(node.interval);
node.removeListener('input', node.onMsg);
done();
});
}
Note that in current Node-RED the conventional hook is the 'close' event (node.on('close', ...)) rather than a literal stop() method — if your node truly has neither, nothing runs at teardown. The close handler receives a done callback; call it when cleanup finishes so the runtime isn't left waiting.
A quick experiment confirms all of this in a few minutes:
setInterval logging a tick, with no close/stop handler. Deploy, then redeploy three times. You should see the tick frequency multiply — each orphaned interval is still firing.'close' handler that clears the interval. Redeploy and confirm ticks stop after the first redeploy.clearInterval. Redeploy and check the Node-RED log: the error appears, the redeploy completes, and the interval keeps firing.One caveat worth flagging for review: exact log wording and close-handler signature details have shifted slightly across major versions, so confirm against the version you run (node-red --version) rather than assuming 3.x and 4.x behave identically in the details.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.