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