@nsshunt/stsappframework 2.19.172 → 2.19.174
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/.github/workflows/npm-publish.yml +2 -2
- package/dist/authutilsnode.js +152 -178
- package/dist/authutilsnode.js.map +1 -1
- package/dist/masterprocessbase.js +454 -484
- package/dist/masterprocessbase.js.map +1 -1
- package/dist/processbase.js +161 -180
- package/dist/processbase.js.map +1 -1
- package/dist/server.js +3 -16
- package/dist/server.js.map +1 -1
- package/dist/singleprocessbase.js +216 -240
- package/dist/singleprocessbase.js.map +1 -1
- package/dist/socketioHelper.js +132 -145
- package/dist/socketioHelper.js.map +1 -1
- package/dist/stscontrollerbase.js +3 -16
- package/dist/stscontrollerbase.js.map +1 -1
- package/dist/stslatencycontroller.js +9 -20
- package/dist/stslatencycontroller.js.map +1 -1
- package/dist/stsrouterbase.js +6 -19
- package/dist/stsrouterbase.js.map +1 -1
- package/dist/testHelpers.js +238 -262
- package/dist/testHelpers.js.map +1 -1
- package/dist/workerprocessbase.js +297 -321
- package/dist/workerprocessbase.js.map +1 -1
- package/dist/workerprocessbase.test.js +2 -11
- package/dist/workerprocessbase.test.js.map +1 -1
- package/package.json +12 -11
- package/tsconfig.json +28 -9
|
@@ -1,28 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
12
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
13
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
14
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
15
|
-
};
|
|
16
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
17
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
18
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
19
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
20
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
21
|
-
};
|
|
22
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
4
|
};
|
|
25
|
-
var _WorkerProcessBase_io, _WorkerProcessBase_httpServer, _WorkerProcessBase_expressServer, _WorkerProcessBase_inFlightMessage, _WorkerProcessBase_requestResponseMessageTimeout, _WorkerProcessBase_SendMessageToParentProcess, _WorkerProcessBase_SetupPrometheusEndPoints;
|
|
26
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
6
|
exports.WorkerProcessBase = void 0;
|
|
28
7
|
const debug_1 = __importDefault(require("debug"));
|
|
@@ -46,307 +25,16 @@ const server_1 = require("./server");
|
|
|
46
25
|
* @property {boolean} [wssServer=false] - Create a web socket server on this worker instance
|
|
47
26
|
*/
|
|
48
27
|
class WorkerProcessBase extends processbase_1.ProcessBase {
|
|
28
|
+
#io = null;
|
|
29
|
+
#httpServer = null;
|
|
30
|
+
#expressServer = null;
|
|
31
|
+
#inFlightMessage = {};
|
|
32
|
+
#requestResponseMessageTimeout = 2000; //@@ config
|
|
49
33
|
constructor(options) {
|
|
50
34
|
super(options);
|
|
51
|
-
_WorkerProcessBase_io.set(this, null);
|
|
52
|
-
_WorkerProcessBase_httpServer.set(this, null);
|
|
53
|
-
_WorkerProcessBase_expressServer.set(this, null);
|
|
54
|
-
_WorkerProcessBase_inFlightMessage.set(this, {});
|
|
55
|
-
_WorkerProcessBase_requestResponseMessageTimeout.set(this, 2000); //@@ config
|
|
56
|
-
_WorkerProcessBase_SendMessageToParentProcess.set(this, (message) => {
|
|
57
|
-
return new Promise((resolve, reject) => {
|
|
58
|
-
if (__classPrivateFieldGet(this, _WorkerProcessBase_inFlightMessage, "f")[message.id]) {
|
|
59
|
-
reject(`Message with id: [${message.id}] already exists within the Request/Response record structure`);
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
__classPrivateFieldGet(this, _WorkerProcessBase_inFlightMessage, "f")[message.id] = {
|
|
63
|
-
iPCMessagePayload: Object.assign({}, message),
|
|
64
|
-
cb: () => {
|
|
65
|
-
const detail = __classPrivateFieldGet(this, _WorkerProcessBase_inFlightMessage, "f")[message.id].iPCMessagePayload.responseDetail;
|
|
66
|
-
clearTimeout(__classPrivateFieldGet(this, _WorkerProcessBase_inFlightMessage, "f")[message.id].timeout);
|
|
67
|
-
setTimeout(() => {
|
|
68
|
-
delete __classPrivateFieldGet(this, _WorkerProcessBase_inFlightMessage, "f")[message.id];
|
|
69
|
-
}, 0).unref();
|
|
70
|
-
debug(`Resolving response message with id: [${message.id}] from parent process via IPC. Details: [${JSON.stringify(__classPrivateFieldGet(this, _WorkerProcessBase_inFlightMessage, "f")[message.id].iPCMessagePayload)}]`.green);
|
|
71
|
-
resolve(detail);
|
|
72
|
-
},
|
|
73
|
-
timeout: setTimeout(() => {
|
|
74
|
-
setTimeout(() => {
|
|
75
|
-
delete __classPrivateFieldGet(this, _WorkerProcessBase_inFlightMessage, "f")[message.id];
|
|
76
|
-
}, 0).unref();
|
|
77
|
-
debug(`Timeout has occurred after: [${__classPrivateFieldGet(this, _WorkerProcessBase_requestResponseMessageTimeout, "f")}]ms with message id: [${message.id}]. Details: [${JSON.stringify(__classPrivateFieldGet(this, _WorkerProcessBase_inFlightMessage, "f")[message.id].iPCMessagePayload)}]`.red);
|
|
78
|
-
reject('Did not receive response form parent process.');
|
|
79
|
-
}, __classPrivateFieldGet(this, _WorkerProcessBase_requestResponseMessageTimeout, "f")) // max message timeout allowed
|
|
80
|
-
};
|
|
81
|
-
debug(`Sending message with id: [${message.id}] to parent process via IPC. Details: [${JSON.stringify(__classPrivateFieldGet(this, _WorkerProcessBase_inFlightMessage, "f")[message.id].iPCMessagePayload)}]`.yellow);
|
|
82
|
-
process.send(message);
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
87
|
-
);
|
|
88
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
-
this.AddWorker = (options) => __awaiter(this, void 0, void 0, function* () {
|
|
90
|
-
const workerResponse = yield __classPrivateFieldGet(this, _WorkerProcessBase_SendMessageToParentProcess, "f").call(this, {
|
|
91
|
-
requestResponse: true,
|
|
92
|
-
id: (0, uuid_1.v4)(),
|
|
93
|
-
command: commonTypes_1.IPCMessageCommand.AddWorker,
|
|
94
|
-
requestDetail: {
|
|
95
|
-
options
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
return workerResponse.workerId;
|
|
99
|
-
});
|
|
100
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
|
-
this.DeleteWorker = (workerId, options) => __awaiter(this, void 0, void 0, function* () {
|
|
102
|
-
const workerResponse = yield __classPrivateFieldGet(this, _WorkerProcessBase_SendMessageToParentProcess, "f").call(this, {
|
|
103
|
-
requestResponse: true,
|
|
104
|
-
id: (0, uuid_1.v4)(),
|
|
105
|
-
command: commonTypes_1.IPCMessageCommand.DeleteWorker,
|
|
106
|
-
requestDetail: {
|
|
107
|
-
options,
|
|
108
|
-
workerId
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
return workerResponse;
|
|
112
|
-
});
|
|
113
|
-
// Setup server to Prometheus scrapes:
|
|
114
|
-
_WorkerProcessBase_SetupPrometheusEndPoints.set(this, (expressServer) => {
|
|
115
|
-
// AggregatorRegistry is required here in the worker as well as the master in order for prom-client to work correctly.
|
|
116
|
-
new prom_client_1.AggregatorRegistry();
|
|
117
|
-
const prefix = 'sts_';
|
|
118
|
-
(0, prom_client_1.collectDefaultMetrics)({
|
|
119
|
-
labels: { NODE_APP_INSTANCE: process.pid },
|
|
120
|
-
prefix: prefix
|
|
121
|
-
});
|
|
122
|
-
const c = new prom_client_1.Counter({
|
|
123
|
-
name: 'sts_test_counter',
|
|
124
|
-
help: 'Example of a counter',
|
|
125
|
-
labelNames: ['code'],
|
|
126
|
-
});
|
|
127
|
-
setInterval(() => {
|
|
128
|
-
c.inc({ code: 200 });
|
|
129
|
-
}, 1000).unref();
|
|
130
|
-
setInterval(() => {
|
|
131
|
-
c.inc({ code: 400 });
|
|
132
|
-
c.inc({ code: 'worker_' + process.pid });
|
|
133
|
-
}, 500).unref();
|
|
134
|
-
expressServer.get('/metrics', (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
135
|
-
try {
|
|
136
|
-
res.set('Content-Type', prom_client_1.register.contentType);
|
|
137
|
-
res.end(yield prom_client_1.register.metrics());
|
|
138
|
-
}
|
|
139
|
-
catch (ex) {
|
|
140
|
-
res.status(500).end(ex);
|
|
141
|
-
}
|
|
142
|
-
}));
|
|
143
|
-
expressServer.get('/metrics/counter', (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
144
|
-
try {
|
|
145
|
-
res.set('Content-Type', prom_client_1.register.contentType);
|
|
146
|
-
res.end(yield prom_client_1.register.getSingleMetricAsString('test_counter'));
|
|
147
|
-
}
|
|
148
|
-
catch (ex) {
|
|
149
|
-
res.status(500).end(ex);
|
|
150
|
-
}
|
|
151
|
-
}));
|
|
152
|
-
});
|
|
153
|
-
this.SetupServer = () => __awaiter(this, void 0, void 0, function* () {
|
|
154
|
-
this.SetupInstrumentation();
|
|
155
|
-
setTimeout(() => {
|
|
156
|
-
this.SetupServerEx();
|
|
157
|
-
}, 100);
|
|
158
|
-
});
|
|
159
|
-
this.SetupServerEx = () => __awaiter(this, void 0, void 0, function* () {
|
|
160
|
-
this.ProcessStartup();
|
|
161
|
-
if (this.options.expressServerRouteFactory || this.options.expressServerRouteStaticFactory) {
|
|
162
|
-
__classPrivateFieldSet(this, _WorkerProcessBase_expressServer, new server_1.STSExpressServer(this.options, this), "f");
|
|
163
|
-
}
|
|
164
|
-
const LogEx = this.LogEx;
|
|
165
|
-
/*
|
|
166
|
-
if (this.instruments !== null) {
|
|
167
|
-
this.instruments[Gauge.LOGGER].consoleLogging = this.options.consoleLogging;
|
|
168
|
-
this.instruments[Gauge.LOGGER].instrumentLogging = this.options.instrumentLogging;
|
|
169
|
-
}
|
|
170
|
-
*/
|
|
171
|
-
LogEx(`Worker instance starting. Service instance Id: [${this.options.serviceInstanceId}]`);
|
|
172
|
-
__classPrivateFieldSet(this, _WorkerProcessBase_httpServer, null, "f");
|
|
173
|
-
let shuttingDown = false;
|
|
174
|
-
const Terminate = (performExit = true) => __awaiter(this, void 0, void 0, function* () {
|
|
175
|
-
if (shuttingDown === false) {
|
|
176
|
-
shuttingDown = true;
|
|
177
|
-
yield this.ProcessTerminate();
|
|
178
|
-
this.ProcessTerminating();
|
|
179
|
-
LogEx(`Stopping instruments.`);
|
|
180
|
-
//@@StopInstruments(this.instruments);
|
|
181
|
-
if (this.options.wssServer === true && __classPrivateFieldGet(this, _WorkerProcessBase_io, "f") !== null) {
|
|
182
|
-
LogEx(`Disconnect Sockets.`);
|
|
183
|
-
if (this.socketIoHelper !== null) {
|
|
184
|
-
this.socketIoHelper.DisconnectSockets();
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
__classPrivateFieldGet(this, _WorkerProcessBase_io, "f").disconnectSockets();
|
|
188
|
-
}
|
|
189
|
-
this.socketIoHelper = null;
|
|
190
|
-
__classPrivateFieldSet(this, _WorkerProcessBase_io, null, "f");
|
|
191
|
-
}
|
|
192
|
-
if (this.options.httpServer === true) {
|
|
193
|
-
LogEx(`Closing httpServer.`);
|
|
194
|
-
yield __classPrivateFieldGet(this, _WorkerProcessBase_httpServer, "f").close();
|
|
195
|
-
}
|
|
196
|
-
if (this.options.useDatabase) {
|
|
197
|
-
LogEx(`Ending database connections and pools.`);
|
|
198
|
-
yield this.TerminateDatabase();
|
|
199
|
-
//await this.accessLayer.enddatabase();
|
|
200
|
-
}
|
|
201
|
-
LogEx(`Performing exit value: [${performExit}]`);
|
|
202
|
-
if (performExit) {
|
|
203
|
-
LogEx(`Process will self terminate with process.exit(0).`);
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
LogEx(`Child process will not self terminate. Terminate will be handled by master process.`);
|
|
207
|
-
}
|
|
208
|
-
if (this.InstrumentController && this.InstrumentController.Workers.length > 0) {
|
|
209
|
-
setTimeout(() => {
|
|
210
|
-
if (this.InstrumentController && this.InstrumentController.Workers.length > 0) {
|
|
211
|
-
this.InstrumentController.InstrumentTerminate();
|
|
212
|
-
}
|
|
213
|
-
}, 100);
|
|
214
|
-
}
|
|
215
|
-
//@@ always return here appears to always cleanly exit
|
|
216
|
-
// and cleanly exit from socket.io cluster adaptor
|
|
217
|
-
// without return here, socket.io cluster adaptor terminates in an error state
|
|
218
|
-
// as the implementation relies on cluster.on to send messages to worker threads
|
|
219
|
-
// but these have already been closed from the process.exit(0) below.
|
|
220
|
-
yield (0, stsutils_1.Sleep)(1000); // Allow socket.io time to clean-up
|
|
221
|
-
if (performExit) {
|
|
222
|
-
setTimeout(() => {
|
|
223
|
-
process.exit(0);
|
|
224
|
-
}, 0);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
LogEx(`Process already terminating.`);
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
// Receive messages from the master process.
|
|
232
|
-
//const self = this;
|
|
233
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
234
|
-
process.on('message', (msg) => __awaiter(this, void 0, void 0, function* () {
|
|
235
|
-
if (msg.requestResponse) {
|
|
236
|
-
const iPCMessagePayload = msg;
|
|
237
|
-
if (iPCMessagePayload.id) {
|
|
238
|
-
if (__classPrivateFieldGet(this, _WorkerProcessBase_inFlightMessage, "f")[iPCMessagePayload.id]) {
|
|
239
|
-
const responseMessage = __classPrivateFieldGet(this, _WorkerProcessBase_inFlightMessage, "f")[iPCMessagePayload.id];
|
|
240
|
-
responseMessage.iPCMessagePayload.responseDetail = Object.assign({}, iPCMessagePayload.responseDetail);
|
|
241
|
-
responseMessage.cb();
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
throw new Error(`Could not find Request/Response message with id: [${iPCMessagePayload.id}]`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
throw new Error(`Message does not have id attribute. [${JSON.stringify(iPCMessagePayload)}]`);
|
|
249
|
-
}
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (msg.command) //@@ constants
|
|
253
|
-
{
|
|
254
|
-
switch (msg.command) {
|
|
255
|
-
case 'Terminate':
|
|
256
|
-
LogEx((`Received ` + colors_1.default.bold(`Terminate`.italic) + ` message from master thread`).gray);
|
|
257
|
-
yield Terminate(false); // Don't kill the child process here, the master will take care of that ...
|
|
258
|
-
break;
|
|
259
|
-
case 'TerminateAndKill':
|
|
260
|
-
LogEx((`Received ` + colors_1.default.bold(`Terminate`.italic) + ` message from master thread`).gray);
|
|
261
|
-
yield Terminate(true);
|
|
262
|
-
break;
|
|
263
|
-
case 'Message':
|
|
264
|
-
LogEx((`Received ` + colors_1.default.bold(`Message`.italic) + ` message from master thread`).gray);
|
|
265
|
-
this.ReceivedMessageFromMaster(msg.data);
|
|
266
|
-
break;
|
|
267
|
-
case 'Response': // General response to a req/response interaction
|
|
268
|
-
msg.details;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}));
|
|
272
|
-
if (this.options.httpServer === true) {
|
|
273
|
-
if (this.options.httpsServer === true) {
|
|
274
|
-
const options = {
|
|
275
|
-
key: fs_1.default.readFileSync(this.options.httpsServerKeyPath),
|
|
276
|
-
cert: fs_1.default.readFileSync(this.options.httpsServerCertificatePath)
|
|
277
|
-
};
|
|
278
|
-
__classPrivateFieldSet(this, _WorkerProcessBase_httpServer, (0, https_1.createServer)(options, __classPrivateFieldGet(this, _WorkerProcessBase_expressServer, "f").App), "f");
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
__classPrivateFieldSet(this, _WorkerProcessBase_httpServer, (0, http_1.createServer)(__classPrivateFieldGet(this, _WorkerProcessBase_expressServer, "f").App), "f");
|
|
282
|
-
}
|
|
283
|
-
//this.#httpServer.maxConnections = 50;
|
|
284
|
-
if (this.options.prometheusSupport === true) {
|
|
285
|
-
__classPrivateFieldGet(this, _WorkerProcessBase_SetupPrometheusEndPoints, "f").call(this, __classPrivateFieldGet(this, _WorkerProcessBase_expressServer, "f").App);
|
|
286
|
-
}
|
|
287
|
-
// Setup the web socket server (using socket.io) if enabled.
|
|
288
|
-
if (this.options.wssServer === true) {
|
|
289
|
-
// socket.io
|
|
290
|
-
// WebSocket
|
|
291
|
-
const options = {
|
|
292
|
-
transports: ["websocket"] // or [ "websocket", "polling" ] (to use long-poolling. Note that the order matters)
|
|
293
|
-
// The default path is /socket.io
|
|
294
|
-
// This can be changed with the path option as shown below
|
|
295
|
-
//,path: '/zzz'
|
|
296
|
-
};
|
|
297
|
-
//this.#io = require("socket.io")(this.#httpServer, options);
|
|
298
|
-
__classPrivateFieldSet(this, _WorkerProcessBase_io, new socket_io_1.Server(__classPrivateFieldGet(this, _WorkerProcessBase_httpServer, "f"), options), "f");
|
|
299
|
-
__classPrivateFieldGet(this, _WorkerProcessBase_io, "f").adapter((0, cluster_adapter_1.createAdapter)());
|
|
300
|
-
// To use a seperate socket server, the code below can be applied.
|
|
301
|
-
// this.#io = require("socket.io")(options);
|
|
302
|
-
// this.#io.adapter(createAdapter());
|
|
303
|
-
// this.#io.listen(3006);
|
|
304
|
-
// LogEx(`socket.io init`);
|
|
305
|
-
__classPrivateFieldGet(this, _WorkerProcessBase_io, "f").engine.on("connection_error", (err) => {
|
|
306
|
-
LogEx(err.req); // the request object
|
|
307
|
-
LogEx(err.code); // the error code, for example 1
|
|
308
|
-
LogEx(err.message); // the error message, for example "Session ID unknown"
|
|
309
|
-
LogEx(err.context); // some additional error context
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
try {
|
|
313
|
-
// https://stackoverflow.com/questions/21342828/node-express-unix-domain-socket-permissions
|
|
314
|
-
//@@httpServer.listen('/tmp/stsrest01.sock').on('listening', () =>
|
|
315
|
-
//@@httpServer.listen('/var/run/sts/stsrest01.sock').on('listening', () =>
|
|
316
|
-
__classPrivateFieldGet(this, _WorkerProcessBase_httpServer, "f").listen(this.options.listenPort, () => {
|
|
317
|
-
//@@chmodSync(this.options.port, 511);
|
|
318
|
-
}).on('listening', () => {
|
|
319
|
-
LogEx(`live on ${this.options.endpoint}:${this.options.listenPort}${this.options.apiRoot}`);
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
catch (error) {
|
|
323
|
-
console.error(error);
|
|
324
|
-
throw error;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
/*
|
|
328
|
-
if (this.publishBroker !== null) {
|
|
329
|
-
setTimeout(() => {
|
|
330
|
-
this.publishBroker.StartPublish();
|
|
331
|
-
}, 0);
|
|
332
|
-
}
|
|
333
|
-
*/
|
|
334
|
-
// Signal Codes
|
|
335
|
-
// https://en.wikipedia.org/wiki/Signal_(IPC)
|
|
336
|
-
process.on('SIGTERM', () => __awaiter(this, void 0, void 0, function* () {
|
|
337
|
-
LogEx(`SIGTERM signal received for worker: ${process.pid}`);
|
|
338
|
-
yield Terminate();
|
|
339
|
-
}));
|
|
340
|
-
process.on('SIGINT', () => __awaiter(this, void 0, void 0, function* () {
|
|
341
|
-
LogEx(`SIGINT signal received for worker: ${process.pid}`);
|
|
342
|
-
yield Terminate();
|
|
343
|
-
}));
|
|
344
|
-
this.WorkerStarted();
|
|
345
|
-
LogEx(`Worker process:${process.pid} started`.green);
|
|
346
|
-
});
|
|
347
35
|
}
|
|
348
36
|
get httpServer() {
|
|
349
|
-
return
|
|
37
|
+
return this.#httpServer;
|
|
350
38
|
}
|
|
351
39
|
WorkerStarted() {
|
|
352
40
|
return null;
|
|
@@ -364,15 +52,303 @@ class WorkerProcessBase extends processbase_1.ProcessBase {
|
|
|
364
52
|
});
|
|
365
53
|
}
|
|
366
54
|
get io() {
|
|
367
|
-
return
|
|
55
|
+
return this.#io;
|
|
368
56
|
}
|
|
57
|
+
#SendMessageToParentProcess = (message) => {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
if (this.#inFlightMessage[message.id]) {
|
|
60
|
+
reject(`Message with id: [${message.id}] already exists within the Request/Response record structure`);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.#inFlightMessage[message.id] = {
|
|
64
|
+
iPCMessagePayload: { ...message },
|
|
65
|
+
cb: () => {
|
|
66
|
+
const detail = this.#inFlightMessage[message.id].iPCMessagePayload.responseDetail;
|
|
67
|
+
clearTimeout(this.#inFlightMessage[message.id].timeout);
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
delete this.#inFlightMessage[message.id];
|
|
70
|
+
}, 0).unref();
|
|
71
|
+
debug(`Resolving response message with id: [${message.id}] from parent process via IPC. Details: [${JSON.stringify(this.#inFlightMessage[message.id].iPCMessagePayload)}]`.green);
|
|
72
|
+
resolve(detail);
|
|
73
|
+
},
|
|
74
|
+
timeout: setTimeout(() => {
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
delete this.#inFlightMessage[message.id];
|
|
77
|
+
}, 0).unref();
|
|
78
|
+
debug(`Timeout has occurred after: [${this.#requestResponseMessageTimeout}]ms with message id: [${message.id}]. Details: [${JSON.stringify(this.#inFlightMessage[message.id].iPCMessagePayload)}]`.red);
|
|
79
|
+
reject('Did not receive response form parent process.');
|
|
80
|
+
}, this.#requestResponseMessageTimeout) // max message timeout allowed
|
|
81
|
+
};
|
|
82
|
+
debug(`Sending message with id: [${message.id}] to parent process via IPC. Details: [${JSON.stringify(this.#inFlightMessage[message.id].iPCMessagePayload)}]`.yellow);
|
|
83
|
+
process.send(message);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
88
|
+
AddWorker = async (options) => {
|
|
89
|
+
const workerResponse = await this.#SendMessageToParentProcess({
|
|
90
|
+
requestResponse: true,
|
|
91
|
+
id: (0, uuid_1.v4)(),
|
|
92
|
+
command: commonTypes_1.IPCMessageCommand.AddWorker,
|
|
93
|
+
requestDetail: {
|
|
94
|
+
options
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
return workerResponse.workerId;
|
|
98
|
+
};
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
+
DeleteWorker = async (workerId, options) => {
|
|
101
|
+
const workerResponse = await this.#SendMessageToParentProcess({
|
|
102
|
+
requestResponse: true,
|
|
103
|
+
id: (0, uuid_1.v4)(),
|
|
104
|
+
command: commonTypes_1.IPCMessageCommand.DeleteWorker,
|
|
105
|
+
requestDetail: {
|
|
106
|
+
options,
|
|
107
|
+
workerId
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return workerResponse;
|
|
111
|
+
};
|
|
369
112
|
ProcessTerminating() {
|
|
370
113
|
return null;
|
|
371
114
|
}
|
|
115
|
+
// Setup server to Prometheus scrapes:
|
|
116
|
+
#SetupPrometheusEndPoints = (expressServer) => {
|
|
117
|
+
// AggregatorRegistry is required here in the worker as well as the master in order for prom-client to work correctly.
|
|
118
|
+
new prom_client_1.AggregatorRegistry();
|
|
119
|
+
const prefix = 'sts_';
|
|
120
|
+
(0, prom_client_1.collectDefaultMetrics)({
|
|
121
|
+
labels: { NODE_APP_INSTANCE: process.pid },
|
|
122
|
+
prefix: prefix
|
|
123
|
+
});
|
|
124
|
+
const c = new prom_client_1.Counter({
|
|
125
|
+
name: 'sts_test_counter',
|
|
126
|
+
help: 'Example of a counter',
|
|
127
|
+
labelNames: ['code'],
|
|
128
|
+
});
|
|
129
|
+
setInterval(() => {
|
|
130
|
+
c.inc({ code: 200 });
|
|
131
|
+
}, 1000).unref();
|
|
132
|
+
setInterval(() => {
|
|
133
|
+
c.inc({ code: 400 });
|
|
134
|
+
c.inc({ code: 'worker_' + process.pid });
|
|
135
|
+
}, 500).unref();
|
|
136
|
+
expressServer.get('/metrics', async (req, res) => {
|
|
137
|
+
try {
|
|
138
|
+
res.set('Content-Type', prom_client_1.register.contentType);
|
|
139
|
+
res.end(await prom_client_1.register.metrics());
|
|
140
|
+
}
|
|
141
|
+
catch (ex) {
|
|
142
|
+
res.status(500).end(ex);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
expressServer.get('/metrics/counter', async (req, res) => {
|
|
146
|
+
try {
|
|
147
|
+
res.set('Content-Type', prom_client_1.register.contentType);
|
|
148
|
+
res.end(await prom_client_1.register.getSingleMetricAsString('test_counter'));
|
|
149
|
+
}
|
|
150
|
+
catch (ex) {
|
|
151
|
+
res.status(500).end(ex);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
};
|
|
372
155
|
get expressServer() {
|
|
373
|
-
return
|
|
156
|
+
return this.#expressServer;
|
|
374
157
|
}
|
|
158
|
+
SetupServer = async () => {
|
|
159
|
+
this.SetupInstrumentation();
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
this.SetupServerEx();
|
|
162
|
+
}, 100);
|
|
163
|
+
};
|
|
164
|
+
SetupServerEx = async () => {
|
|
165
|
+
this.ProcessStartup();
|
|
166
|
+
if (this.options.expressServerRouteFactory || this.options.expressServerRouteStaticFactory) {
|
|
167
|
+
this.#expressServer = new server_1.STSExpressServer(this.options, this);
|
|
168
|
+
}
|
|
169
|
+
const LogEx = this.LogEx;
|
|
170
|
+
/*
|
|
171
|
+
if (this.instruments !== null) {
|
|
172
|
+
this.instruments[Gauge.LOGGER].consoleLogging = this.options.consoleLogging;
|
|
173
|
+
this.instruments[Gauge.LOGGER].instrumentLogging = this.options.instrumentLogging;
|
|
174
|
+
}
|
|
175
|
+
*/
|
|
176
|
+
LogEx(`Worker instance starting. Service instance Id: [${this.options.serviceInstanceId}]`);
|
|
177
|
+
this.#httpServer = null;
|
|
178
|
+
let shuttingDown = false;
|
|
179
|
+
const Terminate = async (performExit = true) => {
|
|
180
|
+
if (shuttingDown === false) {
|
|
181
|
+
shuttingDown = true;
|
|
182
|
+
await this.ProcessTerminate();
|
|
183
|
+
this.ProcessTerminating();
|
|
184
|
+
LogEx(`Stopping instruments.`);
|
|
185
|
+
//@@StopInstruments(this.instruments);
|
|
186
|
+
if (this.options.wssServer === true && this.#io !== null) {
|
|
187
|
+
LogEx(`Disconnect Sockets.`);
|
|
188
|
+
if (this.socketIoHelper !== null) {
|
|
189
|
+
this.socketIoHelper.DisconnectSockets();
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
this.#io.disconnectSockets();
|
|
193
|
+
}
|
|
194
|
+
this.socketIoHelper = null;
|
|
195
|
+
this.#io = null;
|
|
196
|
+
}
|
|
197
|
+
if (this.options.httpServer === true) {
|
|
198
|
+
LogEx(`Closing httpServer.`);
|
|
199
|
+
await this.#httpServer.close();
|
|
200
|
+
}
|
|
201
|
+
if (this.options.useDatabase) {
|
|
202
|
+
LogEx(`Ending database connections and pools.`);
|
|
203
|
+
await this.TerminateDatabase();
|
|
204
|
+
//await this.accessLayer.enddatabase();
|
|
205
|
+
}
|
|
206
|
+
LogEx(`Performing exit value: [${performExit}]`);
|
|
207
|
+
if (performExit) {
|
|
208
|
+
LogEx(`Process will self terminate with process.exit(0).`);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
LogEx(`Child process will not self terminate. Terminate will be handled by master process.`);
|
|
212
|
+
}
|
|
213
|
+
if (this.InstrumentController && this.InstrumentController.Workers.length > 0) {
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
if (this.InstrumentController && this.InstrumentController.Workers.length > 0) {
|
|
216
|
+
this.InstrumentController.InstrumentTerminate();
|
|
217
|
+
}
|
|
218
|
+
}, 100);
|
|
219
|
+
}
|
|
220
|
+
//@@ always return here appears to always cleanly exit
|
|
221
|
+
// and cleanly exit from socket.io cluster adaptor
|
|
222
|
+
// without return here, socket.io cluster adaptor terminates in an error state
|
|
223
|
+
// as the implementation relies on cluster.on to send messages to worker threads
|
|
224
|
+
// but these have already been closed from the process.exit(0) below.
|
|
225
|
+
await (0, stsutils_1.Sleep)(1000); // Allow socket.io time to clean-up
|
|
226
|
+
if (performExit) {
|
|
227
|
+
setTimeout(() => {
|
|
228
|
+
process.exit(0);
|
|
229
|
+
}, 0);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
LogEx(`Process already terminating.`);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
// Receive messages from the master process.
|
|
237
|
+
//const self = this;
|
|
238
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
239
|
+
process.on('message', async (msg) => {
|
|
240
|
+
if (msg.requestResponse) {
|
|
241
|
+
const iPCMessagePayload = msg;
|
|
242
|
+
if (iPCMessagePayload.id) {
|
|
243
|
+
if (this.#inFlightMessage[iPCMessagePayload.id]) {
|
|
244
|
+
const responseMessage = this.#inFlightMessage[iPCMessagePayload.id];
|
|
245
|
+
responseMessage.iPCMessagePayload.responseDetail = { ...iPCMessagePayload.responseDetail };
|
|
246
|
+
responseMessage.cb();
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
throw new Error(`Could not find Request/Response message with id: [${iPCMessagePayload.id}]`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
throw new Error(`Message does not have id attribute. [${JSON.stringify(iPCMessagePayload)}]`);
|
|
254
|
+
}
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (msg.command) //@@ constants
|
|
258
|
+
{
|
|
259
|
+
switch (msg.command) {
|
|
260
|
+
case 'Terminate':
|
|
261
|
+
LogEx((`Received ` + colors_1.default.bold(`Terminate`.italic) + ` message from master thread`).gray);
|
|
262
|
+
await Terminate(false); // Don't kill the child process here, the master will take care of that ...
|
|
263
|
+
break;
|
|
264
|
+
case 'TerminateAndKill':
|
|
265
|
+
LogEx((`Received ` + colors_1.default.bold(`Terminate`.italic) + ` message from master thread`).gray);
|
|
266
|
+
await Terminate(true);
|
|
267
|
+
break;
|
|
268
|
+
case 'Message':
|
|
269
|
+
LogEx((`Received ` + colors_1.default.bold(`Message`.italic) + ` message from master thread`).gray);
|
|
270
|
+
this.ReceivedMessageFromMaster(msg.data);
|
|
271
|
+
break;
|
|
272
|
+
case 'Response': // General response to a req/response interaction
|
|
273
|
+
msg.details;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
if (this.options.httpServer === true) {
|
|
278
|
+
if (this.options.httpsServer === true) {
|
|
279
|
+
const options = {
|
|
280
|
+
key: fs_1.default.readFileSync(this.options.httpsServerKeyPath),
|
|
281
|
+
cert: fs_1.default.readFileSync(this.options.httpsServerCertificatePath)
|
|
282
|
+
};
|
|
283
|
+
this.#httpServer = (0, https_1.createServer)(options, this.#expressServer.App);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
this.#httpServer = (0, http_1.createServer)(this.#expressServer.App);
|
|
287
|
+
}
|
|
288
|
+
//this.#httpServer.maxConnections = 50;
|
|
289
|
+
if (this.options.prometheusSupport === true) {
|
|
290
|
+
this.#SetupPrometheusEndPoints(this.#expressServer.App);
|
|
291
|
+
}
|
|
292
|
+
// Setup the web socket server (using socket.io) if enabled.
|
|
293
|
+
if (this.options.wssServer === true) {
|
|
294
|
+
// socket.io
|
|
295
|
+
// WebSocket
|
|
296
|
+
const options = {
|
|
297
|
+
transports: ["websocket"] // or [ "websocket", "polling" ] (to use long-poolling. Note that the order matters)
|
|
298
|
+
// The default path is /socket.io
|
|
299
|
+
// This can be changed with the path option as shown below
|
|
300
|
+
//,path: '/zzz'
|
|
301
|
+
};
|
|
302
|
+
//this.#io = require("socket.io")(this.#httpServer, options);
|
|
303
|
+
this.#io = new socket_io_1.Server(this.#httpServer, options);
|
|
304
|
+
this.#io.adapter((0, cluster_adapter_1.createAdapter)());
|
|
305
|
+
// To use a seperate socket server, the code below can be applied.
|
|
306
|
+
// this.#io = require("socket.io")(options);
|
|
307
|
+
// this.#io.adapter(createAdapter());
|
|
308
|
+
// this.#io.listen(3006);
|
|
309
|
+
// LogEx(`socket.io init`);
|
|
310
|
+
this.#io.engine.on("connection_error", (err) => {
|
|
311
|
+
LogEx(err.req); // the request object
|
|
312
|
+
LogEx(err.code); // the error code, for example 1
|
|
313
|
+
LogEx(err.message); // the error message, for example "Session ID unknown"
|
|
314
|
+
LogEx(err.context); // some additional error context
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
// https://stackoverflow.com/questions/21342828/node-express-unix-domain-socket-permissions
|
|
319
|
+
//@@httpServer.listen('/tmp/stsrest01.sock').on('listening', () =>
|
|
320
|
+
//@@httpServer.listen('/var/run/sts/stsrest01.sock').on('listening', () =>
|
|
321
|
+
this.#httpServer.listen(this.options.listenPort, () => {
|
|
322
|
+
//@@chmodSync(this.options.port, 511);
|
|
323
|
+
}).on('listening', () => {
|
|
324
|
+
LogEx(`live on ${this.options.endpoint}:${this.options.listenPort}${this.options.apiRoot}`);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
console.error(error);
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/*
|
|
333
|
+
if (this.publishBroker !== null) {
|
|
334
|
+
setTimeout(() => {
|
|
335
|
+
this.publishBroker.StartPublish();
|
|
336
|
+
}, 0);
|
|
337
|
+
}
|
|
338
|
+
*/
|
|
339
|
+
// Signal Codes
|
|
340
|
+
// https://en.wikipedia.org/wiki/Signal_(IPC)
|
|
341
|
+
process.on('SIGTERM', async () => {
|
|
342
|
+
LogEx(`SIGTERM signal received for worker: ${process.pid}`);
|
|
343
|
+
await Terminate();
|
|
344
|
+
});
|
|
345
|
+
process.on('SIGINT', async () => {
|
|
346
|
+
LogEx(`SIGINT signal received for worker: ${process.pid}`);
|
|
347
|
+
await Terminate();
|
|
348
|
+
});
|
|
349
|
+
this.WorkerStarted();
|
|
350
|
+
LogEx(`Worker process:${process.pid} started`.green);
|
|
351
|
+
};
|
|
375
352
|
}
|
|
376
353
|
exports.WorkerProcessBase = WorkerProcessBase;
|
|
377
|
-
_WorkerProcessBase_io = new WeakMap(), _WorkerProcessBase_httpServer = new WeakMap(), _WorkerProcessBase_expressServer = new WeakMap(), _WorkerProcessBase_inFlightMessage = new WeakMap(), _WorkerProcessBase_requestResponseMessageTimeout = new WeakMap(), _WorkerProcessBase_SendMessageToParentProcess = new WeakMap(), _WorkerProcessBase_SetupPrometheusEndPoints = new WeakMap();
|
|
378
354
|
//# sourceMappingURL=workerprocessbase.js.map
|