@eggjs/cluster 3.0.1 → 3.1.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -21
- package/dist/agent_worker.d.ts +1 -0
- package/dist/agent_worker.js +55 -0
- package/dist/app_worker.d.ts +1 -0
- package/dist/app_worker.js +131 -0
- package/dist/dirname.js +11 -0
- package/dist/error/ClusterAgentWorkerError.d.ts +13 -0
- package/dist/error/ClusterAgentWorkerError.js +22 -0
- package/dist/error/ClusterWorkerExceptionError.d.ts +10 -0
- package/dist/error/ClusterWorkerExceptionError.js +17 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +24 -0
- package/dist/master.d.ts +96 -0
- package/dist/master.js +426 -0
- package/dist/utils/messenger.d.ts +96 -0
- package/dist/utils/messenger.js +144 -0
- package/dist/utils/mode/base/agent.d.ts +45 -0
- package/dist/utils/mode/base/agent.js +63 -0
- package/dist/utils/mode/base/app.d.ts +56 -0
- package/dist/utils/mode/base/app.js +77 -0
- package/dist/utils/mode/impl/process/agent.d.ts +22 -0
- package/dist/utils/mode/impl/process/agent.js +93 -0
- package/dist/utils/mode/impl/process/app.d.ts +12 -0
- package/dist/utils/mode/impl/process/app.js +117 -0
- package/dist/utils/mode/impl/worker_threads/agent.d.ts +22 -0
- package/dist/utils/mode/impl/worker_threads/agent.js +79 -0
- package/dist/utils/mode/impl/worker_threads/app.d.ts +13 -0
- package/dist/utils/mode/impl/worker_threads/app.js +128 -0
- package/dist/utils/options.d.ts +83 -0
- package/dist/utils/options.js +56 -0
- package/dist/utils/terminate.js +62 -0
- package/dist/utils/worker_manager.d.ts +32 -0
- package/dist/utils/worker_manager.js +68 -0
- package/package.json +41 -63
- package/dist/commonjs/agent_worker.d.ts +0 -1
- package/dist/commonjs/agent_worker.js +0 -69
- package/dist/commonjs/app_worker.d.ts +0 -1
- package/dist/commonjs/app_worker.js +0 -173
- package/dist/commonjs/dirname.d.ts +0 -1
- package/dist/commonjs/dirname.js +0 -17
- package/dist/commonjs/error/ClusterAgentWorkerError.d.ts +0 -10
- package/dist/commonjs/error/ClusterAgentWorkerError.js +0 -23
- package/dist/commonjs/error/ClusterWorkerExceptionError.d.ts +0 -7
- package/dist/commonjs/error/ClusterWorkerExceptionError.js +0 -18
- package/dist/commonjs/error/index.d.ts +0 -2
- package/dist/commonjs/error/index.js +0 -19
- package/dist/commonjs/index.d.ts +0 -17
- package/dist/commonjs/index.js +0 -37
- package/dist/commonjs/master.d.ts +0 -90
- package/dist/commonjs/master.js +0 -560
- package/dist/commonjs/package.json +0 -3
- package/dist/commonjs/utils/messenger.d.ts +0 -92
- package/dist/commonjs/utils/messenger.js +0 -186
- package/dist/commonjs/utils/mode/base/agent.d.ts +0 -38
- package/dist/commonjs/utils/mode/base/agent.js +0 -68
- package/dist/commonjs/utils/mode/base/app.d.ts +0 -48
- package/dist/commonjs/utils/mode/base/app.js +0 -83
- package/dist/commonjs/utils/mode/impl/process/agent.d.ts +0 -18
- package/dist/commonjs/utils/mode/impl/process/agent.js +0 -108
- package/dist/commonjs/utils/mode/impl/process/app.d.ts +0 -21
- package/dist/commonjs/utils/mode/impl/process/app.js +0 -127
- package/dist/commonjs/utils/mode/impl/worker_threads/agent.d.ts +0 -18
- package/dist/commonjs/utils/mode/impl/worker_threads/agent.js +0 -91
- package/dist/commonjs/utils/mode/impl/worker_threads/app.d.ts +0 -26
- package/dist/commonjs/utils/mode/impl/worker_threads/app.js +0 -142
- package/dist/commonjs/utils/options.d.ts +0 -80
- package/dist/commonjs/utils/options.js +0 -83
- package/dist/commonjs/utils/terminate.d.ts +0 -6
- package/dist/commonjs/utils/terminate.js +0 -89
- package/dist/commonjs/utils/worker_manager.d.ts +0 -25
- package/dist/commonjs/utils/worker_manager.js +0 -76
- package/dist/esm/agent_worker.d.ts +0 -1
- package/dist/esm/agent_worker.js +0 -67
- package/dist/esm/app_worker.d.ts +0 -1
- package/dist/esm/app_worker.js +0 -168
- package/dist/esm/dirname.d.ts +0 -1
- package/dist/esm/dirname.js +0 -11
- package/dist/esm/error/ClusterAgentWorkerError.d.ts +0 -10
- package/dist/esm/error/ClusterAgentWorkerError.js +0 -19
- package/dist/esm/error/ClusterWorkerExceptionError.d.ts +0 -7
- package/dist/esm/error/ClusterWorkerExceptionError.js +0 -14
- package/dist/esm/error/index.d.ts +0 -2
- package/dist/esm/error/index.js +0 -3
- package/dist/esm/index.d.ts +0 -17
- package/dist/esm/index.js +0 -19
- package/dist/esm/master.d.ts +0 -90
- package/dist/esm/master.js +0 -553
- package/dist/esm/package.json +0 -3
- package/dist/esm/utils/messenger.d.ts +0 -92
- package/dist/esm/utils/messenger.js +0 -179
- package/dist/esm/utils/mode/base/agent.d.ts +0 -38
- package/dist/esm/utils/mode/base/agent.js +0 -60
- package/dist/esm/utils/mode/base/app.d.ts +0 -48
- package/dist/esm/utils/mode/base/app.js +0 -75
- package/dist/esm/utils/mode/impl/process/agent.d.ts +0 -18
- package/dist/esm/utils/mode/impl/process/agent.js +0 -103
- package/dist/esm/utils/mode/impl/process/app.d.ts +0 -21
- package/dist/esm/utils/mode/impl/process/app.js +0 -119
- package/dist/esm/utils/mode/impl/worker_threads/agent.d.ts +0 -18
- package/dist/esm/utils/mode/impl/worker_threads/agent.js +0 -83
- package/dist/esm/utils/mode/impl/worker_threads/app.d.ts +0 -26
- package/dist/esm/utils/mode/impl/worker_threads/app.js +0 -137
- package/dist/esm/utils/options.d.ts +0 -80
- package/dist/esm/utils/options.js +0 -77
- package/dist/esm/utils/terminate.d.ts +0 -6
- package/dist/esm/utils/terminate.js +0 -86
- package/dist/esm/utils/worker_manager.d.ts +0 -25
- package/dist/esm/utils/worker_manager.js +0 -72
- package/dist/package.json +0 -4
- package/src/agent_worker.ts +0 -80
- package/src/app_worker.ts +0 -196
- package/src/dirname.ts +0 -11
- package/src/error/ClusterAgentWorkerError.ts +0 -19
- package/src/error/ClusterWorkerExceptionError.ts +0 -17
- package/src/error/index.ts +0 -2
- package/src/index.ts +0 -26
- package/src/master.ts +0 -658
- package/src/utils/messenger.ts +0 -207
- package/src/utils/mode/base/agent.ts +0 -90
- package/src/utils/mode/base/app.ts +0 -119
- package/src/utils/mode/impl/process/agent.ts +0 -119
- package/src/utils/mode/impl/process/app.ts +0 -140
- package/src/utils/mode/impl/worker_threads/agent.ts +0 -99
- package/src/utils/mode/impl/worker_threads/app.ts +0 -164
- package/src/utils/options.ts +0 -171
- package/src/utils/terminate.ts +0 -97
- 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
|
-
}
|