@foxford/den 4.0.0 → 4.1.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.mdx +30 -4
- package/bin/den.cjs +3 -3
- package/bin/den.js +1 -1
- package/builder/index.cjs +2 -2
- package/builder/index.js +1 -1
- package/{chunk-FMRORLLE.js → chunk-7FOG2MJV.js} +3 -2
- package/{chunk-DUU4MQ4B.js → chunk-A2CYL5LY.js} +15 -2
- package/{chunk-2P5TAWRW.cjs → chunk-HJFBEJT6.cjs} +15 -2
- package/{chunk-OC4LQ2CK.cjs → chunk-IATA5QZ7.cjs} +3 -2
- package/ops/index.cjs +2 -2
- package/ops/index.js +1 -1
- package/package.json +6 -6
- package/serve/index.cjs +205 -6
- package/serve/index.d.cts +11 -1
- package/serve/index.d.ts +11 -1
- package/serve/index.js +203 -4
package/README.mdx
CHANGED
|
@@ -182,6 +182,23 @@ describeDocument(): DocumentContribution {
|
|
|
182
182
|
}
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
+
Для нескольких процессов одного приложения:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
WORKERS=2 node build/server.mjs
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
По умолчанию `WORKERS=1`. Все воркеры слушают общий прикладной порт, primary держит
|
|
192
|
+
один служебный порт с их суммарными метриками. `server.configure` выполняется в каждом
|
|
193
|
+
воркере; память и контейнеры процессов отдельные. Значение задаётся каждому приложению
|
|
194
|
+
и должно быть положительным целым. После обновления Den приложение нужно пересобрать,
|
|
195
|
+
чтобы новая точка входа начала читать `WORKERS`. Переменная работает и в `den dev`,
|
|
196
|
+
но локальное хранилище dev-трасс между воркерами не объединяется.
|
|
197
|
+
|
|
198
|
+
При остановке Den дожидается активных запросов; зависшие воркеры принудительно
|
|
199
|
+
завершаются через 10 секунд. Ошибка запуска или выход любого воркера завершает группу
|
|
200
|
+
с ошибкой, чтобы внешний супервизор мог перезапустить приложение целиком.
|
|
201
|
+
|
|
185
202
|
Рантайм вокруг дескриптора отдаёт три ручки:
|
|
186
203
|
|
|
187
204
|
| Ручка | Ответ |
|
|
@@ -189,7 +206,7 @@ describeDocument(): DocumentContribution {
|
|
|
189
206
|
| `QUERY /_render` + `{ url, inputs?, backendHeaders? }` | `200` + ответ `renderApp` телом |
|
|
190
207
|
| `GET /_render?url=<path+search>` | то же без входных данных хоста |
|
|
191
208
|
| `GET /_routes` | `200` + массив regex-строк заявки на адреса |
|
|
192
|
-
| `GET /healthz`, `GET /livez` на втором порту | `200 ok` |
|
|
209
|
+
| `GET /healthz`, `GET /livez` на втором порту | `200 ok`; в кластере `/livez` проверяет heartbeat воркеров |
|
|
193
210
|
| `GET /metrics` на втором порту | Prometheus-метрики Node/process и HTTP |
|
|
194
211
|
|
|
195
212
|
`QUERY` — безопасный метод с телом (RFC 9110): адрес и входные данные хоста едут JSON-ом,
|
|
@@ -215,10 +232,19 @@ describeDocument(): DocumentContribution {
|
|
|
215
232
|
Для совместной разработки объявляются локальные `server.port` и `server.opsPort`;
|
|
216
233
|
production server.mjs использует production-умолчания.
|
|
217
234
|
|
|
218
|
-
`/healthz` — простой GET с `200`, `/livez` —
|
|
235
|
+
`/healthz` — простой GET с `200`, `/livez` — проверка доступности, `/metrics` — экспорт полного
|
|
219
236
|
штатного набора Node/process, дополнительных resourceUsage и HTTP-измерений.
|
|
220
237
|
На основном порту служебные пути отвечают `404`.
|
|
221
|
-
Метрики не зависят от DSN Sentry
|
|
238
|
+
Метрики не зависят от DSN Sentry. Метод `close()` результата `await serveAppProcess(...)`
|
|
239
|
+
закрывает всю группу; у прямого `serveApp` прежний `instance.close()` закрывает оба
|
|
240
|
+
сервера одного процесса.
|
|
241
|
+
|
|
242
|
+
Для занятости event loop используйте `nodejs_eventloop_utilization_ratio`:
|
|
243
|
+
в кластере это максимум по воркерам, от 0 до 1. Сопоставляйте его с lag и временем
|
|
244
|
+
ответа. Штатный `nodejs_eventloop_utilization_summary` суммирует quantile воркеров
|
|
245
|
+
и для такого алерта не подходит. `/livez` остаётся зелёным, пока жив хотя бы один
|
|
246
|
+
воркер; `/metrics` при неответившем воркере вернёт `503` через 5 секунд. Ошибки и
|
|
247
|
+
длительность scrape тоже должны попадать в мониторинг.
|
|
222
248
|
|
|
223
249
|
Для своего сервера доступен отдельный подпуть без Fastify:
|
|
224
250
|
|
|
@@ -345,6 +371,6 @@ export default defineAppConfig({
|
|
|
345
371
|
| `@foxford/den/island` | остров: жизненный цикл, шов `renderApp`, порт `ViewAdapter`, очередь клиентских падений (`reportIslandError`) |
|
|
346
372
|
|
|
347
373
|
|
|
348
|
-
| `@foxford/den/serve` | сетевой транспорт: `serveApp`, `appPlugin`
|
|
374
|
+
| `@foxford/den/serve` | сетевой транспорт: `serveAppProcess`, `serveApp`, `appPlugin` |
|
|
349
375
|
| `@foxford/den/build` | конфиг приложения и сборка: `defineAppConfig`, `buildApp` |
|
|
350
376
|
| `@foxford/den/bin` | точка входа команды `den` |
|
package/bin/den.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var _chunkIATA5QZ7cjs = require('../chunk-IATA5QZ7.cjs');
|
|
5
5
|
require('../chunk-KQAB6C6Q.cjs');
|
|
6
6
|
require('../chunk-A6ZPAM6Z.cjs');
|
|
7
7
|
|
|
@@ -70,11 +70,11 @@ function run(argv) {
|
|
|
70
70
|
}
|
|
71
71
|
const target = { base: options["base"], root: (_a = options["root"]) != null ? _a : process.cwd() };
|
|
72
72
|
if (command === "build") {
|
|
73
|
-
yield
|
|
73
|
+
yield _chunkIATA5QZ7cjs.buildApp.call(void 0, target);
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
76
|
if (command === "dev") {
|
|
77
|
-
yield
|
|
77
|
+
yield _chunkIATA5QZ7cjs.devApp.call(void 0, target);
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
fail(`\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \xAB${command}\xBB`);
|
package/bin/den.js
CHANGED
package/builder/index.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkIATA5QZ7cjs = require('../chunk-IATA5QZ7.cjs');
|
|
6
6
|
require('../chunk-KQAB6C6Q.cjs');
|
|
7
7
|
require('../chunk-A6ZPAM6Z.cjs');
|
|
8
8
|
require('../chunk-T5QVCXVB.cjs');
|
|
@@ -10,4 +10,4 @@ require('../chunk-T5QVCXVB.cjs');
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
exports.buildApp =
|
|
13
|
+
exports.buildApp = _chunkIATA5QZ7cjs.buildApp; exports.defineAppConfig = _chunkIATA5QZ7cjs.defineAppConfig; exports.devApp = _chunkIATA5QZ7cjs.devApp;
|
package/builder/index.js
CHANGED
|
@@ -134,7 +134,7 @@ function generateServerProcess(configPath, base, mode) {
|
|
|
134
134
|
` : "";
|
|
135
135
|
return `import { fileURLToPath } from 'node:url'
|
|
136
136
|
|
|
137
|
-
import { loadAppEnv,
|
|
137
|
+
import { loadAppEnv, serveAppProcess } from '@foxford/den/serve'
|
|
138
138
|
|
|
139
139
|
import app from ${JSON.stringify(VIRTUAL_SERVER_APP)}
|
|
140
140
|
import config from ${JSON.stringify(configPath)}
|
|
@@ -142,11 +142,12 @@ import config from ${JSON.stringify(configPath)}
|
|
|
142
142
|
// \u0423\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044F \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u2014 \u0434\u043E \u043F\u0435\u0440\u0432\u043E\u0433\u043E \u0447\u0442\u0435\u043D\u0438\u044F: \u043F\u043E\u0440\u0442 \u0438 \u0430\u0434\u0440\u0435\u0441\u0430 \u0447\u0438\u0442\u0430\u044E\u0442\u0441\u044F \u043D\u0438\u0436\u0435
|
|
143
143
|
loadAppEnv(fileURLToPath(new URL('..', import.meta.url)))
|
|
144
144
|
|
|
145
|
-
await
|
|
145
|
+
await serveAppProcess(app, {
|
|
146
146
|
${assets} configure: config.server?.configure,
|
|
147
147
|
host: process.env.HOST,
|
|
148
148
|
ops: { host: process.env.OPS_HOST, port: Number(process.env.SERVICE_PORT ?? ${mode === "development" ? "config.server?.opsPort ?? 9090" : "9090"}) },
|
|
149
149
|
port: Number(process.env.PORT ?? ${mode === "development" ? "config.server?.port ?? " : ""}${String(DEFAULT_PORT)}),
|
|
150
|
+
workers: Number(process.env.WORKERS ?? 1),
|
|
150
151
|
})
|
|
151
152
|
`;
|
|
152
153
|
}
|
|
@@ -11,17 +11,30 @@ import { Counter, Gauge as Gauge2, Histogram, Registry } from "@prometheus-io/cl
|
|
|
11
11
|
|
|
12
12
|
// src/ops/runtime.ts
|
|
13
13
|
import cluster from "cluster";
|
|
14
|
+
import { performance as performance2 } from "perf_hooks";
|
|
14
15
|
import { collectDefaultMetrics, Gauge } from "@prometheus-io/client";
|
|
15
16
|
function collectRuntime(registry, appName) {
|
|
16
17
|
registry.setDefaultLabels({ app: appName, process_role: cluster.isWorker ? "worker" : "main" });
|
|
17
18
|
collectDefaultMetrics({ register: registry });
|
|
19
|
+
let previousElu = performance2.eventLoopUtilization();
|
|
20
|
+
new Gauge({
|
|
21
|
+
aggregator: "max",
|
|
22
|
+
collect() {
|
|
23
|
+
const current = performance2.eventLoopUtilization();
|
|
24
|
+
this.set(performance2.eventLoopUtilization(current, previousElu).utilization);
|
|
25
|
+
previousElu = current;
|
|
26
|
+
},
|
|
27
|
+
help: "Event loop utilization since the last scrape; maximum across workers",
|
|
28
|
+
name: "nodejs_eventloop_utilization_ratio",
|
|
29
|
+
registers: [registry]
|
|
30
|
+
});
|
|
18
31
|
let previousCpu = process.cpuUsage();
|
|
19
|
-
let previousTime =
|
|
32
|
+
let previousTime = performance2.now();
|
|
20
33
|
new Gauge({
|
|
21
34
|
aggregator: "sum",
|
|
22
35
|
collect() {
|
|
23
36
|
const currentCpu = process.cpuUsage();
|
|
24
|
-
const currentTime =
|
|
37
|
+
const currentTime = performance2.now();
|
|
25
38
|
const elapsed = currentTime - previousTime;
|
|
26
39
|
this.set(
|
|
27
40
|
elapsed > 0 ? (currentCpu.user - previousCpu.user + currentCpu.system - previousCpu.system) / (elapsed * 1e3) : 0
|
|
@@ -11,17 +11,30 @@ var _client = require('@prometheus-io/client');
|
|
|
11
11
|
|
|
12
12
|
// src/ops/runtime.ts
|
|
13
13
|
var _cluster = require('cluster'); var _cluster2 = _interopRequireDefault(_cluster);
|
|
14
|
+
var _perf_hooks = require('perf_hooks');
|
|
14
15
|
|
|
15
16
|
function collectRuntime(registry, appName) {
|
|
16
17
|
registry.setDefaultLabels({ app: appName, process_role: _cluster2.default.isWorker ? "worker" : "main" });
|
|
17
18
|
_client.collectDefaultMetrics.call(void 0, { register: registry });
|
|
19
|
+
let previousElu = _perf_hooks.performance.eventLoopUtilization();
|
|
20
|
+
new (0, _client.Gauge)({
|
|
21
|
+
aggregator: "max",
|
|
22
|
+
collect() {
|
|
23
|
+
const current = _perf_hooks.performance.eventLoopUtilization();
|
|
24
|
+
this.set(_perf_hooks.performance.eventLoopUtilization(current, previousElu).utilization);
|
|
25
|
+
previousElu = current;
|
|
26
|
+
},
|
|
27
|
+
help: "Event loop utilization since the last scrape; maximum across workers",
|
|
28
|
+
name: "nodejs_eventloop_utilization_ratio",
|
|
29
|
+
registers: [registry]
|
|
30
|
+
});
|
|
18
31
|
let previousCpu = process.cpuUsage();
|
|
19
|
-
let previousTime = performance.now();
|
|
32
|
+
let previousTime = _perf_hooks.performance.now();
|
|
20
33
|
new (0, _client.Gauge)({
|
|
21
34
|
aggregator: "sum",
|
|
22
35
|
collect() {
|
|
23
36
|
const currentCpu = process.cpuUsage();
|
|
24
|
-
const currentTime = performance.now();
|
|
37
|
+
const currentTime = _perf_hooks.performance.now();
|
|
25
38
|
const elapsed = currentTime - previousTime;
|
|
26
39
|
this.set(
|
|
27
40
|
elapsed > 0 ? (currentCpu.user - previousCpu.user + currentCpu.system - previousCpu.system) / (elapsed * 1e3) : 0
|
|
@@ -134,7 +134,7 @@ function generateServerProcess(configPath, base, mode) {
|
|
|
134
134
|
` : "";
|
|
135
135
|
return `import { fileURLToPath } from 'node:url'
|
|
136
136
|
|
|
137
|
-
import { loadAppEnv,
|
|
137
|
+
import { loadAppEnv, serveAppProcess } from '@foxford/den/serve'
|
|
138
138
|
|
|
139
139
|
import app from ${JSON.stringify(VIRTUAL_SERVER_APP)}
|
|
140
140
|
import config from ${JSON.stringify(configPath)}
|
|
@@ -142,11 +142,12 @@ import config from ${JSON.stringify(configPath)}
|
|
|
142
142
|
// \u0423\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044F \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u2014 \u0434\u043E \u043F\u0435\u0440\u0432\u043E\u0433\u043E \u0447\u0442\u0435\u043D\u0438\u044F: \u043F\u043E\u0440\u0442 \u0438 \u0430\u0434\u0440\u0435\u0441\u0430 \u0447\u0438\u0442\u0430\u044E\u0442\u0441\u044F \u043D\u0438\u0436\u0435
|
|
143
143
|
loadAppEnv(fileURLToPath(new URL('..', import.meta.url)))
|
|
144
144
|
|
|
145
|
-
await
|
|
145
|
+
await serveAppProcess(app, {
|
|
146
146
|
${assets} configure: config.server?.configure,
|
|
147
147
|
host: process.env.HOST,
|
|
148
148
|
ops: { host: process.env.OPS_HOST, port: Number(process.env.SERVICE_PORT ?? ${mode === "development" ? "config.server?.opsPort ?? 9090" : "9090"}) },
|
|
149
149
|
port: Number(process.env.PORT ?? ${mode === "development" ? "config.server?.port ?? " : ""}${String(DEFAULT_PORT)}),
|
|
150
|
+
workers: Number(process.env.WORKERS ?? 1),
|
|
150
151
|
})
|
|
151
152
|
`;
|
|
152
153
|
}
|
package/ops/index.cjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _chunkHJFBEJT6cjs = require('../chunk-HJFBEJT6.cjs');
|
|
9
9
|
require('../chunk-T5QVCXVB.cjs');
|
|
10
10
|
|
|
11
11
|
|
|
@@ -14,4 +14,4 @@ require('../chunk-T5QVCXVB.cjs');
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
exports.ProcessMetrics =
|
|
17
|
+
exports.ProcessMetrics = _chunkHJFBEJT6cjs.ProcessMetrics; exports.closeHttpServer = _chunkHJFBEJT6cjs.closeHttpServer; exports.clusterMetrics = _chunkHJFBEJT6cjs.clusterMetrics; exports.getProcessMetrics = _chunkHJFBEJT6cjs.getProcessMetrics; exports.isOpsPath = _chunkHJFBEJT6cjs.isOpsPath; exports.startOpsServer = _chunkHJFBEJT6cjs.startOpsServer;
|
package/ops/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foxford/den",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Den — декларативный метафреймворк Foxford (core runtime)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"foxford",
|
|
@@ -95,19 +95,19 @@
|
|
|
95
95
|
"adapter.js",
|
|
96
96
|
"bin",
|
|
97
97
|
"builder",
|
|
98
|
-
"chunk-2P5TAWRW.cjs",
|
|
99
98
|
"chunk-3RX2OSUL.js",
|
|
100
99
|
"chunk-6FNC3XMI.js",
|
|
101
100
|
"chunk-6QV4L6R2.cjs",
|
|
101
|
+
"chunk-7FOG2MJV.js",
|
|
102
|
+
"chunk-A2CYL5LY.js",
|
|
102
103
|
"chunk-A6ZPAM6Z.cjs",
|
|
103
104
|
"chunk-C7BM4DGX.cjs",
|
|
104
|
-
"chunk-DUU4MQ4B.js",
|
|
105
105
|
"chunk-E23E2VUC.js",
|
|
106
106
|
"chunk-E56BKBQZ.js",
|
|
107
|
-
"chunk-FMRORLLE.js",
|
|
108
107
|
"chunk-H5DKRHHQ.cjs",
|
|
108
|
+
"chunk-HJFBEJT6.cjs",
|
|
109
|
+
"chunk-IATA5QZ7.cjs",
|
|
109
110
|
"chunk-KQAB6C6Q.cjs",
|
|
110
|
-
"chunk-OC4LQ2CK.cjs",
|
|
111
111
|
"chunk-QTWJTYZW.cjs",
|
|
112
112
|
"chunk-RG7ZV5BD.js",
|
|
113
113
|
"chunk-T5QVCXVB.cjs",
|
|
@@ -143,6 +143,6 @@
|
|
|
143
143
|
"view-adapter-CrHh7CjB.d.cts",
|
|
144
144
|
"view-adapter-DPDvv09J.d.ts"
|
|
145
145
|
],
|
|
146
|
-
"sha": "
|
|
146
|
+
"sha": "c941b51",
|
|
147
147
|
"scripts": {}
|
|
148
148
|
}
|
package/serve/index.cjs
CHANGED
|
@@ -17,7 +17,9 @@ var _chunkA6ZPAM6Zcjs = require('../chunk-A6ZPAM6Z.cjs');
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
var _chunkHJFBEJT6cjs = require('../chunk-HJFBEJT6.cjs');
|
|
22
|
+
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
|
|
@@ -177,7 +179,203 @@ var appPlugin = (instance, options) => {
|
|
|
177
179
|
return Promise.resolve();
|
|
178
180
|
};
|
|
179
181
|
|
|
182
|
+
// src/serve/process.ts
|
|
183
|
+
var _cluster = require('cluster'); var _cluster2 = _interopRequireDefault(_cluster);
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
// src/serve/worker.ts
|
|
187
|
+
|
|
188
|
+
var ALIVE = "den:app-worker-alive";
|
|
189
|
+
var SIGNALS = ["SIGTERM", "SIGINT"];
|
|
190
|
+
var SHUTDOWN_MS = 1e4;
|
|
191
|
+
function startWorker(appName, start) {
|
|
192
|
+
return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
|
|
193
|
+
const aggregation = _chunkHJFBEJT6cjs.clusterMetrics.call(void 0, );
|
|
194
|
+
const logger2 = _logger.log.getLogger(appName).getLogger("cluster");
|
|
195
|
+
let timer;
|
|
196
|
+
let closing;
|
|
197
|
+
const starting = start();
|
|
198
|
+
const close = () => {
|
|
199
|
+
closing != null ? closing : closing = (() => _chunkT5QVCXVBcjs.__async.call(void 0, null, null, function* () {
|
|
200
|
+
clearInterval(timer);
|
|
201
|
+
try {
|
|
202
|
+
const instance = yield starting;
|
|
203
|
+
yield instance.close();
|
|
204
|
+
} finally {
|
|
205
|
+
try {
|
|
206
|
+
if (process.connected) yield aggregation.shutdown();
|
|
207
|
+
} finally {
|
|
208
|
+
for (const signal of SIGNALS) process.removeListener(signal, shutdown);
|
|
209
|
+
process.removeListener("disconnect", shutdown);
|
|
210
|
+
if (process.connected) process.disconnect();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}))();
|
|
214
|
+
return closing;
|
|
215
|
+
};
|
|
216
|
+
const shutdown = () => {
|
|
217
|
+
const deadline = setTimeout(() => process.exit(1), SHUTDOWN_MS);
|
|
218
|
+
deadline.unref();
|
|
219
|
+
void close().then(() => process.exit(0)).catch((error) => {
|
|
220
|
+
logger2.error("\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u0432\u043E\u0440\u043A\u0435\u0440\u0430 \u043D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C:", error);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
for (const signal of SIGNALS) process.on(signal, shutdown);
|
|
225
|
+
process.once("disconnect", shutdown);
|
|
226
|
+
try {
|
|
227
|
+
yield starting;
|
|
228
|
+
if (!closing) {
|
|
229
|
+
const notify = () => {
|
|
230
|
+
var _a;
|
|
231
|
+
if (process.connected) (_a = process.send) == null ? void 0 : _a.call(process, { type: ALIVE });
|
|
232
|
+
};
|
|
233
|
+
timer = setInterval(notify, 2e3);
|
|
234
|
+
timer.unref();
|
|
235
|
+
notify();
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
yield close().catch(() => void 0);
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
return { close };
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/serve/process.ts
|
|
246
|
+
var STARTUP_MS = 6e4;
|
|
247
|
+
function startAppProcess(appName, options, start) {
|
|
248
|
+
return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
|
|
249
|
+
var _a;
|
|
250
|
+
const workers = (_a = options.workers) != null ? _a : 1;
|
|
251
|
+
if (!Number.isSafeInteger(workers) || workers < 1) {
|
|
252
|
+
throw new Error("WORKERS must be a positive integer");
|
|
253
|
+
}
|
|
254
|
+
if (workers === 1) {
|
|
255
|
+
const instance = yield start(options);
|
|
256
|
+
return { close: () => instance.close() };
|
|
257
|
+
}
|
|
258
|
+
const metrics = _chunkHJFBEJT6cjs.getProcessMetrics.call(void 0, );
|
|
259
|
+
metrics.startRuntime(appName);
|
|
260
|
+
if (_cluster2.default.isWorker) {
|
|
261
|
+
return startWorker(appName, () => start(_chunkT5QVCXVBcjs.__spreadProps.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, options), { gracefulShutdown: false, ops: false })));
|
|
262
|
+
}
|
|
263
|
+
return startPrimary(appName, options, workers);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function startPrimary(appName, options, workers) {
|
|
267
|
+
return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
|
|
268
|
+
const aggregation = _chunkHJFBEJT6cjs.clusterMetrics.call(void 0, );
|
|
269
|
+
const logger2 = _logger.log.getLogger(appName).getLogger("cluster");
|
|
270
|
+
const children = /* @__PURE__ */ new Set();
|
|
271
|
+
const alive = /* @__PURE__ */ new Map();
|
|
272
|
+
let ops;
|
|
273
|
+
let closing;
|
|
274
|
+
let started = false;
|
|
275
|
+
let resolveReady = () => void 0;
|
|
276
|
+
let rejectReady = () => void 0;
|
|
277
|
+
const ready = new Promise((resolve, reject) => {
|
|
278
|
+
resolveReady = resolve;
|
|
279
|
+
rejectReady = reject;
|
|
280
|
+
});
|
|
281
|
+
void ready.catch(() => void 0);
|
|
282
|
+
const timeout = setTimeout(() => rejectReady(new Error("Den workers startup timeout")), STARTUP_MS);
|
|
283
|
+
const onMessage = (worker, message) => {
|
|
284
|
+
if (children.has(worker) && typeof message === "object" && message !== null && "type" in message && message.type === ALIVE) {
|
|
285
|
+
alive.set(worker.id, Date.now());
|
|
286
|
+
if (alive.size === workers) resolveReady();
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
const close = () => {
|
|
290
|
+
closing != null ? closing : closing = (() => _chunkT5QVCXVBcjs.__async.call(void 0, null, null, function* () {
|
|
291
|
+
clearTimeout(timeout);
|
|
292
|
+
resolveReady();
|
|
293
|
+
_cluster2.default.removeListener("message", onMessage);
|
|
294
|
+
yield Promise.all([...children].map(stopWorker));
|
|
295
|
+
if (ops) yield _chunkHJFBEJT6cjs.closeHttpServer.call(void 0, ops);
|
|
296
|
+
yield aggregation.shutdown();
|
|
297
|
+
for (const signal of SIGNALS) process.removeListener(signal, shutdown);
|
|
298
|
+
}))();
|
|
299
|
+
return closing;
|
|
300
|
+
};
|
|
301
|
+
const shutdown = () => {
|
|
302
|
+
void close().then(() => process.exit()).catch((error) => {
|
|
303
|
+
logger2.error("\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043A\u043B\u0430\u0441\u0442\u0435\u0440\u0430 \u043D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C:", error);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
const failed = (error) => {
|
|
308
|
+
if (closing) return;
|
|
309
|
+
if (!started) {
|
|
310
|
+
rejectReady(error);
|
|
311
|
+
} else {
|
|
312
|
+
logger2.error(error);
|
|
313
|
+
process.exitCode = 1;
|
|
314
|
+
shutdown();
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
_cluster2.default.on("message", onMessage);
|
|
318
|
+
if (options.gracefulShutdown !== false) {
|
|
319
|
+
for (const signal of SIGNALS) process.on(signal, shutdown);
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
if (options.ops !== false) {
|
|
323
|
+
const supplied = options.ops;
|
|
324
|
+
ops = yield _chunkHJFBEJT6cjs.startOpsServer.call(void 0, _chunkT5QVCXVBcjs.__spreadProps.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, supplied), {
|
|
325
|
+
collect: aggregation.collect,
|
|
326
|
+
contentType: aggregation.contentType,
|
|
327
|
+
isLive: () => {
|
|
328
|
+
var _a;
|
|
329
|
+
return [...children].some((worker) => {
|
|
330
|
+
var _a2;
|
|
331
|
+
return worker.isConnected() && Date.now() - ((_a2 = alive.get(worker.id)) != null ? _a2 : 0) < 1e4;
|
|
332
|
+
}) && ((_a = supplied == null ? void 0 : supplied.isLive) == null ? void 0 : _a.call(supplied)) !== false;
|
|
333
|
+
}
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
if (closing) {
|
|
337
|
+
if (ops) yield _chunkHJFBEJT6cjs.closeHttpServer.call(void 0, ops);
|
|
338
|
+
yield closing;
|
|
339
|
+
return { close };
|
|
340
|
+
}
|
|
341
|
+
for (let i = 0; i < workers; i++) {
|
|
342
|
+
const worker = _cluster2.default.fork();
|
|
343
|
+
children.add(worker);
|
|
344
|
+
worker.once("error", failed);
|
|
345
|
+
worker.once("exit", (code, signal) => {
|
|
346
|
+
alive.delete(worker.id);
|
|
347
|
+
failed(new Error(`Den worker ${String(worker.process.pid)} exited (${String(code != null ? code : signal)})`));
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
yield ready;
|
|
351
|
+
started = true;
|
|
352
|
+
clearTimeout(timeout);
|
|
353
|
+
if (!closing) logger2.info(`\u0437\u0430\u043F\u0443\u0449\u0435\u043D\u043E \u0432\u043E\u0440\u043A\u0435\u0440\u043E\u0432: ${String(workers)}`);
|
|
354
|
+
return { close };
|
|
355
|
+
} catch (error) {
|
|
356
|
+
yield close();
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function stopWorker(worker) {
|
|
362
|
+
return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
|
|
363
|
+
if (worker.isDead()) return;
|
|
364
|
+
yield new Promise((resolve) => {
|
|
365
|
+
const timeout = setTimeout(() => worker.kill("SIGKILL"), SHUTDOWN_MS);
|
|
366
|
+
worker.once("exit", () => {
|
|
367
|
+
clearTimeout(timeout);
|
|
368
|
+
resolve();
|
|
369
|
+
});
|
|
370
|
+
worker.kill("SIGTERM");
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
180
375
|
// src/serve/index.ts
|
|
376
|
+
function serveAppProcess(app, options = {}) {
|
|
377
|
+
return startAppProcess(app.name, options, (serveOptions) => serveApp(app, serveOptions));
|
|
378
|
+
}
|
|
181
379
|
var SHUTDOWN_SIGNALS = ["SIGTERM", "SIGINT"];
|
|
182
380
|
function serveApp(_0) {
|
|
183
381
|
return _chunkT5QVCXVBcjs.__async.call(void 0, this, arguments, function* (app, options = {}) {
|
|
@@ -185,14 +383,14 @@ function serveApp(_0) {
|
|
|
185
383
|
const appLog = (_a = _logger.log.getLogger(app.name)) != null ? _a : _chunkA6ZPAM6Zcjs.logger;
|
|
186
384
|
const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
|
|
187
385
|
const instance = _fastify2.default.call(void 0, { logger: false });
|
|
188
|
-
const metrics =
|
|
386
|
+
const metrics = _chunkHJFBEJT6cjs.getProcessMetrics.call(void 0, );
|
|
189
387
|
metrics.startRuntime(app.name);
|
|
190
388
|
let ops;
|
|
191
389
|
const shutdown = () => {
|
|
192
390
|
void instance.close();
|
|
193
391
|
};
|
|
194
392
|
instance.addHook("onRequest", (req, reply, done) => {
|
|
195
|
-
if (
|
|
393
|
+
if (_chunkHJFBEJT6cjs.isOpsPath.call(void 0, req.url)) {
|
|
196
394
|
void reply.code(404).send();
|
|
197
395
|
return;
|
|
198
396
|
}
|
|
@@ -207,7 +405,7 @@ function serveApp(_0) {
|
|
|
207
405
|
process.removeListener(signal, shutdown);
|
|
208
406
|
}
|
|
209
407
|
if (ops) {
|
|
210
|
-
yield
|
|
408
|
+
yield _chunkHJFBEJT6cjs.closeHttpServer.call(void 0, ops);
|
|
211
409
|
}
|
|
212
410
|
}));
|
|
213
411
|
yield instance.register(appPlugin, { app, slowRenderMs: options.slowRenderMs });
|
|
@@ -226,7 +424,7 @@ function serveApp(_0) {
|
|
|
226
424
|
try {
|
|
227
425
|
yield (_c = options.configure) == null ? void 0 : _c.call(options, instance);
|
|
228
426
|
if (options.ops !== false) {
|
|
229
|
-
ops = yield
|
|
427
|
+
ops = yield _chunkHJFBEJT6cjs.startOpsServer.call(void 0, options.ops);
|
|
230
428
|
}
|
|
231
429
|
yield instance.listen({ host: (_d = options.host) != null ? _d : "0.0.0.0", port: (_e = options.port) != null ? _e : 8080 });
|
|
232
430
|
} catch (error) {
|
|
@@ -249,4 +447,5 @@ function serveApp(_0) {
|
|
|
249
447
|
|
|
250
448
|
|
|
251
449
|
|
|
252
|
-
|
|
450
|
+
|
|
451
|
+
exports.appPlugin = appPlugin; exports.loadAppEnv = _chunkKQAB6C6Qcjs.loadAppEnv; exports.registerAssets = registerAssets; exports.serveApp = serveApp; exports.serveAppProcess = serveAppProcess;
|
package/serve/index.d.cts
CHANGED
|
@@ -44,6 +44,14 @@ interface AppPluginOptions {
|
|
|
44
44
|
*/
|
|
45
45
|
declare const appPlugin: FastifyPluginAsync<AppPluginOptions>;
|
|
46
46
|
|
|
47
|
+
interface AppProcess {
|
|
48
|
+
close: () => Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
interface ServeAppProcessOptions extends ServeAppOptions {
|
|
51
|
+
/** Число процессов приложения; по умолчанию 1. Каждый воркер имеет свой контейнер. */
|
|
52
|
+
workers?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
/**
|
|
48
56
|
* Кладёт умолчания из `<root>/.env` в окружение процесса.
|
|
49
57
|
*
|
|
@@ -53,6 +61,8 @@ declare const appPlugin: FastifyPluginAsync<AppPluginOptions>;
|
|
|
53
61
|
*/
|
|
54
62
|
declare function loadAppEnv(root: string): void;
|
|
55
63
|
|
|
64
|
+
/** Точка входа самостоятельного процесса: один инстанс либо cluster с общим портом. */
|
|
65
|
+
declare function serveAppProcess(app: ServableApp, options?: ServeAppProcessOptions): Promise<AppProcess>;
|
|
56
66
|
/** Настройки инстанса. */
|
|
57
67
|
interface ServeAppOptions {
|
|
58
68
|
/** Порт; `0` — свободный, фактический смотри в `server.addresses()`. */
|
|
@@ -93,4 +103,4 @@ interface ServeAppOptions {
|
|
|
93
103
|
*/
|
|
94
104
|
declare function serveApp(app: ServableApp, options?: ServeAppOptions): Promise<FastifyInstance>;
|
|
95
105
|
|
|
96
|
-
export { type AppAssetsSource, type AppPluginOptions, type ServableApp, type ServeAppOptions, appPlugin, loadAppEnv, registerAssets, serveApp };
|
|
106
|
+
export { type AppAssetsSource, type AppPluginOptions, type AppProcess, type ServableApp, type ServeAppOptions, type ServeAppProcessOptions, appPlugin, loadAppEnv, registerAssets, serveApp, serveAppProcess };
|
package/serve/index.d.ts
CHANGED
|
@@ -44,6 +44,14 @@ interface AppPluginOptions {
|
|
|
44
44
|
*/
|
|
45
45
|
declare const appPlugin: FastifyPluginAsync<AppPluginOptions>;
|
|
46
46
|
|
|
47
|
+
interface AppProcess {
|
|
48
|
+
close: () => Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
interface ServeAppProcessOptions extends ServeAppOptions {
|
|
51
|
+
/** Число процессов приложения; по умолчанию 1. Каждый воркер имеет свой контейнер. */
|
|
52
|
+
workers?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
/**
|
|
48
56
|
* Кладёт умолчания из `<root>/.env` в окружение процесса.
|
|
49
57
|
*
|
|
@@ -53,6 +61,8 @@ declare const appPlugin: FastifyPluginAsync<AppPluginOptions>;
|
|
|
53
61
|
*/
|
|
54
62
|
declare function loadAppEnv(root: string): void;
|
|
55
63
|
|
|
64
|
+
/** Точка входа самостоятельного процесса: один инстанс либо cluster с общим портом. */
|
|
65
|
+
declare function serveAppProcess(app: ServableApp, options?: ServeAppProcessOptions): Promise<AppProcess>;
|
|
56
66
|
/** Настройки инстанса. */
|
|
57
67
|
interface ServeAppOptions {
|
|
58
68
|
/** Порт; `0` — свободный, фактический смотри в `server.addresses()`. */
|
|
@@ -93,4 +103,4 @@ interface ServeAppOptions {
|
|
|
93
103
|
*/
|
|
94
104
|
declare function serveApp(app: ServableApp, options?: ServeAppOptions): Promise<FastifyInstance>;
|
|
95
105
|
|
|
96
|
-
export { type AppAssetsSource, type AppPluginOptions, type ServableApp, type ServeAppOptions, appPlugin, loadAppEnv, registerAssets, serveApp };
|
|
106
|
+
export { type AppAssetsSource, type AppPluginOptions, type AppProcess, type ServableApp, type ServeAppOptions, type ServeAppProcessOptions, appPlugin, loadAppEnv, registerAssets, serveApp, serveAppProcess };
|
package/serve/index.js
CHANGED
|
@@ -14,18 +14,20 @@ import {
|
|
|
14
14
|
} from "../chunk-TPBI6TOU.js";
|
|
15
15
|
import {
|
|
16
16
|
closeHttpServer,
|
|
17
|
+
clusterMetrics,
|
|
17
18
|
getProcessMetrics,
|
|
18
19
|
isOpsPath,
|
|
19
20
|
startOpsServer
|
|
20
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-A2CYL5LY.js";
|
|
21
22
|
import {
|
|
22
23
|
__async,
|
|
24
|
+
__spreadProps,
|
|
23
25
|
__spreadValues
|
|
24
26
|
} from "../chunk-6FNC3XMI.js";
|
|
25
27
|
|
|
26
28
|
// src/serve/index.ts
|
|
27
29
|
import Fastify from "fastify";
|
|
28
|
-
import { log as
|
|
30
|
+
import { log as log4 } from "@foxford/logger";
|
|
29
31
|
|
|
30
32
|
// src/serve/assets.ts
|
|
31
33
|
import { createReadStream, existsSync, statSync } from "fs";
|
|
@@ -177,12 +179,208 @@ var appPlugin = (instance, options) => {
|
|
|
177
179
|
return Promise.resolve();
|
|
178
180
|
};
|
|
179
181
|
|
|
182
|
+
// src/serve/process.ts
|
|
183
|
+
import cluster from "cluster";
|
|
184
|
+
import { log as log3 } from "@foxford/logger";
|
|
185
|
+
|
|
186
|
+
// src/serve/worker.ts
|
|
187
|
+
import { log as log2 } from "@foxford/logger";
|
|
188
|
+
var ALIVE = "den:app-worker-alive";
|
|
189
|
+
var SIGNALS = ["SIGTERM", "SIGINT"];
|
|
190
|
+
var SHUTDOWN_MS = 1e4;
|
|
191
|
+
function startWorker(appName, start) {
|
|
192
|
+
return __async(this, null, function* () {
|
|
193
|
+
const aggregation = clusterMetrics();
|
|
194
|
+
const logger2 = log2.getLogger(appName).getLogger("cluster");
|
|
195
|
+
let timer;
|
|
196
|
+
let closing;
|
|
197
|
+
const starting = start();
|
|
198
|
+
const close = () => {
|
|
199
|
+
closing != null ? closing : closing = (() => __async(null, null, function* () {
|
|
200
|
+
clearInterval(timer);
|
|
201
|
+
try {
|
|
202
|
+
const instance = yield starting;
|
|
203
|
+
yield instance.close();
|
|
204
|
+
} finally {
|
|
205
|
+
try {
|
|
206
|
+
if (process.connected) yield aggregation.shutdown();
|
|
207
|
+
} finally {
|
|
208
|
+
for (const signal of SIGNALS) process.removeListener(signal, shutdown);
|
|
209
|
+
process.removeListener("disconnect", shutdown);
|
|
210
|
+
if (process.connected) process.disconnect();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}))();
|
|
214
|
+
return closing;
|
|
215
|
+
};
|
|
216
|
+
const shutdown = () => {
|
|
217
|
+
const deadline = setTimeout(() => process.exit(1), SHUTDOWN_MS);
|
|
218
|
+
deadline.unref();
|
|
219
|
+
void close().then(() => process.exit(0)).catch((error) => {
|
|
220
|
+
logger2.error("\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u0432\u043E\u0440\u043A\u0435\u0440\u0430 \u043D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C:", error);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
for (const signal of SIGNALS) process.on(signal, shutdown);
|
|
225
|
+
process.once("disconnect", shutdown);
|
|
226
|
+
try {
|
|
227
|
+
yield starting;
|
|
228
|
+
if (!closing) {
|
|
229
|
+
const notify = () => {
|
|
230
|
+
var _a;
|
|
231
|
+
if (process.connected) (_a = process.send) == null ? void 0 : _a.call(process, { type: ALIVE });
|
|
232
|
+
};
|
|
233
|
+
timer = setInterval(notify, 2e3);
|
|
234
|
+
timer.unref();
|
|
235
|
+
notify();
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
yield close().catch(() => void 0);
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
return { close };
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/serve/process.ts
|
|
246
|
+
var STARTUP_MS = 6e4;
|
|
247
|
+
function startAppProcess(appName, options, start) {
|
|
248
|
+
return __async(this, null, function* () {
|
|
249
|
+
var _a;
|
|
250
|
+
const workers = (_a = options.workers) != null ? _a : 1;
|
|
251
|
+
if (!Number.isSafeInteger(workers) || workers < 1) {
|
|
252
|
+
throw new Error("WORKERS must be a positive integer");
|
|
253
|
+
}
|
|
254
|
+
if (workers === 1) {
|
|
255
|
+
const instance = yield start(options);
|
|
256
|
+
return { close: () => instance.close() };
|
|
257
|
+
}
|
|
258
|
+
const metrics = getProcessMetrics();
|
|
259
|
+
metrics.startRuntime(appName);
|
|
260
|
+
if (cluster.isWorker) {
|
|
261
|
+
return startWorker(appName, () => start(__spreadProps(__spreadValues({}, options), { gracefulShutdown: false, ops: false })));
|
|
262
|
+
}
|
|
263
|
+
return startPrimary(appName, options, workers);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function startPrimary(appName, options, workers) {
|
|
267
|
+
return __async(this, null, function* () {
|
|
268
|
+
const aggregation = clusterMetrics();
|
|
269
|
+
const logger2 = log3.getLogger(appName).getLogger("cluster");
|
|
270
|
+
const children = /* @__PURE__ */ new Set();
|
|
271
|
+
const alive = /* @__PURE__ */ new Map();
|
|
272
|
+
let ops;
|
|
273
|
+
let closing;
|
|
274
|
+
let started = false;
|
|
275
|
+
let resolveReady = () => void 0;
|
|
276
|
+
let rejectReady = () => void 0;
|
|
277
|
+
const ready = new Promise((resolve, reject) => {
|
|
278
|
+
resolveReady = resolve;
|
|
279
|
+
rejectReady = reject;
|
|
280
|
+
});
|
|
281
|
+
void ready.catch(() => void 0);
|
|
282
|
+
const timeout = setTimeout(() => rejectReady(new Error("Den workers startup timeout")), STARTUP_MS);
|
|
283
|
+
const onMessage = (worker, message) => {
|
|
284
|
+
if (children.has(worker) && typeof message === "object" && message !== null && "type" in message && message.type === ALIVE) {
|
|
285
|
+
alive.set(worker.id, Date.now());
|
|
286
|
+
if (alive.size === workers) resolveReady();
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
const close = () => {
|
|
290
|
+
closing != null ? closing : closing = (() => __async(null, null, function* () {
|
|
291
|
+
clearTimeout(timeout);
|
|
292
|
+
resolveReady();
|
|
293
|
+
cluster.removeListener("message", onMessage);
|
|
294
|
+
yield Promise.all([...children].map(stopWorker));
|
|
295
|
+
if (ops) yield closeHttpServer(ops);
|
|
296
|
+
yield aggregation.shutdown();
|
|
297
|
+
for (const signal of SIGNALS) process.removeListener(signal, shutdown);
|
|
298
|
+
}))();
|
|
299
|
+
return closing;
|
|
300
|
+
};
|
|
301
|
+
const shutdown = () => {
|
|
302
|
+
void close().then(() => process.exit()).catch((error) => {
|
|
303
|
+
logger2.error("\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043A\u043B\u0430\u0441\u0442\u0435\u0440\u0430 \u043D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C:", error);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
const failed = (error) => {
|
|
308
|
+
if (closing) return;
|
|
309
|
+
if (!started) {
|
|
310
|
+
rejectReady(error);
|
|
311
|
+
} else {
|
|
312
|
+
logger2.error(error);
|
|
313
|
+
process.exitCode = 1;
|
|
314
|
+
shutdown();
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
cluster.on("message", onMessage);
|
|
318
|
+
if (options.gracefulShutdown !== false) {
|
|
319
|
+
for (const signal of SIGNALS) process.on(signal, shutdown);
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
if (options.ops !== false) {
|
|
323
|
+
const supplied = options.ops;
|
|
324
|
+
ops = yield startOpsServer(__spreadProps(__spreadValues({}, supplied), {
|
|
325
|
+
collect: aggregation.collect,
|
|
326
|
+
contentType: aggregation.contentType,
|
|
327
|
+
isLive: () => {
|
|
328
|
+
var _a;
|
|
329
|
+
return [...children].some((worker) => {
|
|
330
|
+
var _a2;
|
|
331
|
+
return worker.isConnected() && Date.now() - ((_a2 = alive.get(worker.id)) != null ? _a2 : 0) < 1e4;
|
|
332
|
+
}) && ((_a = supplied == null ? void 0 : supplied.isLive) == null ? void 0 : _a.call(supplied)) !== false;
|
|
333
|
+
}
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
if (closing) {
|
|
337
|
+
if (ops) yield closeHttpServer(ops);
|
|
338
|
+
yield closing;
|
|
339
|
+
return { close };
|
|
340
|
+
}
|
|
341
|
+
for (let i = 0; i < workers; i++) {
|
|
342
|
+
const worker = cluster.fork();
|
|
343
|
+
children.add(worker);
|
|
344
|
+
worker.once("error", failed);
|
|
345
|
+
worker.once("exit", (code, signal) => {
|
|
346
|
+
alive.delete(worker.id);
|
|
347
|
+
failed(new Error(`Den worker ${String(worker.process.pid)} exited (${String(code != null ? code : signal)})`));
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
yield ready;
|
|
351
|
+
started = true;
|
|
352
|
+
clearTimeout(timeout);
|
|
353
|
+
if (!closing) logger2.info(`\u0437\u0430\u043F\u0443\u0449\u0435\u043D\u043E \u0432\u043E\u0440\u043A\u0435\u0440\u043E\u0432: ${String(workers)}`);
|
|
354
|
+
return { close };
|
|
355
|
+
} catch (error) {
|
|
356
|
+
yield close();
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function stopWorker(worker) {
|
|
362
|
+
return __async(this, null, function* () {
|
|
363
|
+
if (worker.isDead()) return;
|
|
364
|
+
yield new Promise((resolve) => {
|
|
365
|
+
const timeout = setTimeout(() => worker.kill("SIGKILL"), SHUTDOWN_MS);
|
|
366
|
+
worker.once("exit", () => {
|
|
367
|
+
clearTimeout(timeout);
|
|
368
|
+
resolve();
|
|
369
|
+
});
|
|
370
|
+
worker.kill("SIGTERM");
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
180
375
|
// src/serve/index.ts
|
|
376
|
+
function serveAppProcess(app, options = {}) {
|
|
377
|
+
return startAppProcess(app.name, options, (serveOptions) => serveApp(app, serveOptions));
|
|
378
|
+
}
|
|
181
379
|
var SHUTDOWN_SIGNALS = ["SIGTERM", "SIGINT"];
|
|
182
380
|
function serveApp(_0) {
|
|
183
381
|
return __async(this, arguments, function* (app, options = {}) {
|
|
184
382
|
var _a, _b, _c, _d, _e;
|
|
185
|
-
const appLog = (_a =
|
|
383
|
+
const appLog = (_a = log4.getLogger(app.name)) != null ? _a : logger;
|
|
186
384
|
const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
|
|
187
385
|
const instance = Fastify({ logger: false });
|
|
188
386
|
const metrics = getProcessMetrics();
|
|
@@ -248,5 +446,6 @@ export {
|
|
|
248
446
|
appPlugin,
|
|
249
447
|
loadAppEnv,
|
|
250
448
|
registerAssets,
|
|
251
|
-
serveApp
|
|
449
|
+
serveApp,
|
|
450
|
+
serveAppProcess
|
|
252
451
|
};
|