@morojs/moro 1.8.9 → 1.8.10
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/dist/core/cluster/thread-cluster.d.ts +178 -0
- package/dist/core/cluster/thread-cluster.js +389 -0
- package/dist/core/cluster/thread-cluster.js.map +1 -0
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/moro-engine-server.d.ts +28 -1
- package/dist/core/http/moro-engine-server.js +274 -36
- package/dist/core/http/moro-engine-server.js.map +1 -1
- package/dist/core/http/utils/compression.js +12 -6
- package/dist/core/http/utils/compression.js.map +1 -1
- package/dist/core/http/utils/uws-worker-clustering.js +10 -6
- package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
- package/dist/core/http/uws-http-server.js +3 -2
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/jobs/leader-election.js +16 -5
- package/dist/core/jobs/leader-election.js.map +1 -1
- package/dist/core/networking/websocket-manager.js +4 -2
- package/dist/core/networking/websocket-manager.js.map +1 -1
- package/dist/core/runtime/node-adapter.d.ts +1 -1
- package/dist/core/runtime/node-adapter.js.map +1 -1
- package/dist/core/utilities/builtin.d.ts +1 -0
- package/dist/core/utilities/builtin.js +25 -0
- package/dist/core/utilities/builtin.js.map +1 -0
- package/dist/core/utilities/package-utils.d.ts +28 -3
- package/dist/core/utilities/package-utils.js +24 -12
- package/dist/core/utilities/package-utils.js.map +1 -1
- package/dist/core/workers/worker-manager.js +4 -3
- package/dist/core/workers/worker-manager.js.map +1 -1
- package/dist/moro.d.ts +5 -0
- package/dist/moro.js +364 -204
- package/dist/moro.js.map +1 -1
- package/dist/types/http.d.ts +1 -1
- package/dist/types/http.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,11 +8,17 @@
|
|
|
8
8
|
* engine's Content-Length short-write detection. That is a deliberate,
|
|
9
9
|
* documented non-goal.
|
|
10
10
|
*/
|
|
11
|
-
import { gzip, deflate, brotliCompress, constants as zlibConstants } from 'zlib';
|
|
12
11
|
import { promisify } from 'util';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
import { requireBuiltin } from '../../utilities/builtin.js';
|
|
13
|
+
// zlib is loaded on the first compressed response, not at startup
|
|
14
|
+
// (core/utilities/builtin.ts): compression is off by default.
|
|
15
|
+
const z = () => requireBuiltin('zlib');
|
|
16
|
+
let gzipFn;
|
|
17
|
+
let deflateFn;
|
|
18
|
+
let brotliFn;
|
|
19
|
+
const gzipAsync = (buf, opts) => (gzipFn ??= promisify(z().gzip))(buf, opts);
|
|
20
|
+
const deflateAsync = (buf, opts) => (deflateFn ??= promisify(z().deflate))(buf, opts);
|
|
21
|
+
const brotliAsync = (buf, opts) => (brotliFn ??= promisify(z().brotliCompress))(buf, opts);
|
|
16
22
|
export const DEFAULT_ENCODINGS = ['br', 'gzip', 'deflate'];
|
|
17
23
|
/**
|
|
18
24
|
* Build CompressionSettings from the framework's performance config (the
|
|
@@ -93,8 +99,8 @@ export async function compressBuffer(data, encoding, level = 6) {
|
|
|
93
99
|
case 'br':
|
|
94
100
|
return brotliAsync(buf, {
|
|
95
101
|
params: {
|
|
96
|
-
[
|
|
97
|
-
[
|
|
102
|
+
[z().constants.BROTLI_PARAM_QUALITY]: Math.max(0, Math.min(11, Math.round((level * 11) / 9))),
|
|
103
|
+
[z().constants.BROTLI_PARAM_SIZE_HINT]: buf.length,
|
|
98
104
|
},
|
|
99
105
|
});
|
|
100
106
|
case 'gzip':
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compression.js","sourceRoot":"","sources":["../../../../src/core/http/utils/compression.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared response compression, used by every runtime (Node, uWS, HTTP/2, and\n * the native engine) so switching engines never silently drops compression.\n *\n * Scope: BUFFERED responses only (json/send/end with a full body). The\n * streaming path (writeHead + write + write + end) stays uncompressed on all\n * runtimes — per-chunk flush semantics would break SSE latency and the\n * engine's Content-Length short-write detection. That is a deliberate,\n * documented non-goal.\n */\nimport { gzip, deflate, brotliCompress, constants as zlibConstants } from 'zlib';\nimport { promisify } from 'util';\n\nconst gzipAsync = promisify(gzip);\nconst deflateAsync = promisify(deflate);\nconst brotliAsync = promisify(brotliCompress);\n\nexport type Encoding = 'br' | 'gzip' | 'deflate';\n\nexport interface CompressionSettings {\n enabled: boolean;\n threshold: number;\n level: number; // 1-9 (mapped to brotli quality)\n encodings: Encoding[]; // server preference order\n}\n\nexport const DEFAULT_ENCODINGS: Encoding[] = ['br', 'gzip', 'deflate'];\n\n/**\n * Build CompressionSettings from the framework's performance config (the\n * shape passed to every server's configurePerformance). Shared so all four\n * runtimes interpret `performance.compression` identically.\n */\nexport function resolveCompressionSettings(config?: {\n compression?: { enabled?: boolean; threshold?: number; level?: number; encodings?: Encoding[] };\n minimal?: boolean;\n}): CompressionSettings {\n const c = config?.compression;\n // Minimal mode disables all response overhead for pure throughput.\n const enabled = config?.minimal ? false : c?.enabled !== false && c !== undefined;\n return {\n enabled,\n threshold: c?.threshold ?? 1024,\n level: c?.level ?? 6,\n encodings: c?.encodings ?? DEFAULT_ENCODINGS,\n };\n}\n\n/**\n * Content types worth compressing. Text and structured formats yes; already\n * compressed binary (images, video, zip, octet-stream) no.\n */\nexport function isCompressible(contentType: string | undefined): boolean {\n if (!contentType) return false;\n const ct = (contentType.split(';')[0] ?? '').trim().toLowerCase();\n if (ct.startsWith('text/')) return true;\n if (ct.endsWith('+json') || ct.endsWith('+xml')) return true;\n return (\n ct === 'application/json' ||\n ct === 'application/javascript' ||\n ct === 'application/xml' ||\n ct === 'application/ld+json' ||\n ct === 'application/manifest+json' ||\n ct === 'image/svg+xml'\n );\n}\n\n/**\n * Pick the best encoding the client accepts from the server's preference\n * list, honoring q-values (q=0 excludes). Returns null when the client\n * accepts none / only identity.\n */\nexport function negotiateEncoding(\n acceptEncoding: string | undefined,\n allowed: Encoding[] = DEFAULT_ENCODINGS\n): Encoding | null {\n if (!acceptEncoding) return null;\n\n // Parse \"gzip;q=0.8, br, deflate;q=0\" into a q-value map.\n const q = new Map<string, number>();\n for (const part of acceptEncoding.split(',')) {\n const [tokenRaw, ...params] = part.trim().split(';');\n const token = (tokenRaw ?? '').trim().toLowerCase();\n if (!token) continue;\n let weight = 1;\n for (const p of params) {\n const m = /^\\s*q=([0-9.]+)\\s*$/i.exec(p);\n if (m && m[1] !== undefined) weight = parseFloat(m[1]);\n }\n q.set(token, weight);\n }\n\n const wildcard = q.get('*');\n const accepts = (enc: Encoding): boolean => {\n if (q.has(enc)) return (q.get(enc) as number) > 0;\n if (wildcard !== undefined) return wildcard > 0;\n return false;\n };\n\n for (const enc of allowed) {\n if (accepts(enc)) return enc;\n }\n return null;\n}\n\n/** Compress a buffer/string with the chosen encoding. */\nexport async function compressBuffer(\n data: Buffer | string,\n encoding: Encoding,\n level = 6\n): Promise<Buffer> {\n const buf = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;\n switch (encoding) {\n case 'br':\n return brotliAsync(buf, {\n params: {\n [zlibConstants.BROTLI_PARAM_QUALITY]: Math.max(\n 0,\n Math.min(11, Math.round((level * 11) / 9))\n ),\n [zlibConstants.BROTLI_PARAM_SIZE_HINT]: buf.length,\n },\n });\n case 'gzip':\n return gzipAsync(buf, { level });\n case 'deflate':\n return deflateAsync(buf, { level });\n }\n}\n\nexport interface MaybeCompressResult {\n body: Buffer | string;\n encoding: Encoding | null;\n}\n\n/**\n * The one-call helper the servers use: decide whether to compress `body`\n * given the response content type, the client's Accept-Encoding, and the\n * server's compression settings. Returns the (possibly compressed) body plus\n * the chosen encoding (null = sent as-is). Never throws — a compression error\n * degrades to the original body.\n */\nexport async function maybeCompress(\n body: Buffer | string,\n contentType: string | undefined,\n acceptEncoding: string | undefined,\n settings: CompressionSettings\n): Promise<MaybeCompressResult> {\n if (!settings.enabled) return { body, encoding: null };\n const byteLen = typeof body === 'string' ? Buffer.byteLength(body) : body.length;\n if (byteLen < settings.threshold) return { body, encoding: null };\n if (!isCompressible(contentType)) return { body, encoding: null };\n\n const encoding = negotiateEncoding(acceptEncoding, settings.encodings);\n if (!encoding) return { body, encoding: null };\n\n try {\n const compressed = await compressBuffer(body, encoding, settings.level);\n return { body: compressed, encoding };\n } catch {\n return { body, encoding: null };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"compression.js","sourceRoot":"","sources":["../../../../src/core/http/utils/compression.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared response compression, used by every runtime (Node, uWS, HTTP/2, and\n * the native engine) so switching engines never silently drops compression.\n *\n * Scope: BUFFERED responses only (json/send/end with a full body). The\n * streaming path (writeHead + write + write + end) stays uncompressed on all\n * runtimes — per-chunk flush semantics would break SSE latency and the\n * engine's Content-Length short-write detection. That is a deliberate,\n * documented non-goal.\n */\nimport { promisify } from 'util';\nimport { requireBuiltin } from '../../utilities/builtin.js';\n\n// zlib is loaded on the first compressed response, not at startup\n// (core/utilities/builtin.ts): compression is off by default.\nconst z = () => requireBuiltin<typeof import('zlib')>('zlib');\ntype Compressor = (buf: Buffer, opts: any) => Promise<Buffer>;\nlet gzipFn: Compressor | undefined;\nlet deflateFn: Compressor | undefined;\nlet brotliFn: Compressor | undefined;\nconst gzipAsync: Compressor = (buf, opts) =>\n (gzipFn ??= promisify(z().gzip) as unknown as Compressor)(buf, opts);\nconst deflateAsync: Compressor = (buf, opts) =>\n (deflateFn ??= promisify(z().deflate) as unknown as Compressor)(buf, opts);\nconst brotliAsync: Compressor = (buf, opts) =>\n (brotliFn ??= promisify(z().brotliCompress) as unknown as Compressor)(buf, opts);\n\nexport type Encoding = 'br' | 'gzip' | 'deflate';\n\nexport interface CompressionSettings {\n enabled: boolean;\n threshold: number;\n level: number; // 1-9 (mapped to brotli quality)\n encodings: Encoding[]; // server preference order\n}\n\nexport const DEFAULT_ENCODINGS: Encoding[] = ['br', 'gzip', 'deflate'];\n\n/**\n * Build CompressionSettings from the framework's performance config (the\n * shape passed to every server's configurePerformance). Shared so all four\n * runtimes interpret `performance.compression` identically.\n */\nexport function resolveCompressionSettings(config?: {\n compression?: { enabled?: boolean; threshold?: number; level?: number; encodings?: Encoding[] };\n minimal?: boolean;\n}): CompressionSettings {\n const c = config?.compression;\n // Minimal mode disables all response overhead for pure throughput.\n const enabled = config?.minimal ? false : c?.enabled !== false && c !== undefined;\n return {\n enabled,\n threshold: c?.threshold ?? 1024,\n level: c?.level ?? 6,\n encodings: c?.encodings ?? DEFAULT_ENCODINGS,\n };\n}\n\n/**\n * Content types worth compressing. Text and structured formats yes; already\n * compressed binary (images, video, zip, octet-stream) no.\n */\nexport function isCompressible(contentType: string | undefined): boolean {\n if (!contentType) return false;\n const ct = (contentType.split(';')[0] ?? '').trim().toLowerCase();\n if (ct.startsWith('text/')) return true;\n if (ct.endsWith('+json') || ct.endsWith('+xml')) return true;\n return (\n ct === 'application/json' ||\n ct === 'application/javascript' ||\n ct === 'application/xml' ||\n ct === 'application/ld+json' ||\n ct === 'application/manifest+json' ||\n ct === 'image/svg+xml'\n );\n}\n\n/**\n * Pick the best encoding the client accepts from the server's preference\n * list, honoring q-values (q=0 excludes). Returns null when the client\n * accepts none / only identity.\n */\nexport function negotiateEncoding(\n acceptEncoding: string | undefined,\n allowed: Encoding[] = DEFAULT_ENCODINGS\n): Encoding | null {\n if (!acceptEncoding) return null;\n\n // Parse \"gzip;q=0.8, br, deflate;q=0\" into a q-value map.\n const q = new Map<string, number>();\n for (const part of acceptEncoding.split(',')) {\n const [tokenRaw, ...params] = part.trim().split(';');\n const token = (tokenRaw ?? '').trim().toLowerCase();\n if (!token) continue;\n let weight = 1;\n for (const p of params) {\n const m = /^\\s*q=([0-9.]+)\\s*$/i.exec(p);\n if (m && m[1] !== undefined) weight = parseFloat(m[1]);\n }\n q.set(token, weight);\n }\n\n const wildcard = q.get('*');\n const accepts = (enc: Encoding): boolean => {\n if (q.has(enc)) return (q.get(enc) as number) > 0;\n if (wildcard !== undefined) return wildcard > 0;\n return false;\n };\n\n for (const enc of allowed) {\n if (accepts(enc)) return enc;\n }\n return null;\n}\n\n/** Compress a buffer/string with the chosen encoding. */\nexport async function compressBuffer(\n data: Buffer | string,\n encoding: Encoding,\n level = 6\n): Promise<Buffer> {\n const buf = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;\n switch (encoding) {\n case 'br':\n return brotliAsync(buf, {\n params: {\n [z().constants.BROTLI_PARAM_QUALITY]: Math.max(\n 0,\n Math.min(11, Math.round((level * 11) / 9))\n ),\n [z().constants.BROTLI_PARAM_SIZE_HINT]: buf.length,\n },\n });\n case 'gzip':\n return gzipAsync(buf, { level });\n case 'deflate':\n return deflateAsync(buf, { level });\n }\n}\n\nexport interface MaybeCompressResult {\n body: Buffer | string;\n encoding: Encoding | null;\n}\n\n/**\n * The one-call helper the servers use: decide whether to compress `body`\n * given the response content type, the client's Accept-Encoding, and the\n * server's compression settings. Returns the (possibly compressed) body plus\n * the chosen encoding (null = sent as-is). Never throws — a compression error\n * degrades to the original body.\n */\nexport async function maybeCompress(\n body: Buffer | string,\n contentType: string | undefined,\n acceptEncoding: string | undefined,\n settings: CompressionSettings\n): Promise<MaybeCompressResult> {\n if (!settings.enabled) return { body, encoding: null };\n const byteLen = typeof body === 'string' ? Buffer.byteLength(body) : body.length;\n if (byteLen < settings.threshold) return { body, encoding: null };\n if (!isCompressible(contentType)) return { body, encoding: null };\n\n const encoding = negotiateEncoding(acceptEncoding, settings.encodings);\n if (!encoding) return { body, encoding: null };\n\n try {\n const compressed = await compressBuffer(body, encoding, settings.level);\n return { body: compressed, encoding };\n } catch {\n return { body, encoding: null };\n }\n}\n"]}
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
// through JavaScript APIs. These warnings are harmless - the process will still exit cleanly.
|
|
8
8
|
// To suppress these warnings, you can run Node.js with: --no-warnings or
|
|
9
9
|
// NODE_NO_WARNINGS=1 environment variable.
|
|
10
|
-
import {
|
|
10
|
+
import { requireBuiltin } from '../../utilities/builtin.js';
|
|
11
|
+
// worker_threads is loaded on first use (core/utilities/builtin.ts).
|
|
12
|
+
const wt = () => requireBuiltin('worker_threads');
|
|
11
13
|
import os from 'os';
|
|
12
14
|
import { createFrameworkLogger } from '../../logger/index.js';
|
|
13
15
|
import { loadNativeEngine } from '../../utilities/package-utils.js';
|
|
@@ -43,7 +45,7 @@ export class UWSWorkerClusterManager {
|
|
|
43
45
|
return workerCount;
|
|
44
46
|
}
|
|
45
47
|
async startAcceptorAndWorkers(_appFactory) {
|
|
46
|
-
if (!isMainThread) {
|
|
48
|
+
if (!wt().isMainThread) {
|
|
47
49
|
throw new Error('startAcceptorAndWorkers can only be called from main thread');
|
|
48
50
|
}
|
|
49
51
|
try {
|
|
@@ -74,10 +76,10 @@ export class UWSWorkerClusterManager {
|
|
|
74
76
|
this.acceptorApp.listen(port, (token) => {
|
|
75
77
|
if (token) {
|
|
76
78
|
this.acceptorListenSocket = token;
|
|
77
|
-
logger.info(`Acceptor listening on port ${port} from thread ${threadId}`, 'Acceptor');
|
|
79
|
+
logger.info(`Acceptor listening on port ${port} from thread ${wt().threadId}`, 'Acceptor');
|
|
78
80
|
}
|
|
79
81
|
else {
|
|
80
|
-
throw new Error(`Failed to listen on port ${port} from thread ${threadId}`);
|
|
82
|
+
throw new Error(`Failed to listen on port ${port} from thread ${wt().threadId}`);
|
|
81
83
|
}
|
|
82
84
|
});
|
|
83
85
|
// Spawn workers
|
|
@@ -102,7 +104,7 @@ export class UWSWorkerClusterManager {
|
|
|
102
104
|
reject(new Error('Cannot spawn worker: process.argv[1] (entry script) is unavailable'));
|
|
103
105
|
return;
|
|
104
106
|
}
|
|
105
|
-
const worker = new Worker(scriptPath, {
|
|
107
|
+
const worker = new (wt().Worker)(scriptPath, {
|
|
106
108
|
workerData: {
|
|
107
109
|
isUWSWorker: true,
|
|
108
110
|
workerIndex: index,
|
|
@@ -260,9 +262,10 @@ export class UWSWorkerClusterManager {
|
|
|
260
262
|
process.on('SIGTERM', () => void gracefulShutdown());
|
|
261
263
|
}
|
|
262
264
|
static isUWSWorker() {
|
|
263
|
-
return !isMainThread && process.env.UWS_WORKER_MODE === 'true';
|
|
265
|
+
return !wt().isMainThread && process.env.UWS_WORKER_MODE === 'true';
|
|
264
266
|
}
|
|
265
267
|
static async sendDescriptorToAcceptor(app) {
|
|
268
|
+
const { isMainThread, parentPort, threadId } = wt();
|
|
266
269
|
if (isMainThread || !parentPort) {
|
|
267
270
|
throw new Error('sendDescriptorToAcceptor can only be called from worker thread');
|
|
268
271
|
}
|
|
@@ -288,6 +291,7 @@ export class UWSWorkerClusterManager {
|
|
|
288
291
|
}
|
|
289
292
|
}
|
|
290
293
|
static setupWorkerShutdownHandler(closeCallback) {
|
|
294
|
+
const { parentPort, threadId } = wt();
|
|
291
295
|
if (!parentPort) {
|
|
292
296
|
return;
|
|
293
297
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uws-worker-clustering.js","sourceRoot":"","sources":["../../../../src/core/http/utils/uws-worker-clustering.ts"],"names":[],"mappings":"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","sourcesContent":["// uWebSockets.js Worker Thread Clustering Implementation\n// Uses worker threads with acceptor pattern for maximum performance\n//\n// NOTE: When terminating, you may see \"uv_loop_close() while having open handles\" warnings\n// from Node.js. This is a known limitation when using uWebSockets.js with worker threads.\n// The uWS C++ addon maintains internal libuv poll handles that cannot be cleanly closed\n// through JavaScript APIs. These warnings are harmless - the process will still exit cleanly.\n// To suppress these warnings, you can run Node.js with: --no-warnings or\n// NODE_NO_WARNINGS=1 environment variable.\n\nimport { Worker, isMainThread, threadId, parentPort } from 'worker_threads';\nimport os from 'os';\nimport { createFrameworkLogger } from '../../logger/index.js';\nimport { loadNativeEngine } from '../../utilities/package-utils.js';\n\nconst logger = createFrameworkLogger('UWSClustering');\n\nexport interface UWSClusterConfig {\n workers?: number | 'auto';\n memoryPerWorkerGB?: number;\n port: number;\n host?: string;\n ssl?: {\n key_file_name?: string;\n cert_file_name?: string;\n passphrase?: string;\n };\n}\n\nexport class UWSWorkerClusterManager {\n private workers: Worker[] = [];\n private acceptorApp: any = null;\n private acceptorListenSocket: any = null;\n private uws: any = null;\n private config: UWSClusterConfig;\n private workerCount: number;\n private isShuttingDown = false;\n\n constructor(config: UWSClusterConfig) {\n this.config = config;\n this.workerCount = this.calculateWorkerCount();\n }\n\n private calculateWorkerCount(): number {\n let workerCount = this.config.workers || os.cpus().length;\n\n if (workerCount === 'auto') {\n const cpuCount = os.cpus().length;\n const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);\n\n let memoryPerWorkerGB = this.config.memoryPerWorkerGB;\n\n if (!memoryPerWorkerGB) {\n const headroomGB = 4;\n memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));\n }\n\n workerCount = Math.min(cpuCount, Math.floor(totalMemoryGB / memoryPerWorkerGB));\n\n logger.info(\n `Auto-calculated worker count: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,\n 'WorkerCalculation'\n );\n } else if (typeof workerCount === 'number') {\n logger.info(`Using specified worker count: ${workerCount}`, 'WorkerCalculation');\n }\n\n return workerCount;\n }\n\n async startAcceptorAndWorkers(_appFactory: () => any): Promise<void> {\n if (!isMainThread) {\n throw new Error('startAcceptorAndWorkers can only be called from main thread');\n }\n\n try {\n // Worker clustering uses the uWS-shaped App/SSLApp + SO_REUSEPORT API,\n // so it needs uWebSockets.js specifically (not @morojs/engine).\n const engine = loadNativeEngine({ candidates: ['uWebSockets.js'] });\n if (!engine) {\n throw new Error('uWebSockets.js is not available for uWS worker clustering');\n }\n const uwsModule = engine.module;\n this.uws = uwsModule.default || uwsModule;\n\n // Create acceptor app - must match SSL config of worker apps\n const ssl = this.config.ssl;\n if (ssl && ssl.key_file_name && ssl.cert_file_name) {\n this.acceptorApp = this.uws.SSLApp({\n key_file_name: ssl.key_file_name,\n cert_file_name: ssl.cert_file_name,\n passphrase: ssl.passphrase,\n });\n logger.info('uWebSockets SSL acceptor app created', 'Acceptor');\n } else {\n this.acceptorApp = this.uws.App();\n logger.info('uWebSockets acceptor app created', 'Acceptor');\n }\n\n // Start listening on acceptor\n const port = this.config.port;\n this.acceptorApp.listen(port, (token: any) => {\n if (token) {\n this.acceptorListenSocket = token;\n logger.info(`Acceptor listening on port ${port} from thread ${threadId}`, 'Acceptor');\n } else {\n throw new Error(`Failed to listen on port ${port} from thread ${threadId}`);\n }\n });\n\n // Spawn workers\n logger.info(`Starting ${this.workerCount} worker threads`, 'Workers');\n\n for (let i = 0; i < this.workerCount; i++) {\n await this.spawnWorker(i);\n }\n\n // Setup graceful shutdown\n this.setupGracefulShutdown();\n } catch (error) {\n logger.error('Failed to start acceptor and workers', 'Startup', {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n\n private async spawnWorker(index: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const scriptPath = process.argv[1];\n if (scriptPath === undefined) {\n reject(new Error('Cannot spawn worker: process.argv[1] (entry script) is unavailable'));\n return;\n }\n const worker = new Worker(scriptPath, {\n workerData: {\n isUWSWorker: true,\n workerIndex: index,\n config: this.config,\n },\n argv: process.argv.slice(2),\n env: {\n ...process.env,\n UWS_WORKER_MODE: 'true',\n UWS_WORKER_INDEX: String(index),\n },\n });\n\n let descriptorReceived = false;\n\n worker.on('message', (message: any) => {\n if (message.type === 'descriptor' && message.descriptor) {\n if (!this.acceptorApp) {\n logger.error('Acceptor app not initialized', 'WorkerSpawn');\n reject(new Error('Acceptor app not initialized'));\n return;\n }\n\n try {\n this.acceptorApp.addChildAppDescriptor(message.descriptor);\n descriptorReceived = true;\n logger.info(\n `Worker ${index} (thread ${message.threadId}) registered with acceptor`,\n 'WorkerSpawn'\n );\n resolve();\n } catch (error) {\n logger.error('Failed to add child app descriptor', 'WorkerSpawn', {\n error: error instanceof Error ? error.message : String(error),\n });\n reject(error);\n }\n } else if (message.type === 'error') {\n logger.error(`Worker ${index} initialization error: ${message.error}`, 'WorkerSpawn');\n reject(new Error(message.error));\n }\n });\n\n worker.on('error', (error: Error) => {\n logger.error(`Worker ${index} error`, 'Worker', {\n error: error.message,\n });\n if (!descriptorReceived) {\n reject(error);\n }\n });\n\n worker.on('exit', (code: number) => {\n // Worker threads don't have exitedAfterDisconnect like cluster workers\n // Restart on any non-zero exit code\n if (code !== 0) {\n logger.warn(`Worker ${index} died unexpectedly (code: ${code}). Restarting...`, 'Worker');\n\n // Remove from workers array\n const workerIdx = this.workers.indexOf(worker);\n if (workerIdx > -1) {\n this.workers.splice(workerIdx, 1);\n }\n\n // Restart worker\n this.spawnWorker(index).catch(error => {\n logger.error('Failed to restart worker', 'Worker', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }\n });\n\n this.workers.push(worker);\n\n // Timeout for descriptor reception\n setTimeout(() => {\n if (!descriptorReceived) {\n logger.error(`Worker ${index} failed to send descriptor within timeout`, 'WorkerSpawn');\n void worker.terminate();\n reject(new Error(`Worker ${index} initialization timeout`));\n }\n }, 10000); // 10 second timeout\n });\n }\n\n private setupGracefulShutdown(): void {\n const gracefulShutdown = async () => {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n\n logger.info('Gracefully shutting down worker cluster...', 'Shutdown');\n\n // Close acceptor listen socket first\n if (this.acceptorListenSocket && this.uws) {\n try {\n this.uws.us_listen_socket_close(this.acceptorListenSocket);\n this.acceptorListenSocket = null;\n logger.info('Acceptor listen socket closed', 'Shutdown');\n } catch (error) {\n logger.error('Error closing acceptor socket', 'Shutdown', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Send shutdown message to all workers and wait for them to close\n const shutdownPromises = this.workers.map((worker, index) => {\n return new Promise<void>(resolve => {\n let workerExited = false;\n\n const timeout = setTimeout(() => {\n if (!workerExited) {\n logger.info(\n `Worker ${index} taking too long to exit, terminating forcefully`,\n 'Shutdown'\n );\n try {\n // Terminate is the last resort for stuck workers\n void worker.terminate();\n } catch {\n logger.error(`Error terminating worker ${index}`, 'Shutdown');\n }\n }\n resolve();\n }, 2000); // Give 2 seconds for graceful exit\n\n worker.once('exit', code => {\n workerExited = true;\n clearTimeout(timeout);\n if (code === 0) {\n logger.info(`Worker ${index} exited cleanly`, 'Shutdown');\n } else if (code === 1) {\n logger.info(`Worker ${index} terminated (exit code ${code})`, 'Shutdown');\n } else {\n logger.warn(`Worker ${index} exited with unexpected code ${code}`, 'Shutdown');\n }\n resolve();\n });\n\n // Send shutdown message\n try {\n worker.postMessage({ type: 'shutdown' });\n } catch {\n logger.error(`Failed to send shutdown message to worker ${index}`, 'Shutdown');\n clearTimeout(timeout);\n try {\n void worker.terminate();\n } catch {\n // Ignore termination errors\n }\n resolve();\n }\n });\n });\n\n await Promise.all(shutdownPromises);\n logger.info('All workers shut down', 'Shutdown');\n\n // Clear acceptor app reference to allow uWebSockets to cleanup\n if (this.acceptorApp) {\n this.acceptorApp = null;\n }\n\n // Give uWebSockets event loop time to fully cleanup before exiting\n await new Promise(resolve => setTimeout(resolve, 200));\n\n logger.info('Shutdown complete', 'Shutdown');\n process.exit(0);\n };\n\n process.on('SIGINT', () => void gracefulShutdown());\n process.on('SIGTERM', () => void gracefulShutdown());\n }\n\n static isUWSWorker(): boolean {\n return !isMainThread && process.env.UWS_WORKER_MODE === 'true';\n }\n\n static async sendDescriptorToAcceptor(app: any): Promise<void> {\n if (isMainThread || !parentPort) {\n throw new Error('sendDescriptorToAcceptor can only be called from worker thread');\n }\n\n try {\n const descriptor = app.getDescriptor();\n\n parentPort.postMessage({\n type: 'descriptor',\n descriptor,\n threadId,\n });\n\n logger.info(`Worker thread ${threadId} sent descriptor to acceptor`, 'Worker');\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Failed to send descriptor to acceptor', 'Worker', { error: errorMessage });\n\n if (parentPort) {\n parentPort.postMessage({\n type: 'error',\n error: errorMessage,\n });\n }\n\n throw error;\n }\n }\n\n static setupWorkerShutdownHandler(closeCallback: () => void | Promise<void>): void {\n if (!parentPort) {\n return;\n }\n\n parentPort.on('message', (message: any) => {\n void (async () => {\n if (message.type === 'shutdown') {\n logger.info(`Worker thread ${threadId} shutting down...`, 'Worker');\n\n try {\n // Call the close callback and wait if it's async\n const result = closeCallback();\n if (result && typeof result.then === 'function') {\n await result;\n }\n\n // Give uWebSockets event loop time to fully cleanup\n await new Promise(resolve => setTimeout(resolve, 150));\n\n // Close parent port to signal we're ready to exit\n if (parentPort) {\n parentPort.close();\n }\n\n logger.info(`Worker thread ${threadId} cleanup complete`, 'Worker');\n\n // DON'T call process.exit() - let Node.js exit naturally\n // This allows the event loop to properly drain all uWebSockets handles\n // The worker will exit on its own once all handles are closed\n } catch (error) {\n logger.error('Error during worker shutdown', 'Worker', {\n error: error instanceof Error ? error.message : String(error),\n });\n // Still don't force exit - let it drain naturally\n }\n }\n })();\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"uws-worker-clustering.js","sourceRoot":"","sources":["../../../../src/core/http/utils/uws-worker-clustering.ts"],"names":[],"mappings":"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","sourcesContent":["// uWebSockets.js Worker Thread Clustering Implementation\n// Uses worker threads with acceptor pattern for maximum performance\n//\n// NOTE: When terminating, you may see \"uv_loop_close() while having open handles\" warnings\n// from Node.js. This is a known limitation when using uWebSockets.js with worker threads.\n// The uWS C++ addon maintains internal libuv poll handles that cannot be cleanly closed\n// through JavaScript APIs. These warnings are harmless - the process will still exit cleanly.\n// To suppress these warnings, you can run Node.js with: --no-warnings or\n// NODE_NO_WARNINGS=1 environment variable.\n\nimport type { Worker } from 'worker_threads';\nimport { requireBuiltin } from '../../utilities/builtin.js';\n\n// worker_threads is loaded on first use (core/utilities/builtin.ts).\nconst wt = () => requireBuiltin<typeof import('worker_threads')>('worker_threads');\nimport os from 'os';\nimport { createFrameworkLogger } from '../../logger/index.js';\nimport { loadNativeEngine } from '../../utilities/package-utils.js';\n\nconst logger = createFrameworkLogger('UWSClustering');\n\nexport interface UWSClusterConfig {\n workers?: number | 'auto';\n memoryPerWorkerGB?: number;\n port: number;\n host?: string;\n ssl?: {\n key_file_name?: string;\n cert_file_name?: string;\n passphrase?: string;\n };\n}\n\nexport class UWSWorkerClusterManager {\n private workers: Worker[] = [];\n private acceptorApp: any = null;\n private acceptorListenSocket: any = null;\n private uws: any = null;\n private config: UWSClusterConfig;\n private workerCount: number;\n private isShuttingDown = false;\n\n constructor(config: UWSClusterConfig) {\n this.config = config;\n this.workerCount = this.calculateWorkerCount();\n }\n\n private calculateWorkerCount(): number {\n let workerCount = this.config.workers || os.cpus().length;\n\n if (workerCount === 'auto') {\n const cpuCount = os.cpus().length;\n const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);\n\n let memoryPerWorkerGB = this.config.memoryPerWorkerGB;\n\n if (!memoryPerWorkerGB) {\n const headroomGB = 4;\n memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));\n }\n\n workerCount = Math.min(cpuCount, Math.floor(totalMemoryGB / memoryPerWorkerGB));\n\n logger.info(\n `Auto-calculated worker count: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,\n 'WorkerCalculation'\n );\n } else if (typeof workerCount === 'number') {\n logger.info(`Using specified worker count: ${workerCount}`, 'WorkerCalculation');\n }\n\n return workerCount;\n }\n\n async startAcceptorAndWorkers(_appFactory: () => any): Promise<void> {\n if (!wt().isMainThread) {\n throw new Error('startAcceptorAndWorkers can only be called from main thread');\n }\n\n try {\n // Worker clustering uses the uWS-shaped App/SSLApp + SO_REUSEPORT API,\n // so it needs uWebSockets.js specifically (not @morojs/engine).\n const engine = loadNativeEngine({ candidates: ['uWebSockets.js'] });\n if (!engine) {\n throw new Error('uWebSockets.js is not available for uWS worker clustering');\n }\n const uwsModule = engine.module;\n this.uws = uwsModule.default || uwsModule;\n\n // Create acceptor app - must match SSL config of worker apps\n const ssl = this.config.ssl;\n if (ssl && ssl.key_file_name && ssl.cert_file_name) {\n this.acceptorApp = this.uws.SSLApp({\n key_file_name: ssl.key_file_name,\n cert_file_name: ssl.cert_file_name,\n passphrase: ssl.passphrase,\n });\n logger.info('uWebSockets SSL acceptor app created', 'Acceptor');\n } else {\n this.acceptorApp = this.uws.App();\n logger.info('uWebSockets acceptor app created', 'Acceptor');\n }\n\n // Start listening on acceptor\n const port = this.config.port;\n this.acceptorApp.listen(port, (token: any) => {\n if (token) {\n this.acceptorListenSocket = token;\n logger.info(\n `Acceptor listening on port ${port} from thread ${wt().threadId}`,\n 'Acceptor'\n );\n } else {\n throw new Error(`Failed to listen on port ${port} from thread ${wt().threadId}`);\n }\n });\n\n // Spawn workers\n logger.info(`Starting ${this.workerCount} worker threads`, 'Workers');\n\n for (let i = 0; i < this.workerCount; i++) {\n await this.spawnWorker(i);\n }\n\n // Setup graceful shutdown\n this.setupGracefulShutdown();\n } catch (error) {\n logger.error('Failed to start acceptor and workers', 'Startup', {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n\n private async spawnWorker(index: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const scriptPath = process.argv[1];\n if (scriptPath === undefined) {\n reject(new Error('Cannot spawn worker: process.argv[1] (entry script) is unavailable'));\n return;\n }\n const worker = new (wt().Worker)(scriptPath, {\n workerData: {\n isUWSWorker: true,\n workerIndex: index,\n config: this.config,\n },\n argv: process.argv.slice(2),\n env: {\n ...process.env,\n UWS_WORKER_MODE: 'true',\n UWS_WORKER_INDEX: String(index),\n },\n });\n\n let descriptorReceived = false;\n\n worker.on('message', (message: any) => {\n if (message.type === 'descriptor' && message.descriptor) {\n if (!this.acceptorApp) {\n logger.error('Acceptor app not initialized', 'WorkerSpawn');\n reject(new Error('Acceptor app not initialized'));\n return;\n }\n\n try {\n this.acceptorApp.addChildAppDescriptor(message.descriptor);\n descriptorReceived = true;\n logger.info(\n `Worker ${index} (thread ${message.threadId}) registered with acceptor`,\n 'WorkerSpawn'\n );\n resolve();\n } catch (error) {\n logger.error('Failed to add child app descriptor', 'WorkerSpawn', {\n error: error instanceof Error ? error.message : String(error),\n });\n reject(error);\n }\n } else if (message.type === 'error') {\n logger.error(`Worker ${index} initialization error: ${message.error}`, 'WorkerSpawn');\n reject(new Error(message.error));\n }\n });\n\n worker.on('error', (error: Error) => {\n logger.error(`Worker ${index} error`, 'Worker', {\n error: error.message,\n });\n if (!descriptorReceived) {\n reject(error);\n }\n });\n\n worker.on('exit', (code: number) => {\n // Worker threads don't have exitedAfterDisconnect like cluster workers\n // Restart on any non-zero exit code\n if (code !== 0) {\n logger.warn(`Worker ${index} died unexpectedly (code: ${code}). Restarting...`, 'Worker');\n\n // Remove from workers array\n const workerIdx = this.workers.indexOf(worker);\n if (workerIdx > -1) {\n this.workers.splice(workerIdx, 1);\n }\n\n // Restart worker\n this.spawnWorker(index).catch(error => {\n logger.error('Failed to restart worker', 'Worker', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }\n });\n\n this.workers.push(worker);\n\n // Timeout for descriptor reception\n setTimeout(() => {\n if (!descriptorReceived) {\n logger.error(`Worker ${index} failed to send descriptor within timeout`, 'WorkerSpawn');\n void worker.terminate();\n reject(new Error(`Worker ${index} initialization timeout`));\n }\n }, 10000); // 10 second timeout\n });\n }\n\n private setupGracefulShutdown(): void {\n const gracefulShutdown = async () => {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n\n logger.info('Gracefully shutting down worker cluster...', 'Shutdown');\n\n // Close acceptor listen socket first\n if (this.acceptorListenSocket && this.uws) {\n try {\n this.uws.us_listen_socket_close(this.acceptorListenSocket);\n this.acceptorListenSocket = null;\n logger.info('Acceptor listen socket closed', 'Shutdown');\n } catch (error) {\n logger.error('Error closing acceptor socket', 'Shutdown', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Send shutdown message to all workers and wait for them to close\n const shutdownPromises = this.workers.map((worker, index) => {\n return new Promise<void>(resolve => {\n let workerExited = false;\n\n const timeout = setTimeout(() => {\n if (!workerExited) {\n logger.info(\n `Worker ${index} taking too long to exit, terminating forcefully`,\n 'Shutdown'\n );\n try {\n // Terminate is the last resort for stuck workers\n void worker.terminate();\n } catch {\n logger.error(`Error terminating worker ${index}`, 'Shutdown');\n }\n }\n resolve();\n }, 2000); // Give 2 seconds for graceful exit\n\n worker.once('exit', code => {\n workerExited = true;\n clearTimeout(timeout);\n if (code === 0) {\n logger.info(`Worker ${index} exited cleanly`, 'Shutdown');\n } else if (code === 1) {\n logger.info(`Worker ${index} terminated (exit code ${code})`, 'Shutdown');\n } else {\n logger.warn(`Worker ${index} exited with unexpected code ${code}`, 'Shutdown');\n }\n resolve();\n });\n\n // Send shutdown message\n try {\n worker.postMessage({ type: 'shutdown' });\n } catch {\n logger.error(`Failed to send shutdown message to worker ${index}`, 'Shutdown');\n clearTimeout(timeout);\n try {\n void worker.terminate();\n } catch {\n // Ignore termination errors\n }\n resolve();\n }\n });\n });\n\n await Promise.all(shutdownPromises);\n logger.info('All workers shut down', 'Shutdown');\n\n // Clear acceptor app reference to allow uWebSockets to cleanup\n if (this.acceptorApp) {\n this.acceptorApp = null;\n }\n\n // Give uWebSockets event loop time to fully cleanup before exiting\n await new Promise(resolve => setTimeout(resolve, 200));\n\n logger.info('Shutdown complete', 'Shutdown');\n process.exit(0);\n };\n\n process.on('SIGINT', () => void gracefulShutdown());\n process.on('SIGTERM', () => void gracefulShutdown());\n }\n\n static isUWSWorker(): boolean {\n return !wt().isMainThread && process.env.UWS_WORKER_MODE === 'true';\n }\n\n static async sendDescriptorToAcceptor(app: any): Promise<void> {\n const { isMainThread, parentPort, threadId } = wt();\n if (isMainThread || !parentPort) {\n throw new Error('sendDescriptorToAcceptor can only be called from worker thread');\n }\n\n try {\n const descriptor = app.getDescriptor();\n\n parentPort.postMessage({\n type: 'descriptor',\n descriptor,\n threadId,\n });\n\n logger.info(`Worker thread ${threadId} sent descriptor to acceptor`, 'Worker');\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Failed to send descriptor to acceptor', 'Worker', { error: errorMessage });\n\n if (parentPort) {\n parentPort.postMessage({\n type: 'error',\n error: errorMessage,\n });\n }\n\n throw error;\n }\n }\n\n static setupWorkerShutdownHandler(closeCallback: () => void | Promise<void>): void {\n const { parentPort, threadId } = wt();\n if (!parentPort) {\n return;\n }\n\n parentPort.on('message', (message: any) => {\n void (async () => {\n if (message.type === 'shutdown') {\n logger.info(`Worker thread ${threadId} shutting down...`, 'Worker');\n\n try {\n // Call the close callback and wait if it's async\n const result = closeCallback();\n if (result && typeof result.then === 'function') {\n await result;\n }\n\n // Give uWebSockets event loop time to fully cleanup\n await new Promise(resolve => setTimeout(resolve, 150));\n\n // Close parent port to signal we're ready to exit\n if (parentPort) {\n parentPort.close();\n }\n\n logger.info(`Worker thread ${threadId} cleanup complete`, 'Worker');\n\n // DON'T call process.exit() - let Node.js exit naturally\n // This allows the event loop to properly drain all uWebSockets handles\n // The worker will exit on its own once all handles are closed\n } catch (error) {\n logger.error('Error during worker shutdown', 'Worker', {\n error: error instanceof Error ? error.message : String(error),\n });\n // Still don't force exit - let it drain naturally\n }\n }\n })();\n });\n }\n}\n"]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// uWebSockets.js HTTP Server Implementation for Moro Framework
|
|
2
2
|
// Provides high-performance HTTP and WebSocket server using uWebSockets.js
|
|
3
|
-
import cluster from 'cluster';
|
|
4
3
|
import { randomUUID } from 'crypto';
|
|
5
4
|
import { createFrameworkLogger } from '../logger/index.js';
|
|
6
5
|
import { loadNativeEngine, getNativeEngineLoadErrors } from '../utilities/package-utils.js';
|
|
@@ -1744,7 +1743,9 @@ export class UWebSocketsHttpServer {
|
|
|
1744
1743
|
this.logger.warn(`Native fast-path route registration failed, falling back to catchall: ${error instanceof Error ? error.message : String(error)}`, 'NativeRoutes');
|
|
1745
1744
|
}
|
|
1746
1745
|
// Check if we're in a cluster environment
|
|
1747
|
-
|
|
1746
|
+
// node:cluster's own definition of a worker, without loading node:cluster
|
|
1747
|
+
// (which drags child_process/dgram/net into every uWS app).
|
|
1748
|
+
const isClusterWorker = process.env.NODE_UNIQUE_ID !== undefined;
|
|
1748
1749
|
// uWebSockets.js automatically enables SO_REUSEPORT when in cluster mode
|
|
1749
1750
|
// Do NOT pass listenOptions in cluster mode - let uWS handle it automatically
|
|
1750
1751
|
// In non-cluster mode, we don't need SO_REUSEPORT
|