Skip to content

Commit 2b36f74

Browse files
authored
fix: Optional watchdog for "adapter disconnected"-type events (non-node-crash) (#23043)
* Watchdog for "adapter disconnected"-type events (non-node-crash) * Reset watchdog count on successful start. * Cleanup * Switch from argv to env.
1 parent bcc3681 commit 2b36f74

File tree

1 file changed

+74
-14
lines changed

1 file changed

+74
-14
lines changed

index.js

+74-14
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,76 @@ require('source-map-support').install();
99

1010
let controller;
1111
let stopping = false;
12+
let watchdog = process.env.Z2M_WATCHDOG != undefined;
13+
let watchdogCount = 0;
14+
let unsolicitedStop = false;
15+
// csv in minutes, default: 1min, 5min, 15min, 30min, 60min
16+
let watchdogDelays = [60000, 300000, 900000, 1800000, 3600000];
17+
18+
if (watchdog && process.env.Z2M_WATCHDOG !== 'default') {
19+
if (/^(?:(?:[0-9]*[.])?[0-9]+)+(?:,?(?:[0-9]*[.])?[0-9]+)*$/.test(process.env.Z2M_WATCHDOG)) {
20+
watchdogDelays = process.env.Z2M_WATCHDOG.split(',').map((v) => parseFloat(v) * 60000);
21+
} else {
22+
console.log(`Invalid watchdog delays (must use number-only CSV format representing minutes, example: 'Z2M_WATCHDOG=1,5,15,30,60'.`);
23+
process.exit(1);
24+
}
25+
}
1226

1327
const hashFile = path.join(__dirname, 'dist', '.hash');
1428

29+
async function triggerWatchdog(code) {
30+
const delay = watchdogDelays[watchdogCount];
31+
watchdogCount += 1;
32+
33+
if (delay) {
34+
// garbage collector
35+
controller = undefined;
36+
37+
console.log(`WATCHDOG: Waiting ${delay/60000}min before next start try.`);
38+
await new Promise((resolve) => setTimeout(resolve, delay));
39+
await start();
40+
} else {
41+
process.exit(code);
42+
}
43+
}
44+
1545
async function restart() {
1646
await stop(true);
1747
await start();
1848
}
1949

20-
async function exit(code, restart) {
50+
async function exit(code, restart = false) {
2151
if (!restart) {
22-
process.exit(code);
52+
if (watchdog && unsolicitedStop) {
53+
await triggerWatchdog(code);
54+
} else {
55+
process.exit(code);
56+
}
2357
}
2458
}
2559

2660
async function currentHash() {
2761
const git = require('git-last-commit');
62+
2863
return new Promise((resolve) => {
29-
git.getLastCommit((err, commit) => {
30-
if (err) resolve('unknown');
31-
else resolve(commit.shortHash);
32-
});
64+
git.getLastCommit((err, commit) => err ? resolve('unknown') : resolve(commit.shortHash));
3365
});
3466
}
3567

3668
async function writeHash() {
3769
const hash = await currentHash();
70+
3871
fs.writeFileSync(hashFile, hash);
3972
}
4073

4174
async function build(reason) {
4275
return new Promise((resolve, reject) => {
4376
process.stdout.write(`Building Zigbee2MQTT... (${reason})`);
4477
rimrafSync('dist');
78+
4579
const env = {...process.env};
4680
const _600mb = 629145600;
81+
4782
if (_600mb > os.totalmem() && !env.NODE_OPTIONS) {
4883
// Prevent OOM on tsc compile for system with low memory
4984
// https://github.com/Koenkk/zigbee2mqtt/issues/12034
@@ -53,10 +88,11 @@ async function build(reason) {
5388
exec('npm run build', {env, cwd: __dirname}, async (err, stdout, stderr) => {
5489
if (err) {
5590
process.stdout.write(', failed\n');
91+
5692
if (err.code === 134) {
57-
process.stderr.write(
58-
'\n\nBuild failed; ran out-of-memory, free some memory (RAM) and start again\n\n');
93+
process.stderr.write('\n\nBuild failed; ran out-of-memory, free some memory (RAM) and start again\n\n');
5994
}
95+
6096
reject(err);
6197
} else {
6298
process.stdout.write(', finished\n');
@@ -79,42 +115,65 @@ async function checkDist() {
79115
}
80116

81117
async function start() {
118+
console.log(`Starting Zigbee2MQTT ${watchdog ? `with watchdog (${watchdogDelays})` : `without watchdog`}.`);
82119
await checkDist();
83120

84121
const version = engines.node;
122+
85123
if (!semver.satisfies(process.version, version)) {
86-
console.log(`\t\tZigbee2MQTT requires node version ${version}, you are running ${process.version}!\n`); // eslint-disable-line
124+
console.log(`\t\tZigbee2MQTT requires node version ${version}, you are running ${process.version}!\n`);
87125
}
88126

89127
// Validate settings
90128
const settings = require('./dist/util/settings');
129+
91130
settings.reRead();
131+
92132
const errors = settings.validate();
133+
93134
if (errors.length > 0) {
135+
unsolicitedStop = false;
136+
94137
console.log(`\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`);
95138
console.log(' READ THIS CAREFULLY\n');
96139
console.log(`Refusing to start because configuration is not valid, found the following errors:`);
140+
97141
for (const error of errors) {
98142
console.log(`- ${error}`);
99143
}
100-
console.log(`\nIf you don't know how to solve this, read https://www.zigbee2mqtt.io/guide/configuration`); // eslint-disable-line
144+
145+
console.log(`\nIf you don't know how to solve this, read https://www.zigbee2mqtt.io/guide/configuration`);
101146
console.log(`\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n`);
102-
exit(1);
147+
148+
return exit(1);
103149
}
104150

105151
const Controller = require('./dist/controller');
106152
controller = new Controller(restart, exit);
153+
107154
await controller.start();
155+
156+
// consider next controller.stop() call as unsolicited, only after successful first start
157+
unsolicitedStop = true;
158+
watchdogCount = 0;// reset
108159
}
109160

110161
async function stop(restart) {
162+
// `handleQuit` or `restart` never unsolicited
163+
unsolicitedStop = false;
164+
111165
await controller.stop(restart);
112166
}
113167

114168
async function handleQuit() {
115-
if (!stopping && controller) {
116-
stopping = true;
117-
await stop(false);
169+
if (!stopping) {
170+
if (controller) {
171+
stopping = true;
172+
173+
await stop(false);
174+
} else {
175+
process.exit(0);
176+
}
118177
}
119178
}
120179

@@ -129,5 +188,6 @@ if (require.main === module || require.main.filename.endsWith(path.sep + 'cli.js
129188
} else {
130189
process.on('SIGINT', handleQuit);
131190
process.on('SIGTERM', handleQuit);
191+
132192
module.exports = {start};
133193
}

0 commit comments

Comments
 (0)