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