dsh-autorestart 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +460 -12
- package/assets/screenshot-1.png +0 -0
- package/assets/screenshot-2.png +0 -0
- package/assets/screenshot-3.png +0 -0
- package/assets/screenshot-4.png +0 -0
- package/assets/screenshot-5.png +0 -0
- package/client.js +6329 -3863
- package/cordis.patch.yml +44 -1
- package/docs/README.zh.md +297 -9
- package/docs/platform-matrix.md +92 -0
- package/docs/rescue-drill.md +606 -0
- package/index.js +2867 -199
- package/lib/access.js +212 -0
- package/lib/autostart-advice.js +198 -0
- package/lib/backup.js +131 -4
- package/lib/blockers.js +228 -0
- package/lib/checkpoint.js +147 -0
- package/lib/diagnostics.js +188 -0
- package/lib/health.js +264 -0
- package/lib/host-kind.js +145 -0
- package/lib/node-runtime.js +144 -0
- package/lib/rescue-paths.js +253 -0
- package/lib/rescue.js +596 -0
- package/lib/restart-verdict.js +327 -0
- package/lib/restart.js +823 -56
- package/lib/scheduler-capability.js +300 -0
- package/lib/sleep.js +46 -49
- package/lib/startup-mode.js +491 -0
- package/lib/supervisor-config.js +735 -0
- package/lib/supervisor-install.js +557 -0
- package/lib/supervisor.js +1276 -0
- package/lib/watchdog-arbiter.js +117 -0
- package/lib/watchdog-schedule.js +310 -0
- package/lib/watchdog-tier.js +182 -0
- package/lib/watchdog.js +479 -0
- package/locale/en.json +274 -64
- package/locale/zh.json +274 -64
- package/package.json +7 -2
- package/screenshots.json +6 -4
- package/scripts/README.md +65 -0
- package/scripts/drill-rescue.ps1 +508 -0
- package/scripts/emergency-disable.cmd +27 -0
- package/scripts/emergency-toggle.ps1 +373 -0
- package/scripts/restore-profile.cmd +44 -0
- package/scripts/restore-profile.ps1 +608 -0
- package/scripts/run-tests.ps1 +46 -0
- package/supervisor/supervisor.js +884 -0
package/lib/watchdog.js
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE WATCHDOG — one flow, three ways to trigger it.
|
|
3
|
+
*
|
|
4
|
+
* The flow is the same everywhere; only the way the watchdog is made to run
|
|
5
|
+
* differs (see lib/watchdog-tier.js and lib/watchdog-schedule.js). This file
|
|
6
|
+
* is that flow:
|
|
7
|
+
*
|
|
8
|
+
* 1. wait for the READY marker (the new host wrote it after its health
|
|
9
|
+
* check passed);
|
|
10
|
+
* 2. no marker? Wait a bit and ASK AGAIN — a host that is slow to start is
|
|
11
|
+
* not a host that is dead (the misjudgement protection);
|
|
12
|
+
* 3. still nothing? Is an instance ALIVE? If one is listening, the rollback
|
|
13
|
+
* is not needed: stand down (the no-double-start protection);
|
|
14
|
+
* 4. take the shared lock (`rollback.lock`) — exactly ONE of us restores;
|
|
15
|
+
* 5. restore the snapshot, then START THE HOST AGAIN;
|
|
16
|
+
* 6. wait for the ready marker once more: it came → `recovered`, it did not
|
|
17
|
+
* → `failed` — and then we STOP (at most one restore and one start).
|
|
18
|
+
*
|
|
19
|
+
* Whatever happens, the scheduling item and the pending marker go; the report
|
|
20
|
+
* stays.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from 'node:fs';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
|
|
26
|
+
/** The marker that says "a restart is coming, a watchdog is watching". */
|
|
27
|
+
export const PENDING_NAME = 'watchdog-pending.json';
|
|
28
|
+
/** The marker that says "the new host is up and healthy". */
|
|
29
|
+
export const READY_NAME = 'watchdog-ready.json';
|
|
30
|
+
/** The report of the last watchdog run. */
|
|
31
|
+
export const REPORT_NAME = 'last-watchdog-report.json';
|
|
32
|
+
/** The lock that both the plugin and the watchdog take before a restore. */
|
|
33
|
+
export const LOCK_NAME = 'rollback.lock';
|
|
34
|
+
|
|
35
|
+
/** How long the watchdog waits for the new host. */
|
|
36
|
+
export const DEFAULT_TIMEOUT_MS = 180000;
|
|
37
|
+
/** How long it waits before it asks again (the misjudgement protection). */
|
|
38
|
+
export const DEFAULT_RECHECK_MS = 20000;
|
|
39
|
+
/** How often it looks at the ready marker. */
|
|
40
|
+
export const DEFAULT_POLL_MS = 2000;
|
|
41
|
+
/** How long it waits for the host after the restore. */
|
|
42
|
+
export const DEFAULT_READY_TIMEOUT_MS = 90000;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Write a JSON file (no BOM, so every reader can parse it).
|
|
46
|
+
*
|
|
47
|
+
* @param {string} file
|
|
48
|
+
* @param {object} value
|
|
49
|
+
*/
|
|
50
|
+
function writeJson(file, value) {
|
|
51
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
52
|
+
fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Read a JSON file (a file that cannot be read is a file that is not there).
|
|
57
|
+
*
|
|
58
|
+
* @param {string} file
|
|
59
|
+
* @returns {object|null}
|
|
60
|
+
*/
|
|
61
|
+
function readJson(file) {
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
64
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The pending marker: what the watchdog has to know.
|
|
72
|
+
*
|
|
73
|
+
* @param {{
|
|
74
|
+
* tier?: string,
|
|
75
|
+
* tierReason?: string,
|
|
76
|
+
* taskName?: string,
|
|
77
|
+
* unitId?: string,
|
|
78
|
+
* pidPath?: string,
|
|
79
|
+
* snapshotDir?: string,
|
|
80
|
+
* profileDir?: string,
|
|
81
|
+
* restoreCommand?: object|null,
|
|
82
|
+
* relaunchCommand?: object|null,
|
|
83
|
+
* healthUrl?: string,
|
|
84
|
+
* healthPath?: string,
|
|
85
|
+
* portFile?: string,
|
|
86
|
+
* expectedBootId?: string|null,
|
|
87
|
+
* timeoutMs?: number,
|
|
88
|
+
* recheckMs?: number,
|
|
89
|
+
* readyTimeoutMs?: number,
|
|
90
|
+
* hostKind?: string,
|
|
91
|
+
* attempts?: number,
|
|
92
|
+
* now?: number,
|
|
93
|
+
* }} options
|
|
94
|
+
* @returns {object}
|
|
95
|
+
*/
|
|
96
|
+
export function buildPending({
|
|
97
|
+
tier = 'none',
|
|
98
|
+
tierReason = '',
|
|
99
|
+
arbiter = 'v2',
|
|
100
|
+
taskName = '',
|
|
101
|
+
unitId = '',
|
|
102
|
+
pidPath = '',
|
|
103
|
+
snapshotDir = '',
|
|
104
|
+
profileDir = '',
|
|
105
|
+
restoreCommand = null,
|
|
106
|
+
relaunchCommand = null,
|
|
107
|
+
healthUrl = '',
|
|
108
|
+
healthPath = '/__dsh_autorestart/health',
|
|
109
|
+
portFile = '',
|
|
110
|
+
expectedBootId = null,
|
|
111
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
112
|
+
recheckMs = DEFAULT_RECHECK_MS,
|
|
113
|
+
readyTimeoutMs = DEFAULT_READY_TIMEOUT_MS,
|
|
114
|
+
hostKind = '',
|
|
115
|
+
attempts = 1,
|
|
116
|
+
now = Date.now(),
|
|
117
|
+
} = {}) {
|
|
118
|
+
return {
|
|
119
|
+
version: 1,
|
|
120
|
+
at: new Date(now).toISOString(),
|
|
121
|
+
tier,
|
|
122
|
+
tierReason,
|
|
123
|
+
arbiter,
|
|
124
|
+
taskName,
|
|
125
|
+
unitId,
|
|
126
|
+
pidPath,
|
|
127
|
+
snapshotDir,
|
|
128
|
+
profileDir,
|
|
129
|
+
restoreCommand,
|
|
130
|
+
relaunchCommand,
|
|
131
|
+
healthUrl,
|
|
132
|
+
healthPath,
|
|
133
|
+
portFile,
|
|
134
|
+
expectedBootId,
|
|
135
|
+
timeoutMs,
|
|
136
|
+
recheckMs,
|
|
137
|
+
readyTimeoutMs,
|
|
138
|
+
hostKind,
|
|
139
|
+
attempts,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @param {string} stateDir
|
|
145
|
+
* @returns {string}
|
|
146
|
+
*/
|
|
147
|
+
export function pendingPath(stateDir) {
|
|
148
|
+
return path.join(stateDir, PENDING_NAME);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {string} stateDir
|
|
153
|
+
* @returns {string}
|
|
154
|
+
*/
|
|
155
|
+
export function readyPath(stateDir) {
|
|
156
|
+
return path.join(stateDir, READY_NAME);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {string} stateDir
|
|
161
|
+
* @returns {string}
|
|
162
|
+
*/
|
|
163
|
+
export function reportPath(stateDir) {
|
|
164
|
+
return path.join(stateDir, REPORT_NAME);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Write the pending marker.
|
|
169
|
+
*
|
|
170
|
+
* @param {string} stateDir
|
|
171
|
+
* @param {object} pending
|
|
172
|
+
*/
|
|
173
|
+
export function writePending(stateDir, pending) {
|
|
174
|
+
writeJson(pendingPath(stateDir), pending);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @param {string} stateDir
|
|
179
|
+
* @returns {object|null}
|
|
180
|
+
*/
|
|
181
|
+
export function readPending(stateDir) {
|
|
182
|
+
return readJson(pendingPath(stateDir));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Remove the pending marker (the watchdog is over).
|
|
187
|
+
*
|
|
188
|
+
* @param {string} stateDir
|
|
189
|
+
*/
|
|
190
|
+
export function clearPending(stateDir) {
|
|
191
|
+
try {
|
|
192
|
+
fs.rmSync(pendingPath(stateDir), { force: true });
|
|
193
|
+
} catch {
|
|
194
|
+
// It is gone either way.
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Write the ready marker: "the new host is up".
|
|
200
|
+
*
|
|
201
|
+
* @param {string} stateDir
|
|
202
|
+
* @param {{ bootId?: string, port?: number, at?: number }} [info]
|
|
203
|
+
*/
|
|
204
|
+
export function writeReady(stateDir, info = {}) {
|
|
205
|
+
writeJson(readyPath(stateDir), {
|
|
206
|
+
at: new Date(info.at ?? Date.now()).toISOString(),
|
|
207
|
+
bootId: info.bootId ?? null,
|
|
208
|
+
port: info.port ?? null,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @param {string} stateDir
|
|
214
|
+
* @returns {object|null}
|
|
215
|
+
*/
|
|
216
|
+
export function readReady(stateDir) {
|
|
217
|
+
return readJson(readyPath(stateDir));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Remove the ready marker (it belongs to one restart).
|
|
222
|
+
*
|
|
223
|
+
* @param {string} stateDir
|
|
224
|
+
*/
|
|
225
|
+
export function clearReady(stateDir) {
|
|
226
|
+
try {
|
|
227
|
+
fs.rmSync(readyPath(stateDir), { force: true });
|
|
228
|
+
} catch {
|
|
229
|
+
// It is gone either way.
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Write the report of the last watchdog run.
|
|
235
|
+
*
|
|
236
|
+
* @param {string} stateDir
|
|
237
|
+
* @param {object} report
|
|
238
|
+
*/
|
|
239
|
+
export function writeReport(stateDir, report) {
|
|
240
|
+
writeJson(reportPath(stateDir), report);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @param {string} stateDir
|
|
245
|
+
* @returns {object|null}
|
|
246
|
+
*/
|
|
247
|
+
export function readReport(stateDir) {
|
|
248
|
+
return readJson(reportPath(stateDir));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Take the lock (exactly one restore at a time).
|
|
253
|
+
*
|
|
254
|
+
* @param {string} lockFile
|
|
255
|
+
* @param {{ now?: number, exists?: Function, mkdir?: Function, open?: Function }} [deps]
|
|
256
|
+
* @returns {boolean}
|
|
257
|
+
*/
|
|
258
|
+
export function acquireLock(lockFile, { now = Date.now(), exists = fs.existsSync, mkdir = (dir) => fs.mkdirSync(dir, { recursive: true }), open = null } = {}) {
|
|
259
|
+
try {
|
|
260
|
+
mkdir(path.dirname(lockFile));
|
|
261
|
+
if (exists(lockFile)) {
|
|
262
|
+
// A lock that is older than 10 minutes belongs to a process that died
|
|
263
|
+
// in the middle of its restore: it must not block the rollback for ever.
|
|
264
|
+
let age = 0;
|
|
265
|
+
try {
|
|
266
|
+
age = now - fs.statSync(lockFile).mtimeMs;
|
|
267
|
+
} catch {
|
|
268
|
+
age = 0;
|
|
269
|
+
}
|
|
270
|
+
if (age > 10 * 60 * 1000) fs.rmSync(lockFile, { force: true });
|
|
271
|
+
else return false;
|
|
272
|
+
}
|
|
273
|
+
const handle = open === null
|
|
274
|
+
? fs.openSync(lockFile, 'wx')
|
|
275
|
+
: open(lockFile);
|
|
276
|
+
if (handle !== null && handle !== undefined && typeof handle.close === 'function') handle.close();
|
|
277
|
+
return true;
|
|
278
|
+
} catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Give the lock back.
|
|
285
|
+
*
|
|
286
|
+
* @param {string} lockFile
|
|
287
|
+
*/
|
|
288
|
+
export function releaseLock(lockFile) {
|
|
289
|
+
try {
|
|
290
|
+
fs.rmSync(lockFile, { force: true });
|
|
291
|
+
} catch {
|
|
292
|
+
// It is gone either way.
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Put the snapshot back.
|
|
298
|
+
*
|
|
299
|
+
* The same for every platform: the configuration files and the plugin tree
|
|
300
|
+
* are copied out of the snapshot into the profile. (The profile is NOT
|
|
301
|
+
* deleted first — the broken files are overwritten, so a half-restored
|
|
302
|
+
* profile is never worse than the broken one.)
|
|
303
|
+
*
|
|
304
|
+
* @param {{ snapshotDir?: string, profileDir?: string, copy?: Function }} options
|
|
305
|
+
* @returns {{ ok: boolean, error: string|null, copied: string[] }}
|
|
306
|
+
*/
|
|
307
|
+
export function restoreFromSnapshot({ snapshotDir = '', profileDir = '', copy = null } = {}) {
|
|
308
|
+
const from = String(snapshotDir ?? '');
|
|
309
|
+
const into = String(profileDir ?? '');
|
|
310
|
+
if (from === '' || into === '') {
|
|
311
|
+
return { ok: false, error: 'the snapshot or the profile directory is unknown', copied: [] };
|
|
312
|
+
}
|
|
313
|
+
const copied = [];
|
|
314
|
+
const copyInto = copy === null
|
|
315
|
+
? (source, target) => { fs.cpSync(source, target, { recursive: true, force: true }); }
|
|
316
|
+
: copy;
|
|
317
|
+
try {
|
|
318
|
+
for (const [part, target] of [['config', into], ['node_modules', path.join(into, 'node_modules')]]) {
|
|
319
|
+
const source = path.join(from, part);
|
|
320
|
+
if (!fs.existsSync(source)) continue;
|
|
321
|
+
copyInto(source, target);
|
|
322
|
+
copied.push(part);
|
|
323
|
+
}
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return {
|
|
326
|
+
ok: false,
|
|
327
|
+
error: error && error.message ? error.message : String(error),
|
|
328
|
+
copied,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return { ok: copied.length > 0, error: copied.length > 0 ? null : 'the snapshot carries nothing to restore', copied };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Wait for the ready marker.
|
|
336
|
+
*
|
|
337
|
+
* @param {object} deps
|
|
338
|
+
* @param {number} timeoutMs
|
|
339
|
+
* @returns {Promise<boolean>}
|
|
340
|
+
*/
|
|
341
|
+
async function waitForReady(deps, timeoutMs) {
|
|
342
|
+
const deadline = deps.now() + Math.max(0, timeoutMs);
|
|
343
|
+
while (deps.now() < deadline) {
|
|
344
|
+
if (deps.readReady()) return true;
|
|
345
|
+
// The plugin may have decided on its own (the restart failed): stand down.
|
|
346
|
+
if (deps.isWithdrawn && deps.isWithdrawn()) return false;
|
|
347
|
+
await deps.sleep(Math.min(deps.pollMs, Math.max(0, deadline - deps.now())));
|
|
348
|
+
}
|
|
349
|
+
return deps.readReady() === true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* THE FLOW.
|
|
354
|
+
*
|
|
355
|
+
* @param {{
|
|
356
|
+
* pending: object,
|
|
357
|
+
* stateDir?: string,
|
|
358
|
+
* deps?: object,
|
|
359
|
+
* }} options
|
|
360
|
+
* @returns {Promise<object>} the report
|
|
361
|
+
*/
|
|
362
|
+
export async function runWatchdogFlow({ pending, stateDir = '', deps = {} }) {
|
|
363
|
+
const d = {
|
|
364
|
+
sleep: async (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
365
|
+
now: () => Date.now(),
|
|
366
|
+
pollMs: DEFAULT_POLL_MS,
|
|
367
|
+
readReady: () => readReady(stateDir),
|
|
368
|
+
isWithdrawn: () => readPending(stateDir) === null,
|
|
369
|
+
probeAlive: () => false,
|
|
370
|
+
runRestore: () => ({ ok: false, error: 'no restore command' }),
|
|
371
|
+
runRelaunch: () => ({ ok: false, error: 'no relaunch command' }),
|
|
372
|
+
acquireLock: () => true,
|
|
373
|
+
releaseLock: () => {},
|
|
374
|
+
removeSchedule: () => {},
|
|
375
|
+
writeReport: (report) => writeReport(stateDir, report),
|
|
376
|
+
...deps,
|
|
377
|
+
};
|
|
378
|
+
const startedAt = d.now();
|
|
379
|
+
const report = {
|
|
380
|
+
at: new Date(startedAt).toISOString(),
|
|
381
|
+
result: 'unknown',
|
|
382
|
+
tier: pending && pending.tier ? pending.tier : 'none',
|
|
383
|
+
tierReason: pending && pending.tierReason ? pending.tierReason : '',
|
|
384
|
+
// WHO watched this restart. There is only ever one watchdog at a time
|
|
385
|
+
// (see lib/watchdog-arbiter.js), and the report says which one it was.
|
|
386
|
+
arbiter: pending && pending.arbiter ? pending.arbiter : 'v2',
|
|
387
|
+
waitedMs: 0,
|
|
388
|
+
rechecked: false,
|
|
389
|
+
restored: false,
|
|
390
|
+
relaunched: false,
|
|
391
|
+
ready: false,
|
|
392
|
+
error: null,
|
|
393
|
+
};
|
|
394
|
+
try {
|
|
395
|
+
// (1) The new host writes the ready marker after its health check.
|
|
396
|
+
const ready = await waitForReady(d, pending.timeoutMs);
|
|
397
|
+
if (ready) {
|
|
398
|
+
report.result = 'not-needed';
|
|
399
|
+
report.ready = true;
|
|
400
|
+
return report;
|
|
401
|
+
}
|
|
402
|
+
if (d.isWithdrawn()) {
|
|
403
|
+
report.result = 'decided';
|
|
404
|
+
report.error = 'the plugin decided on its own: the watchdog stood down';
|
|
405
|
+
return report;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// (2) ASK AGAIN. A host that is slow to start is not a host that is dead.
|
|
409
|
+
report.rechecked = true;
|
|
410
|
+
await d.sleep(Math.max(0, pending.recheckMs));
|
|
411
|
+
if (d.readReady()) {
|
|
412
|
+
report.result = 'not-needed';
|
|
413
|
+
report.ready = true;
|
|
414
|
+
return report;
|
|
415
|
+
}
|
|
416
|
+
if (d.isWithdrawn()) {
|
|
417
|
+
report.result = 'decided';
|
|
418
|
+
report.error = 'the plugin decided on its own: the watchdog stood down';
|
|
419
|
+
return report;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// (3) Is an instance ALIVE? Then there is nothing to restore.
|
|
423
|
+
if (await d.probeAlive()) {
|
|
424
|
+
report.result = 'abandoned';
|
|
425
|
+
report.error = 'an instance is alive: the snapshot was not restored';
|
|
426
|
+
return report;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// (4) The lock: exactly ONE of us restores.
|
|
430
|
+
if (!d.acquireLock()) {
|
|
431
|
+
report.result = 'abandoned';
|
|
432
|
+
report.error = 'another rollback is already running';
|
|
433
|
+
return report;
|
|
434
|
+
}
|
|
435
|
+
try {
|
|
436
|
+
// (5) Restore the snapshot.
|
|
437
|
+
const restored = d.runRestore();
|
|
438
|
+
report.restored = restored !== null && restored !== undefined && restored.ok === true;
|
|
439
|
+
if (!report.restored) {
|
|
440
|
+
report.result = 'failed';
|
|
441
|
+
report.error = restored && restored.error ? restored.error : 'the restore failed';
|
|
442
|
+
return report;
|
|
443
|
+
}
|
|
444
|
+
// (6) Start the host again.
|
|
445
|
+
const relaunched = d.runRelaunch();
|
|
446
|
+
report.relaunched = relaunched !== null && relaunched !== undefined && relaunched.ok === true;
|
|
447
|
+
if (!report.relaunched) {
|
|
448
|
+
report.result = 'failed';
|
|
449
|
+
report.error = relaunched && relaunched.error ? relaunched.error : 'the host could not be started again';
|
|
450
|
+
return report;
|
|
451
|
+
}
|
|
452
|
+
// (7) Did it come up this time?
|
|
453
|
+
const readyAgain = await waitForReady(d, pending.readyTimeoutMs);
|
|
454
|
+
report.ready = readyAgain;
|
|
455
|
+
report.result = readyAgain ? 'recovered' : 'failed';
|
|
456
|
+
if (!readyAgain) report.error = 'the host did not come up after the restore';
|
|
457
|
+
} finally {
|
|
458
|
+
d.releaseLock();
|
|
459
|
+
}
|
|
460
|
+
} catch (error) {
|
|
461
|
+
report.result = 'failed';
|
|
462
|
+
report.error = error && error.message ? error.message : String(error);
|
|
463
|
+
} finally {
|
|
464
|
+
report.waitedMs = d.now() - startedAt;
|
|
465
|
+
// The scheduling item and the pending marker go; the report stays.
|
|
466
|
+
try {
|
|
467
|
+
d.removeSchedule();
|
|
468
|
+
} catch {
|
|
469
|
+
// The scheduling item is cleaned up by the next run if this fails.
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
if (stateDir !== '') clearPending(stateDir);
|
|
473
|
+
} catch {
|
|
474
|
+
// It is gone either way.
|
|
475
|
+
}
|
|
476
|
+
d.writeReport(report);
|
|
477
|
+
}
|
|
478
|
+
return report;
|
|
479
|
+
}
|