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