@dxos/phoenix 0.1.58-main.0e9c99e
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/LICENSE +8 -0
- package/dist/lib/node/index.cjs +444 -0
- package/dist/lib/node/index.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -0
- package/dist/types/src/daemon-manager.d.ts +17 -0
- package/dist/types/src/daemon-manager.d.ts.map +1 -0
- package/dist/types/src/daemon-manager.test.d.ts +2 -0
- package/dist/types/src/daemon-manager.test.d.ts.map +1 -0
- package/dist/types/src/defs.d.ts +4 -0
- package/dist/types/src/defs.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +3 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/testing-utils.d.ts +4 -0
- package/dist/types/src/testing-utils.d.ts.map +1 -0
- package/dist/types/src/utils.d.ts +4 -0
- package/dist/types/src/utils.d.ts.map +1 -0
- package/dist/types/src/watchdog.d.ts +56 -0
- package/dist/types/src/watchdog.d.ts.map +1 -0
- package/dist/types/src/watchdog.test.d.ts +2 -0
- package/dist/types/src/watchdog.test.d.ts.map +1 -0
- package/package.json +36 -0
- package/src/daemon-manager.test.ts +74 -0
- package/src/daemon-manager.ts +138 -0
- package/src/defs.ts +7 -0
- package/src/index.ts +10 -0
- package/src/testing-utils.ts +23 -0
- package/src/utils.ts +34 -0
- package/src/watchdog.test.ts +40 -0
- package/src/watchdog.ts +226 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
Copyright (c) 2023 DXOS
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// packages/common/phoenix/src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
DaemonManager: () => DaemonManager,
|
|
34
|
+
WatchDog: () => WatchDog
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(src_exports);
|
|
37
|
+
|
|
38
|
+
// packages/common/phoenix/src/daemon-manager.ts
|
|
39
|
+
var import_node_child_process = require("node:child_process");
|
|
40
|
+
var import_node_fs2 = require("node:fs");
|
|
41
|
+
var import_promises = require("node:fs/promises");
|
|
42
|
+
var import_node_path = require("node:path");
|
|
43
|
+
var import_pkg_up = __toESM(require("pkg-up"));
|
|
44
|
+
var import_invariant = require("@dxos/invariant");
|
|
45
|
+
var import_lock_file2 = require("@dxos/lock-file");
|
|
46
|
+
var import_log = require("@dxos/log");
|
|
47
|
+
|
|
48
|
+
// packages/common/phoenix/src/utils.ts
|
|
49
|
+
var import_node_fs = require("node:fs");
|
|
50
|
+
var import_async = require("@dxos/async");
|
|
51
|
+
var import_lock_file = require("@dxos/lock-file");
|
|
52
|
+
|
|
53
|
+
// packages/common/phoenix/src/defs.ts
|
|
54
|
+
var LOCK_TIMEOUT = 1e3;
|
|
55
|
+
var LOCK_CHECK_INTERVAL = 50;
|
|
56
|
+
|
|
57
|
+
// packages/common/phoenix/src/utils.ts
|
|
58
|
+
var waitForLockAcquisition = async (lockFile) => (0, import_async.waitForCondition)({
|
|
59
|
+
condition: async () => await import_lock_file.LockFile.isLocked(lockFile),
|
|
60
|
+
timeout: LOCK_TIMEOUT,
|
|
61
|
+
interval: LOCK_CHECK_INTERVAL,
|
|
62
|
+
error: new Error("Lock file is not being acquired.")
|
|
63
|
+
});
|
|
64
|
+
var waitForLockFileBeingFilledWithInfo = async (lockFile) => (0, import_async.waitForCondition)({
|
|
65
|
+
condition: () => (0, import_node_fs.readFileSync)(lockFile, {
|
|
66
|
+
encoding: "utf-8"
|
|
67
|
+
}).includes("pid"),
|
|
68
|
+
timeout: LOCK_TIMEOUT,
|
|
69
|
+
interval: LOCK_CHECK_INTERVAL,
|
|
70
|
+
error: new Error("Lock file is not being propagated with info.")
|
|
71
|
+
});
|
|
72
|
+
var waitForLockRelease = async (lockFile) => (0, import_async.waitForCondition)({
|
|
73
|
+
condition: async () => !await import_lock_file.LockFile.isLocked(lockFile),
|
|
74
|
+
timeout: LOCK_TIMEOUT,
|
|
75
|
+
interval: LOCK_CHECK_INTERVAL,
|
|
76
|
+
error: new Error("Lock file is not being released.")
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// packages/common/phoenix/src/daemon-manager.ts
|
|
80
|
+
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/phoenix/src/daemon-manager.ts";
|
|
81
|
+
var LOCK_FILE_NAME = "lockfile";
|
|
82
|
+
var DaemonManager = class {
|
|
83
|
+
constructor(_rootPath) {
|
|
84
|
+
this._rootPath = _rootPath;
|
|
85
|
+
if (!(0, import_node_fs2.existsSync)((0, import_node_path.join)(_rootPath, "profile"))) {
|
|
86
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path.join)(_rootPath, "profile"), {
|
|
87
|
+
recursive: true
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
_getConfigFiles(uid) {
|
|
92
|
+
const defaultConfigDir = (0, import_node_path.join)(this._rootPath, "profile", uid);
|
|
93
|
+
if (!(0, import_node_fs2.existsSync)(defaultConfigDir)) {
|
|
94
|
+
(0, import_node_fs2.mkdirSync)(defaultConfigDir, {
|
|
95
|
+
recursive: true
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
lockFile: (0, import_node_path.join)(defaultConfigDir, LOCK_FILE_NAME),
|
|
100
|
+
logFile: (0, import_node_path.join)(defaultConfigDir, "file.log"),
|
|
101
|
+
errFile: (0, import_node_path.join)(defaultConfigDir, "err.log")
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async start(params) {
|
|
105
|
+
(0, import_invariant.invariant)(params.command, "command is required", {
|
|
106
|
+
F: __dxlog_file,
|
|
107
|
+
L: 46,
|
|
108
|
+
S: this,
|
|
109
|
+
A: [
|
|
110
|
+
"params.command",
|
|
111
|
+
"'command is required'"
|
|
112
|
+
]
|
|
113
|
+
});
|
|
114
|
+
const watchDogParams = {
|
|
115
|
+
...this._getConfigFiles(params.uid),
|
|
116
|
+
...params
|
|
117
|
+
};
|
|
118
|
+
{
|
|
119
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path.dirname)(watchDogParams.logFile), {
|
|
120
|
+
recursive: true
|
|
121
|
+
});
|
|
122
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path.dirname)(watchDogParams.errFile), {
|
|
123
|
+
recursive: true
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
{
|
|
127
|
+
if (await import_lock_file2.LockFile.isLocked(watchDogParams.lockFile)) {
|
|
128
|
+
throw new Error("Lock file is already locked.");
|
|
129
|
+
}
|
|
130
|
+
(0, import_node_fs2.unlinkSync)(watchDogParams.lockFile);
|
|
131
|
+
}
|
|
132
|
+
const watchdogPath = (0, import_node_path.join)((0, import_node_path.dirname)(import_pkg_up.default.sync({
|
|
133
|
+
cwd: __dirname
|
|
134
|
+
})), "bin", "watchdog");
|
|
135
|
+
const watchDog = (0, import_node_child_process.fork)(watchdogPath, [
|
|
136
|
+
JSON.stringify(watchDogParams)
|
|
137
|
+
], {
|
|
138
|
+
detached: true,
|
|
139
|
+
cwd: __dirname
|
|
140
|
+
});
|
|
141
|
+
watchDog.on("exit", (code, signal) => {
|
|
142
|
+
if (code && code !== 0) {
|
|
143
|
+
import_log.log.error("Monitor died unexpectedly", {
|
|
144
|
+
code,
|
|
145
|
+
signal
|
|
146
|
+
}, {
|
|
147
|
+
F: __dxlog_file,
|
|
148
|
+
L: 75,
|
|
149
|
+
S: this,
|
|
150
|
+
C: (f, a) => f(...a)
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
await waitForLockAcquisition(watchDogParams.lockFile);
|
|
155
|
+
await waitForLockFileBeingFilledWithInfo(watchDogParams.lockFile);
|
|
156
|
+
watchDog.disconnect();
|
|
157
|
+
watchDog.unref();
|
|
158
|
+
return this.getInfo(params.uid);
|
|
159
|
+
}
|
|
160
|
+
async stop(uid, force) {
|
|
161
|
+
const lockFile = this._getConfigFiles(uid).lockFile;
|
|
162
|
+
const processInfo = JSON.parse((0, import_node_fs2.readFileSync)(lockFile, {
|
|
163
|
+
encoding: "utf-8"
|
|
164
|
+
}));
|
|
165
|
+
try {
|
|
166
|
+
if (force) {
|
|
167
|
+
process.kill(processInfo.pid, "SIGKILL");
|
|
168
|
+
} else {
|
|
169
|
+
process.kill(processInfo.pid, "SIGINT");
|
|
170
|
+
}
|
|
171
|
+
} catch (err) {
|
|
172
|
+
(0, import_invariant.invariant)(err instanceof Error, "Invalid error type.", {
|
|
173
|
+
F: __dxlog_file,
|
|
174
|
+
L: 98,
|
|
175
|
+
S: this,
|
|
176
|
+
A: [
|
|
177
|
+
"err instanceof Error",
|
|
178
|
+
"'Invalid error type.'"
|
|
179
|
+
]
|
|
180
|
+
});
|
|
181
|
+
if (!err.name.includes("ESRCH") && !err.message.includes("ESRCH")) {
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
await waitForLockRelease(lockFile);
|
|
186
|
+
return this.getInfo(uid);
|
|
187
|
+
}
|
|
188
|
+
async list() {
|
|
189
|
+
const uids = (await (0, import_promises.readdir)((0, import_node_path.join)(this._rootPath, "profile"))).filter((uid) => !uid.startsWith("."));
|
|
190
|
+
return Promise.all(uids.map(async (uid) => {
|
|
191
|
+
return this.getInfo(uid);
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
async getInfo(uid) {
|
|
195
|
+
const files = this._getConfigFiles(uid);
|
|
196
|
+
let info = {
|
|
197
|
+
running: await import_lock_file2.LockFile.isLocked(files.lockFile),
|
|
198
|
+
uid,
|
|
199
|
+
...files
|
|
200
|
+
};
|
|
201
|
+
if ((0, import_node_fs2.existsSync)(files.lockFile)) {
|
|
202
|
+
try {
|
|
203
|
+
info = {
|
|
204
|
+
...info,
|
|
205
|
+
...JSON.parse((0, import_node_fs2.readFileSync)(files.lockFile, {
|
|
206
|
+
encoding: "utf-8"
|
|
207
|
+
}))
|
|
208
|
+
};
|
|
209
|
+
} catch (err) {
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return info;
|
|
213
|
+
}
|
|
214
|
+
async isRunning(uid) {
|
|
215
|
+
const lockFile = this._getConfigFiles(uid).lockFile;
|
|
216
|
+
return import_lock_file2.LockFile.isLocked(lockFile);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// packages/common/phoenix/src/watchdog.ts
|
|
221
|
+
var import_node_child_process2 = require("node:child_process");
|
|
222
|
+
var import_node_fs3 = require("node:fs");
|
|
223
|
+
var import_node_util = require("node:util");
|
|
224
|
+
var import_ps_tree = __toESM(require("ps-tree"));
|
|
225
|
+
var import_async2 = require("@dxos/async");
|
|
226
|
+
var import_context = require("@dxos/context");
|
|
227
|
+
var import_invariant2 = require("@dxos/invariant");
|
|
228
|
+
var import_lock_file3 = require("@dxos/lock-file");
|
|
229
|
+
var import_log2 = require("@dxos/log");
|
|
230
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
231
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
232
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
233
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
234
|
+
else
|
|
235
|
+
for (var i = decorators.length - 1; i >= 0; i--)
|
|
236
|
+
if (d = decorators[i])
|
|
237
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
238
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
239
|
+
}
|
|
240
|
+
var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/common/phoenix/src/watchdog.ts";
|
|
241
|
+
var WatchDog = class {
|
|
242
|
+
constructor(_params) {
|
|
243
|
+
this._params = _params;
|
|
244
|
+
this._restarts = 0;
|
|
245
|
+
}
|
|
246
|
+
async start() {
|
|
247
|
+
await this._acquireLock();
|
|
248
|
+
this._log("Lock acquired.");
|
|
249
|
+
const { cwd, shell, env, command, args } = {
|
|
250
|
+
cwd: process.cwd(),
|
|
251
|
+
...this._params
|
|
252
|
+
};
|
|
253
|
+
(0, import_invariant2.invariant)(command, "Command is not defined.", {
|
|
254
|
+
F: __dxlog_file2,
|
|
255
|
+
L: 86,
|
|
256
|
+
S: this,
|
|
257
|
+
A: [
|
|
258
|
+
"command",
|
|
259
|
+
"'Command is not defined.'"
|
|
260
|
+
]
|
|
261
|
+
});
|
|
262
|
+
this._log(`Spawning process ${command} ${args?.join(" ")}`);
|
|
263
|
+
this._child = (0, import_node_child_process2.spawn)(command, args, {
|
|
264
|
+
cwd,
|
|
265
|
+
shell,
|
|
266
|
+
env,
|
|
267
|
+
stdio: "pipe"
|
|
268
|
+
});
|
|
269
|
+
this._processCtx = new import_context.Context();
|
|
270
|
+
const childInfo = {
|
|
271
|
+
pid: process.pid,
|
|
272
|
+
timestamp: Date.now(),
|
|
273
|
+
restarts: this._restarts,
|
|
274
|
+
...this._params
|
|
275
|
+
};
|
|
276
|
+
(0, import_node_fs3.writeFileSync)(this._params.lockFile, JSON.stringify(childInfo, void 0, 2), {
|
|
277
|
+
encoding: "utf-8"
|
|
278
|
+
});
|
|
279
|
+
this._child.stdout.on("data", (data) => {
|
|
280
|
+
this._log(String(data));
|
|
281
|
+
});
|
|
282
|
+
this._child.stderr.on("data", (data) => {
|
|
283
|
+
this._err(data);
|
|
284
|
+
});
|
|
285
|
+
{
|
|
286
|
+
const restartHandler = async (code, signal) => {
|
|
287
|
+
if (code && code !== 0) {
|
|
288
|
+
this._err(`Died unexpectedly with exit code ${code} (signal: ${signal}).`);
|
|
289
|
+
await this.restart();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
this._child.on("close", restartHandler);
|
|
293
|
+
this._processCtx.onDispose(() => {
|
|
294
|
+
this._child.off("close", restartHandler);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
this._child.on("close", (code, signal) => {
|
|
298
|
+
this._log(`Stopped with exit code ${code} (signal: ${signal}).`);
|
|
299
|
+
});
|
|
300
|
+
(0, import_invariant2.invariant)(this._child.pid, "Child process has no pid.", {
|
|
301
|
+
F: __dxlog_file2,
|
|
302
|
+
L: 129,
|
|
303
|
+
S: this,
|
|
304
|
+
A: [
|
|
305
|
+
"this._child.pid",
|
|
306
|
+
"'Child process has no pid.'"
|
|
307
|
+
]
|
|
308
|
+
});
|
|
309
|
+
this._log(`Started with pid ${this._child.pid}.`);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Sends SIGINT to the child process and the tree it spawned (if `killTree` param is `true`).
|
|
313
|
+
*/
|
|
314
|
+
async stop() {
|
|
315
|
+
if (!this._child) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
await this._killWithSignal("SIGKILL");
|
|
319
|
+
await this._releaseLock();
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Sends SIGKILL to the child process and the tree it spawned (if `killTree` param is `true`).
|
|
323
|
+
*/
|
|
324
|
+
async kill() {
|
|
325
|
+
if (!this._child) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
await this._killWithSignal("SIGKILL");
|
|
329
|
+
await this._releaseLock();
|
|
330
|
+
}
|
|
331
|
+
async restart() {
|
|
332
|
+
await this.kill();
|
|
333
|
+
if (this._params.maxRestarts !== void 0 && this._restarts >= this._params.maxRestarts) {
|
|
334
|
+
this._err("Max restarts number is reached");
|
|
335
|
+
} else {
|
|
336
|
+
(0, import_log2.log)("Restarting...", void 0, {
|
|
337
|
+
F: __dxlog_file2,
|
|
338
|
+
L: 166,
|
|
339
|
+
S: this,
|
|
340
|
+
C: (f, a) => f(...a)
|
|
341
|
+
});
|
|
342
|
+
this._restarts++;
|
|
343
|
+
await this.start();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async _killWithSignal(signal) {
|
|
347
|
+
(0, import_invariant2.invariant)(this._processCtx, "Process context is not defined.", {
|
|
348
|
+
F: __dxlog_file2,
|
|
349
|
+
L: 173,
|
|
350
|
+
S: this,
|
|
351
|
+
A: [
|
|
352
|
+
"this._processCtx",
|
|
353
|
+
"'Process context is not defined.'"
|
|
354
|
+
]
|
|
355
|
+
});
|
|
356
|
+
await this._processCtx.dispose();
|
|
357
|
+
if (this._params.killTree) {
|
|
358
|
+
if (process.platform !== "win32") {
|
|
359
|
+
(0, import_invariant2.invariant)(this._child?.pid, "Child process has no pid.", {
|
|
360
|
+
F: __dxlog_file2,
|
|
361
|
+
L: 179,
|
|
362
|
+
S: this,
|
|
363
|
+
A: [
|
|
364
|
+
"this._child?.pid",
|
|
365
|
+
"'Child process has no pid.'"
|
|
366
|
+
]
|
|
367
|
+
});
|
|
368
|
+
const children = await (0, import_node_util.promisify)(import_ps_tree.default)(this._child.pid);
|
|
369
|
+
children.map((p) => p.PID).forEach((tpid) => {
|
|
370
|
+
(0, import_invariant2.invariant)(tpid, "Process id is not defined.", {
|
|
371
|
+
F: __dxlog_file2,
|
|
372
|
+
L: 184,
|
|
373
|
+
S: this,
|
|
374
|
+
A: [
|
|
375
|
+
"tpid",
|
|
376
|
+
"'Process id is not defined.'"
|
|
377
|
+
]
|
|
378
|
+
});
|
|
379
|
+
process.kill(Number(tpid), signal);
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
(0, import_invariant2.invariant)(this._child?.pid, "Child process has no pid.", {
|
|
384
|
+
F: __dxlog_file2,
|
|
385
|
+
L: 190,
|
|
386
|
+
S: this,
|
|
387
|
+
A: [
|
|
388
|
+
"this._child?.pid",
|
|
389
|
+
"'Child process has no pid.'"
|
|
390
|
+
]
|
|
391
|
+
});
|
|
392
|
+
this._child.kill(signal);
|
|
393
|
+
this._child = void 0;
|
|
394
|
+
}
|
|
395
|
+
async _acquireLock() {
|
|
396
|
+
if (await import_lock_file3.LockFile.isLocked(this._params.lockFile)) {
|
|
397
|
+
throw new Error("Lock file is already locked.");
|
|
398
|
+
}
|
|
399
|
+
this._lock = await import_lock_file3.LockFile.acquire(this._params.lockFile);
|
|
400
|
+
await waitForLockAcquisition(this._params.lockFile);
|
|
401
|
+
}
|
|
402
|
+
async _releaseLock() {
|
|
403
|
+
(0, import_invariant2.invariant)(this._lock, "Lock is not defined.", {
|
|
404
|
+
F: __dxlog_file2,
|
|
405
|
+
L: 204,
|
|
406
|
+
S: this,
|
|
407
|
+
A: [
|
|
408
|
+
"this._lock",
|
|
409
|
+
"'Lock is not defined.'"
|
|
410
|
+
]
|
|
411
|
+
});
|
|
412
|
+
await import_lock_file3.LockFile.release(this._lock);
|
|
413
|
+
await waitForLockRelease(this._params.lockFile);
|
|
414
|
+
this._lock = void 0;
|
|
415
|
+
}
|
|
416
|
+
_log(message) {
|
|
417
|
+
(0, import_node_fs3.writeFileSync)(this._params.logFile, message + "\n", {
|
|
418
|
+
flag: "a+",
|
|
419
|
+
encoding: "utf-8"
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
_err(message) {
|
|
423
|
+
this._log(message);
|
|
424
|
+
(0, import_node_fs3.writeFileSync)(this._params.errFile, message + "\n", {
|
|
425
|
+
flag: "a+",
|
|
426
|
+
encoding: "utf-8"
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
_ts_decorate([
|
|
431
|
+
import_async2.synchronized
|
|
432
|
+
], WatchDog.prototype, "start", null);
|
|
433
|
+
_ts_decorate([
|
|
434
|
+
import_async2.synchronized
|
|
435
|
+
], WatchDog.prototype, "stop", null);
|
|
436
|
+
_ts_decorate([
|
|
437
|
+
import_async2.synchronized
|
|
438
|
+
], WatchDog.prototype, "kill", null);
|
|
439
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
440
|
+
0 && (module.exports = {
|
|
441
|
+
DaemonManager,
|
|
442
|
+
WatchDog
|
|
443
|
+
});
|
|
444
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/index.ts", "../../../src/daemon-manager.ts", "../../../src/utils.ts", "../../../src/defs.ts", "../../../src/watchdog.ts"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n// Inspired by https://github.com/foreversd/forever\n// Their copyright notice is included below.\n// Copyright (C) 2010 Charlie Robbins & the Contributors\n//\n\nexport * from './daemon-manager';\nexport * from './watchdog';\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { fork } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, unlinkSync } from 'node:fs';\nimport { readdir } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport pkgUp from 'pkg-up';\n\nimport { invariant } from '@dxos/invariant';\nimport { LockFile } from '@dxos/lock-file';\nimport { log } from '@dxos/log';\n\nimport { waitForLockAcquisition, waitForLockFileBeingFilledWithInfo, waitForLockRelease } from './utils';\nimport { ChildParams, Logs, Lock, ProcessInfo, WatchDogParams } from './watchdog';\n\nconst LOCK_FILE_NAME = 'lockfile';\n\n/**\n * Params to start a daemon.\n * User have no control over the lock file.\n */\nexport type StartParams = ChildParams & Partial<Logs>;\n\nexport class DaemonManager {\n constructor(private readonly _rootPath: string) {\n if (!existsSync(join(_rootPath, 'profile'))) {\n mkdirSync(join(_rootPath, 'profile'), { recursive: true });\n }\n }\n\n private _getConfigFiles(uid: string): Logs & Lock {\n const defaultConfigDir = join(this._rootPath, 'profile', uid);\n if (!existsSync(defaultConfigDir)) {\n mkdirSync(defaultConfigDir, { recursive: true });\n }\n return {\n lockFile: join(defaultConfigDir, LOCK_FILE_NAME),\n logFile: join(defaultConfigDir, 'file.log'),\n errFile: join(defaultConfigDir, 'err.log'),\n };\n }\n\n async start(params: StartParams) {\n invariant(params.command, 'command is required');\n const watchDogParams: WatchDogParams = {\n ...this._getConfigFiles(params.uid),\n ...params,\n };\n\n {\n // Create log folders.\n mkdirSync(dirname(watchDogParams.logFile), { recursive: true });\n mkdirSync(dirname(watchDogParams.errFile), { recursive: true });\n }\n\n {\n // Clear stale lock file if process is not running.\n if (await LockFile.isLocked(watchDogParams.lockFile)) {\n throw new Error('Lock file is already locked.');\n }\n unlinkSync(watchDogParams.lockFile);\n }\n\n const watchdogPath = join(dirname(pkgUp.sync({ cwd: __dirname })!), 'bin', 'watchdog');\n\n const watchDog = fork(watchdogPath, [JSON.stringify(watchDogParams)], {\n detached: true,\n cwd: __dirname,\n });\n\n watchDog.on('exit', (code, signal) => {\n if (code && code !== 0) {\n log.error('Monitor died unexpectedly', { code, signal });\n }\n });\n\n await waitForLockAcquisition(watchDogParams.lockFile);\n await waitForLockFileBeingFilledWithInfo(watchDogParams.lockFile);\n\n watchDog.disconnect();\n watchDog.unref();\n\n return this.getInfo(params.uid);\n }\n\n async stop(uid: string, force?: boolean) {\n const lockFile = this._getConfigFiles(uid).lockFile;\n const processInfo = JSON.parse(readFileSync(lockFile, { encoding: 'utf-8' }));\n try {\n if (force) {\n process.kill(processInfo.pid, 'SIGKILL');\n } else {\n process.kill(processInfo.pid, 'SIGINT');\n }\n } catch (err) {\n invariant(err instanceof Error, 'Invalid error type.');\n if (!err.name.includes('ESRCH') && !err.message.includes('ESRCH')) {\n throw err;\n }\n }\n await waitForLockRelease(lockFile);\n\n return this.getInfo(uid);\n }\n\n async list(): Promise<ProcessInfo[]> {\n const uids = (await readdir(join(this._rootPath, 'profile'))).filter((uid) => !uid.startsWith('.'));\n\n return Promise.all(\n uids.map(async (uid) => {\n return this.getInfo(uid);\n }),\n );\n }\n\n async getInfo(uid: string): Promise<ProcessInfo> {\n const files = this._getConfigFiles(uid);\n\n let info: ProcessInfo = { running: await LockFile.isLocked(files.lockFile), uid, ...files };\n if (existsSync(files.lockFile)) {\n try {\n info = {\n ...info,\n ...JSON.parse(readFileSync(files.lockFile, { encoding: 'utf-8' })),\n };\n } catch (err) {}\n }\n\n return info;\n }\n\n async isRunning(uid: string): Promise<boolean> {\n const lockFile = this._getConfigFiles(uid).lockFile;\n return LockFile.isLocked(lockFile);\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { readFileSync } from 'node:fs';\n\nimport { waitForCondition } from '@dxos/async';\nimport { LockFile } from '@dxos/lock-file';\n\nimport { LOCK_CHECK_INTERVAL, LOCK_TIMEOUT } from './defs';\n\nexport const waitForLockAcquisition = async (lockFile: string) =>\n waitForCondition({\n condition: async () => await LockFile.isLocked(lockFile),\n timeout: LOCK_TIMEOUT,\n interval: LOCK_CHECK_INTERVAL,\n error: new Error('Lock file is not being acquired.'),\n });\n\nexport const waitForLockFileBeingFilledWithInfo = async (lockFile: string) =>\n waitForCondition({\n condition: () => readFileSync(lockFile, { encoding: 'utf-8' }).includes('pid'),\n timeout: LOCK_TIMEOUT,\n interval: LOCK_CHECK_INTERVAL,\n error: new Error('Lock file is not being propagated with info.'),\n });\n\nexport const waitForLockRelease = async (lockFile: string) =>\n waitForCondition({\n condition: async () => !(await LockFile.isLocked(lockFile)),\n timeout: LOCK_TIMEOUT,\n interval: LOCK_CHECK_INTERVAL,\n error: new Error('Lock file is not being released.'),\n });\n", "//\n// Copyright 2023 DXOS.org\n//\n\nexport const LOCK_TIMEOUT = 1_000;\nexport const LOCK_CHECK_INTERVAL = 50;\nexport const DAEMON_START_TIMEOUT = 10_000;\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { ChildProcessWithoutNullStreams, spawn } from 'node:child_process';\nimport { writeFileSync } from 'node:fs';\nimport { FileHandle } from 'node:fs/promises';\nimport { promisify } from 'node:util';\nimport psTree from 'ps-tree';\n\nimport { synchronized } from '@dxos/async';\nimport { Context } from '@dxos/context';\nimport { invariant } from '@dxos/invariant';\nimport { LockFile } from '@dxos/lock-file';\nimport { log } from '@dxos/log';\n\nimport { waitForLockAcquisition, waitForLockRelease } from './utils';\n\nexport type DaemonInfo = {\n pid: number;\n command: string;\n args: string[];\n cwd: string;\n timestamp: number;\n};\n\nexport type ProcessInfo = WatchDogParams & {\n pid?: number;\n timestamp?: number;\n restarts?: number;\n running?: boolean;\n};\n\nexport type Lock = {\n lockFile: string; // Path to lock file\n};\n\nexport type Logs = {\n //\n // Log files and associated logging options for this instance\n //\n logFile: string; // Path to log output all logs\n errFile: string; // Path to log output from child stderr\n};\n\nexport type ChildParams = {\n uid: string; // Unique identifier for this instance\n\n //\n // Basic configuration options\n //\n maxRestarts?: number | undefined; // Sets the maximum number of times a given script should run\n killTree?: boolean | undefined; // Kills the entire child process tree on `exit`\n\n //\n // Command to spawn as well as options and other vars\n // (env, cwd, etc) to pass along\n //\n command?: string; // Binary to run (default: 'node')\n args?: string[] | undefined; // Additional arguments to pass to the script,\n\n //\n // More specific options to pass along to `child_process.spawn` which\n // will override anything passed to the `spawnWith` option\n //\n env?: NodeJS.ProcessEnv | undefined;\n cwd?: string | undefined;\n shell?: boolean | undefined;\n};\n\nexport type WatchDogParams = ChildParams & Lock & Logs;\n\nexport class WatchDog {\n private _lock?: FileHandle;\n private _child?: ChildProcessWithoutNullStreams;\n private _restarts = 0;\n private _processCtx?: Context;\n\n constructor(private readonly _params: WatchDogParams) {}\n\n @synchronized\n async start() {\n await this._acquireLock();\n this._log('Lock acquired.');\n const { cwd, shell, env, command, args } = { cwd: process.cwd(), ...this._params };\n invariant(command, 'Command is not defined.');\n\n this._log(`Spawning process ${command} ${args?.join(' ')}`);\n this._child = spawn(command, args, { cwd, shell, env, stdio: 'pipe' });\n this._processCtx = new Context();\n\n const childInfo: ProcessInfo = {\n pid: process.pid,\n timestamp: Date.now(),\n restarts: this._restarts,\n ...this._params,\n };\n\n writeFileSync(this._params.lockFile, JSON.stringify(childInfo, undefined, 2), { encoding: 'utf-8' });\n\n this._child.stdout.on('data', (data: Uint8Array) => {\n this._log(String(data));\n });\n this._child.stderr.on('data', (data: Uint8Array) => {\n this._err(data);\n });\n\n // Setup restart handler.\n {\n const restartHandler = async (code: number, signal: number | NodeJS.Signals) => {\n if (code && code !== 0) {\n this._err(`Died unexpectedly with exit code ${code} (signal: ${signal}).`);\n await this.restart();\n }\n };\n\n this._child.on('close', restartHandler);\n\n // We should unsubscribe from the event when the process is killed by us to not try to restart it.\n this._processCtx.onDispose(() => {\n this._child!.off('close', restartHandler);\n });\n }\n\n this._child.on('close', (code: number, signal: number | NodeJS.Signals) => {\n this._log(`Stopped with exit code ${code} (signal: ${signal}).`);\n });\n\n invariant(this._child.pid, 'Child process has no pid.');\n this._log(`Started with pid ${this._child.pid}.`);\n }\n\n /**\n * Sends SIGINT to the child process and the tree it spawned (if `killTree` param is `true`).\n */\n @synchronized\n async stop() {\n if (!this._child) {\n return;\n }\n\n await this._killWithSignal('SIGKILL');\n\n await this._releaseLock();\n }\n\n /**\n * Sends SIGKILL to the child process and the tree it spawned (if `killTree` param is `true`).\n */\n @synchronized\n async kill() {\n if (!this._child) {\n return;\n }\n\n await this._killWithSignal('SIGKILL');\n\n await this._releaseLock();\n }\n\n async restart() {\n await this.kill();\n if (this._params.maxRestarts !== undefined && this._restarts >= this._params.maxRestarts) {\n this._err('Max restarts number is reached');\n } else {\n log('Restarting...');\n this._restarts++;\n await this.start();\n }\n }\n\n async _killWithSignal(signal: number | NodeJS.Signals) {\n invariant(this._processCtx, 'Process context is not defined.');\n await this._processCtx.dispose();\n\n // Kill child process tree.\n if (this._params.killTree) {\n if (process.platform !== 'win32') {\n invariant(this._child?.pid, 'Child process has no pid.');\n const children = await promisify(psTree)(this._child.pid);\n children\n .map((p) => p.PID)\n .forEach((tpid) => {\n invariant(tpid, 'Process id is not defined.');\n process.kill(Number(tpid), signal);\n });\n }\n }\n\n invariant(this._child?.pid, 'Child process has no pid.');\n this._child.kill(signal);\n this._child = undefined;\n }\n\n private async _acquireLock() {\n if (await LockFile.isLocked(this._params.lockFile)) {\n throw new Error('Lock file is already locked.');\n }\n this._lock = await LockFile.acquire(this._params.lockFile);\n await waitForLockAcquisition(this._params.lockFile);\n }\n\n private async _releaseLock() {\n invariant(this._lock, 'Lock is not defined.');\n\n await LockFile.release(this._lock);\n await waitForLockRelease(this._params.lockFile);\n\n this._lock = undefined;\n }\n\n private _log(message: string | Uint8Array) {\n writeFileSync(this._params.logFile, message + '\\n', {\n flag: 'a+',\n encoding: 'utf-8',\n });\n }\n\n private _err(message: string | Uint8Array) {\n this._log(message);\n writeFileSync(this._params.errFile, message + '\\n', {\n flag: 'a+',\n encoding: 'utf-8',\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_node_fs", "import_lock_file", "LOCK_TIMEOUT", "LOCK_CHECK_INTERVAL", "waitForLockAcquisition", "lockFile", "waitForCondition", "condition", "LockFile", "isLocked", "timeout", "LOCK_TIMEOUT", "interval", "LOCK_CHECK_INTERVAL", "error", "Error", "waitForLockFileBeingFilledWithInfo", "readFileSync", "encoding", "includes", "waitForLockRelease", "LOCK_FILE_NAME", "DaemonManager", "constructor", "_rootPath", "existsSync", "join", "mkdirSync", "recursive", "_getConfigFiles", "uid", "defaultConfigDir", "lockFile", "logFile", "errFile", "start", "params", "invariant", "command", "watchDogParams", "dirname", "LockFile", "isLocked", "Error", "unlinkSync", "watchdogPath", "pkgUp", "sync", "cwd", "__dirname", "watchDog", "fork", "JSON", "stringify", "detached", "on", "code", "signal", "log", "error", "waitForLockAcquisition", "waitForLockFileBeingFilledWithInfo", "disconnect", "unref", "getInfo", "stop", "force", "processInfo", "parse", "readFileSync", "encoding", "process", "kill", "pid", "err", "name", "includes", "message", "waitForLockRelease", "list", "uids", "readdir", "filter", "startsWith", "Promise", "all", "map", "files", "info", "running", "isRunning", "import_node_child_process", "import_node_fs", "import_async", "import_invariant", "import_lock_file", "import_log", "WatchDog", "constructor", "_params", "_restarts", "start", "_acquireLock", "_log", "cwd", "shell", "env", "command", "args", "process", "invariant", "join", "_child", "spawn", "stdio", "_processCtx", "Context", "childInfo", "pid", "timestamp", "Date", "now", "restarts", "writeFileSync", "lockFile", "JSON", "stringify", "undefined", "encoding", "stdout", "on", "data", "String", "stderr", "_err", "restartHandler", "code", "signal", "restart", "onDispose", "off", "stop", "_killWithSignal", "_releaseLock", "kill", "maxRestarts", "log", "dispose", "killTree", "platform", "children", "promisify", "psTree", "map", "p", "PID", "forEach", "tpid", "Number", "LockFile", "isLocked", "Error", "_lock", "acquire", "waitForLockAcquisition", "release", "waitForLockRelease", "message", "logFile", "flag", "errFile", "synchronized"]
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"inputs":{"packages/common/phoenix/src/defs.ts":{"bytes":844,"imports":[],"format":"esm"},"packages/common/phoenix/src/utils.ts":{"bytes":4044,"imports":[{"path":"node:fs","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/lock-file","kind":"import-statement","external":true},{"path":"packages/common/phoenix/src/defs.ts","kind":"import-statement","original":"./defs"}],"format":"esm"},"packages/common/phoenix/src/daemon-manager.ts":{"bytes":15641,"imports":[{"path":"node:child_process","kind":"import-statement","external":true},{"path":"node:fs","kind":"import-statement","external":true},{"path":"node:fs/promises","kind":"import-statement","external":true},{"path":"node:path","kind":"import-statement","external":true},{"path":"pkg-up","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/lock-file","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"packages/common/phoenix/src/utils.ts","kind":"import-statement","original":"./utils"}],"format":"esm"},"packages/common/phoenix/src/watchdog.ts":{"bytes":22416,"imports":[{"path":"node:child_process","kind":"import-statement","external":true},{"path":"node:fs","kind":"import-statement","external":true},{"path":"node:util","kind":"import-statement","external":true},{"path":"ps-tree","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/lock-file","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"packages/common/phoenix/src/utils.ts","kind":"import-statement","original":"./utils"}],"format":"esm"},"packages/common/phoenix/src/index.ts":{"bytes":1006,"imports":[{"path":"packages/common/phoenix/src/daemon-manager.ts","kind":"import-statement","original":"./daemon-manager"},{"path":"packages/common/phoenix/src/watchdog.ts","kind":"import-statement","original":"./watchdog"}],"format":"esm"}},"outputs":{"packages/common/phoenix/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":20862},"packages/common/phoenix/dist/lib/node/index.cjs":{"imports":[{"path":"node:child_process","kind":"require-call","external":true},{"path":"node:fs","kind":"require-call","external":true},{"path":"node:fs/promises","kind":"require-call","external":true},{"path":"node:path","kind":"require-call","external":true},{"path":"pkg-up","kind":"require-call","external":true},{"path":"@dxos/invariant","kind":"require-call","external":true},{"path":"@dxos/lock-file","kind":"require-call","external":true},{"path":"@dxos/log","kind":"require-call","external":true},{"path":"node:fs","kind":"require-call","external":true},{"path":"@dxos/async","kind":"require-call","external":true},{"path":"@dxos/lock-file","kind":"require-call","external":true},{"path":"node:child_process","kind":"require-call","external":true},{"path":"node:fs","kind":"require-call","external":true},{"path":"node:util","kind":"require-call","external":true},{"path":"ps-tree","kind":"require-call","external":true},{"path":"@dxos/async","kind":"require-call","external":true},{"path":"@dxos/context","kind":"require-call","external":true},{"path":"@dxos/invariant","kind":"require-call","external":true},{"path":"@dxos/lock-file","kind":"require-call","external":true},{"path":"@dxos/log","kind":"require-call","external":true}],"exports":[],"entryPoint":"packages/common/phoenix/src/index.ts","inputs":{"packages/common/phoenix/src/index.ts":{"bytesInOutput":159},"packages/common/phoenix/src/daemon-manager.ts":{"bytesInOutput":4716},"packages/common/phoenix/src/utils.ts":{"bytesInOutput":1027},"packages/common/phoenix/src/defs.ts":{"bytesInOutput":54},"packages/common/phoenix/src/watchdog.ts":{"bytesInOutput":6674}},"bytes":14487}}}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ChildParams, Logs, ProcessInfo } from './watchdog';
|
|
2
|
+
/**
|
|
3
|
+
* Params to start a daemon.
|
|
4
|
+
* User have no control over the lock file.
|
|
5
|
+
*/
|
|
6
|
+
export type StartParams = ChildParams & Partial<Logs>;
|
|
7
|
+
export declare class DaemonManager {
|
|
8
|
+
private readonly _rootPath;
|
|
9
|
+
constructor(_rootPath: string);
|
|
10
|
+
private _getConfigFiles;
|
|
11
|
+
start(params: StartParams): Promise<ProcessInfo>;
|
|
12
|
+
stop(uid: string, force?: boolean): Promise<ProcessInfo>;
|
|
13
|
+
list(): Promise<ProcessInfo[]>;
|
|
14
|
+
getInfo(uid: string): Promise<ProcessInfo>;
|
|
15
|
+
isRunning(uid: string): Promise<boolean>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=daemon-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-manager.d.ts","sourceRoot":"","sources":["../../../src/daemon-manager.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,WAAW,EAAE,IAAI,EAAQ,WAAW,EAAkB,MAAM,YAAY,CAAC;AAIlF;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEtD,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM;IAM9C,OAAO,CAAC,eAAe;IAYjB,KAAK,CAAC,MAAM,EAAE,WAAW;IA2CzB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;IAoBjC,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAU9B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAgB1C,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAI/C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-manager.test.d.ts","sourceRoot":"","sources":["../../../src/daemon-manager.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defs.d.ts","sourceRoot":"","sources":["../../../src/defs.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,YAAY,OAAQ,CAAC;AAClC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,oBAAoB,QAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAQA,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing-utils.d.ts","sourceRoot":"","sources":["../../../src/testing-utils.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,QAAQ,8BAA8B,CAAC;AAKpD,eAAO,MAAM,kBAAkB,YAG9B,CAAC;AAEF,eAAO,MAAM,UAAU,iBAAkB,MAAM,EAAE,SAMhD,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const waitForLockAcquisition: (lockFile: string) => Promise<boolean>;
|
|
2
|
+
export declare const waitForLockFileBeingFilledWithInfo: (lockFile: string) => Promise<boolean>;
|
|
3
|
+
export declare const waitForLockRelease: (lockFile: string) => Promise<boolean>;
|
|
4
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/utils.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,sBAAsB,aAAoB,MAAM,qBAMzD,CAAC;AAEL,eAAO,MAAM,kCAAkC,aAAoB,MAAM,qBAMrE,CAAC;AAEL,eAAO,MAAM,kBAAkB,aAAoB,MAAM,qBAMrD,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
export type DaemonInfo = {
|
|
3
|
+
pid: number;
|
|
4
|
+
command: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
cwd: string;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
};
|
|
9
|
+
export type ProcessInfo = WatchDogParams & {
|
|
10
|
+
pid?: number;
|
|
11
|
+
timestamp?: number;
|
|
12
|
+
restarts?: number;
|
|
13
|
+
running?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type Lock = {
|
|
16
|
+
lockFile: string;
|
|
17
|
+
};
|
|
18
|
+
export type Logs = {
|
|
19
|
+
logFile: string;
|
|
20
|
+
errFile: string;
|
|
21
|
+
};
|
|
22
|
+
export type ChildParams = {
|
|
23
|
+
uid: string;
|
|
24
|
+
maxRestarts?: number | undefined;
|
|
25
|
+
killTree?: boolean | undefined;
|
|
26
|
+
command?: string;
|
|
27
|
+
args?: string[] | undefined;
|
|
28
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
29
|
+
cwd?: string | undefined;
|
|
30
|
+
shell?: boolean | undefined;
|
|
31
|
+
};
|
|
32
|
+
export type WatchDogParams = ChildParams & Lock & Logs;
|
|
33
|
+
export declare class WatchDog {
|
|
34
|
+
private readonly _params;
|
|
35
|
+
private _lock?;
|
|
36
|
+
private _child?;
|
|
37
|
+
private _restarts;
|
|
38
|
+
private _processCtx?;
|
|
39
|
+
constructor(_params: WatchDogParams);
|
|
40
|
+
start(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Sends SIGINT to the child process and the tree it spawned (if `killTree` param is `true`).
|
|
43
|
+
*/
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Sends SIGKILL to the child process and the tree it spawned (if `killTree` param is `true`).
|
|
47
|
+
*/
|
|
48
|
+
kill(): Promise<void>;
|
|
49
|
+
restart(): Promise<void>;
|
|
50
|
+
_killWithSignal(signal: number | NodeJS.Signals): Promise<void>;
|
|
51
|
+
private _acquireLock;
|
|
52
|
+
private _releaseLock;
|
|
53
|
+
private _log;
|
|
54
|
+
private _err;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=watchdog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watchdog.d.ts","sourceRoot":"","sources":["../../../src/watchdog.ts"],"names":[],"mappings":";AAkBA,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IAIjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IAKZ,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAM/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAM5B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;IACpC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC;AAEvD,qBAAa,QAAQ;IAMP,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC,OAAO,CAAC,KAAK,CAAC,CAAa;IAC3B,OAAO,CAAC,MAAM,CAAC,CAAiC;IAChD,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,WAAW,CAAC,CAAU;gBAED,OAAO,EAAE,cAAc;IAG9C,KAAK;IAmDX;;OAEG;IAEG,IAAI;IAUV;;OAEG;IAEG,IAAI;IAUJ,OAAO;IAWP,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO;YAuBvC,YAAY;YAQZ,YAAY;IAS1B,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,IAAI;CAOb"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watchdog.test.d.ts","sourceRoot":"","sources":["../../../src/watchdog.test.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dxos/phoenix",
|
|
3
|
+
"version": "0.1.58-main.0e9c99e",
|
|
4
|
+
"description": "Basic node daemon.",
|
|
5
|
+
"homepage": "https://dxos.org",
|
|
6
|
+
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "DXOS.org",
|
|
9
|
+
"main": "dist/lib/node/index.cjs",
|
|
10
|
+
"types": "dist/types/src/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"fs-extra": "^8.1.0",
|
|
17
|
+
"pkg-up": "^3.1.0",
|
|
18
|
+
"ps-tree": "^1.2.0",
|
|
19
|
+
"@dxos/async": "0.1.58-main.0e9c99e",
|
|
20
|
+
"@dxos/context": "0.1.58-main.0e9c99e",
|
|
21
|
+
"@dxos/invariant": "0.1.58-main.0e9c99e",
|
|
22
|
+
"@dxos/keys": "0.1.58-main.0e9c99e",
|
|
23
|
+
"@dxos/lock-file": "0.1.58-main.0e9c99e",
|
|
24
|
+
"@dxos/log": "0.1.58-main.0e9c99e",
|
|
25
|
+
"@dxos/node-std": "0.1.58-main.0e9c99e",
|
|
26
|
+
"@dxos/util": "0.1.58-main.0e9c99e"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/fs-extra": "^9.0.4",
|
|
30
|
+
"@types/node": "^18.11.9",
|
|
31
|
+
"@types/ps-tree": "^1.1.2"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
import fse from 'fs-extra';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import waitForExpect from 'wait-for-expect';
|
|
11
|
+
|
|
12
|
+
import { Trigger } from '@dxos/async';
|
|
13
|
+
import { PublicKey } from '@dxos/keys';
|
|
14
|
+
import { afterTest, describe, test } from '@dxos/test';
|
|
15
|
+
|
|
16
|
+
import { DaemonManager } from './daemon-manager';
|
|
17
|
+
import { TEST_DIR, neverEndingProcess } from './testing-utils';
|
|
18
|
+
|
|
19
|
+
describe('DaemonManager', () => {
|
|
20
|
+
test('kill process by pid', async () => {
|
|
21
|
+
const child = spawn('node', ['-e', `(${neverEndingProcess.toString()})()`]);
|
|
22
|
+
const trigger = new Trigger();
|
|
23
|
+
child.on('exit', () => {
|
|
24
|
+
trigger.wake();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
process.kill(child.pid!, 'SIGKILL');
|
|
28
|
+
|
|
29
|
+
await trigger.wait({ timeout: 1_000 });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Fails on CI
|
|
33
|
+
test.skip('start/stop detached watchdog', async () => {
|
|
34
|
+
const uid = `test-${PublicKey.random().toHex()}`;
|
|
35
|
+
const root = join(TEST_DIR, uid);
|
|
36
|
+
afterTest(() => {
|
|
37
|
+
fse.removeSync(root);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Start
|
|
41
|
+
{
|
|
42
|
+
const manager = new DaemonManager(root);
|
|
43
|
+
const params = await manager.start({
|
|
44
|
+
uid,
|
|
45
|
+
command: 'node',
|
|
46
|
+
args: ['-e', `(${neverEndingProcess.toString()})()`],
|
|
47
|
+
maxRestarts: 0,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await waitForExpect(() => {
|
|
51
|
+
expect(existsSync(params.logFile)).to.be.true;
|
|
52
|
+
const logs = readFileSync(params.logFile, { encoding: 'utf-8' });
|
|
53
|
+
expect(logs).to.contain('neverEndingProcess started');
|
|
54
|
+
}, 1000);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Stop
|
|
58
|
+
{
|
|
59
|
+
const manager = new DaemonManager(root);
|
|
60
|
+
const info = await manager.list();
|
|
61
|
+
expect(info.length).to.equal(1);
|
|
62
|
+
expect(info[0].running).to.be.true;
|
|
63
|
+
expect(info[0].uid).to.equal(uid);
|
|
64
|
+
expect(await manager.isRunning(uid)).to.be.true;
|
|
65
|
+
|
|
66
|
+
const params = await manager.stop(uid);
|
|
67
|
+
|
|
68
|
+
await waitForExpect(() => {
|
|
69
|
+
const logs = readFileSync(params.logFile, { encoding: 'utf-8' });
|
|
70
|
+
expect(logs).to.contain('Stopped with exit code');
|
|
71
|
+
}, 1000);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { fork } from 'node:child_process';
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
7
|
+
import { readdir } from 'node:fs/promises';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import pkgUp from 'pkg-up';
|
|
10
|
+
|
|
11
|
+
import { invariant } from '@dxos/invariant';
|
|
12
|
+
import { LockFile } from '@dxos/lock-file';
|
|
13
|
+
import { log } from '@dxos/log';
|
|
14
|
+
|
|
15
|
+
import { waitForLockAcquisition, waitForLockFileBeingFilledWithInfo, waitForLockRelease } from './utils';
|
|
16
|
+
import { ChildParams, Logs, Lock, ProcessInfo, WatchDogParams } from './watchdog';
|
|
17
|
+
|
|
18
|
+
const LOCK_FILE_NAME = 'lockfile';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Params to start a daemon.
|
|
22
|
+
* User have no control over the lock file.
|
|
23
|
+
*/
|
|
24
|
+
export type StartParams = ChildParams & Partial<Logs>;
|
|
25
|
+
|
|
26
|
+
export class DaemonManager {
|
|
27
|
+
constructor(private readonly _rootPath: string) {
|
|
28
|
+
if (!existsSync(join(_rootPath, 'profile'))) {
|
|
29
|
+
mkdirSync(join(_rootPath, 'profile'), { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private _getConfigFiles(uid: string): Logs & Lock {
|
|
34
|
+
const defaultConfigDir = join(this._rootPath, 'profile', uid);
|
|
35
|
+
if (!existsSync(defaultConfigDir)) {
|
|
36
|
+
mkdirSync(defaultConfigDir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
lockFile: join(defaultConfigDir, LOCK_FILE_NAME),
|
|
40
|
+
logFile: join(defaultConfigDir, 'file.log'),
|
|
41
|
+
errFile: join(defaultConfigDir, 'err.log'),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async start(params: StartParams) {
|
|
46
|
+
invariant(params.command, 'command is required');
|
|
47
|
+
const watchDogParams: WatchDogParams = {
|
|
48
|
+
...this._getConfigFiles(params.uid),
|
|
49
|
+
...params,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
{
|
|
53
|
+
// Create log folders.
|
|
54
|
+
mkdirSync(dirname(watchDogParams.logFile), { recursive: true });
|
|
55
|
+
mkdirSync(dirname(watchDogParams.errFile), { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
{
|
|
59
|
+
// Clear stale lock file if process is not running.
|
|
60
|
+
if (await LockFile.isLocked(watchDogParams.lockFile)) {
|
|
61
|
+
throw new Error('Lock file is already locked.');
|
|
62
|
+
}
|
|
63
|
+
unlinkSync(watchDogParams.lockFile);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const watchdogPath = join(dirname(pkgUp.sync({ cwd: __dirname })!), 'bin', 'watchdog');
|
|
67
|
+
|
|
68
|
+
const watchDog = fork(watchdogPath, [JSON.stringify(watchDogParams)], {
|
|
69
|
+
detached: true,
|
|
70
|
+
cwd: __dirname,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
watchDog.on('exit', (code, signal) => {
|
|
74
|
+
if (code && code !== 0) {
|
|
75
|
+
log.error('Monitor died unexpectedly', { code, signal });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await waitForLockAcquisition(watchDogParams.lockFile);
|
|
80
|
+
await waitForLockFileBeingFilledWithInfo(watchDogParams.lockFile);
|
|
81
|
+
|
|
82
|
+
watchDog.disconnect();
|
|
83
|
+
watchDog.unref();
|
|
84
|
+
|
|
85
|
+
return this.getInfo(params.uid);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async stop(uid: string, force?: boolean) {
|
|
89
|
+
const lockFile = this._getConfigFiles(uid).lockFile;
|
|
90
|
+
const processInfo = JSON.parse(readFileSync(lockFile, { encoding: 'utf-8' }));
|
|
91
|
+
try {
|
|
92
|
+
if (force) {
|
|
93
|
+
process.kill(processInfo.pid, 'SIGKILL');
|
|
94
|
+
} else {
|
|
95
|
+
process.kill(processInfo.pid, 'SIGINT');
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
invariant(err instanceof Error, 'Invalid error type.');
|
|
99
|
+
if (!err.name.includes('ESRCH') && !err.message.includes('ESRCH')) {
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
await waitForLockRelease(lockFile);
|
|
104
|
+
|
|
105
|
+
return this.getInfo(uid);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async list(): Promise<ProcessInfo[]> {
|
|
109
|
+
const uids = (await readdir(join(this._rootPath, 'profile'))).filter((uid) => !uid.startsWith('.'));
|
|
110
|
+
|
|
111
|
+
return Promise.all(
|
|
112
|
+
uids.map(async (uid) => {
|
|
113
|
+
return this.getInfo(uid);
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async getInfo(uid: string): Promise<ProcessInfo> {
|
|
119
|
+
const files = this._getConfigFiles(uid);
|
|
120
|
+
|
|
121
|
+
let info: ProcessInfo = { running: await LockFile.isLocked(files.lockFile), uid, ...files };
|
|
122
|
+
if (existsSync(files.lockFile)) {
|
|
123
|
+
try {
|
|
124
|
+
info = {
|
|
125
|
+
...info,
|
|
126
|
+
...JSON.parse(readFileSync(files.lockFile, { encoding: 'utf-8' })),
|
|
127
|
+
};
|
|
128
|
+
} catch (err) {}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return info;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async isRunning(uid: string): Promise<boolean> {
|
|
135
|
+
const lockFile = this._getConfigFiles(uid).lockFile;
|
|
136
|
+
return LockFile.isLocked(lockFile);
|
|
137
|
+
}
|
|
138
|
+
}
|
package/src/defs.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
// Inspired by https://github.com/foreversd/forever
|
|
5
|
+
// Their copyright notice is included below.
|
|
6
|
+
// Copyright (C) 2010 Charlie Robbins & the Contributors
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
export * from './daemon-manager';
|
|
10
|
+
export * from './watchdog';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { existsSync, mkdirSync, unlinkSync } from 'node:fs';
|
|
6
|
+
|
|
7
|
+
export const TEST_DIR = '/tmp/dxos/testing/phoenix';
|
|
8
|
+
if (!existsSync(TEST_DIR)) {
|
|
9
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const neverEndingProcess = () => {
|
|
13
|
+
console.log('neverEndingProcess started');
|
|
14
|
+
setTimeout(() => {}, 1_000_000);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const clearFiles = (...filenames: string[]) => {
|
|
18
|
+
filenames.forEach((filename) => {
|
|
19
|
+
if (existsSync(filename)) {
|
|
20
|
+
unlinkSync(filename);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
};
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
|
|
7
|
+
import { waitForCondition } from '@dxos/async';
|
|
8
|
+
import { LockFile } from '@dxos/lock-file';
|
|
9
|
+
|
|
10
|
+
import { LOCK_CHECK_INTERVAL, LOCK_TIMEOUT } from './defs';
|
|
11
|
+
|
|
12
|
+
export const waitForLockAcquisition = async (lockFile: string) =>
|
|
13
|
+
waitForCondition({
|
|
14
|
+
condition: async () => await LockFile.isLocked(lockFile),
|
|
15
|
+
timeout: LOCK_TIMEOUT,
|
|
16
|
+
interval: LOCK_CHECK_INTERVAL,
|
|
17
|
+
error: new Error('Lock file is not being acquired.'),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const waitForLockFileBeingFilledWithInfo = async (lockFile: string) =>
|
|
21
|
+
waitForCondition({
|
|
22
|
+
condition: () => readFileSync(lockFile, { encoding: 'utf-8' }).includes('pid'),
|
|
23
|
+
timeout: LOCK_TIMEOUT,
|
|
24
|
+
interval: LOCK_CHECK_INTERVAL,
|
|
25
|
+
error: new Error('Lock file is not being propagated with info.'),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const waitForLockRelease = async (lockFile: string) =>
|
|
29
|
+
waitForCondition({
|
|
30
|
+
condition: async () => !(await LockFile.isLocked(lockFile)),
|
|
31
|
+
timeout: LOCK_TIMEOUT,
|
|
32
|
+
interval: LOCK_CHECK_INTERVAL,
|
|
33
|
+
error: new Error('Lock file is not being released.'),
|
|
34
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
import { asyncTimeout } from '@dxos/async';
|
|
9
|
+
import { LockFile } from '@dxos/lock-file';
|
|
10
|
+
import { afterTest, describe, test } from '@dxos/test';
|
|
11
|
+
|
|
12
|
+
import { TEST_DIR, clearFiles, neverEndingProcess } from './testing-utils';
|
|
13
|
+
import { WatchDog } from './watchdog';
|
|
14
|
+
|
|
15
|
+
describe('WatchDog', () => {
|
|
16
|
+
test('Start/stop process', async () => {
|
|
17
|
+
const runId = Math.random();
|
|
18
|
+
const lockFile = join(TEST_DIR, `lock-${runId}.lock`);
|
|
19
|
+
const logFile = join(TEST_DIR, `file-${runId}.log`);
|
|
20
|
+
const errFile = join(TEST_DIR, `err-${runId}.log`);
|
|
21
|
+
afterTest(() => clearFiles(lockFile, logFile, errFile));
|
|
22
|
+
|
|
23
|
+
const watchDog = new WatchDog({
|
|
24
|
+
uid: 'test',
|
|
25
|
+
command: 'node',
|
|
26
|
+
args: ['-e', `(${neverEndingProcess.toString()})()`],
|
|
27
|
+
lockFile,
|
|
28
|
+
logFile,
|
|
29
|
+
errFile,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(await asyncTimeout(LockFile.isLocked(lockFile), 1000)).to.be.false;
|
|
33
|
+
await watchDog.start();
|
|
34
|
+
|
|
35
|
+
expect(await asyncTimeout(LockFile.isLocked(lockFile), 1000)).to.be.true;
|
|
36
|
+
|
|
37
|
+
await watchDog.stop();
|
|
38
|
+
expect(await asyncTimeout(LockFile.isLocked(lockFile), 1000)).to.be.false;
|
|
39
|
+
});
|
|
40
|
+
});
|
package/src/watchdog.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { ChildProcessWithoutNullStreams, spawn } from 'node:child_process';
|
|
6
|
+
import { writeFileSync } from 'node:fs';
|
|
7
|
+
import { FileHandle } from 'node:fs/promises';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
import psTree from 'ps-tree';
|
|
10
|
+
|
|
11
|
+
import { synchronized } from '@dxos/async';
|
|
12
|
+
import { Context } from '@dxos/context';
|
|
13
|
+
import { invariant } from '@dxos/invariant';
|
|
14
|
+
import { LockFile } from '@dxos/lock-file';
|
|
15
|
+
import { log } from '@dxos/log';
|
|
16
|
+
|
|
17
|
+
import { waitForLockAcquisition, waitForLockRelease } from './utils';
|
|
18
|
+
|
|
19
|
+
export type DaemonInfo = {
|
|
20
|
+
pid: number;
|
|
21
|
+
command: string;
|
|
22
|
+
args: string[];
|
|
23
|
+
cwd: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ProcessInfo = WatchDogParams & {
|
|
28
|
+
pid?: number;
|
|
29
|
+
timestamp?: number;
|
|
30
|
+
restarts?: number;
|
|
31
|
+
running?: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type Lock = {
|
|
35
|
+
lockFile: string; // Path to lock file
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type Logs = {
|
|
39
|
+
//
|
|
40
|
+
// Log files and associated logging options for this instance
|
|
41
|
+
//
|
|
42
|
+
logFile: string; // Path to log output all logs
|
|
43
|
+
errFile: string; // Path to log output from child stderr
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type ChildParams = {
|
|
47
|
+
uid: string; // Unique identifier for this instance
|
|
48
|
+
|
|
49
|
+
//
|
|
50
|
+
// Basic configuration options
|
|
51
|
+
//
|
|
52
|
+
maxRestarts?: number | undefined; // Sets the maximum number of times a given script should run
|
|
53
|
+
killTree?: boolean | undefined; // Kills the entire child process tree on `exit`
|
|
54
|
+
|
|
55
|
+
//
|
|
56
|
+
// Command to spawn as well as options and other vars
|
|
57
|
+
// (env, cwd, etc) to pass along
|
|
58
|
+
//
|
|
59
|
+
command?: string; // Binary to run (default: 'node')
|
|
60
|
+
args?: string[] | undefined; // Additional arguments to pass to the script,
|
|
61
|
+
|
|
62
|
+
//
|
|
63
|
+
// More specific options to pass along to `child_process.spawn` which
|
|
64
|
+
// will override anything passed to the `spawnWith` option
|
|
65
|
+
//
|
|
66
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
67
|
+
cwd?: string | undefined;
|
|
68
|
+
shell?: boolean | undefined;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type WatchDogParams = ChildParams & Lock & Logs;
|
|
72
|
+
|
|
73
|
+
export class WatchDog {
|
|
74
|
+
private _lock?: FileHandle;
|
|
75
|
+
private _child?: ChildProcessWithoutNullStreams;
|
|
76
|
+
private _restarts = 0;
|
|
77
|
+
private _processCtx?: Context;
|
|
78
|
+
|
|
79
|
+
constructor(private readonly _params: WatchDogParams) {}
|
|
80
|
+
|
|
81
|
+
@synchronized
|
|
82
|
+
async start() {
|
|
83
|
+
await this._acquireLock();
|
|
84
|
+
this._log('Lock acquired.');
|
|
85
|
+
const { cwd, shell, env, command, args } = { cwd: process.cwd(), ...this._params };
|
|
86
|
+
invariant(command, 'Command is not defined.');
|
|
87
|
+
|
|
88
|
+
this._log(`Spawning process ${command} ${args?.join(' ')}`);
|
|
89
|
+
this._child = spawn(command, args, { cwd, shell, env, stdio: 'pipe' });
|
|
90
|
+
this._processCtx = new Context();
|
|
91
|
+
|
|
92
|
+
const childInfo: ProcessInfo = {
|
|
93
|
+
pid: process.pid,
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
restarts: this._restarts,
|
|
96
|
+
...this._params,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
writeFileSync(this._params.lockFile, JSON.stringify(childInfo, undefined, 2), { encoding: 'utf-8' });
|
|
100
|
+
|
|
101
|
+
this._child.stdout.on('data', (data: Uint8Array) => {
|
|
102
|
+
this._log(String(data));
|
|
103
|
+
});
|
|
104
|
+
this._child.stderr.on('data', (data: Uint8Array) => {
|
|
105
|
+
this._err(data);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Setup restart handler.
|
|
109
|
+
{
|
|
110
|
+
const restartHandler = async (code: number, signal: number | NodeJS.Signals) => {
|
|
111
|
+
if (code && code !== 0) {
|
|
112
|
+
this._err(`Died unexpectedly with exit code ${code} (signal: ${signal}).`);
|
|
113
|
+
await this.restart();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
this._child.on('close', restartHandler);
|
|
118
|
+
|
|
119
|
+
// We should unsubscribe from the event when the process is killed by us to not try to restart it.
|
|
120
|
+
this._processCtx.onDispose(() => {
|
|
121
|
+
this._child!.off('close', restartHandler);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this._child.on('close', (code: number, signal: number | NodeJS.Signals) => {
|
|
126
|
+
this._log(`Stopped with exit code ${code} (signal: ${signal}).`);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
invariant(this._child.pid, 'Child process has no pid.');
|
|
130
|
+
this._log(`Started with pid ${this._child.pid}.`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Sends SIGINT to the child process and the tree it spawned (if `killTree` param is `true`).
|
|
135
|
+
*/
|
|
136
|
+
@synchronized
|
|
137
|
+
async stop() {
|
|
138
|
+
if (!this._child) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
await this._killWithSignal('SIGKILL');
|
|
143
|
+
|
|
144
|
+
await this._releaseLock();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Sends SIGKILL to the child process and the tree it spawned (if `killTree` param is `true`).
|
|
149
|
+
*/
|
|
150
|
+
@synchronized
|
|
151
|
+
async kill() {
|
|
152
|
+
if (!this._child) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
await this._killWithSignal('SIGKILL');
|
|
157
|
+
|
|
158
|
+
await this._releaseLock();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async restart() {
|
|
162
|
+
await this.kill();
|
|
163
|
+
if (this._params.maxRestarts !== undefined && this._restarts >= this._params.maxRestarts) {
|
|
164
|
+
this._err('Max restarts number is reached');
|
|
165
|
+
} else {
|
|
166
|
+
log('Restarting...');
|
|
167
|
+
this._restarts++;
|
|
168
|
+
await this.start();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async _killWithSignal(signal: number | NodeJS.Signals) {
|
|
173
|
+
invariant(this._processCtx, 'Process context is not defined.');
|
|
174
|
+
await this._processCtx.dispose();
|
|
175
|
+
|
|
176
|
+
// Kill child process tree.
|
|
177
|
+
if (this._params.killTree) {
|
|
178
|
+
if (process.platform !== 'win32') {
|
|
179
|
+
invariant(this._child?.pid, 'Child process has no pid.');
|
|
180
|
+
const children = await promisify(psTree)(this._child.pid);
|
|
181
|
+
children
|
|
182
|
+
.map((p) => p.PID)
|
|
183
|
+
.forEach((tpid) => {
|
|
184
|
+
invariant(tpid, 'Process id is not defined.');
|
|
185
|
+
process.kill(Number(tpid), signal);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
invariant(this._child?.pid, 'Child process has no pid.');
|
|
191
|
+
this._child.kill(signal);
|
|
192
|
+
this._child = undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async _acquireLock() {
|
|
196
|
+
if (await LockFile.isLocked(this._params.lockFile)) {
|
|
197
|
+
throw new Error('Lock file is already locked.');
|
|
198
|
+
}
|
|
199
|
+
this._lock = await LockFile.acquire(this._params.lockFile);
|
|
200
|
+
await waitForLockAcquisition(this._params.lockFile);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async _releaseLock() {
|
|
204
|
+
invariant(this._lock, 'Lock is not defined.');
|
|
205
|
+
|
|
206
|
+
await LockFile.release(this._lock);
|
|
207
|
+
await waitForLockRelease(this._params.lockFile);
|
|
208
|
+
|
|
209
|
+
this._lock = undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private _log(message: string | Uint8Array) {
|
|
213
|
+
writeFileSync(this._params.logFile, message + '\n', {
|
|
214
|
+
flag: 'a+',
|
|
215
|
+
encoding: 'utf-8',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private _err(message: string | Uint8Array) {
|
|
220
|
+
this._log(message);
|
|
221
|
+
writeFileSync(this._params.errFile, message + '\n', {
|
|
222
|
+
flag: 'a+',
|
|
223
|
+
encoding: 'utf-8',
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|