## Types of changes
- [ ] Bugfix (non-breaking change which fixes an issue)
…- [x] New feature (non-breaking change which adds functionality)
## Proposed changes
Introduce a graceful period in shutdown process to prevent interrupting in-progress processes.
Rework of the excellent work done in #2296 by @k-toumura. This PR contains an updated implementation and a more extensive concept. See key differences.
### Definitions
- `Message Initiator Nodes`: any node that triggers the sending of a message outside of the processing of an incoming message.
- `Shutdown Scope`: any flow capable of receiving a message through the link and subflow nodes for the same workflow.
So, if flow A contains an `inject` node and a `link out` node, flow B contains a `link in` node and a `link out` node, and flow C contains a `link in` node and a `debug` node, the scope will be A, B, and C because the workflow will traverse all three flows.
- `failFast`: cancels graceful shutdown of other flows in the scope. In other words, it forces the shutdown of all nodes of other flows in the scope.
### Concept
Currently, when a flow closes, all nodes are closed in a single-step sequence. With *Graceful Shutdown*, there are **three** steps in the shutdown sequence:
1. Close all message initiator nodes
2. Wait in-progress messages
3. Close remaining nodes
#### Message initiator nodes
These nodes are defined by the user in the UI.
Each flow and subflow contains a new pane that allows the user to choose these nodes.
<img width="506" height="671" src="https://github.com/user-attachments/assets/0b49df43-0892-40e7-ac12-82974bca2f5c" />
>[!WARNING]
> We assume that a message initiator node must have an output and cannot be a link node (it transmits a message, it does not create one).
#### In-progress messages
Each flow contains a message counter. This counter is incremented when a node sends a message and decremented when a node receives a message.
Step 2 will end when the counter reaches 0 or when the timeout is exceeded.
The counter works for a single flow as follows:

If the scope is the flow itself, the counter is that of the flow. Otherwise, the counter is the sum of the counters that make up the scope.
The counter works for a multiple flows as follows:

Each flow can have its own timeout, but the flow's configuration will affect this. If a flow simply forwards the message to the next flow, it may be wanted to close it once the message has been sent.
#### `failFast`
If a flow fails to close in time (timeout exceeded), it can force the shutdown of other flows in the scope that are in shutdown phase.
#### flow/node change deployment
The sequence is identical, but the calculation in step 2 is based on "is one of the nodes I'm stopping waiting for a message?" . We can't use the counter here because other nodes are active. Therefore, we must try to find a point at which nodes we're stopping are no longer receiving messages.
### Key differences
- Support for messages crossing a flow (link or subflow node)
- Simulated Node Messaging API
- Timeout per flow
- Cancel other flow if this one fails
- Support for flow/node change deployment
### Limitations
- An asynchronous job must call `done()` itself, otherwise the counter may be inaccurate (offset). e.g. `function` node.
- Any loop, whatever its nature, is unmanageable.
### Related links
- Originel design: https://github.com/node-red-hitachi/designs/blob/graceful-shutdown/designs/graceful-shutdown/README.md
- Originel implementation: https://github.com/node-red/node-red/pull/2296
### Test flow
```json
[{"id":"b8f08e1162331d0a","type":"subflow","name":"Subflow 3","info":"","category":"","in":[],"out":[{"x":480,"y":100,"wires":[{"id":"57d37452c847dd05","port":0},{"id":"2d54d91583af6c6e","port":0}]}],"env":[],"failFast":false,"timeout":10000,"initiatorNodes":["5a16348a0fb755d2"],"meta":{},"color":"#DDAA99"},{"id":"9a3fae6bcb73e105","type":"debug","z":"b8f08e1162331d0a","name":"debug 10","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":340,"y":160,"wires":[]},{"id":"5a16348a0fb755d2","type":"inject","z":"b8f08e1162331d0a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"","payloadType":"date","x":150,"y":100,"wires":[["9a3fae6bcb73e105","57d37452c847dd05"]]},{"id":"57d37452c847dd05","type":"delay","z":"b8f08e1162331d0a","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":340,"y":100,"wires":[[]]},{"id":"2d54d91583af6c6e","type":"http in","z":"b8f08e1162331d0a","name":"","url":"/test","method":"get","upload":false,"skipBodyParsing":false,"swaggerDoc":"","x":340,"y":60,"wires":[[]]},{"id":"52fbdebd011809bf","type":"subflow","name":"Subflow 2","info":"","in":[{"x":60,"y":40,"wires":[{"id":"914b123ba249824b"}]}],"out":[]},{"id":"7e6b5f724f3828d3","type":"debug","z":"52fbdebd011809bf","name":"debug 5","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":340,"y":40,"wires":[]},{"id":"4caf849443ebc04e","type":"debug","z":"52fbdebd011809bf","name":"debug 6","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":340,"y":100,"wires":[]},{"id":"914b123ba249824b","type":"delay","z":"52fbdebd011809bf","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":180,"y":40,"wires":[["7e6b5f724f3828d3","4caf849443ebc04e"]]},{"id":"5025fc31b2f76672","type":"subflow","name":"Subflow 1","info":"","category":"","in":[{"x":80,"y":60,"wires":[{"id":"a24214ee0347c2a3"}]}],"out":[{"x":320,"y":60,"wires":[{"id":"a24214ee0347c2a3","port":0}]}],"env":[],"failFast":false,"timeout":10000,"initiatorNodes":["1930e055ab59b657"],"meta":{},"color":"#DDAA99"},{"id":"a24214ee0347c2a3","type":"delay","z":"5025fc31b2f76672","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":200,"y":60,"wires":[[]]},{"id":"48f3cacf88806e9b","type":"tab","label":"Flow 1","disabled":false,"info":"","env":[],"failFast":false,"timeout":6000},{"id":"fcfe154798ae59b2","type":"debug","z":"48f3cacf88806e9b","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":680,"y":140,"wires":[]},{"id":"027c18126a348a83","type":"link out","z":"48f3cacf88806e9b","name":"link out flow 1","mode":"link","links":["9ddc5b8b594ea056"],"x":435,"y":220,"wires":[]},{"id":"9539e95a4aa3f3a2","type":"function","z":"48f3cacf88806e9b","name":"Wait 5s","func":"setTimeout(() => {\n node.send(msg);\n // MUST be defined!!!\n node.done();\n}, 5000);\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":60,"wires":[["8649a17f0f1ac737"]]},{"id":"9cae538fddad00ef","type":"inject","z":"48f3cacf88806e9b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":60,"wires":[["9539e95a4aa3f3a2"]]},{"id":"8649a17f0f1ac737","type":"debug","z":"48f3cacf88806e9b","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":480,"y":60,"wires":[]},{"id":"3dc426ba800918cf","type":"function","z":"48f3cacf88806e9b","name":"Send 3 msgs","func":"\nreturn [msg, msg, msg];","outputs":3,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":140,"wires":[["fcfe154798ae59b2"],["fcfe154798ae59b2"],["fcfe154798ae59b2"]]},{"id":"1e884590101d0f1d","type":"delay","z":"48f3cacf88806e9b","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":320,"y":140,"wires":[["3dc426ba800918cf"]]},{"id":"3c33837e00ce38f1","type":"inject","z":"48f3cacf88806e9b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":140,"wires":[["1e884590101d0f1d"]]},{"id":"767740c89774fc4b","type":"delay","z":"48f3cacf88806e9b","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":320,"y":220,"wires":[["027c18126a348a83"]]},{"id":"d85477921ee620dd","type":"inject","z":"48f3cacf88806e9b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":220,"wires":[["767740c89774fc4b"]]},{"id":"309e1785f9eee23e","type":"link out","z":"48f3cacf88806e9b","name":"link out flow 1 (1)","mode":"link","links":["8391252e5b710669"],"x":435,"y":280,"wires":[]},{"id":"ecdde9e76693e516","type":"delay","z":"48f3cacf88806e9b","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":320,"y":280,"wires":[["309e1785f9eee23e"]]},{"id":"8703afd442944623","type":"inject","z":"48f3cacf88806e9b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":280,"wires":[["ecdde9e76693e516"]]},{"id":"fde8bb0eb5d93e19","type":"inject","z":"48f3cacf88806e9b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":380,"wires":[["19bdf1fe6fa090bf"]]},{"id":"19bdf1fe6fa090bf","type":"subflow:5025fc31b2f76672","z":"48f3cacf88806e9b","name":"","x":320,"y":380,"wires":[["894a802e1adfd39b"]]},{"id":"894a802e1adfd39b","type":"debug","z":"48f3cacf88806e9b","name":"debug 5","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":480,"y":380,"wires":[]},{"id":"ee1a92bf8822ff1b","type":"inject","z":"48f3cacf88806e9b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":440,"wires":[["27511002b62ff11f"]]},{"id":"27511002b62ff11f","type":"subflow:52fbdebd011809bf","z":"48f3cacf88806e9b","name":"","x":320,"y":440,"wires":[]},{"id":"b42e930f2ec522a9","type":"subflow:b8f08e1162331d0a","z":"48f3cacf88806e9b","name":"","x":160,"y":500,"wires":[["105f660a67b234f7"]]},{"id":"e1b0701f42f10808","type":"debug","z":"48f3cacf88806e9b","name":"debug 7","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":480,"y":500,"wires":[]},{"id":"105f660a67b234f7","type":"delay","z":"48f3cacf88806e9b","name":"","pauseType":"delay","timeout":"2","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":320,"y":500,"wires":[["e1b0701f42f10808"]]},{"id":"a951db0574f9b04f","type":"tab","label":"Flow 2","disabled":false,"info":"","env":[]},{"id":"9ddc5b8b594ea056","type":"link in","z":"a951db0574f9b04f","name":"link in flow 2","links":["027c18126a348a83"],"x":165,"y":60,"wires":[["8965b65b9e976c82"]]},{"id":"8965b65b9e976c82","type":"debug","z":"a951db0574f9b04f","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":300,"y":60,"wires":[]},{"id":"8391252e5b710669","type":"link in","z":"a951db0574f9b04f","name":"link in flow 2 (1)","links":["309e1785f9eee23e"],"x":165,"y":120,"wires":[["7c6b9fee0e4338c0"]]},{"id":"7c6b9fee0e4338c0","type":"link out","z":"a951db0574f9b04f","name":"link out flow 2 (1)","mode":"link","links":["f6553a234c2cbd1c"],"x":255,"y":120,"wires":[]},{"id":"38d0de50ec78556c","type":"tab","label":"Flow 3","disabled":false,"info":"","env":[],"failFast":false,"timeout":10000,"initiatorNodes":[]},{"id":"f6553a234c2cbd1c","type":"link in","z":"38d0de50ec78556c","name":"link in 1","links":["7c6b9fee0e4338c0"],"x":165,"y":60,"wires":[["76627d74979e5144"]]},{"id":"76627d74979e5144","type":"debug","z":"38d0de50ec78556c","name":"debug 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":300,"y":60,"wires":[]}]
```
### Todo list
- [ ] Add unit tests 😭
- [ ] TODO comments
- [ ] Improve the UI + translation
- [ ] Improve this comment, but for now my head hurts too much.
## Checklist
- [x] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
- [x] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [x] I have run `npm run test` to verify the unit tests pass
- [ ] I have added suitable unit tests to cover the new/changed functionality