@eggjs/cluster 4.0.0-beta.19 → 4.0.0-beta.20

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.
Files changed (33) hide show
  1. package/dist/agent-griHEaCW.js +246 -0
  2. package/dist/agent_worker.js +2 -2
  3. package/dist/app-5Was1vub.js +315 -0
  4. package/dist/app_worker.js +2 -2
  5. package/dist/index.d.ts +440 -5
  6. package/dist/index.js +692 -4
  7. package/dist/{utils/terminate.js → terminate-w3g0oQgq.js} +10 -1
  8. package/package.json +5 -5
  9. package/dist/dirname.js +0 -11
  10. package/dist/error/ClusterAgentWorkerError.d.ts +0 -13
  11. package/dist/error/ClusterAgentWorkerError.js +0 -22
  12. package/dist/error/ClusterWorkerExceptionError.d.ts +0 -10
  13. package/dist/error/ClusterWorkerExceptionError.js +0 -17
  14. package/dist/master.d.ts +0 -96
  15. package/dist/master.js +0 -426
  16. package/dist/utils/messenger.d.ts +0 -96
  17. package/dist/utils/messenger.js +0 -144
  18. package/dist/utils/mode/base/agent.d.ts +0 -45
  19. package/dist/utils/mode/base/agent.js +0 -63
  20. package/dist/utils/mode/base/app.d.ts +0 -56
  21. package/dist/utils/mode/base/app.js +0 -77
  22. package/dist/utils/mode/impl/process/agent.d.ts +0 -22
  23. package/dist/utils/mode/impl/process/agent.js +0 -93
  24. package/dist/utils/mode/impl/process/app.d.ts +0 -12
  25. package/dist/utils/mode/impl/process/app.js +0 -117
  26. package/dist/utils/mode/impl/worker_threads/agent.d.ts +0 -22
  27. package/dist/utils/mode/impl/worker_threads/agent.js +0 -79
  28. package/dist/utils/mode/impl/worker_threads/app.d.ts +0 -13
  29. package/dist/utils/mode/impl/worker_threads/app.js +0 -128
  30. package/dist/utils/options.d.ts +0 -83
  31. package/dist/utils/options.js +0 -56
  32. package/dist/utils/worker_manager.d.ts +0 -32
  33. package/dist/utils/worker_manager.js +0 -68
package/dist/index.js CHANGED
@@ -1,8 +1,696 @@
1
- import "./utils/options.js";
2
- import { ClusterAgentWorkerError } from "./error/ClusterAgentWorkerError.js";
3
- import { ClusterWorkerExceptionError } from "./error/ClusterWorkerExceptionError.js";
4
- import { Master } from "./master.js";
1
+ import "./terminate-w3g0oQgq.js";
2
+ import { AgentProcessUtils, AgentThreadUtils, ClusterAgentWorkerError } from "./agent-griHEaCW.js";
3
+ import { AppProcessUtils, AppThreadUtils } from "./app-5Was1vub.js";
4
+ import os from "node:os";
5
+ import v8 from "node:v8";
6
+ import util, { debuglog } from "node:util";
7
+ import path from "node:path";
8
+ import fs from "node:fs";
9
+ import net from "node:net";
10
+ import { ReadyEventEmitter } from "get-ready";
11
+ import { detectPort } from "detect-port";
12
+ import { reload } from "cluster-reload";
13
+ import { EggConsoleLogger } from "egg-logger";
14
+ import { readJSONSync } from "utility";
15
+ import terminalLink from "terminal-link";
16
+ import assert from "node:assert";
17
+ import { getFrameworkPath, importModule } from "@eggjs/utils";
18
+ import { EventEmitter } from "node:events";
19
+ import workerThreads from "node:worker_threads";
5
20
 
21
+ //#region src/utils/options.ts
22
+ const debug$2 = debuglog("egg/cluster/utils/options");
23
+ async function parseOptions(options) {
24
+ options = {
25
+ baseDir: process.cwd(),
26
+ port: options?.https ? 8443 : void 0,
27
+ startMode: "process",
28
+ env: process.env.EGG_SERVER_ENV,
29
+ ...options
30
+ };
31
+ const pkgPath = path.join(options.baseDir, "package.json");
32
+ assert(fs.existsSync(pkgPath), `${pkgPath} should exist`);
33
+ options.framework = getFrameworkPath({
34
+ baseDir: options.baseDir,
35
+ framework: options.framework ?? options.customEgg
36
+ });
37
+ debug$2("[parseOptions] %o", options);
38
+ const egg = await importModule(options.framework, { paths: [options.baseDir] });
39
+ assert(egg.Application, `should define Application in ${options.framework}`);
40
+ assert(egg.Agent, `should define Agent in ${options.framework}`);
41
+ if (options.https === true) {
42
+ console.warn("[@eggjs/cluster:deprecated] [master] Please use `https: { key, cert, ca }` instead of `https: true`");
43
+ options.https = {
44
+ key: options.key,
45
+ cert: options.cert
46
+ };
47
+ }
48
+ if (options.https) {
49
+ assert(options.https.key, "options.https.key should exists");
50
+ if (typeof options.https.key === "string") assert(fs.existsSync(options.https.key), "options.https.key file should exists");
51
+ assert(options.https.cert, "options.https.cert should exists");
52
+ if (typeof options.https.cert === "string") assert(fs.existsSync(options.https.cert), "options.https.cert file should exists");
53
+ if (typeof options.https.ca === "string") assert(fs.existsSync(options.https.ca), "options.https.ca file should exists");
54
+ }
55
+ if (options.port && typeof options.port === "string") options.port = parseInt(options.port);
56
+ if (options.port === null) options.port = void 0;
57
+ if (options.workers && typeof options.workers === "string") options.workers = parseInt(options.workers);
58
+ if (!options.workers) options.workers = os.cpus().length;
59
+ if (options.require) {
60
+ if (typeof options.require === "string") options.require = [options.require];
61
+ }
62
+ if (process.env.NODE_ENV === "production") process.env.NO_DEPRECATION = "*";
63
+ const isDebug = process.execArgv.some((argv) => argv.includes("--debug") || argv.includes("--inspect"));
64
+ if (isDebug) options.isDebug = isDebug;
65
+ return options;
66
+ }
67
+
68
+ //#endregion
69
+ //#region src/utils/worker_manager.ts
70
+ var WorkerManager = class extends EventEmitter {
71
+ agent;
72
+ workers = /* @__PURE__ */ new Map();
73
+ exception = 0;
74
+ timer;
75
+ constructor() {
76
+ super();
77
+ this.agent = null;
78
+ }
79
+ getWorkers() {
80
+ return Array.from(this.workers.keys());
81
+ }
82
+ setAgent(agent) {
83
+ this.agent = agent;
84
+ }
85
+ getAgent() {
86
+ return this.agent;
87
+ }
88
+ deleteAgent() {
89
+ this.agent = null;
90
+ }
91
+ setWorker(worker) {
92
+ this.workers.set(worker.workerId, worker);
93
+ }
94
+ getWorker(workerId) {
95
+ return this.workers.get(workerId);
96
+ }
97
+ deleteWorker(workerId) {
98
+ this.workers.delete(workerId);
99
+ }
100
+ listWorkerIds() {
101
+ return Array.from(this.workers.keys());
102
+ }
103
+ listWorkers() {
104
+ return Array.from(this.workers.values());
105
+ }
106
+ getListeningWorkerIds() {
107
+ const keys = [];
108
+ for (const [id, worker] of this.workers.entries()) if (worker.state === "listening") keys.push(id);
109
+ return keys;
110
+ }
111
+ count() {
112
+ return {
113
+ agent: this.agent?.status === "started" ? 1 : 0,
114
+ worker: this.listWorkerIds().length
115
+ };
116
+ }
117
+ startCheck() {
118
+ this.timer = setInterval(() => {
119
+ const count = this.count();
120
+ if (count.agent > 0 && count.worker > 0) {
121
+ this.exception = 0;
122
+ return;
123
+ }
124
+ this.exception++;
125
+ if (this.exception >= 3) {
126
+ this.emit("exception", count);
127
+ clearInterval(this.timer);
128
+ }
129
+ }, 1e4);
130
+ }
131
+ };
132
+
133
+ //#endregion
134
+ //#region src/utils/messenger.ts
135
+ const debug$1 = debuglog("egg/cluster/messenger");
136
+ /**
137
+ * master messenger, provide communication between parent, master, agent and app.
138
+ *
139
+ * ┌────────┐
140
+ * │ parent │
141
+ * /└────────┘\
142
+ * / | \
143
+ * / ┌────────┐ \
144
+ * / │ master │ \
145
+ * / └────────┘ \
146
+ * / / \ \
147
+ * ┌───────┐ ┌───────┐
148
+ * │ agent │ ------- │ app │
149
+ * └───────┘ └───────┘
150
+ *
151
+ *
152
+ * in app worker
153
+ *
154
+ * ```js
155
+ * process.send({
156
+ * action: 'xxx',
157
+ * data: '',
158
+ * to: 'agent/master/parent', // default to agent
159
+ * });
160
+ * ```
161
+ *
162
+ * in agent worker
163
+ *
164
+ * ```js
165
+ * process.send({
166
+ * action: 'xxx',
167
+ * data: '',
168
+ * to: 'app/master/parent', // default to app
169
+ * });
170
+ * ```
171
+ *
172
+ * in parent
173
+ *
174
+ * ```js
175
+ * process.send({
176
+ * action: 'xxx',
177
+ * data: '',
178
+ * to: 'app/agent/master', // default to master
179
+ * });
180
+ * ```
181
+ */
182
+ var Messenger = class {
183
+ #master;
184
+ #workerManager;
185
+ #hasParent;
186
+ constructor(master, workerManager) {
187
+ this.#master = master;
188
+ this.#workerManager = workerManager;
189
+ this.#hasParent = !!workerThreads.parentPort || !!process.send;
190
+ process.on("message", (msg) => {
191
+ msg.from = "parent";
192
+ this.send(msg);
193
+ });
194
+ process.once("disconnect", () => {
195
+ this.#hasParent = false;
196
+ });
197
+ }
198
+ /**
199
+ * send message
200
+ * @param {Object} data message body
201
+ * - {String} from from who
202
+ * - {String} to to who
203
+ */
204
+ send(data) {
205
+ if (!data.from) data.from = "master";
206
+ const receiverWorkerId = data.receiverWorkerId ?? data.receiverPid;
207
+ if (receiverWorkerId) if (receiverWorkerId === String(process.pid)) data.to = "master";
208
+ else if (receiverWorkerId === String(this.#workerManager.getAgent().workerId)) data.to = "agent";
209
+ else data.to = "app";
210
+ if (!data.to) {
211
+ if (data.from === "agent") data.to = "app";
212
+ if (data.from === "app") data.to = "agent";
213
+ if (data.from === "parent") data.to = "master";
214
+ }
215
+ if (data.to === "master") {
216
+ debug$1("%s -> master, data: %j", data.from, data);
217
+ this.sendToMaster(data);
218
+ return;
219
+ }
220
+ if (data.to === "parent") {
221
+ debug$1("%s -> parent, data: %j", data.from, data);
222
+ this.sendToParent(data);
223
+ return;
224
+ }
225
+ if (data.to === "app") {
226
+ debug$1("%s -> %s, data: %j", data.from, data.to, data);
227
+ this.sendToAppWorker(data);
228
+ return;
229
+ }
230
+ if (data.to === "agent") {
231
+ debug$1("%s -> %s, data: %j", data.from, data.to, data);
232
+ this.sendToAgentWorker(data);
233
+ return;
234
+ }
235
+ }
236
+ /**
237
+ * send message to master self
238
+ * @param {Object} data message body
239
+ */
240
+ sendToMaster(data) {
241
+ this.#master.emit(data.action, data.data);
242
+ }
243
+ /**
244
+ * send message to parent process
245
+ * @param {Object} data message body
246
+ */
247
+ sendToParent(data) {
248
+ if (!this.#hasParent) return;
249
+ process.send(data);
250
+ }
251
+ /**
252
+ * send message to app worker
253
+ * @param {Object} data message body
254
+ */
255
+ sendToAppWorker(data) {
256
+ for (const worker of this.#workerManager.listWorkers()) {
257
+ if (worker.state === "disconnected") continue;
258
+ const receiverWorkerId = data.receiverWorkerId ?? data.receiverPid;
259
+ if (receiverWorkerId && receiverWorkerId !== String(worker.workerId)) continue;
260
+ worker.send(data);
261
+ }
262
+ }
263
+ /**
264
+ * send message to agent worker
265
+ * @param {Object} data message body
266
+ */
267
+ sendToAgentWorker(data) {
268
+ const agent = this.#workerManager.getAgent();
269
+ if (agent) agent.send(data);
270
+ }
271
+ };
272
+
273
+ //#endregion
274
+ //#region src/error/ClusterWorkerExceptionError.ts
275
+ var ClusterWorkerExceptionError = class extends Error {
276
+ count;
277
+ constructor(agent, worker) {
278
+ const message = `[master] ${agent} agent and ${worker} worker(s) alive, exit to avoid unknown state`;
279
+ super(message);
280
+ this.name = this.constructor.name;
281
+ this.count = {
282
+ agent,
283
+ worker
284
+ };
285
+ Error.captureStackTrace(this, this.constructor);
286
+ }
287
+ };
288
+
289
+ //#endregion
290
+ //#region src/master.ts
291
+ const debug = debuglog("egg/cluster/master");
292
+ var Master = class extends ReadyEventEmitter {
293
+ options;
294
+ isStarted = false;
295
+ workerManager;
296
+ messenger;
297
+ isProduction;
298
+ agentWorkerIndex = 0;
299
+ closed = false;
300
+ logger;
301
+ agentWorker;
302
+ appWorker;
303
+ #logMethod;
304
+ #realPort;
305
+ #protocol;
306
+ #appAddress;
307
+ constructor(options) {
308
+ super();
309
+ this.#start(options).catch((err) => {
310
+ this.ready(err);
311
+ });
312
+ }
313
+ async #start(options) {
314
+ this.options = await parseOptions(options);
315
+ this.workerManager = new WorkerManager();
316
+ this.messenger = new Messenger(this, this.workerManager);
317
+ this.isProduction = isProduction(this.options);
318
+ this.#realPort = this.options.port;
319
+ this.#protocol = this.options.https ? "https" : "http";
320
+ this.isStarted = false;
321
+ this.logger = new EggConsoleLogger({ level: process.env.EGG_MASTER_LOGGER_LEVEL ?? "INFO" });
322
+ this.#logMethod = "info";
323
+ if (this.options.env === "local" || process.env.NODE_ENV === "development") this.#logMethod = "debug";
324
+ const frameworkPath = this.options.framework;
325
+ const frameworkPkg = readJSONSync(path.join(frameworkPath, "package.json"));
326
+ if (this.options.startMode === "worker_threads") this.startByWorkerThreads();
327
+ else this.startByProcess();
328
+ this.log(`[master] =================== ${frameworkPkg.name} start 🥚🥚🥚🥚 =====================`);
329
+ this.logger.info(`[master] node version ${process.version}`);
330
+ /* istanbul ignore next */
331
+ if ("alinode" in process) this.logger.info(`[master] alinode version ${process.alinode}`);
332
+ this.logger.info(`[master] ${frameworkPkg.name} version ${frameworkPkg.version}`);
333
+ if (this.isProduction) this.logger.info("[master] start with options:%s%s", os.EOL, JSON.stringify(this.options, null, 2));
334
+ else this.log("[master] start with options: %j", this.options);
335
+ this.log("[master] start with env: isProduction: %s, EGG_SERVER_ENV: %s, NODE_ENV: %s", this.isProduction, this.options.env, process.env.NODE_ENV);
336
+ const startTime = Date.now();
337
+ this.ready(() => {
338
+ this.isStarted = true;
339
+ const stickyMsg = this.options.sticky ? " with STICKY MODE!" : "";
340
+ const startedURL = terminalLink(this.#appAddress, this.#appAddress, { fallback: false });
341
+ this.logger.info("[master] %s started on %s (%sms)%s", frameworkPkg.name, startedURL, Date.now() - startTime, stickyMsg);
342
+ if (this.options.debugPort) {
343
+ const url = getAddress({
344
+ port: this.options.debugPort,
345
+ protocol: "http"
346
+ });
347
+ const debugPortURL = terminalLink(url, url, { fallback: false });
348
+ this.logger.info("[master] %s started debug port on %s", frameworkPkg.name, debugPortURL);
349
+ }
350
+ const action = "egg-ready";
351
+ this.messenger.send({
352
+ action,
353
+ to: "parent",
354
+ data: {
355
+ port: this.#realPort,
356
+ debugPort: this.options.debugPort,
357
+ address: this.#appAddress,
358
+ protocol: this.#protocol
359
+ }
360
+ });
361
+ this.messenger.send({
362
+ action,
363
+ to: "app",
364
+ data: this.options
365
+ });
366
+ this.messenger.send({
367
+ action,
368
+ to: "agent",
369
+ data: this.options
370
+ });
371
+ if (this.isProduction) this.workerManager.startCheck();
372
+ });
373
+ this.on("agent-exit", this.onAgentExit.bind(this));
374
+ this.on("agent-start", this.onAgentStart.bind(this));
375
+ this.on("app-exit", this.onAppExit.bind(this));
376
+ this.on("app-start", this.onAppStart.bind(this));
377
+ this.on("reload-worker", this.onReload.bind(this));
378
+ this.once("agent-start", this.forkAppWorkers.bind(this));
379
+ this.on("realport", ({ port, protocol }) => {
380
+ if (port) this.#realPort = port;
381
+ if (protocol) this.#protocol = protocol;
382
+ });
383
+ process.once("SIGINT", this.onSignal.bind(this, "SIGINT"));
384
+ process.once("SIGQUIT", this.onSignal.bind(this, "SIGQUIT"));
385
+ process.once("SIGTERM", this.onSignal.bind(this, "SIGTERM"));
386
+ process.once("exit", this.onExit.bind(this));
387
+ if (this.options.pidFile) {
388
+ fs.mkdirSync(path.dirname(this.options.pidFile), { recursive: true });
389
+ fs.writeFileSync(this.options.pidFile, process.pid.toString(), "utf-8");
390
+ }
391
+ this.detectPorts().then(() => {
392
+ this.forkAgentWorker();
393
+ });
394
+ this.workerManager.on("exception", (count) => {
395
+ const err = new ClusterWorkerExceptionError(count.agent, count.worker);
396
+ this.logger.error(err);
397
+ process.exit(1);
398
+ });
399
+ }
400
+ startByProcess() {
401
+ this.agentWorker = new AgentProcessUtils(this.options, {
402
+ log: this.log.bind(this),
403
+ logger: this.logger,
404
+ messenger: this.messenger
405
+ });
406
+ this.appWorker = new AppProcessUtils(this.options, {
407
+ log: this.log.bind(this),
408
+ logger: this.logger,
409
+ messenger: this.messenger,
410
+ isProduction: this.isProduction
411
+ });
412
+ }
413
+ startByWorkerThreads() {
414
+ this.agentWorker = new AgentThreadUtils(this.options, {
415
+ log: this.log.bind(this),
416
+ logger: this.logger,
417
+ messenger: this.messenger
418
+ });
419
+ this.appWorker = new AppThreadUtils(this.options, {
420
+ log: this.log.bind(this),
421
+ logger: this.logger,
422
+ messenger: this.messenger,
423
+ isProduction: this.isProduction
424
+ });
425
+ }
426
+ async detectPorts() {
427
+ try {
428
+ const clusterPort = await detectPort();
429
+ this.options.clusterPort = clusterPort;
430
+ if (this.options.sticky) {
431
+ const stickyWorkerPort = await detectPort();
432
+ this.options.stickyWorkerPort = stickyWorkerPort;
433
+ }
434
+ } catch (err) {
435
+ this.logger.error(err);
436
+ process.exit(1);
437
+ }
438
+ }
439
+ log(msg, ...args) {
440
+ this.logger[this.#logMethod](msg, ...args);
441
+ }
442
+ startMasterSocketServer(cb) {
443
+ net.createServer({ pauseOnConnect: true }, (connection) => {
444
+ /* istanbul ignore next */
445
+ if (!connection.remoteAddress) connection.destroy();
446
+ else this.stickyWorker(connection.remoteAddress).instance.send("sticky-session:connection", connection);
447
+ }).listen(this.#realPort, cb);
448
+ }
449
+ stickyWorker(ip) {
450
+ const workerNumbers = this.options.workers;
451
+ const ws = this.workerManager.listWorkerIds();
452
+ let s = "";
453
+ for (let i = 0; i < ip.length; i++) if (!isNaN(parseInt(ip[i]))) s += ip[i];
454
+ const pid = ws[Number(s) % workerNumbers];
455
+ return this.workerManager.getWorker(pid);
456
+ }
457
+ forkAgentWorker() {
458
+ this.agentWorker.on("agent_forked", (agent) => {
459
+ this.workerManager.setAgent(agent);
460
+ });
461
+ this.agentWorker.fork();
462
+ }
463
+ forkAppWorkers() {
464
+ this.appWorker.on("worker_forked", (worker) => {
465
+ this.workerManager.setWorker(worker);
466
+ });
467
+ this.appWorker.fork();
468
+ }
469
+ /**
470
+ * close agent worker, App Worker will closed by cluster
471
+ *
472
+ * https://www.exratione.com/2013/05/die-child-process-die/
473
+ * make sure Agent Worker exit before master exit
474
+ *
475
+ * @param {number} timeout - kill agent timeout
476
+ * @return {Promise} -
477
+ */
478
+ async killAgentWorker(timeout) {
479
+ await this.agentWorker.kill(timeout);
480
+ }
481
+ async killAppWorkers(timeout) {
482
+ await this.appWorker.kill(timeout);
483
+ }
484
+ /**
485
+ * Agent Worker exit handler
486
+ * Will exit during startup, and refork during running.
487
+ */
488
+ onAgentExit(data) {
489
+ if (this.closed) return;
490
+ this.messenger.send({
491
+ action: "egg-pids",
492
+ to: "app",
493
+ data: []
494
+ });
495
+ const agentWorker = this.agentWorker;
496
+ this.workerManager.deleteAgent();
497
+ const err = new Error(util.format("[master] agent_worker#%s:%s died (code: %s, signal: %s)", agentWorker.instance.id, agentWorker.instance.workerId, data.code, data.signal));
498
+ err.name = "AgentWorkerDiedError";
499
+ this.logger.error(err);
500
+ agentWorker.clean();
501
+ if (this.isStarted) {
502
+ this.log("[master] try to start a new agent_worker after 1s ...");
503
+ setTimeout(() => {
504
+ this.logger.info("[master] new agent_worker starting...");
505
+ this.forkAgentWorker();
506
+ }, 1e3);
507
+ this.messenger.send({
508
+ action: "agent-worker-died",
509
+ to: "parent"
510
+ });
511
+ } else {
512
+ this.logger.error("[master] agent_worker#%s:%s start fail, exiting with code:1", agentWorker.instance.id, agentWorker.instance.workerId);
513
+ process.exit(1);
514
+ }
515
+ }
516
+ onAgentStart() {
517
+ this.agentWorker.instance.status = "started";
518
+ if (this.appWorker.isAllWorkerStarted) this.messenger.send({
519
+ action: "egg-ready",
520
+ to: "agent",
521
+ data: this.options
522
+ });
523
+ this.messenger.send({
524
+ action: "egg-pids",
525
+ to: "app",
526
+ data: [this.agentWorker.instance.workerId]
527
+ });
528
+ if (this.isStarted) this.messenger.send({
529
+ action: "egg-pids",
530
+ to: "agent",
531
+ data: this.workerManager.getListeningWorkerIds()
532
+ });
533
+ this.messenger.send({
534
+ action: "agent-start",
535
+ to: "app"
536
+ });
537
+ this.logger.info("[master] agent_worker#%s:%s started (%sms)", this.agentWorker.instance.id, this.agentWorker.instance.workerId, Date.now() - this.agentWorker.startTime);
538
+ }
539
+ /**
540
+ * App Worker exit handler
541
+ */
542
+ onAppExit(data) {
543
+ if (this.closed) return;
544
+ const worker = this.workerManager.getWorker(data.workerId);
545
+ if (!worker.isDevReload) {
546
+ const signal = data.signal;
547
+ const message = util.format("[master] app_worker#%s:%s died (code: %s, signal: %s, suicide: %s, state: %s), current workers: %j", worker.id, worker.workerId, worker.exitCode, signal, worker.exitedAfterDisconnect, worker.state, this.workerManager.listWorkerIds());
548
+ if (this.options.isDebug && signal === "SIGKILL") {
549
+ this.logger.error(message);
550
+ this.logger.error("[master] worker kill by debugger, exiting...");
551
+ setTimeout(() => this.close(), 10);
552
+ } else {
553
+ const err = new Error(message);
554
+ err.name = "AppWorkerDiedError";
555
+ this.logger.error(err);
556
+ }
557
+ }
558
+ worker.clean();
559
+ this.workerManager.deleteWorker(data.workerId);
560
+ this.messenger.send({
561
+ action: "egg-pids",
562
+ to: "agent",
563
+ data: this.workerManager.getListeningWorkerIds()
564
+ });
565
+ if (this.appWorker.isAllWorkerStarted) this.messenger.send({
566
+ action: "app-worker-died",
567
+ to: "parent"
568
+ });
569
+ else {
570
+ this.logger.error("[master] app_worker#%s:%s start fail, exiting with code:1", worker.id, worker.workerId);
571
+ process.exit(1);
572
+ }
573
+ }
574
+ /**
575
+ * after app worker
576
+ */
577
+ onAppStart(data) {
578
+ const worker = this.workerManager.getWorker(data.workerId);
579
+ debug("got app_worker#%s:%s app-start event, data: %j", worker.id, worker.workerId, data);
580
+ const address = data.address;
581
+ if (this.options.sticky) {
582
+ if (String(address.port) !== String(this.options.stickyWorkerPort)) return;
583
+ } else if (this.options.startMode !== "worker_threads" && !isUnixSock(address) && String(address.port) !== String(this.#realPort)) return;
584
+ worker.state = "listening";
585
+ this.messenger.send({
586
+ action: "egg-pids",
587
+ to: "agent",
588
+ data: this.workerManager.getListeningWorkerIds()
589
+ });
590
+ this.messenger.send({
591
+ action: "egg-pids",
592
+ to: "app",
593
+ data: [this.agentWorker.instance.workerId],
594
+ receiverWorkerId: String(worker.workerId),
595
+ receiverPid: String(worker.workerId)
596
+ });
597
+ this.appWorker.startSuccessCount++;
598
+ const remain = this.appWorker.isAllWorkerStarted ? 0 : this.options.workers - this.appWorker.startSuccessCount;
599
+ this.log("[master] app_worker#%s:%s started at %s, remain %s (%sms)", worker.id, worker.workerId, address.port, remain, Date.now() - this.appWorker.startTime);
600
+ if (this.appWorker.isAllWorkerStarted) this.messenger.send({
601
+ action: "egg-ready",
602
+ to: "app",
603
+ data: this.options
604
+ });
605
+ if (this.appWorker.isAllWorkerStarted) worker.disableRefork = false;
606
+ if (this.appWorker.isAllWorkerStarted || this.appWorker.startSuccessCount < this.options.workers) return;
607
+ this.appWorker.isAllWorkerStarted = true;
608
+ for (const worker$1 of this.workerManager.listWorkers()) worker$1.disableRefork = false;
609
+ address.protocol = this.#protocol;
610
+ address.port = this.options.sticky ? this.#realPort : address.port;
611
+ this.#appAddress = getAddress(address);
612
+ if (this.options.sticky) this.startMasterSocketServer((err) => {
613
+ if (err) return this.ready(err);
614
+ this.ready(true);
615
+ });
616
+ else this.ready(true);
617
+ }
618
+ /**
619
+ * master exit handler
620
+ */
621
+ onExit(code) {
622
+ if (this.options.pidFile && fs.existsSync(this.options.pidFile)) try {
623
+ fs.unlinkSync(this.options.pidFile);
624
+ } catch (err) {
625
+ /* istanbul ignore next */
626
+ this.logger.error("[master] delete pidFile %s fail with %s", this.options.pidFile, err.message);
627
+ }
628
+ const level = code === 0 ? "info" : "error";
629
+ this.logger[level]("[master] exit with code:%s", code);
630
+ }
631
+ onSignal(signal) {
632
+ if (this.closed) return;
633
+ this.logger.info("[master] master is killed by signal %s, closing", signal);
634
+ const { used_heap_size, heap_size_limit } = v8.getHeapStatistics();
635
+ this.logger.info("[master] system memory: total %s, free %s", os.totalmem(), os.freemem());
636
+ this.logger.info("[master] process info: heap_limit %s, heap_used %s", heap_size_limit, used_heap_size);
637
+ this.close();
638
+ }
639
+ /**
640
+ * reload workers, for develop purpose
641
+ */
642
+ onReload() {
643
+ this.log("[master] reload %s workers...", this.options.workers);
644
+ for (const worker of this.workerManager.listWorkers()) worker.isDevReload = true;
645
+ reload(this.options.workers);
646
+ }
647
+ async close() {
648
+ this.closed = true;
649
+ try {
650
+ await this._doClose();
651
+ this.log("[master] close done, exiting with code:0");
652
+ process.exit(0);
653
+ } catch (e) {
654
+ this.logger.error("[master] close with error: ", e);
655
+ process.exit(1);
656
+ }
657
+ }
658
+ async _doClose() {
659
+ const legacyTimeout = process.env.EGG_MASTER_CLOSE_TIMEOUT || "5000";
660
+ const appTimeout = parseInt(process.env.EGG_APP_CLOSE_TIMEOUT || legacyTimeout);
661
+ const agentTimeout = parseInt(process.env.EGG_AGENT_CLOSE_TIMEOUT || legacyTimeout);
662
+ this.logger.info("[master] send kill SIGTERM to app workers, will exit with code:0 after %sms", appTimeout);
663
+ this.logger.info("[master] wait %sms", appTimeout);
664
+ try {
665
+ await this.killAppWorkers(appTimeout);
666
+ } catch (e) {
667
+ this.logger.error("[master] app workers exit error: ", e);
668
+ }
669
+ this.logger.info("[master] send kill SIGTERM to agent worker, will exit with code:0 after %sms", agentTimeout);
670
+ this.logger.info("[master] wait %sms", agentTimeout);
671
+ try {
672
+ await this.killAgentWorker(agentTimeout);
673
+ } catch (e) {
674
+ this.logger.error("[master] agent worker exit error: ", e);
675
+ }
676
+ }
677
+ };
678
+ function isProduction(options) {
679
+ if (options.env) return options.env !== "local" && options.env !== "unittest";
680
+ return process.env.NODE_ENV === "production";
681
+ }
682
+ function getAddress({ addressType, address, port, protocol }) {
683
+ if (addressType === -1) return address;
684
+ if (address === "::") address = "";
685
+ if (!address && process.env.HOST && process.env.HOST !== "0.0.0.0") address = process.env.HOST;
686
+ if (!address) address = "127.0.0.1";
687
+ return `${protocol}://${address}:${port}`;
688
+ }
689
+ function isUnixSock(address) {
690
+ return address.addressType === -1;
691
+ }
692
+
693
+ //#endregion
6
694
  //#region src/index.ts
7
695
  /**
8
696
  * cluster start flow: