@eggjs/cluster 3.0.1 → 3.1.0-beta.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.
Files changed (123) hide show
  1. package/README.md +17 -21
  2. package/dist/dirname.js +11 -0
  3. package/dist/error/ClusterAgentWorkerError.d.ts +13 -0
  4. package/dist/error/ClusterAgentWorkerError.js +22 -0
  5. package/dist/error/ClusterWorkerExceptionError.d.ts +10 -0
  6. package/dist/error/ClusterWorkerExceptionError.js +17 -0
  7. package/dist/index.d.ts +22 -0
  8. package/dist/index.js +24 -0
  9. package/dist/master.d.ts +96 -0
  10. package/dist/master.js +426 -0
  11. package/dist/utils/messenger.d.ts +96 -0
  12. package/dist/utils/messenger.js +144 -0
  13. package/dist/utils/mode/base/agent.d.ts +45 -0
  14. package/dist/utils/mode/base/agent.js +63 -0
  15. package/dist/utils/mode/base/app.d.ts +56 -0
  16. package/dist/utils/mode/base/app.js +77 -0
  17. package/dist/utils/mode/impl/process/agent.d.ts +22 -0
  18. package/dist/utils/mode/impl/process/agent.js +93 -0
  19. package/dist/utils/mode/impl/process/app.d.ts +12 -0
  20. package/dist/utils/mode/impl/process/app.js +117 -0
  21. package/dist/utils/mode/impl/worker_threads/agent.d.ts +22 -0
  22. package/dist/utils/mode/impl/worker_threads/agent.js +79 -0
  23. package/dist/utils/mode/impl/worker_threads/app.d.ts +13 -0
  24. package/dist/utils/mode/impl/worker_threads/app.js +128 -0
  25. package/dist/utils/options.d.ts +83 -0
  26. package/dist/utils/options.js +56 -0
  27. package/dist/utils/terminate.js +62 -0
  28. package/dist/utils/worker_manager.d.ts +32 -0
  29. package/dist/utils/worker_manager.js +68 -0
  30. package/package.json +39 -63
  31. package/dist/commonjs/agent_worker.d.ts +0 -1
  32. package/dist/commonjs/agent_worker.js +0 -69
  33. package/dist/commonjs/app_worker.d.ts +0 -1
  34. package/dist/commonjs/app_worker.js +0 -173
  35. package/dist/commonjs/dirname.d.ts +0 -1
  36. package/dist/commonjs/dirname.js +0 -17
  37. package/dist/commonjs/error/ClusterAgentWorkerError.d.ts +0 -10
  38. package/dist/commonjs/error/ClusterAgentWorkerError.js +0 -23
  39. package/dist/commonjs/error/ClusterWorkerExceptionError.d.ts +0 -7
  40. package/dist/commonjs/error/ClusterWorkerExceptionError.js +0 -18
  41. package/dist/commonjs/error/index.d.ts +0 -2
  42. package/dist/commonjs/error/index.js +0 -19
  43. package/dist/commonjs/index.d.ts +0 -17
  44. package/dist/commonjs/index.js +0 -37
  45. package/dist/commonjs/master.d.ts +0 -90
  46. package/dist/commonjs/master.js +0 -560
  47. package/dist/commonjs/package.json +0 -3
  48. package/dist/commonjs/utils/messenger.d.ts +0 -92
  49. package/dist/commonjs/utils/messenger.js +0 -186
  50. package/dist/commonjs/utils/mode/base/agent.d.ts +0 -38
  51. package/dist/commonjs/utils/mode/base/agent.js +0 -68
  52. package/dist/commonjs/utils/mode/base/app.d.ts +0 -48
  53. package/dist/commonjs/utils/mode/base/app.js +0 -83
  54. package/dist/commonjs/utils/mode/impl/process/agent.d.ts +0 -18
  55. package/dist/commonjs/utils/mode/impl/process/agent.js +0 -108
  56. package/dist/commonjs/utils/mode/impl/process/app.d.ts +0 -21
  57. package/dist/commonjs/utils/mode/impl/process/app.js +0 -127
  58. package/dist/commonjs/utils/mode/impl/worker_threads/agent.d.ts +0 -18
  59. package/dist/commonjs/utils/mode/impl/worker_threads/agent.js +0 -91
  60. package/dist/commonjs/utils/mode/impl/worker_threads/app.d.ts +0 -26
  61. package/dist/commonjs/utils/mode/impl/worker_threads/app.js +0 -142
  62. package/dist/commonjs/utils/options.d.ts +0 -80
  63. package/dist/commonjs/utils/options.js +0 -83
  64. package/dist/commonjs/utils/terminate.d.ts +0 -6
  65. package/dist/commonjs/utils/terminate.js +0 -89
  66. package/dist/commonjs/utils/worker_manager.d.ts +0 -25
  67. package/dist/commonjs/utils/worker_manager.js +0 -76
  68. package/dist/esm/agent_worker.d.ts +0 -1
  69. package/dist/esm/agent_worker.js +0 -67
  70. package/dist/esm/app_worker.d.ts +0 -1
  71. package/dist/esm/app_worker.js +0 -168
  72. package/dist/esm/dirname.d.ts +0 -1
  73. package/dist/esm/dirname.js +0 -11
  74. package/dist/esm/error/ClusterAgentWorkerError.d.ts +0 -10
  75. package/dist/esm/error/ClusterAgentWorkerError.js +0 -19
  76. package/dist/esm/error/ClusterWorkerExceptionError.d.ts +0 -7
  77. package/dist/esm/error/ClusterWorkerExceptionError.js +0 -14
  78. package/dist/esm/error/index.d.ts +0 -2
  79. package/dist/esm/error/index.js +0 -3
  80. package/dist/esm/index.d.ts +0 -17
  81. package/dist/esm/index.js +0 -19
  82. package/dist/esm/master.d.ts +0 -90
  83. package/dist/esm/master.js +0 -553
  84. package/dist/esm/package.json +0 -3
  85. package/dist/esm/utils/messenger.d.ts +0 -92
  86. package/dist/esm/utils/messenger.js +0 -179
  87. package/dist/esm/utils/mode/base/agent.d.ts +0 -38
  88. package/dist/esm/utils/mode/base/agent.js +0 -60
  89. package/dist/esm/utils/mode/base/app.d.ts +0 -48
  90. package/dist/esm/utils/mode/base/app.js +0 -75
  91. package/dist/esm/utils/mode/impl/process/agent.d.ts +0 -18
  92. package/dist/esm/utils/mode/impl/process/agent.js +0 -103
  93. package/dist/esm/utils/mode/impl/process/app.d.ts +0 -21
  94. package/dist/esm/utils/mode/impl/process/app.js +0 -119
  95. package/dist/esm/utils/mode/impl/worker_threads/agent.d.ts +0 -18
  96. package/dist/esm/utils/mode/impl/worker_threads/agent.js +0 -83
  97. package/dist/esm/utils/mode/impl/worker_threads/app.d.ts +0 -26
  98. package/dist/esm/utils/mode/impl/worker_threads/app.js +0 -137
  99. package/dist/esm/utils/options.d.ts +0 -80
  100. package/dist/esm/utils/options.js +0 -77
  101. package/dist/esm/utils/terminate.d.ts +0 -6
  102. package/dist/esm/utils/terminate.js +0 -86
  103. package/dist/esm/utils/worker_manager.d.ts +0 -25
  104. package/dist/esm/utils/worker_manager.js +0 -72
  105. package/dist/package.json +0 -4
  106. package/src/agent_worker.ts +0 -80
  107. package/src/app_worker.ts +0 -196
  108. package/src/dirname.ts +0 -11
  109. package/src/error/ClusterAgentWorkerError.ts +0 -19
  110. package/src/error/ClusterWorkerExceptionError.ts +0 -17
  111. package/src/error/index.ts +0 -2
  112. package/src/index.ts +0 -26
  113. package/src/master.ts +0 -658
  114. package/src/utils/messenger.ts +0 -207
  115. package/src/utils/mode/base/agent.ts +0 -90
  116. package/src/utils/mode/base/app.ts +0 -119
  117. package/src/utils/mode/impl/process/agent.ts +0 -119
  118. package/src/utils/mode/impl/process/app.ts +0 -140
  119. package/src/utils/mode/impl/worker_threads/agent.ts +0 -99
  120. package/src/utils/mode/impl/worker_threads/app.ts +0 -164
  121. package/src/utils/options.ts +0 -171
  122. package/src/utils/terminate.ts +0 -97
  123. package/src/utils/worker_manager.ts +0 -87
package/dist/master.js ADDED
@@ -0,0 +1,426 @@
1
+ import { parseOptions } from "./utils/options.js";
2
+ import { WorkerManager } from "./utils/worker_manager.js";
3
+ import { Messenger } from "./utils/messenger.js";
4
+ import { AgentProcessUtils } from "./utils/mode/impl/process/agent.js";
5
+ import { AppProcessUtils } from "./utils/mode/impl/process/app.js";
6
+ import { AgentThreadUtils } from "./utils/mode/impl/worker_threads/agent.js";
7
+ import { AppThreadUtils } from "./utils/mode/impl/worker_threads/app.js";
8
+ import { ClusterWorkerExceptionError } from "./error/ClusterWorkerExceptionError.js";
9
+ import os from "node:os";
10
+ import v8 from "node:v8";
11
+ import util, { debuglog } from "node:util";
12
+ import path from "node:path";
13
+ import fs from "node:fs";
14
+ import net from "node:net";
15
+ import { ReadyEventEmitter } from "get-ready";
16
+ import { detectPort } from "detect-port";
17
+ import { reload } from "cluster-reload";
18
+ import { EggConsoleLogger } from "egg-logger";
19
+ import { readJSONSync } from "utility";
20
+ import terminalLink from "terminal-link";
21
+
22
+ //#region src/master.ts
23
+ const debug = debuglog("@eggjs/cluster/master");
24
+ var Master = class extends ReadyEventEmitter {
25
+ options;
26
+ isStarted = false;
27
+ workerManager;
28
+ messenger;
29
+ isProduction;
30
+ agentWorkerIndex = 0;
31
+ closed = false;
32
+ logger;
33
+ agentWorker;
34
+ appWorker;
35
+ #logMethod;
36
+ #realPort;
37
+ #protocol;
38
+ #appAddress;
39
+ constructor(options) {
40
+ super();
41
+ this.#start(options).catch((err) => {
42
+ this.ready(err);
43
+ });
44
+ }
45
+ async #start(options) {
46
+ this.options = await parseOptions(options);
47
+ this.workerManager = new WorkerManager();
48
+ this.messenger = new Messenger(this, this.workerManager);
49
+ this.isProduction = isProduction(this.options);
50
+ this.#realPort = this.options.port;
51
+ this.#protocol = this.options.https ? "https" : "http";
52
+ this.isStarted = false;
53
+ this.logger = new EggConsoleLogger({ level: process.env.EGG_MASTER_LOGGER_LEVEL ?? "INFO" });
54
+ this.#logMethod = "info";
55
+ if (this.options.env === "local" || process.env.NODE_ENV === "development") this.#logMethod = "debug";
56
+ const frameworkPath = this.options.framework;
57
+ const frameworkPkg = readJSONSync(path.join(frameworkPath, "package.json"));
58
+ if (this.options.startMode === "worker_threads") this.startByWorkerThreads();
59
+ else this.startByProcess();
60
+ this.log(`[master] =================== ${frameworkPkg.name} start 🥚🥚🥚🥚 =====================`);
61
+ this.logger.info(`[master] node version ${process.version}`);
62
+ /* istanbul ignore next */
63
+ if ("alinode" in process) this.logger.info(`[master] alinode version ${process.alinode}`);
64
+ this.logger.info(`[master] ${frameworkPkg.name} version ${frameworkPkg.version}`);
65
+ if (this.isProduction) this.logger.info("[master] start with options:%s%s", os.EOL, JSON.stringify(this.options, null, 2));
66
+ else this.log("[master] start with options: %j", this.options);
67
+ this.log("[master] start with env: isProduction: %s, EGG_SERVER_ENV: %s, NODE_ENV: %s", this.isProduction, this.options.env, process.env.NODE_ENV);
68
+ const startTime = Date.now();
69
+ this.ready(() => {
70
+ this.isStarted = true;
71
+ const stickyMsg = this.options.sticky ? " with STICKY MODE!" : "";
72
+ const startedURL = terminalLink(this.#appAddress, this.#appAddress, { fallback: false });
73
+ this.logger.info("[master] %s started on %s (%sms)%s", frameworkPkg.name, startedURL, Date.now() - startTime, stickyMsg);
74
+ if (this.options.debugPort) {
75
+ const url = getAddress({
76
+ port: this.options.debugPort,
77
+ protocol: "http"
78
+ });
79
+ const debugPortURL = terminalLink(url, url, { fallback: false });
80
+ this.logger.info("[master] %s started debug port on %s", frameworkPkg.name, debugPortURL);
81
+ }
82
+ const action = "egg-ready";
83
+ this.messenger.send({
84
+ action,
85
+ to: "parent",
86
+ data: {
87
+ port: this.#realPort,
88
+ debugPort: this.options.debugPort,
89
+ address: this.#appAddress,
90
+ protocol: this.#protocol
91
+ }
92
+ });
93
+ this.messenger.send({
94
+ action,
95
+ to: "app",
96
+ data: this.options
97
+ });
98
+ this.messenger.send({
99
+ action,
100
+ to: "agent",
101
+ data: this.options
102
+ });
103
+ if (this.isProduction) this.workerManager.startCheck();
104
+ });
105
+ this.on("agent-exit", this.onAgentExit.bind(this));
106
+ this.on("agent-start", this.onAgentStart.bind(this));
107
+ this.on("app-exit", this.onAppExit.bind(this));
108
+ this.on("app-start", this.onAppStart.bind(this));
109
+ this.on("reload-worker", this.onReload.bind(this));
110
+ this.once("agent-start", this.forkAppWorkers.bind(this));
111
+ this.on("realport", ({ port, protocol }) => {
112
+ if (port) this.#realPort = port;
113
+ if (protocol) this.#protocol = protocol;
114
+ });
115
+ process.once("SIGINT", this.onSignal.bind(this, "SIGINT"));
116
+ process.once("SIGQUIT", this.onSignal.bind(this, "SIGQUIT"));
117
+ process.once("SIGTERM", this.onSignal.bind(this, "SIGTERM"));
118
+ process.once("exit", this.onExit.bind(this));
119
+ if (this.options.pidFile) {
120
+ fs.mkdirSync(path.dirname(this.options.pidFile), { recursive: true });
121
+ fs.writeFileSync(this.options.pidFile, process.pid.toString(), "utf-8");
122
+ }
123
+ this.detectPorts().then(() => {
124
+ this.forkAgentWorker();
125
+ });
126
+ this.workerManager.on("exception", (count) => {
127
+ const err = new ClusterWorkerExceptionError(count.agent, count.worker);
128
+ this.logger.error(err);
129
+ process.exit(1);
130
+ });
131
+ }
132
+ startByProcess() {
133
+ this.agentWorker = new AgentProcessUtils(this.options, {
134
+ log: this.log.bind(this),
135
+ logger: this.logger,
136
+ messenger: this.messenger
137
+ });
138
+ this.appWorker = new AppProcessUtils(this.options, {
139
+ log: this.log.bind(this),
140
+ logger: this.logger,
141
+ messenger: this.messenger,
142
+ isProduction: this.isProduction
143
+ });
144
+ }
145
+ startByWorkerThreads() {
146
+ this.agentWorker = new AgentThreadUtils(this.options, {
147
+ log: this.log.bind(this),
148
+ logger: this.logger,
149
+ messenger: this.messenger
150
+ });
151
+ this.appWorker = new AppThreadUtils(this.options, {
152
+ log: this.log.bind(this),
153
+ logger: this.logger,
154
+ messenger: this.messenger,
155
+ isProduction: this.isProduction
156
+ });
157
+ }
158
+ async detectPorts() {
159
+ try {
160
+ const clusterPort = await detectPort();
161
+ this.options.clusterPort = clusterPort;
162
+ if (this.options.sticky) {
163
+ const stickyWorkerPort = await detectPort();
164
+ this.options.stickyWorkerPort = stickyWorkerPort;
165
+ }
166
+ } catch (err) {
167
+ this.logger.error(err);
168
+ process.exit(1);
169
+ }
170
+ }
171
+ log(msg, ...args) {
172
+ this.logger[this.#logMethod](msg, ...args);
173
+ }
174
+ startMasterSocketServer(cb) {
175
+ net.createServer({ pauseOnConnect: true }, (connection) => {
176
+ /* istanbul ignore next */
177
+ if (!connection.remoteAddress) connection.destroy();
178
+ else this.stickyWorker(connection.remoteAddress).instance.send("sticky-session:connection", connection);
179
+ }).listen(this.#realPort, cb);
180
+ }
181
+ stickyWorker(ip) {
182
+ const workerNumbers = this.options.workers;
183
+ const ws = this.workerManager.listWorkerIds();
184
+ let s = "";
185
+ for (let i = 0; i < ip.length; i++) if (!isNaN(parseInt(ip[i]))) s += ip[i];
186
+ const pid = ws[Number(s) % workerNumbers];
187
+ return this.workerManager.getWorker(pid);
188
+ }
189
+ forkAgentWorker() {
190
+ this.agentWorker.on("agent_forked", (agent) => {
191
+ this.workerManager.setAgent(agent);
192
+ });
193
+ this.agentWorker.fork();
194
+ }
195
+ forkAppWorkers() {
196
+ this.appWorker.on("worker_forked", (worker) => {
197
+ this.workerManager.setWorker(worker);
198
+ });
199
+ this.appWorker.fork();
200
+ }
201
+ /**
202
+ * close agent worker, App Worker will closed by cluster
203
+ *
204
+ * https://www.exratione.com/2013/05/die-child-process-die/
205
+ * make sure Agent Worker exit before master exit
206
+ *
207
+ * @param {number} timeout - kill agent timeout
208
+ * @return {Promise} -
209
+ */
210
+ async killAgentWorker(timeout) {
211
+ await this.agentWorker.kill(timeout);
212
+ }
213
+ async killAppWorkers(timeout) {
214
+ await this.appWorker.kill(timeout);
215
+ }
216
+ /**
217
+ * Agent Worker exit handler
218
+ * Will exit during startup, and refork during running.
219
+ */
220
+ onAgentExit(data) {
221
+ if (this.closed) return;
222
+ this.messenger.send({
223
+ action: "egg-pids",
224
+ to: "app",
225
+ data: []
226
+ });
227
+ const agentWorker = this.agentWorker;
228
+ this.workerManager.deleteAgent();
229
+ 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));
230
+ err.name = "AgentWorkerDiedError";
231
+ this.logger.error(err);
232
+ agentWorker.clean();
233
+ if (this.isStarted) {
234
+ this.log("[master] try to start a new agent_worker after 1s ...");
235
+ setTimeout(() => {
236
+ this.logger.info("[master] new agent_worker starting...");
237
+ this.forkAgentWorker();
238
+ }, 1e3);
239
+ this.messenger.send({
240
+ action: "agent-worker-died",
241
+ to: "parent"
242
+ });
243
+ } else {
244
+ this.logger.error("[master] agent_worker#%s:%s start fail, exiting with code:1", agentWorker.instance.id, agentWorker.instance.workerId);
245
+ process.exit(1);
246
+ }
247
+ }
248
+ onAgentStart() {
249
+ this.agentWorker.instance.status = "started";
250
+ if (this.appWorker.isAllWorkerStarted) this.messenger.send({
251
+ action: "egg-ready",
252
+ to: "agent",
253
+ data: this.options
254
+ });
255
+ this.messenger.send({
256
+ action: "egg-pids",
257
+ to: "app",
258
+ data: [this.agentWorker.instance.workerId]
259
+ });
260
+ if (this.isStarted) this.messenger.send({
261
+ action: "egg-pids",
262
+ to: "agent",
263
+ data: this.workerManager.getListeningWorkerIds()
264
+ });
265
+ this.messenger.send({
266
+ action: "agent-start",
267
+ to: "app"
268
+ });
269
+ this.logger.info("[master] agent_worker#%s:%s started (%sms)", this.agentWorker.instance.id, this.agentWorker.instance.workerId, Date.now() - this.agentWorker.startTime);
270
+ }
271
+ /**
272
+ * App Worker exit handler
273
+ */
274
+ onAppExit(data) {
275
+ if (this.closed) return;
276
+ const worker = this.workerManager.getWorker(data.workerId);
277
+ if (!worker.isDevReload) {
278
+ const signal = data.signal;
279
+ 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());
280
+ if (this.options.isDebug && signal === "SIGKILL") {
281
+ this.logger.error(message);
282
+ this.logger.error("[master] worker kill by debugger, exiting...");
283
+ setTimeout(() => this.close(), 10);
284
+ } else {
285
+ const err = new Error(message);
286
+ err.name = "AppWorkerDiedError";
287
+ this.logger.error(err);
288
+ }
289
+ }
290
+ worker.clean();
291
+ this.workerManager.deleteWorker(data.workerId);
292
+ this.messenger.send({
293
+ action: "egg-pids",
294
+ to: "agent",
295
+ data: this.workerManager.getListeningWorkerIds()
296
+ });
297
+ if (this.appWorker.isAllWorkerStarted) this.messenger.send({
298
+ action: "app-worker-died",
299
+ to: "parent"
300
+ });
301
+ else {
302
+ this.logger.error("[master] app_worker#%s:%s start fail, exiting with code:1", worker.id, worker.workerId);
303
+ process.exit(1);
304
+ }
305
+ }
306
+ /**
307
+ * after app worker
308
+ */
309
+ onAppStart(data) {
310
+ const worker = this.workerManager.getWorker(data.workerId);
311
+ debug("got app_worker#%s:%s app-start event, data: %j", worker.id, worker.workerId, data);
312
+ const address = data.address;
313
+ if (this.options.sticky) {
314
+ if (String(address.port) !== String(this.options.stickyWorkerPort)) return;
315
+ } else if (this.options.startMode !== "worker_threads" && !isUnixSock(address) && String(address.port) !== String(this.#realPort)) return;
316
+ worker.state = "listening";
317
+ this.messenger.send({
318
+ action: "egg-pids",
319
+ to: "agent",
320
+ data: this.workerManager.getListeningWorkerIds()
321
+ });
322
+ this.messenger.send({
323
+ action: "egg-pids",
324
+ to: "app",
325
+ data: [this.agentWorker.instance.workerId],
326
+ receiverWorkerId: String(worker.workerId),
327
+ receiverPid: String(worker.workerId)
328
+ });
329
+ this.appWorker.startSuccessCount++;
330
+ const remain = this.appWorker.isAllWorkerStarted ? 0 : this.options.workers - this.appWorker.startSuccessCount;
331
+ 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);
332
+ if (this.appWorker.isAllWorkerStarted) this.messenger.send({
333
+ action: "egg-ready",
334
+ to: "app",
335
+ data: this.options
336
+ });
337
+ if (this.appWorker.isAllWorkerStarted) worker.disableRefork = false;
338
+ if (this.appWorker.isAllWorkerStarted || this.appWorker.startSuccessCount < this.options.workers) return;
339
+ this.appWorker.isAllWorkerStarted = true;
340
+ for (const worker$1 of this.workerManager.listWorkers()) worker$1.disableRefork = false;
341
+ address.protocol = this.#protocol;
342
+ address.port = this.options.sticky ? this.#realPort : address.port;
343
+ this.#appAddress = getAddress(address);
344
+ if (this.options.sticky) this.startMasterSocketServer((err) => {
345
+ if (err) return this.ready(err);
346
+ this.ready(true);
347
+ });
348
+ else this.ready(true);
349
+ }
350
+ /**
351
+ * master exit handler
352
+ */
353
+ onExit(code) {
354
+ if (this.options.pidFile && fs.existsSync(this.options.pidFile)) try {
355
+ fs.unlinkSync(this.options.pidFile);
356
+ } catch (err) {
357
+ /* istanbul ignore next */
358
+ this.logger.error("[master] delete pidFile %s fail with %s", this.options.pidFile, err.message);
359
+ }
360
+ const level = code === 0 ? "info" : "error";
361
+ this.logger[level]("[master] exit with code:%s", code);
362
+ }
363
+ onSignal(signal) {
364
+ if (this.closed) return;
365
+ this.logger.info("[master] master is killed by signal %s, closing", signal);
366
+ const { used_heap_size, heap_size_limit } = v8.getHeapStatistics();
367
+ this.logger.info("[master] system memory: total %s, free %s", os.totalmem(), os.freemem());
368
+ this.logger.info("[master] process info: heap_limit %s, heap_used %s", heap_size_limit, used_heap_size);
369
+ this.close();
370
+ }
371
+ /**
372
+ * reload workers, for develop purpose
373
+ */
374
+ onReload() {
375
+ this.log("[master] reload %s workers...", this.options.workers);
376
+ for (const worker of this.workerManager.listWorkers()) worker.isDevReload = true;
377
+ reload(this.options.workers);
378
+ }
379
+ async close() {
380
+ this.closed = true;
381
+ try {
382
+ await this._doClose();
383
+ this.log("[master] close done, exiting with code:0");
384
+ process.exit(0);
385
+ } catch (e) {
386
+ this.logger.error("[master] close with error: ", e);
387
+ process.exit(1);
388
+ }
389
+ }
390
+ async _doClose() {
391
+ const legacyTimeout = process.env.EGG_MASTER_CLOSE_TIMEOUT || "5000";
392
+ const appTimeout = parseInt(process.env.EGG_APP_CLOSE_TIMEOUT || legacyTimeout);
393
+ const agentTimeout = parseInt(process.env.EGG_AGENT_CLOSE_TIMEOUT || legacyTimeout);
394
+ this.logger.info("[master] send kill SIGTERM to app workers, will exit with code:0 after %sms", appTimeout);
395
+ this.logger.info("[master] wait %sms", appTimeout);
396
+ try {
397
+ await this.killAppWorkers(appTimeout);
398
+ } catch (e) {
399
+ this.logger.error("[master] app workers exit error: ", e);
400
+ }
401
+ this.logger.info("[master] send kill SIGTERM to agent worker, will exit with code:0 after %sms", agentTimeout);
402
+ this.logger.info("[master] wait %sms", agentTimeout);
403
+ try {
404
+ await this.killAgentWorker(agentTimeout);
405
+ } catch (e) {
406
+ this.logger.error("[master] agent worker exit error: ", e);
407
+ }
408
+ }
409
+ };
410
+ function isProduction(options) {
411
+ if (options.env) return options.env !== "local" && options.env !== "unittest";
412
+ return process.env.NODE_ENV === "production";
413
+ }
414
+ function getAddress({ addressType, address, port, protocol }) {
415
+ if (addressType === -1) return address;
416
+ if (address === "::") address = "";
417
+ if (!address && process.env.HOST && process.env.HOST !== "0.0.0.0") address = process.env.HOST;
418
+ if (!address) address = "127.0.0.1";
419
+ return `${protocol}://${address}:${port}`;
420
+ }
421
+ function isUnixSock(address) {
422
+ return address.addressType === -1;
423
+ }
424
+
425
+ //#endregion
426
+ export { Master };
@@ -0,0 +1,96 @@
1
+ import { WorkerManager } from "./worker_manager.js";
2
+ import { Master } from "../master.js";
3
+
4
+ //#region src/utils/messenger.d.ts
5
+ type MessageCharacter = 'agent' | 'app' | 'master' | 'parent';
6
+ interface MessageBody {
7
+ action: string;
8
+ data?: unknown;
9
+ to?: MessageCharacter;
10
+ from?: MessageCharacter;
11
+ /**
12
+ * @deprecated Keep compatible, please use receiverWorkerId instead
13
+ */
14
+ receiverPid?: string;
15
+ receiverWorkerId?: string;
16
+ senderWorkerId?: string;
17
+ }
18
+ /**
19
+ * master messenger, provide communication between parent, master, agent and app.
20
+ *
21
+ * ┌────────┐
22
+ * │ parent │
23
+ * /└────────┘\
24
+ * / | \
25
+ * / ┌────────┐ \
26
+ * / │ master │ \
27
+ * / └────────┘ \
28
+ * / / \ \
29
+ * ┌───────┐ ┌───────┐
30
+ * │ agent │ ------- │ app │
31
+ * └───────┘ └───────┘
32
+ *
33
+ *
34
+ * in app worker
35
+ *
36
+ * ```js
37
+ * process.send({
38
+ * action: 'xxx',
39
+ * data: '',
40
+ * to: 'agent/master/parent', // default to agent
41
+ * });
42
+ * ```
43
+ *
44
+ * in agent worker
45
+ *
46
+ * ```js
47
+ * process.send({
48
+ * action: 'xxx',
49
+ * data: '',
50
+ * to: 'app/master/parent', // default to app
51
+ * });
52
+ * ```
53
+ *
54
+ * in parent
55
+ *
56
+ * ```js
57
+ * process.send({
58
+ * action: 'xxx',
59
+ * data: '',
60
+ * to: 'app/agent/master', // default to master
61
+ * });
62
+ * ```
63
+ */
64
+ declare class Messenger {
65
+ #private;
66
+ constructor(master: Master, workerManager: WorkerManager);
67
+ /**
68
+ * send message
69
+ * @param {Object} data message body
70
+ * - {String} from from who
71
+ * - {String} to to who
72
+ */
73
+ send(data: MessageBody): void;
74
+ /**
75
+ * send message to master self
76
+ * @param {Object} data message body
77
+ */
78
+ sendToMaster(data: MessageBody): void;
79
+ /**
80
+ * send message to parent process
81
+ * @param {Object} data message body
82
+ */
83
+ sendToParent(data: MessageBody): void;
84
+ /**
85
+ * send message to app worker
86
+ * @param {Object} data message body
87
+ */
88
+ sendToAppWorker(data: MessageBody): void;
89
+ /**
90
+ * send message to agent worker
91
+ * @param {Object} data message body
92
+ */
93
+ sendToAgentWorker(data: MessageBody): void;
94
+ }
95
+ //#endregion
96
+ export { MessageBody, Messenger };
@@ -0,0 +1,144 @@
1
+ import { debuglog } from "node:util";
2
+ import workerThreads from "node:worker_threads";
3
+
4
+ //#region src/utils/messenger.ts
5
+ const debug = debuglog("egg/cluster/messenger");
6
+ /**
7
+ * master messenger, provide communication between parent, master, agent and app.
8
+ *
9
+ * ┌────────┐
10
+ * │ parent │
11
+ * /└────────┘\
12
+ * / | \
13
+ * / ┌────────┐ \
14
+ * / │ master │ \
15
+ * / └────────┘ \
16
+ * / / \ \
17
+ * ┌───────┐ ┌───────┐
18
+ * │ agent │ ------- │ app │
19
+ * └───────┘ └───────┘
20
+ *
21
+ *
22
+ * in app worker
23
+ *
24
+ * ```js
25
+ * process.send({
26
+ * action: 'xxx',
27
+ * data: '',
28
+ * to: 'agent/master/parent', // default to agent
29
+ * });
30
+ * ```
31
+ *
32
+ * in agent worker
33
+ *
34
+ * ```js
35
+ * process.send({
36
+ * action: 'xxx',
37
+ * data: '',
38
+ * to: 'app/master/parent', // default to app
39
+ * });
40
+ * ```
41
+ *
42
+ * in parent
43
+ *
44
+ * ```js
45
+ * process.send({
46
+ * action: 'xxx',
47
+ * data: '',
48
+ * to: 'app/agent/master', // default to master
49
+ * });
50
+ * ```
51
+ */
52
+ var Messenger = class {
53
+ #master;
54
+ #workerManager;
55
+ #hasParent;
56
+ constructor(master, workerManager) {
57
+ this.#master = master;
58
+ this.#workerManager = workerManager;
59
+ this.#hasParent = !!workerThreads.parentPort || !!process.send;
60
+ process.on("message", (msg) => {
61
+ msg.from = "parent";
62
+ this.send(msg);
63
+ });
64
+ process.once("disconnect", () => {
65
+ this.#hasParent = false;
66
+ });
67
+ }
68
+ /**
69
+ * send message
70
+ * @param {Object} data message body
71
+ * - {String} from from who
72
+ * - {String} to to who
73
+ */
74
+ send(data) {
75
+ if (!data.from) data.from = "master";
76
+ const receiverWorkerId = data.receiverWorkerId ?? data.receiverPid;
77
+ if (receiverWorkerId) if (receiverWorkerId === String(process.pid)) data.to = "master";
78
+ else if (receiverWorkerId === String(this.#workerManager.getAgent().workerId)) data.to = "agent";
79
+ else data.to = "app";
80
+ if (!data.to) {
81
+ if (data.from === "agent") data.to = "app";
82
+ if (data.from === "app") data.to = "agent";
83
+ if (data.from === "parent") data.to = "master";
84
+ }
85
+ if (data.to === "master") {
86
+ debug("%s -> master, data: %j", data.from, data);
87
+ this.sendToMaster(data);
88
+ return;
89
+ }
90
+ if (data.to === "parent") {
91
+ debug("%s -> parent, data: %j", data.from, data);
92
+ this.sendToParent(data);
93
+ return;
94
+ }
95
+ if (data.to === "app") {
96
+ debug("%s -> %s, data: %j", data.from, data.to, data);
97
+ this.sendToAppWorker(data);
98
+ return;
99
+ }
100
+ if (data.to === "agent") {
101
+ debug("%s -> %s, data: %j", data.from, data.to, data);
102
+ this.sendToAgentWorker(data);
103
+ return;
104
+ }
105
+ }
106
+ /**
107
+ * send message to master self
108
+ * @param {Object} data message body
109
+ */
110
+ sendToMaster(data) {
111
+ this.#master.emit(data.action, data.data);
112
+ }
113
+ /**
114
+ * send message to parent process
115
+ * @param {Object} data message body
116
+ */
117
+ sendToParent(data) {
118
+ if (!this.#hasParent) return;
119
+ process.send(data);
120
+ }
121
+ /**
122
+ * send message to app worker
123
+ * @param {Object} data message body
124
+ */
125
+ sendToAppWorker(data) {
126
+ for (const worker of this.#workerManager.listWorkers()) {
127
+ if (worker.state === "disconnected") continue;
128
+ const receiverWorkerId = data.receiverWorkerId ?? data.receiverPid;
129
+ if (receiverWorkerId && receiverWorkerId !== String(worker.workerId)) continue;
130
+ worker.send(data);
131
+ }
132
+ }
133
+ /**
134
+ * send message to agent worker
135
+ * @param {Object} data message body
136
+ */
137
+ sendToAgentWorker(data) {
138
+ const agent = this.#workerManager.getAgent();
139
+ if (agent) agent.send(data);
140
+ }
141
+ };
142
+
143
+ //#endregion
144
+ export { Messenger };
@@ -0,0 +1,45 @@
1
+ import { MessageBody, Messenger } from "../../messenger.js";
2
+ import { MasterOptions } from "../../../master.js";
3
+ import { Logger } from "egg-logger";
4
+ import { EventEmitter } from "node:events";
5
+ import { Worker } from "node:worker_threads";
6
+ import { ChildProcess } from "node:child_process";
7
+
8
+ //#region src/utils/mode/base/agent.d.ts
9
+ declare abstract class BaseAgentWorker<T = ChildProcess | Worker> {
10
+ #private;
11
+ instance: T;
12
+ constructor(instance: T);
13
+ abstract get workerId(): number;
14
+ get id(): number;
15
+ set id(id: number);
16
+ get status(): string;
17
+ set status(status: string);
18
+ abstract send(message: MessageBody): void;
19
+ static send(_message: MessageBody): void;
20
+ static kill(): void;
21
+ static gracefulExit(_options: any): void;
22
+ }
23
+ type LogFun = (msg: any, ...args: any[]) => void;
24
+ declare abstract class BaseAgentUtils extends EventEmitter {
25
+ protected options: MasterOptions;
26
+ protected messenger: Messenger;
27
+ protected log: LogFun;
28
+ protected logger: Logger;
29
+ startTime: number;
30
+ constructor(options: MasterOptions, {
31
+ log,
32
+ logger,
33
+ messenger
34
+ }: {
35
+ log: LogFun;
36
+ logger: Logger;
37
+ messenger: Messenger;
38
+ });
39
+ getAgentWorkerFile(): string;
40
+ fork(): void;
41
+ clean(): void;
42
+ abstract kill(timeout: number): Promise<void>;
43
+ }
44
+ //#endregion
45
+ export { BaseAgentUtils, BaseAgentWorker };