@morojs/moro 1.8.12 → 1.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/core/cluster/thread-cluster.d.ts +25 -0
- package/dist/core/cluster/thread-cluster.js +95 -3
- package/dist/core/cluster/thread-cluster.js.map +1 -1
- package/dist/core/config/config-validator.js +15 -0
- package/dist/core/config/config-validator.js.map +1 -1
- package/dist/core/config/index.js +19 -2
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/framework.d.ts +26 -0
- package/dist/core/framework.js +195 -6
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +17 -0
- package/dist/core/http/http-server.js +21 -1
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/http2-server.d.ts +13 -0
- package/dist/core/http/http2-server.js +11 -0
- package/dist/core/http/http2-server.js.map +1 -1
- package/dist/core/http/moro-engine-server.d.ts +28 -0
- package/dist/core/http/moro-engine-server.js +115 -11
- package/dist/core/http/moro-engine-server.js.map +1 -1
- package/dist/core/http/utils/body-type.d.ts +12 -0
- package/dist/core/http/utils/body-type.js +70 -0
- package/dist/core/http/utils/body-type.js.map +1 -0
- package/dist/core/http/utils/ssl-config.d.ts +11 -0
- package/dist/core/http/utils/ssl-config.js +3 -0
- package/dist/core/http/utils/ssl-config.js.map +1 -1
- package/dist/core/http/utils/uws-worker-clustering.js +5 -4
- package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
- package/dist/core/http/uws-http-server.d.ts +3 -0
- package/dist/core/http/uws-http-server.js +22 -1
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/middleware/built-in/body-parsers/index.d.ts +18 -0
- package/dist/core/middleware/built-in/body-parsers/index.js +41 -0
- package/dist/core/middleware/built-in/body-parsers/index.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.d.ts +32 -8
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +117 -25
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
- package/dist/core/middleware/built-in/compression/core.js +5 -0
- package/dist/core/middleware/built-in/compression/core.js.map +1 -1
- package/dist/core/middleware/built-in/session/core.d.ts +1 -0
- package/dist/core/middleware/built-in/session/core.js.map +1 -1
- package/dist/core/middleware/built-in/static/core.d.ts +18 -0
- package/dist/core/middleware/built-in/static/core.js +128 -7
- package/dist/core/middleware/built-in/static/core.js.map +1 -1
- package/dist/core/networking/adapters/engine-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/engine-adapter.js +40 -1
- package/dist/core/networking/adapters/engine-adapter.js.map +1 -1
- package/dist/core/networking/adapters/socketio-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/socketio-adapter.js +10 -1
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
- package/dist/core/networking/adapters/uws-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/uws-adapter.js +54 -3
- package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
- package/dist/core/networking/adapters/ws-adapter.d.ts +9 -2
- package/dist/core/networking/adapters/ws-adapter.js +97 -12
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/websocket-adapter.d.ts +19 -1
- package/dist/core/networking/websocket-adapter.js.map +1 -1
- package/dist/core/routing/index.d.ts +12 -8
- package/dist/core/routing/index.js +7 -2
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/routing/unified-router.d.ts +46 -8
- package/dist/core/routing/unified-router.js +108 -32
- package/dist/core/routing/unified-router.js.map +1 -1
- package/dist/core/utilities/package-utils.d.ts +7 -0
- package/dist/core/utilities/package-utils.js +2 -0
- package/dist/core/utilities/package-utils.js.map +1 -1
- package/dist/core/workers/worker-manager.js +2 -2
- package/dist/core/workers/worker-manager.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +12 -1
- package/dist/moro.js +63 -7
- package/dist/moro.js.map +1 -1
- package/dist/types/cache.d.ts +2 -0
- package/dist/types/cache.js.map +1 -1
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/http.d.ts +6 -0
- package/dist/types/http.js.map +1 -1
- package/dist/types/session.d.ts +1 -0
- package/dist/types/session.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Build high-performance APIs with intelligent routing: declare what a route needs
|
|
|
22
22
|
|
|
23
23
|
**Key Features:**
|
|
24
24
|
|
|
25
|
-
- **Native C++ Engine** - ~
|
|
25
|
+
- **Native C++ Engine** - ~111k req/s real-world through the full framework, 913k pipelined, on a single thread
|
|
26
26
|
- **Intelligent Routing** - Declare auth, validation, rate limits and caching in any order; they always execute in the optimal, security-first one
|
|
27
27
|
- **Enterprise Auth** - Built-in Better Auth with OAuth & RBAC
|
|
28
28
|
- **Universal Validation** - Works with Zod, Joi, Yup, or Class Validator
|
|
@@ -69,8 +69,10 @@ import { createApp, z } from '@morojs/moro';
|
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
// A literal body is the whole handler: on Moro's native engine it is
|
|
72
|
-
// answered without entering JS at all
|
|
72
|
+
// answered without entering JS at all - and so is param('id'), which
|
|
73
|
+
// echoes that path parameter as the body
|
|
73
74
|
app.get('/health').handler('ok');
|
|
75
|
+
app.get('/user/:id').handler(param('id'));
|
|
74
76
|
|
|
75
77
|
app.listen(3000);
|
|
76
78
|
})();
|
|
@@ -24,6 +24,31 @@ export interface SystemInfo {
|
|
|
24
24
|
cpus: number;
|
|
25
25
|
totalMemGB: number;
|
|
26
26
|
}
|
|
27
|
+
/** Injection points for the container-limit probes (unit tests). */
|
|
28
|
+
export interface SystemProbe {
|
|
29
|
+
/** Read a pseudo-file (throws when it does not exist) */
|
|
30
|
+
readFile?: (path: string) => string;
|
|
31
|
+
availableParallelism?: () => number;
|
|
32
|
+
cpuCount?: () => number;
|
|
33
|
+
totalmem?: () => number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* CPUs this process may actually run on. `os.cpus().length` is the HOST's
|
|
37
|
+
* core count, which inside a container is wrong twice over: `--cpuset-cpus`
|
|
38
|
+
* narrows the affinity mask (honoured by os.availableParallelism(), Node
|
|
39
|
+
* 18.14+) and `--cpus` / a Kubernetes CPU limit sets a cgroup quota that no
|
|
40
|
+
* os call reports. 'auto' worker counts take the smaller of the two so a
|
|
41
|
+
* 96-thread host pinned to 32 cores starts 32 workers, not 96.
|
|
42
|
+
*/
|
|
43
|
+
export declare function usableCpuCount(probe?: SystemProbe): number;
|
|
44
|
+
/**
|
|
45
|
+
* Memory available to this process: the cgroup limit when one is set below
|
|
46
|
+
* the host total (`docker run --memory`, Kubernetes memory limit), else
|
|
47
|
+
* os.totalmem(). Sizing workers by host RAM inside a 2 GB container would
|
|
48
|
+
* plan for memory the OOM killer will not let them use.
|
|
49
|
+
*/
|
|
50
|
+
export declare function usableMemoryBytes(probe?: SystemProbe): number;
|
|
51
|
+
export declare function systemInfo(probe?: SystemProbe): SystemInfo;
|
|
27
52
|
/**
|
|
28
53
|
* Worker count: a user number is respected; 'auto' (or unset) is
|
|
29
54
|
* min(cpus, floor(totalRAM / memoryPerWorkerGB)) where memoryPerWorkerGB
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// is dependency-injectable so the sequencing (spawn, restart backoff, health
|
|
20
20
|
// pings, bounded shutdown) is unit-tested with a fake Worker and fake timers.
|
|
21
21
|
import { requireBuiltin } from '../utilities/builtin.js';
|
|
22
|
-
import { existsSync } from 'fs';
|
|
22
|
+
import { existsSync, readFileSync } from 'fs';
|
|
23
23
|
import { resolve as resolvePath } from 'path';
|
|
24
24
|
// Loaded on first use (core/utilities/builtin.ts): an app that never clusters
|
|
25
25
|
// never pays for worker_threads or os.
|
|
@@ -45,8 +45,100 @@ export function threadWorkerInfo(data = wt().workerData) {
|
|
|
45
45
|
return null;
|
|
46
46
|
return { index: info.index, workers: typeof info.workers === 'number' ? info.workers : 1 };
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
const readText = (path) => readFileSync(path, 'utf8');
|
|
49
|
+
/**
|
|
50
|
+
* CPU quota from the cgroup this process runs in (`docker run --cpus=2`,
|
|
51
|
+
* Kubernetes `resources.limits.cpu`), or null when unlimited/unavailable.
|
|
52
|
+
* v2 exposes `cpu.max` as "<quota> <period>" ("max ..." = unlimited); v1
|
|
53
|
+
* splits it over cpu.cfs_quota_us (-1 = unlimited) and cpu.cfs_period_us.
|
|
54
|
+
* A fractional quota (0.5 CPU) still means one runnable worker.
|
|
55
|
+
*/
|
|
56
|
+
function cgroupCpuQuota(read) {
|
|
57
|
+
try {
|
|
58
|
+
const [quota, period] = read('/sys/fs/cgroup/cpu.max').trim().split(/\s+/);
|
|
59
|
+
if (quota !== 'max') {
|
|
60
|
+
const q = Number(quota);
|
|
61
|
+
const p = Number(period) || 100000;
|
|
62
|
+
if (q > 0)
|
|
63
|
+
return Math.max(1, Math.floor(q / p));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// no cgroup v2 - fall through
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const q = Number(read('/sys/fs/cgroup/cpu/cpu.cfs_quota_us').trim());
|
|
71
|
+
const p = Number(read('/sys/fs/cgroup/cpu/cpu.cfs_period_us').trim());
|
|
72
|
+
if (q > 0 && p > 0)
|
|
73
|
+
return Math.max(1, Math.floor(q / p));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// no cgroup v1 either
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* CPUs this process may actually run on. `os.cpus().length` is the HOST's
|
|
82
|
+
* core count, which inside a container is wrong twice over: `--cpuset-cpus`
|
|
83
|
+
* narrows the affinity mask (honoured by os.availableParallelism(), Node
|
|
84
|
+
* 18.14+) and `--cpus` / a Kubernetes CPU limit sets a cgroup quota that no
|
|
85
|
+
* os call reports. 'auto' worker counts take the smaller of the two so a
|
|
86
|
+
* 96-thread host pinned to 32 cores starts 32 workers, not 96.
|
|
87
|
+
*/
|
|
88
|
+
export function usableCpuCount(probe = {}) {
|
|
89
|
+
const read = probe.readFile ?? readText;
|
|
90
|
+
const parallelism = probe.availableParallelism ?? (() => os().availableParallelism?.() ?? 0);
|
|
91
|
+
const cpuCount = probe.cpuCount ?? (() => os().cpus().length);
|
|
92
|
+
let n = 0;
|
|
93
|
+
try {
|
|
94
|
+
n = parallelism();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
n = 0;
|
|
98
|
+
}
|
|
99
|
+
if (!(n >= 1)) {
|
|
100
|
+
try {
|
|
101
|
+
n = cpuCount();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
n = 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!(n >= 1))
|
|
108
|
+
n = 1;
|
|
109
|
+
const quota = cgroupCpuQuota(read);
|
|
110
|
+
return quota === null ? n : Math.max(1, Math.min(n, quota));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Memory available to this process: the cgroup limit when one is set below
|
|
114
|
+
* the host total (`docker run --memory`, Kubernetes memory limit), else
|
|
115
|
+
* os.totalmem(). Sizing workers by host RAM inside a 2 GB container would
|
|
116
|
+
* plan for memory the OOM killer will not let them use.
|
|
117
|
+
*/
|
|
118
|
+
export function usableMemoryBytes(probe = {}) {
|
|
119
|
+
const read = probe.readFile ?? readText;
|
|
120
|
+
const total = (probe.totalmem ?? (() => os().totalmem()))();
|
|
121
|
+
for (const file of ['/sys/fs/cgroup/memory.max', '/sys/fs/cgroup/memory/memory.limit_in_bytes']) {
|
|
122
|
+
try {
|
|
123
|
+
const raw = read(file).trim();
|
|
124
|
+
if (raw === 'max')
|
|
125
|
+
continue;
|
|
126
|
+
const limit = Number(raw);
|
|
127
|
+
// v1 reports a huge sentinel when unlimited; anything >= host RAM is "no limit"
|
|
128
|
+
if (Number.isFinite(limit) && limit > 0 && limit < total)
|
|
129
|
+
return limit;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// file absent - try the next layout
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return total;
|
|
136
|
+
}
|
|
137
|
+
export function systemInfo(probe = {}) {
|
|
138
|
+
return {
|
|
139
|
+
cpus: usableCpuCount(probe),
|
|
140
|
+
totalMemGB: usableMemoryBytes(probe) / (1024 * 1024 * 1024),
|
|
141
|
+
};
|
|
50
142
|
}
|
|
51
143
|
/**
|
|
52
144
|
* Worker count: a user number is respected; 'auto' (or unset) is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread-cluster.js","sourceRoot":"","sources":["../../../src/core/cluster/thread-cluster.ts"],"names":[],"mappings":"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","sourcesContent":["// Worker-thread clustering for the native engine backend.\n//\n// `performance.clustering` keeps its keys; what changes is the transport\n// underneath: on POSIX, with @morojs/engine >= 1.1.6 (capabilities.workerThreads),\n// the workers are worker_threads inside ONE process, each binding the port\n// with SO_REUSEPORT exactly as the process workers did. Everything that\n// cannot be a thread stays on node:cluster automatically - Windows (no\n// SO_REUSEPORT), the node/uWS engines, an older engine, a REPL/eval entry,\n// or a worker thread that fails to boot (see resolveClusterTransport and the\n// boot-phase fallback in Moro.startThreadPrimary).\n//\n// Why threads: one process shares the node binary, the addon and the code\n// pages, so per-worker RSS drops; there is one pid to supervise; and a\n// worker crash is an 'error'/'exit' event on the primary instead of a\n// process death. The trade-off (stated in the release notes): a NATIVE crash\n// in one thread takes the whole process down, where processes isolate.\n//\n// This module is internal (not exported from src/index.ts). Everything here\n// is dependency-injectable so the sequencing (spawn, restart backoff, health\n// pings, bounded shutdown) is unit-tested with a fake Worker and fake timers.\nimport { requireBuiltin } from '../utilities/builtin.js';\nimport { existsSync } from 'fs';\nimport { resolve as resolvePath } from 'path';\nimport type { WorkerOptions } from 'node:worker_threads';\n\n// Loaded on first use (core/utilities/builtin.ts): an app that never clusters\n// never pays for worker_threads or os.\nconst wt = () => requireBuiltin<typeof import('worker_threads')>('worker_threads');\nconst os = () => requireBuiltin<typeof import('os')>('os');\n\n/** Key under workerData that marks a Moro cluster worker thread. */\nexport const WORKER_DATA_KEY = 'moroCluster';\n\n/**\n * The process path pins each worker's V8 young generation with\n * `--max-semi-space-size=4` (A/B-benched 2026-08-01: throughput parity,\n * ~30 MB RSS per worker saved). Worker threads reject V8 flags in execArgv;\n * the equivalent knob is resourceLimits.maxYoungGenerationSizeMb, and V8\n * sizes the young generation at 3x the semi-space: 4 MB semi-space == 12 MB.\n */\nexport const YOUNG_GEN_MB_FOR_SEMI_SPACE_4 = 12;\n\nexport interface ThreadWorkerInfo {\n index: number;\n workers: number;\n}\n\n/** Is this thread a Moro cluster worker (the thread analogue of cluster.isWorker)? */\nexport function threadWorkerInfo(data: unknown = wt().workerData): ThreadWorkerInfo | null {\n if (wt().isMainThread) return null;\n const d = data as Record<string, any> | null | undefined;\n const info = d && typeof d === 'object' ? d[WORKER_DATA_KEY] : undefined;\n if (!info || typeof info !== 'object' || typeof info.index !== 'number') return null;\n return { index: info.index, workers: typeof info.workers === 'number' ? info.workers : 1 };\n}\n\nexport interface ClusteringConfig {\n enabled?: boolean;\n workers?: number | 'auto';\n memoryPerWorkerGB?: number | undefined;\n}\n\nexport interface SystemInfo {\n cpus: number;\n totalMemGB: number;\n}\n\nfunction systemInfo(): SystemInfo {\n return { cpus: os().cpus().length, totalMemGB: os().totalmem() / (1024 * 1024 * 1024) };\n}\n\n/**\n * Worker count: a user number is respected; 'auto' (or unset) is\n * min(cpus, floor(totalRAM / memoryPerWorkerGB)) where memoryPerWorkerGB\n * defaults to max(0.5, floor((totalRAM - 4 GB headroom) / cpus)). Never < 1.\n * (Lifted unchanged from the process path so both transports agree.)\n */\nexport function computeWorkerCount(\n cfg: ClusteringConfig | undefined,\n sys: SystemInfo = systemInfo()\n): { count: number; detail: string } {\n const requested = cfg?.workers ?? 'auto';\n if (typeof requested === 'number' && Number.isFinite(requested) && requested > 0) {\n return {\n count: Math.floor(requested),\n detail: `Using user-specified worker count: ${Math.floor(requested)}`,\n };\n }\n const cpuCount = Math.max(1, sys.cpus);\n const totalMemoryGB = sys.totalMemGB;\n let memoryPerWorkerGB = cfg?.memoryPerWorkerGB;\n if (!memoryPerWorkerGB) {\n const headroomGB = 4;\n memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));\n }\n const count = Math.max(1, Math.min(cpuCount, Math.floor(totalMemoryGB / memoryPerWorkerGB)));\n return {\n count,\n detail: `Auto-calculated worker count: ${count} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,\n };\n}\n\n/** Did the operator tune the young generation themselves? Then leave it alone. */\nexport function isUserTunedYoungGen(\n execArgv: readonly string[] = process.execArgv,\n nodeOptions: string | undefined = process.env.NODE_OPTIONS\n): boolean {\n return (\n execArgv.some(a => a.includes('--max-semi-space-size')) ||\n (nodeOptions || '').includes('--max-semi-space-size')\n );\n}\n\nexport type ClusterTransport =\n { kind: 'threads'; entry: string } | { kind: 'processes'; reason: string };\n\nexport interface TransportInput {\n platform: NodeJS.Platform;\n isMainThread: boolean;\n argv1: string | undefined;\n /** app.engine.server: 'engine' | 'node' | 'uws' */\n engineServer: string;\n /** app.engine.enginePackage, e.g. '@morojs/engine' */\n enginePackage?: string | undefined;\n /** engine capabilities.workerThreads (teardown-safe inside a thread) */\n workerThreadsCapable: boolean;\n fileExists?: (p: string) => boolean;\n}\n\n/**\n * Threads iff every precondition holds; otherwise processes, with the reason\n * (logged once). The reasons are the exact conditions the process path still\n * covers, so nothing that worked before loses clustering.\n */\nexport function resolveClusterTransport(input: TransportInput): ClusterTransport {\n const exists = input.fileExists ?? existsSync;\n if (input.platform === 'win32')\n return { kind: 'processes', reason: 'Windows has no SO_REUSEPORT' };\n if (input.engineServer !== 'engine' || input.enginePackage !== '@morojs/engine') {\n return { kind: 'processes', reason: `engine '${input.engineServer}' clusters as processes` };\n }\n if (!input.workerThreadsCapable) {\n return {\n kind: 'processes',\n reason: 'this @morojs/engine build predates worker-thread support (< 1.1.6)',\n };\n }\n if (!input.isMainThread) return { kind: 'processes', reason: 'not on the main thread' };\n if (typeof input.argv1 !== 'string' || input.argv1.length === 0) {\n return { kind: 'processes', reason: 'no entry script (REPL/eval)' };\n }\n const entry = resolvePath(input.argv1);\n if (!exists(entry)) return { kind: 'processes', reason: `entry script not found: ${entry}` };\n return { kind: 'threads', entry };\n}\n\n// ---- primary <-> worker protocol ----------------------------------------------\n\nexport type WorkerToPrimary =\n | { type: 'ready'; threadId: number; port: number }\n | { type: 'closed'; threadId: number }\n | { type: 'pong'; threadId: number; inflight: number }\n | { type: 'fatal'; threadId: number; code: string; message: string };\n\nexport type PrimaryToWorker = { type: 'shutdown' } | { type: 'ping' };\n\n/** The subset of worker_threads.Worker the primary uses (fakeable). */\nexport interface WorkerLike {\n threadId: number;\n on(event: string, listener: (...args: any[]) => void): this;\n once(event: string, listener: (...args: any[]) => void): this;\n off(event: string, listener: (...args: any[]) => void): this;\n postMessage(message: PrimaryToWorker): void;\n terminate(): Promise<number>;\n unref(): void;\n ref(): void;\n}\n\nexport type SpawnWorker = (entry: string, options: WorkerOptions) => WorkerLike;\n\nexport interface ClusterLogger {\n info(message: string, context?: string, ...rest: any[]): void;\n warn(message: string, context?: string, ...rest: any[]): void;\n error(message: string, context?: string, ...rest: any[]): void;\n debug?(message: string, context?: string, ...rest: any[]): void;\n}\n\nexport interface WorkerRecord {\n worker: WorkerLike;\n index: number;\n startedAt: number;\n ready: boolean;\n port: number | null;\n restarts: number;\n missedPongs: number;\n}\n\nexport type FatalKind = 'boot' | 'bind';\n\nexport interface ThreadClusterPrimaryOptions {\n entry: string;\n argv: string[];\n workerCount: number;\n logger: ClusterLogger;\n resourceLimits?: WorkerOptions['resourceLimits'];\n /** First 'ready' from any worker (fires once). */\n onReady: () => void;\n /** A worker failed before the cluster ever became ready ('boot': spawn or\n * module error - the caller may fall back to processes; 'bind': the port\n * cannot be bound - processes would fail identically, surface it). */\n onFatal: (error: Error, kind: FatalKind) => void;\n spawn?: SpawnWorker;\n now?: () => number;\n setTimer?: (fn: () => void, ms: number) => any;\n clearTimer?: (t: any) => void;\n healthIntervalMs?: number;\n}\n\nconst RESTART_BASE_MS = 200;\nconst RESTART_MAX_MS = 5000;\nconst RESTART_WINDOW_MS = 60_000; // a worker alive this long resets its backoff\nconst HEALTH_INTERVAL_MS = 30_000;\nconst HEALTH_MISSES_BEFORE_WARN = 3;\n\nconst defaultSpawn: SpawnWorker = (entry, options) =>\n new (wt().Worker)(entry, options) as unknown as WorkerLike;\n\nexport class ThreadClusterPrimary {\n private readonly opts: ThreadClusterPrimaryOptions;\n private readonly spawnFn: SpawnWorker;\n private readonly now: () => number;\n private readonly setTimer: (fn: () => void, ms: number) => any;\n private readonly clearTimer: (t: any) => void;\n private readonly records = new Map<number, WorkerRecord>();\n private readonly backoff = new Map<number, { restarts: number; lastStart: number }>();\n private readyFired = false;\n private shuttingDown = false;\n private healthTimer: any = null;\n private pendingTimers = new Set<any>();\n\n constructor(opts: ThreadClusterPrimaryOptions) {\n this.opts = opts;\n this.spawnFn = opts.spawn ?? defaultSpawn;\n this.now = opts.now ?? Date.now;\n this.setTimer =\n opts.setTimer ??\n ((fn, ms) => {\n const t = setTimeout(fn, ms);\n (t as any).unref?.();\n return t;\n });\n this.clearTimer = opts.clearTimer ?? (t => clearTimeout(t));\n }\n\n get workers(): ReadonlyMap<number, WorkerRecord> {\n return this.records;\n }\n\n /** True until every initial worker has reported 'ready'. */\n get bootPhase(): boolean {\n if (this.readyFired) {\n for (const r of this.records.values()) if (!r.ready) return true;\n return false;\n }\n return true;\n }\n\n /** Spawn every worker. Throws synchronously if the FIRST spawn throws (the\n * entry cannot be started as a thread at all). */\n start(): void {\n for (let i = 0; i < this.opts.workerCount; i++) this.spawnAt(i);\n this.healthTimer = this.setTimer(\n () => this.healthTick(),\n this.opts.healthIntervalMs ?? HEALTH_INTERVAL_MS\n );\n }\n\n private spawnAt(index: number): void {\n const options: WorkerOptions = {\n argv: this.opts.argv,\n workerData: { [WORKER_DATA_KEY]: { index, workers: this.opts.workerCount } },\n // No execArgv (inherited: V8 flags passed explicitly are rejected by\n // Worker, inherited ones are tolerated) and no env (a copy of\n // process.env at spawn time - UV_THREADPOOL_SIZE was set before).\n ...(this.opts.resourceLimits ? { resourceLimits: this.opts.resourceLimits } : {}),\n name: `moro-worker-${index}`,\n };\n const worker = this.spawnFn(this.opts.entry, options);\n const record: WorkerRecord = {\n worker,\n index,\n startedAt: this.now(),\n ready: false,\n port: null,\n restarts: this.backoff.get(index)?.restarts ?? 0,\n missedPongs: 0,\n };\n this.records.set(worker.threadId, record);\n let bootFailed = false;\n\n worker.on('message', (m: WorkerToPrimary) => {\n if (!m || typeof m !== 'object') return;\n if (m.type === 'ready') {\n record.ready = true;\n record.port = m.port;\n this.opts.logger.info(\n `Worker thread ${worker.threadId} ready on port ${m.port}`,\n 'Cluster'\n );\n if (!this.readyFired) {\n this.readyFired = true;\n this.opts.onReady();\n }\n } else if (m.type === 'pong') {\n record.missedPongs = 0;\n } else if (m.type === 'fatal') {\n bootFailed = true;\n const err = Object.assign(new Error(m.message), {\n code: m.code,\n threadId: worker.threadId,\n });\n const isBind = m.code === 'EADDRINUSE' || m.code === 'EACCES';\n this.opts.logger.error(\n `Worker thread ${worker.threadId} fatal (${m.code}): ${m.message}`,\n 'Cluster'\n );\n if (!this.readyFired || isBind) this.opts.onFatal(err, isBind ? 'bind' : 'boot');\n }\n });\n worker.on('error', (err: Error) => {\n this.opts.logger.error(`Worker thread ${worker.threadId} error: ${err.message}`, 'Cluster');\n if (!record.ready && !this.readyFired) {\n bootFailed = true;\n this.opts.onFatal(err, 'boot');\n }\n });\n worker.on('exit', (code: number) => {\n this.records.delete(worker.threadId);\n if (this.shuttingDown) return;\n if (bootFailed) return; // onFatal already decided what happens\n if (code === 0 && record.ready) {\n // A worker that finished on its own after serving: nothing to\n // supervise (mirrors cluster's exitedAfterDisconnect).\n this.opts.logger.warn(`Worker thread ${worker.threadId} exited cleanly`, 'Cluster');\n return;\n }\n this.scheduleRestart(index, worker.threadId, code);\n });\n }\n\n private scheduleRestart(index: number, oldThreadId: number, code: number): void {\n const state = this.backoff.get(index) ?? { restarts: 0, lastStart: 0 };\n const alive = this.now() - (this.records.get(oldThreadId)?.startedAt ?? this.now());\n if (this.now() - state.lastStart > RESTART_WINDOW_MS && alive > RESTART_WINDOW_MS)\n state.restarts = 0;\n const delay = Math.min(RESTART_MAX_MS, RESTART_BASE_MS * 2 ** state.restarts);\n state.restarts += 1;\n state.lastStart = this.now();\n this.backoff.set(index, state);\n this.opts.logger.warn(\n `Worker thread ${oldThreadId} exited (code ${code}). Restarting in ${delay} ms...`,\n 'Cluster'\n );\n const t = this.setTimer(() => {\n this.pendingTimers.delete(t);\n if (this.shuttingDown) return;\n try {\n this.spawnAt(index);\n } catch (err) {\n this.opts.logger.error(\n `Worker thread restart failed: ${err instanceof Error ? err.message : String(err)}`,\n 'Cluster'\n );\n this.scheduleRestart(index, oldThreadId, -1);\n }\n }, delay);\n this.pendingTimers.add(t);\n }\n\n private healthTick(): void {\n if (this.shuttingDown) return;\n for (const r of this.records.values()) {\n if (!r.ready) continue;\n r.missedPongs += 1;\n if (r.missedPongs > HEALTH_MISSES_BEFORE_WARN) {\n this.opts.logger.warn(\n `Worker thread ${r.worker.threadId} missed ${r.missedPongs} health pings (event loop blocked?)`,\n 'Cluster'\n );\n }\n try {\n r.worker.postMessage({ type: 'ping' });\n } catch {\n // exiting\n }\n }\n this.healthTimer = this.setTimer(\n () => this.healthTick(),\n this.opts.healthIntervalMs ?? HEALTH_INTERVAL_MS\n );\n }\n\n /**\n * Post 'shutdown' to every worker (each drains its server via app.close()),\n * wait for their 'exit's up to the deadline, then terminate() stragglers.\n * Resolves once every thread is gone.\n */\n async shutdown(deadlineMs = 3000): Promise<void> {\n if (this.shuttingDown) return;\n this.shuttingDown = true;\n if (this.healthTimer) this.clearTimer(this.healthTimer);\n for (const t of this.pendingTimers) this.clearTimer(t);\n this.pendingTimers.clear();\n const live = [...this.records.values()];\n if (live.length === 0) return;\n const exits = live.map(\n r =>\n new Promise<void>(resolve => {\n r.worker.once('exit', () => resolve());\n try {\n r.worker.postMessage({ type: 'shutdown' });\n } catch {\n resolve();\n }\n })\n );\n let timer: any = null;\n const deadline = new Promise<'timeout'>(resolve => {\n timer = this.setTimer(() => resolve('timeout'), deadlineMs);\n });\n const outcome = await Promise.race([\n Promise.all(exits).then(() => 'drained' as const),\n deadline,\n ]);\n if (timer) this.clearTimer(timer);\n if (outcome === 'timeout') {\n const stragglers = [...this.records.values()];\n this.opts.logger.warn(\n `Shutdown deadline (${deadlineMs} ms) reached; terminating ${stragglers.length} worker thread(s)`,\n 'Cluster'\n );\n await Promise.all(\n stragglers.map(async r => {\n try {\n await r.worker.terminate();\n } catch {\n // already gone\n }\n })\n );\n }\n this.records.clear();\n }\n}\n\n/** The worker-thread side: a thin wrapper over parentPort. */\nexport class ThreadClusterWorker {\n private readonly port = wt().parentPort;\n private readonly tid = wt().threadId;\n private inflight: () => number = () => 0;\n private readonly logger: ClusterLogger;\n private shutdownHandler: (() => Promise<void> | void) | null = null;\n\n constructor(logger: ClusterLogger) {\n this.logger = logger;\n this.port?.on('message', (m: PrimaryToWorker) => {\n if (!m || typeof m !== 'object') return;\n if (m.type === 'ping') {\n this.post({ type: 'pong', threadId: this.tid, inflight: this.inflight() });\n } else if (m.type === 'shutdown') {\n void this.runShutdown();\n }\n });\n }\n\n private post(m: WorkerToPrimary): void {\n try {\n this.port?.postMessage(m);\n } catch {\n // primary gone\n }\n }\n\n private async runShutdown(): Promise<void> {\n this.logger.info(`Worker thread ${this.tid} shutting down gracefully...`, 'Worker');\n try {\n await this.shutdownHandler?.();\n } catch (err) {\n this.logger.error(\n `Worker thread ${this.tid} shutdown error: ${err instanceof Error ? err.message : String(err)}`,\n 'Worker'\n );\n }\n this.close();\n }\n\n onShutdown(fn: () => Promise<void> | void): void {\n this.shutdownHandler = fn;\n }\n\n inflightProvider(fn: () => number): void {\n this.inflight = fn;\n }\n\n ready(port: number): void {\n this.post({ type: 'ready', threadId: this.tid, port });\n }\n\n fatal(code: string, message: string): void {\n this.post({ type: 'fatal', threadId: this.tid, code, message });\n }\n\n /** Signal completion and release the port so the thread can exit naturally\n * once its handles are closed (never process.exit() inside a thread). */\n close(): void {\n this.post({ type: 'closed', threadId: this.tid });\n try {\n this.port?.close();\n } catch {\n // already closed\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"thread-cluster.js","sourceRoot":"","sources":["../../../src/core/cluster/thread-cluster.ts"],"names":[],"mappings":"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","sourcesContent":["// Worker-thread clustering for the native engine backend.\n//\n// `performance.clustering` keeps its keys; what changes is the transport\n// underneath: on POSIX, with @morojs/engine >= 1.1.6 (capabilities.workerThreads),\n// the workers are worker_threads inside ONE process, each binding the port\n// with SO_REUSEPORT exactly as the process workers did. Everything that\n// cannot be a thread stays on node:cluster automatically - Windows (no\n// SO_REUSEPORT), the node/uWS engines, an older engine, a REPL/eval entry,\n// or a worker thread that fails to boot (see resolveClusterTransport and the\n// boot-phase fallback in Moro.startThreadPrimary).\n//\n// Why threads: one process shares the node binary, the addon and the code\n// pages, so per-worker RSS drops; there is one pid to supervise; and a\n// worker crash is an 'error'/'exit' event on the primary instead of a\n// process death. The trade-off (stated in the release notes): a NATIVE crash\n// in one thread takes the whole process down, where processes isolate.\n//\n// This module is internal (not exported from src/index.ts). Everything here\n// is dependency-injectable so the sequencing (spawn, restart backoff, health\n// pings, bounded shutdown) is unit-tested with a fake Worker and fake timers.\nimport { requireBuiltin } from '../utilities/builtin.js';\nimport { existsSync, readFileSync } from 'fs';\nimport { resolve as resolvePath } from 'path';\nimport type { WorkerOptions } from 'node:worker_threads';\n\n// Loaded on first use (core/utilities/builtin.ts): an app that never clusters\n// never pays for worker_threads or os.\nconst wt = () => requireBuiltin<typeof import('worker_threads')>('worker_threads');\nconst os = () => requireBuiltin<typeof import('os')>('os');\n\n/** Key under workerData that marks a Moro cluster worker thread. */\nexport const WORKER_DATA_KEY = 'moroCluster';\n\n/**\n * The process path pins each worker's V8 young generation with\n * `--max-semi-space-size=4` (A/B-benched 2026-08-01: throughput parity,\n * ~30 MB RSS per worker saved). Worker threads reject V8 flags in execArgv;\n * the equivalent knob is resourceLimits.maxYoungGenerationSizeMb, and V8\n * sizes the young generation at 3x the semi-space: 4 MB semi-space == 12 MB.\n */\nexport const YOUNG_GEN_MB_FOR_SEMI_SPACE_4 = 12;\n\nexport interface ThreadWorkerInfo {\n index: number;\n workers: number;\n}\n\n/** Is this thread a Moro cluster worker (the thread analogue of cluster.isWorker)? */\nexport function threadWorkerInfo(data: unknown = wt().workerData): ThreadWorkerInfo | null {\n if (wt().isMainThread) return null;\n const d = data as Record<string, any> | null | undefined;\n const info = d && typeof d === 'object' ? d[WORKER_DATA_KEY] : undefined;\n if (!info || typeof info !== 'object' || typeof info.index !== 'number') return null;\n return { index: info.index, workers: typeof info.workers === 'number' ? info.workers : 1 };\n}\n\nexport interface ClusteringConfig {\n enabled?: boolean;\n workers?: number | 'auto';\n memoryPerWorkerGB?: number | undefined;\n}\n\nexport interface SystemInfo {\n cpus: number;\n totalMemGB: number;\n}\n\n/** Injection points for the container-limit probes (unit tests). */\nexport interface SystemProbe {\n /** Read a pseudo-file (throws when it does not exist) */\n readFile?: (path: string) => string;\n availableParallelism?: () => number;\n cpuCount?: () => number;\n totalmem?: () => number;\n}\n\nconst readText = (path: string): string => readFileSync(path, 'utf8');\n\n/**\n * CPU quota from the cgroup this process runs in (`docker run --cpus=2`,\n * Kubernetes `resources.limits.cpu`), or null when unlimited/unavailable.\n * v2 exposes `cpu.max` as \"<quota> <period>\" (\"max ...\" = unlimited); v1\n * splits it over cpu.cfs_quota_us (-1 = unlimited) and cpu.cfs_period_us.\n * A fractional quota (0.5 CPU) still means one runnable worker.\n */\nfunction cgroupCpuQuota(read: (path: string) => string): number | null {\n try {\n const [quota, period] = read('/sys/fs/cgroup/cpu.max').trim().split(/\\s+/);\n if (quota !== 'max') {\n const q = Number(quota);\n const p = Number(period) || 100000;\n if (q > 0) return Math.max(1, Math.floor(q / p));\n }\n } catch {\n // no cgroup v2 - fall through\n }\n try {\n const q = Number(read('/sys/fs/cgroup/cpu/cpu.cfs_quota_us').trim());\n const p = Number(read('/sys/fs/cgroup/cpu/cpu.cfs_period_us').trim());\n if (q > 0 && p > 0) return Math.max(1, Math.floor(q / p));\n } catch {\n // no cgroup v1 either\n }\n return null;\n}\n\n/**\n * CPUs this process may actually run on. `os.cpus().length` is the HOST's\n * core count, which inside a container is wrong twice over: `--cpuset-cpus`\n * narrows the affinity mask (honoured by os.availableParallelism(), Node\n * 18.14+) and `--cpus` / a Kubernetes CPU limit sets a cgroup quota that no\n * os call reports. 'auto' worker counts take the smaller of the two so a\n * 96-thread host pinned to 32 cores starts 32 workers, not 96.\n */\nexport function usableCpuCount(probe: SystemProbe = {}): number {\n const read = probe.readFile ?? readText;\n const parallelism =\n probe.availableParallelism ?? (() => (os() as any).availableParallelism?.() ?? 0);\n const cpuCount = probe.cpuCount ?? (() => os().cpus().length);\n\n let n = 0;\n try {\n n = parallelism();\n } catch {\n n = 0;\n }\n if (!(n >= 1)) {\n try {\n n = cpuCount();\n } catch {\n n = 0;\n }\n }\n if (!(n >= 1)) n = 1;\n\n const quota = cgroupCpuQuota(read);\n return quota === null ? n : Math.max(1, Math.min(n, quota));\n}\n\n/**\n * Memory available to this process: the cgroup limit when one is set below\n * the host total (`docker run --memory`, Kubernetes memory limit), else\n * os.totalmem(). Sizing workers by host RAM inside a 2 GB container would\n * plan for memory the OOM killer will not let them use.\n */\nexport function usableMemoryBytes(probe: SystemProbe = {}): number {\n const read = probe.readFile ?? readText;\n const total = (probe.totalmem ?? (() => os().totalmem()))();\n for (const file of ['/sys/fs/cgroup/memory.max', '/sys/fs/cgroup/memory/memory.limit_in_bytes']) {\n try {\n const raw = read(file).trim();\n if (raw === 'max') continue;\n const limit = Number(raw);\n // v1 reports a huge sentinel when unlimited; anything >= host RAM is \"no limit\"\n if (Number.isFinite(limit) && limit > 0 && limit < total) return limit;\n } catch {\n // file absent - try the next layout\n }\n }\n return total;\n}\n\nexport function systemInfo(probe: SystemProbe = {}): SystemInfo {\n return {\n cpus: usableCpuCount(probe),\n totalMemGB: usableMemoryBytes(probe) / (1024 * 1024 * 1024),\n };\n}\n\n/**\n * Worker count: a user number is respected; 'auto' (or unset) is\n * min(cpus, floor(totalRAM / memoryPerWorkerGB)) where memoryPerWorkerGB\n * defaults to max(0.5, floor((totalRAM - 4 GB headroom) / cpus)). Never < 1.\n * (Lifted unchanged from the process path so both transports agree.)\n */\nexport function computeWorkerCount(\n cfg: ClusteringConfig | undefined,\n sys: SystemInfo = systemInfo()\n): { count: number; detail: string } {\n const requested = cfg?.workers ?? 'auto';\n if (typeof requested === 'number' && Number.isFinite(requested) && requested > 0) {\n return {\n count: Math.floor(requested),\n detail: `Using user-specified worker count: ${Math.floor(requested)}`,\n };\n }\n const cpuCount = Math.max(1, sys.cpus);\n const totalMemoryGB = sys.totalMemGB;\n let memoryPerWorkerGB = cfg?.memoryPerWorkerGB;\n if (!memoryPerWorkerGB) {\n const headroomGB = 4;\n memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));\n }\n const count = Math.max(1, Math.min(cpuCount, Math.floor(totalMemoryGB / memoryPerWorkerGB)));\n return {\n count,\n detail: `Auto-calculated worker count: ${count} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,\n };\n}\n\n/** Did the operator tune the young generation themselves? Then leave it alone. */\nexport function isUserTunedYoungGen(\n execArgv: readonly string[] = process.execArgv,\n nodeOptions: string | undefined = process.env.NODE_OPTIONS\n): boolean {\n return (\n execArgv.some(a => a.includes('--max-semi-space-size')) ||\n (nodeOptions || '').includes('--max-semi-space-size')\n );\n}\n\nexport type ClusterTransport =\n { kind: 'threads'; entry: string } | { kind: 'processes'; reason: string };\n\nexport interface TransportInput {\n platform: NodeJS.Platform;\n isMainThread: boolean;\n argv1: string | undefined;\n /** app.engine.server: 'engine' | 'node' | 'uws' */\n engineServer: string;\n /** app.engine.enginePackage, e.g. '@morojs/engine' */\n enginePackage?: string | undefined;\n /** engine capabilities.workerThreads (teardown-safe inside a thread) */\n workerThreadsCapable: boolean;\n fileExists?: (p: string) => boolean;\n}\n\n/**\n * Threads iff every precondition holds; otherwise processes, with the reason\n * (logged once). The reasons are the exact conditions the process path still\n * covers, so nothing that worked before loses clustering.\n */\nexport function resolveClusterTransport(input: TransportInput): ClusterTransport {\n const exists = input.fileExists ?? existsSync;\n if (input.platform === 'win32')\n return { kind: 'processes', reason: 'Windows has no SO_REUSEPORT' };\n if (input.engineServer !== 'engine' || input.enginePackage !== '@morojs/engine') {\n return { kind: 'processes', reason: `engine '${input.engineServer}' clusters as processes` };\n }\n if (!input.workerThreadsCapable) {\n return {\n kind: 'processes',\n reason: 'this @morojs/engine build predates worker-thread support (< 1.1.6)',\n };\n }\n if (!input.isMainThread) return { kind: 'processes', reason: 'not on the main thread' };\n if (typeof input.argv1 !== 'string' || input.argv1.length === 0) {\n return { kind: 'processes', reason: 'no entry script (REPL/eval)' };\n }\n const entry = resolvePath(input.argv1);\n if (!exists(entry)) return { kind: 'processes', reason: `entry script not found: ${entry}` };\n return { kind: 'threads', entry };\n}\n\n// ---- primary <-> worker protocol ----------------------------------------------\n\nexport type WorkerToPrimary =\n | { type: 'ready'; threadId: number; port: number }\n | { type: 'closed'; threadId: number }\n | { type: 'pong'; threadId: number; inflight: number }\n | { type: 'fatal'; threadId: number; code: string; message: string };\n\nexport type PrimaryToWorker = { type: 'shutdown' } | { type: 'ping' };\n\n/** The subset of worker_threads.Worker the primary uses (fakeable). */\nexport interface WorkerLike {\n threadId: number;\n on(event: string, listener: (...args: any[]) => void): this;\n once(event: string, listener: (...args: any[]) => void): this;\n off(event: string, listener: (...args: any[]) => void): this;\n postMessage(message: PrimaryToWorker): void;\n terminate(): Promise<number>;\n unref(): void;\n ref(): void;\n}\n\nexport type SpawnWorker = (entry: string, options: WorkerOptions) => WorkerLike;\n\nexport interface ClusterLogger {\n info(message: string, context?: string, ...rest: any[]): void;\n warn(message: string, context?: string, ...rest: any[]): void;\n error(message: string, context?: string, ...rest: any[]): void;\n debug?(message: string, context?: string, ...rest: any[]): void;\n}\n\nexport interface WorkerRecord {\n worker: WorkerLike;\n index: number;\n startedAt: number;\n ready: boolean;\n port: number | null;\n restarts: number;\n missedPongs: number;\n}\n\nexport type FatalKind = 'boot' | 'bind';\n\nexport interface ThreadClusterPrimaryOptions {\n entry: string;\n argv: string[];\n workerCount: number;\n logger: ClusterLogger;\n resourceLimits?: WorkerOptions['resourceLimits'];\n /** First 'ready' from any worker (fires once). */\n onReady: () => void;\n /** A worker failed before the cluster ever became ready ('boot': spawn or\n * module error - the caller may fall back to processes; 'bind': the port\n * cannot be bound - processes would fail identically, surface it). */\n onFatal: (error: Error, kind: FatalKind) => void;\n spawn?: SpawnWorker;\n now?: () => number;\n setTimer?: (fn: () => void, ms: number) => any;\n clearTimer?: (t: any) => void;\n healthIntervalMs?: number;\n}\n\nconst RESTART_BASE_MS = 200;\nconst RESTART_MAX_MS = 5000;\nconst RESTART_WINDOW_MS = 60_000; // a worker alive this long resets its backoff\nconst HEALTH_INTERVAL_MS = 30_000;\nconst HEALTH_MISSES_BEFORE_WARN = 3;\n\nconst defaultSpawn: SpawnWorker = (entry, options) =>\n new (wt().Worker)(entry, options) as unknown as WorkerLike;\n\nexport class ThreadClusterPrimary {\n private readonly opts: ThreadClusterPrimaryOptions;\n private readonly spawnFn: SpawnWorker;\n private readonly now: () => number;\n private readonly setTimer: (fn: () => void, ms: number) => any;\n private readonly clearTimer: (t: any) => void;\n private readonly records = new Map<number, WorkerRecord>();\n private readonly backoff = new Map<number, { restarts: number; lastStart: number }>();\n private readyFired = false;\n private shuttingDown = false;\n private healthTimer: any = null;\n private pendingTimers = new Set<any>();\n\n constructor(opts: ThreadClusterPrimaryOptions) {\n this.opts = opts;\n this.spawnFn = opts.spawn ?? defaultSpawn;\n this.now = opts.now ?? Date.now;\n this.setTimer =\n opts.setTimer ??\n ((fn, ms) => {\n const t = setTimeout(fn, ms);\n (t as any).unref?.();\n return t;\n });\n this.clearTimer = opts.clearTimer ?? (t => clearTimeout(t));\n }\n\n get workers(): ReadonlyMap<number, WorkerRecord> {\n return this.records;\n }\n\n /** True until every initial worker has reported 'ready'. */\n get bootPhase(): boolean {\n if (this.readyFired) {\n for (const r of this.records.values()) if (!r.ready) return true;\n return false;\n }\n return true;\n }\n\n /** Spawn every worker. Throws synchronously if the FIRST spawn throws (the\n * entry cannot be started as a thread at all). */\n start(): void {\n for (let i = 0; i < this.opts.workerCount; i++) this.spawnAt(i);\n this.healthTimer = this.setTimer(\n () => this.healthTick(),\n this.opts.healthIntervalMs ?? HEALTH_INTERVAL_MS\n );\n }\n\n private spawnAt(index: number): void {\n const options: WorkerOptions = {\n argv: this.opts.argv,\n workerData: { [WORKER_DATA_KEY]: { index, workers: this.opts.workerCount } },\n // No execArgv (inherited: V8 flags passed explicitly are rejected by\n // Worker, inherited ones are tolerated) and no env (a copy of\n // process.env at spawn time - UV_THREADPOOL_SIZE was set before).\n ...(this.opts.resourceLimits ? { resourceLimits: this.opts.resourceLimits } : {}),\n name: `moro-worker-${index}`,\n };\n const worker = this.spawnFn(this.opts.entry, options);\n const record: WorkerRecord = {\n worker,\n index,\n startedAt: this.now(),\n ready: false,\n port: null,\n restarts: this.backoff.get(index)?.restarts ?? 0,\n missedPongs: 0,\n };\n this.records.set(worker.threadId, record);\n let bootFailed = false;\n\n worker.on('message', (m: WorkerToPrimary) => {\n if (!m || typeof m !== 'object') return;\n if (m.type === 'ready') {\n record.ready = true;\n record.port = m.port;\n this.opts.logger.info(\n `Worker thread ${worker.threadId} ready on port ${m.port}`,\n 'Cluster'\n );\n if (!this.readyFired) {\n this.readyFired = true;\n this.opts.onReady();\n }\n } else if (m.type === 'pong') {\n record.missedPongs = 0;\n } else if (m.type === 'fatal') {\n bootFailed = true;\n const err = Object.assign(new Error(m.message), {\n code: m.code,\n threadId: worker.threadId,\n });\n const isBind = m.code === 'EADDRINUSE' || m.code === 'EACCES';\n this.opts.logger.error(\n `Worker thread ${worker.threadId} fatal (${m.code}): ${m.message}`,\n 'Cluster'\n );\n if (!this.readyFired || isBind) this.opts.onFatal(err, isBind ? 'bind' : 'boot');\n }\n });\n worker.on('error', (err: Error) => {\n this.opts.logger.error(`Worker thread ${worker.threadId} error: ${err.message}`, 'Cluster');\n if (!record.ready && !this.readyFired) {\n bootFailed = true;\n this.opts.onFatal(err, 'boot');\n }\n });\n worker.on('exit', (code: number) => {\n this.records.delete(worker.threadId);\n if (this.shuttingDown) return;\n if (bootFailed) return; // onFatal already decided what happens\n if (code === 0 && record.ready) {\n // A worker that finished on its own after serving: nothing to\n // supervise (mirrors cluster's exitedAfterDisconnect).\n this.opts.logger.warn(`Worker thread ${worker.threadId} exited cleanly`, 'Cluster');\n return;\n }\n this.scheduleRestart(index, worker.threadId, code);\n });\n }\n\n private scheduleRestart(index: number, oldThreadId: number, code: number): void {\n const state = this.backoff.get(index) ?? { restarts: 0, lastStart: 0 };\n const alive = this.now() - (this.records.get(oldThreadId)?.startedAt ?? this.now());\n if (this.now() - state.lastStart > RESTART_WINDOW_MS && alive > RESTART_WINDOW_MS)\n state.restarts = 0;\n const delay = Math.min(RESTART_MAX_MS, RESTART_BASE_MS * 2 ** state.restarts);\n state.restarts += 1;\n state.lastStart = this.now();\n this.backoff.set(index, state);\n this.opts.logger.warn(\n `Worker thread ${oldThreadId} exited (code ${code}). Restarting in ${delay} ms...`,\n 'Cluster'\n );\n const t = this.setTimer(() => {\n this.pendingTimers.delete(t);\n if (this.shuttingDown) return;\n try {\n this.spawnAt(index);\n } catch (err) {\n this.opts.logger.error(\n `Worker thread restart failed: ${err instanceof Error ? err.message : String(err)}`,\n 'Cluster'\n );\n this.scheduleRestart(index, oldThreadId, -1);\n }\n }, delay);\n this.pendingTimers.add(t);\n }\n\n private healthTick(): void {\n if (this.shuttingDown) return;\n for (const r of this.records.values()) {\n if (!r.ready) continue;\n r.missedPongs += 1;\n if (r.missedPongs > HEALTH_MISSES_BEFORE_WARN) {\n this.opts.logger.warn(\n `Worker thread ${r.worker.threadId} missed ${r.missedPongs} health pings (event loop blocked?)`,\n 'Cluster'\n );\n }\n try {\n r.worker.postMessage({ type: 'ping' });\n } catch {\n // exiting\n }\n }\n this.healthTimer = this.setTimer(\n () => this.healthTick(),\n this.opts.healthIntervalMs ?? HEALTH_INTERVAL_MS\n );\n }\n\n /**\n * Post 'shutdown' to every worker (each drains its server via app.close()),\n * wait for their 'exit's up to the deadline, then terminate() stragglers.\n * Resolves once every thread is gone.\n */\n async shutdown(deadlineMs = 3000): Promise<void> {\n if (this.shuttingDown) return;\n this.shuttingDown = true;\n if (this.healthTimer) this.clearTimer(this.healthTimer);\n for (const t of this.pendingTimers) this.clearTimer(t);\n this.pendingTimers.clear();\n const live = [...this.records.values()];\n if (live.length === 0) return;\n const exits = live.map(\n r =>\n new Promise<void>(resolve => {\n r.worker.once('exit', () => resolve());\n try {\n r.worker.postMessage({ type: 'shutdown' });\n } catch {\n resolve();\n }\n })\n );\n let timer: any = null;\n const deadline = new Promise<'timeout'>(resolve => {\n timer = this.setTimer(() => resolve('timeout'), deadlineMs);\n });\n const outcome = await Promise.race([\n Promise.all(exits).then(() => 'drained' as const),\n deadline,\n ]);\n if (timer) this.clearTimer(timer);\n if (outcome === 'timeout') {\n const stragglers = [...this.records.values()];\n this.opts.logger.warn(\n `Shutdown deadline (${deadlineMs} ms) reached; terminating ${stragglers.length} worker thread(s)`,\n 'Cluster'\n );\n await Promise.all(\n stragglers.map(async r => {\n try {\n await r.worker.terminate();\n } catch {\n // already gone\n }\n })\n );\n }\n this.records.clear();\n }\n}\n\n/** The worker-thread side: a thin wrapper over parentPort. */\nexport class ThreadClusterWorker {\n private readonly port = wt().parentPort;\n private readonly tid = wt().threadId;\n private inflight: () => number = () => 0;\n private readonly logger: ClusterLogger;\n private shutdownHandler: (() => Promise<void> | void) | null = null;\n\n constructor(logger: ClusterLogger) {\n this.logger = logger;\n this.port?.on('message', (m: PrimaryToWorker) => {\n if (!m || typeof m !== 'object') return;\n if (m.type === 'ping') {\n this.post({ type: 'pong', threadId: this.tid, inflight: this.inflight() });\n } else if (m.type === 'shutdown') {\n void this.runShutdown();\n }\n });\n }\n\n private post(m: WorkerToPrimary): void {\n try {\n this.port?.postMessage(m);\n } catch {\n // primary gone\n }\n }\n\n private async runShutdown(): Promise<void> {\n this.logger.info(`Worker thread ${this.tid} shutting down gracefully...`, 'Worker');\n try {\n await this.shutdownHandler?.();\n } catch (err) {\n this.logger.error(\n `Worker thread ${this.tid} shutdown error: ${err instanceof Error ? err.message : String(err)}`,\n 'Worker'\n );\n }\n this.close();\n }\n\n onShutdown(fn: () => Promise<void> | void): void {\n this.shutdownHandler = fn;\n }\n\n inflightProvider(fn: () => number): void {\n this.inflight = fn;\n }\n\n ready(port: number): void {\n this.post({ type: 'ready', threadId: this.tid, port });\n }\n\n fatal(code: string, message: string): void {\n this.post({ type: 'fatal', threadId: this.tid, code, message });\n }\n\n /** Signal completion and release the port so the thread can exit naturally\n * once its handles are closed (never process.exit() inside a thread). */\n close(): void {\n this.post({ type: 'closed', threadId: this.tid });\n try {\n this.port?.close();\n } catch {\n // already closed\n }\n }\n}\n"]}
|
|
@@ -174,6 +174,21 @@ function validateSSLConfig(config, path) {
|
|
|
174
174
|
if (config.requestCert !== undefined) {
|
|
175
175
|
result.requestCert = validateBoolean(config.requestCert, `${path}.requestCert`);
|
|
176
176
|
}
|
|
177
|
+
// Certificate rotation on file change: true, or { debounceMs }
|
|
178
|
+
if (config.watch !== undefined) {
|
|
179
|
+
if (typeof config.watch === 'boolean') {
|
|
180
|
+
result.watch = config.watch;
|
|
181
|
+
}
|
|
182
|
+
else if (config.watch && typeof config.watch === 'object') {
|
|
183
|
+
result.watch = {};
|
|
184
|
+
if (config.watch.debounceMs !== undefined) {
|
|
185
|
+
result.watch.debounceMs = validateNumber(config.watch.debounceMs, `${path}.watch.debounceMs`, { min: 1 });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
throw new ConfigValidationError(`${path}.watch`, config.watch, 'boolean | { debounceMs }', 'ssl.watch must be a boolean or an object');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
177
192
|
if (config.rejectUnauthorized !== undefined) {
|
|
178
193
|
result.rejectUnauthorized = validateBoolean(config.rejectUnauthorized, `${path}.rejectUnauthorized`);
|
|
179
194
|
}
|