@nsshunt/stsappframework 3.0.94 → 3.0.96
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/package.json +1 -1
- package/src_new/authDefs.ts +37 -0
- package/src_new/authutilsnode.ts +375 -0
- package/src_new/commonTypes.ts +239 -0
- package/src_new/index.ts +22 -0
- package/src_new/influxdb/influxDBManager.ts +972 -0
- package/src_new/influxdb/influxDBManagerAgent.ts +316 -0
- package/src_new/influxdb/influxDBManagerBase.ts +111 -0
- package/src_new/influxdb/influxDBManagerService.ts +375 -0
- package/src_new/instrumentationsubscriber.ts +286 -0
- package/src_new/kafka/IMKafkaManager.ts +154 -0
- package/src_new/kafka/kafkaconsumer.ts +82 -0
- package/src_new/kafka/kafkamanager.ts +186 -0
- package/src_new/kafka/kafkaproducer.ts +58 -0
- package/src_new/kafkatesting/config.ts +10 -0
- package/src_new/kafkatesting/consume.ts +116 -0
- package/src_new/kafkatesting/produce.ts +155 -0
- package/src_new/masterprocessbase.ts +590 -0
- package/src_new/middleware/serverNetworkMiddleware.ts +240 -0
- package/src_new/network.ts +36 -0
- package/src_new/processbase.ts +413 -0
- package/src_new/processoptions.ts +164 -0
- package/src_new/publishertransports/publishTransportDirect.ts +45 -0
- package/src_new/publishertransports/publishTransportUtils.ts +53 -0
- package/src_new/server.ts +141 -0
- package/src_new/serverprocessbase.ts +393 -0
- package/src_new/singleprocessbase.ts +123 -0
- package/src_new/socketIoServerHelper.ts +177 -0
- package/src_new/stscontrollerbase.ts +15 -0
- package/src_new/stslatencycontroller.ts +27 -0
- package/src_new/stslatencyroute.ts +16 -0
- package/src_new/stsrouterbase.ts +22 -0
- package/src_new/tcpclient/app.ts +19 -0
- package/src_new/tcpclient/app2.ts +56 -0
- package/src_new/tcpserver/app.ts +11 -0
- package/src_new/tcpserver/appConfig.ts +65 -0
- package/src_new/tcpserver/appmaster.ts +522 -0
- package/src_new/validation/errors.ts +6 -0
- package/src_new/webworkertesting/app.ts +49 -0
- package/src_new/webworkertesting/worker.ts +24 -0
- package/src_new/workerprocessbase.test.ts +47 -0
- package/src_new/workerprocessbase.ts +187 -0
- package/src_working/authDefs.ts +37 -0
- package/src_working/authutilsnode.ts +373 -0
- package/src_working/commonTypes.ts +239 -0
- package/src_working/index.ts +22 -0
- package/src_working/influxdb/influxDBManager.ts +970 -0
- package/src_working/influxdb/influxDBManagerAgent.ts +314 -0
- package/src_working/influxdb/influxDBManagerBase.ts +109 -0
- package/src_working/influxdb/influxDBManagerService.ts +373 -0
- package/src_working/instrumentationsubscriber.ts +283 -0
- package/src_working/kafka/IMKafkaManager.ts +152 -0
- package/src_working/kafka/kafkaconsumer.ts +82 -0
- package/src_working/kafka/kafkamanager.ts +186 -0
- package/src_working/kafka/kafkaproducer.ts +58 -0
- package/src_working/kafkatesting/config.ts +10 -0
- package/src_working/kafkatesting/consume.ts +116 -0
- package/src_working/kafkatesting/produce.ts +153 -0
- package/src_working/masterprocessbase.ts +598 -0
- package/src_working/middleware/serverNetworkMiddleware.ts +240 -0
- package/src_working/network.ts +36 -0
- package/src_working/processbase.ts +411 -0
- package/src_working/processoptions.ts +164 -0
- package/src_working/publishertransports/publishTransportDirect.ts +45 -0
- package/src_working/publishertransports/publishTransportUtils.ts +53 -0
- package/src_working/server.ts +141 -0
- package/src_working/serverprocessbase.ts +393 -0
- package/src_working/singleprocessbase.ts +121 -0
- package/src_working/socketIoServerHelper.ts +177 -0
- package/src_working/stscontrollerbase.ts +15 -0
- package/src_working/stslatencycontroller.ts +27 -0
- package/src_working/stslatencyroute.ts +16 -0
- package/src_working/stsrouterbase.ts +22 -0
- package/src_working/tcpclient/app.ts +19 -0
- package/src_working/tcpclient/app2.ts +56 -0
- package/src_working/tcpserver/app.ts +11 -0
- package/src_working/tcpserver/appConfig.ts +65 -0
- package/src_working/tcpserver/appmaster.ts +544 -0
- package/src_working/validation/errors.ts +6 -0
- package/src_working/webworkertesting/app.ts +49 -0
- package/src_working/webworkertesting/worker.ts +24 -0
- package/src_working/workerprocessbase.test.ts +47 -0
- package/src_working/workerprocessbase.ts +185 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
/* eslint @typescript-eslint/no-explicit-any: 0, @typescript-eslint/no-unused-vars: 0 */ // --> OFF
|
|
2
|
+
import fs from "fs"
|
|
3
|
+
import si from 'systeminformation' // https://systeminformation.io/
|
|
4
|
+
import https from 'https'
|
|
5
|
+
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
|
|
8
|
+
import cluster, { Worker } from 'node:cluster'
|
|
9
|
+
|
|
10
|
+
import os from 'os';
|
|
11
|
+
|
|
12
|
+
import colors from 'colors'
|
|
13
|
+
|
|
14
|
+
import express from 'express'
|
|
15
|
+
|
|
16
|
+
import { setupPrimary } from '@socket.io/cluster-adapter'
|
|
17
|
+
import { createServer as createServerHttps } from 'https'
|
|
18
|
+
import { createServer } from 'http'
|
|
19
|
+
import { AggregatorRegistry } from 'prom-client'
|
|
20
|
+
|
|
21
|
+
import { $Options } from '@nsshunt/stsconfig'
|
|
22
|
+
const goptions = $Options()
|
|
23
|
+
|
|
24
|
+
import debugModule from 'debug'
|
|
25
|
+
|
|
26
|
+
import { Gauge, GaugeTypes, InstrumentGaugeTelemetry, InstrumentGaugeOptions, InstrumentHistogramTelemetry } from '@nsshunt/stsinstrumentation'
|
|
27
|
+
import { GetFirstNetworkInterface } from './network'
|
|
28
|
+
|
|
29
|
+
import { ProcessOptions, STSServerType } from './processoptions'
|
|
30
|
+
import { ProcessBase } from './processbase';
|
|
31
|
+
import { IMasterProcessBase } from './commonTypes';
|
|
32
|
+
|
|
33
|
+
import { InstrumentDefinitions } from '@nsshunt/stspublisherserver'
|
|
34
|
+
import { IPCMessagePayload, IPCMessageCommand } from './commonTypes'
|
|
35
|
+
import { REQUEST_HEADER_FIELDS_TOO_LARGE } from "http-status-codes";
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
export class MasterProcessBase extends ProcessBase implements IMasterProcessBase
|
|
39
|
+
{
|
|
40
|
+
//static WORKER_MESSAGE_EVENT = 'sts_worker_message_event';
|
|
41
|
+
#masterProcessExitTime = goptions.masterProcessExitTime;
|
|
42
|
+
#childProcessExitTime = goptions.childProcessExitTime;
|
|
43
|
+
#siValueObject = {
|
|
44
|
+
currentLoad: 'currentLoad'
|
|
45
|
+
}
|
|
46
|
+
#httpServer: any = null; // Prometheus cluster server. See https://github.com/siimon/prom-client/blob/master/example/cluster.js
|
|
47
|
+
#metricsServer: any= null;
|
|
48
|
+
#aggregatorRegistry: any = null;
|
|
49
|
+
#httpsAgent: any;
|
|
50
|
+
#debug = debugModule(`proc:${process.pid}`);
|
|
51
|
+
#checkLatency: NodeJS.Timeout | null = null;
|
|
52
|
+
#killWorkers: Record<string, string> = { };
|
|
53
|
+
#workers = 1; // Start at 1 becuase of main thread
|
|
54
|
+
#shuttingDown = false;
|
|
55
|
+
|
|
56
|
+
constructor(options: ProcessOptions)
|
|
57
|
+
{
|
|
58
|
+
super(options);
|
|
59
|
+
|
|
60
|
+
this.#httpsAgent = new https.Agent({
|
|
61
|
+
// Use basic defaults. This agent will not use keep-alive.
|
|
62
|
+
timeout: goptions.timeout,
|
|
63
|
+
rejectUnauthorized: goptions.isProduction // Allows self-signed certificates if non-production
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override CollectAdditionalTelemetry(): void {
|
|
68
|
+
si.get(this.#siValueObject).then(data => {
|
|
69
|
+
this.UpdateInstrument(Gauge.CPU_SYSTEM_LOAD_GAUGE, {
|
|
70
|
+
val: data.currentLoad.currentLoad
|
|
71
|
+
} as InstrumentGaugeTelemetry);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override GetAdditionalInstruments(): InstrumentDefinitions {
|
|
76
|
+
return [
|
|
77
|
+
[ Gauge.CPU_SYSTEM_LOAD_GAUGE, GaugeTypes.INSTRUMENT_GAUGE, {
|
|
78
|
+
interval: goptions.instrumentationObservationInterval,
|
|
79
|
+
sampleSize: goptions.instrumentationTimeWindow
|
|
80
|
+
} as InstrumentGaugeOptions]
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Note: msg removed from signature but will be passed at run-time.
|
|
86
|
+
* @param {*} msg
|
|
87
|
+
*/
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
|
89
|
+
WorkerMessageEvent(msg: any)
|
|
90
|
+
{
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
+
#WorkerMessageEvent(msg: any)
|
|
96
|
+
{
|
|
97
|
+
if (msg.command) {
|
|
98
|
+
this.WorkerMessageEvent(msg);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#InitCluster = (LogEx: any) =>
|
|
103
|
+
{
|
|
104
|
+
cluster.on('listening', () => {
|
|
105
|
+
let allListening = true;
|
|
106
|
+
for (const worker of Object.values(cluster.workers as NodeJS.Dict<Worker>)) {
|
|
107
|
+
if (worker?.isConnected) {
|
|
108
|
+
allListening = false;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (allListening) {
|
|
114
|
+
LogEx(`Service instance started.`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// https://github.com/siimon/prom-client/blob/master/example/cluster.js
|
|
120
|
+
#SetupPrometheusForMaster = (LogEx: any) => {
|
|
121
|
+
this.#metricsServer = express();
|
|
122
|
+
this.#aggregatorRegistry = new AggregatorRegistry();
|
|
123
|
+
|
|
124
|
+
switch (this.options.serverType) {
|
|
125
|
+
case STSServerType.EXPRESS_TLS : {
|
|
126
|
+
const options = {
|
|
127
|
+
key: fs.readFileSync(this.options.httpsServerKeyPath),
|
|
128
|
+
cert: fs.readFileSync(this.options.httpsServerCertificatePath)
|
|
129
|
+
};
|
|
130
|
+
this.#httpServer = createServerHttps(options, this.#metricsServer);
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
case STSServerType.EXPRESS : {
|
|
134
|
+
this.#httpServer = createServer(this.#metricsServer);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//this.#httpServer.maxConnections = 50;
|
|
138
|
+
|
|
139
|
+
this.#metricsServer.get('/cluster_metrics', async (req: any, res: any) => {
|
|
140
|
+
try {
|
|
141
|
+
const metrics = await this.#aggregatorRegistry.clusterMetrics();
|
|
142
|
+
res.set('Content-Type', this.#aggregatorRegistry.contentType);
|
|
143
|
+
res.send(metrics);
|
|
144
|
+
} catch (ex: any) {
|
|
145
|
+
res.statusCode = 500;
|
|
146
|
+
res.send(ex.message);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
//@@@ options wrong
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
// https://stackoverflow.com/questions/21342828/node-express-unix-domain-socket-permissions
|
|
154
|
+
//@@httpServer.listen('/tmp/stsrest01.sock').on('listening', () =>
|
|
155
|
+
//@@httpServer.listen('/var/run/sts/stsrest01.sock').on('listening', () =>
|
|
156
|
+
//@@httpServer.listen('/var/lib/sts/stsrest01.sock').on('listening', () =>
|
|
157
|
+
this.#httpServer.listen(this.options.prometheusClusterPort, () => {
|
|
158
|
+
//@@chmodSync(this.options.port, 511);
|
|
159
|
+
}).on('listening', () =>
|
|
160
|
+
{
|
|
161
|
+
LogEx(`Prometheus scrapes ready and live on ${this.options.endpoint}:${this.options.prometheusClusterPort}/metrics`);
|
|
162
|
+
});
|
|
163
|
+
} catch (error)
|
|
164
|
+
{
|
|
165
|
+
console.error(error);
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#CheckLatency = async () => {
|
|
171
|
+
const start = process.hrtime();
|
|
172
|
+
await this.#GetLatency();
|
|
173
|
+
this.#LatencyRequestCompleted(start);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
177
|
+
#GetLatency = async (): Promise<any> => {
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
179
|
+
let retVal: any = null;
|
|
180
|
+
// We use port rather than hostport becuase this test going outside the service and comes back in as a regular client
|
|
181
|
+
const url = `${this.options.endpoint}:${this.options.port}${this.options.apiRoot}/latency`;
|
|
182
|
+
const headers = {
|
|
183
|
+
'Content-Type': 'application/json'
|
|
184
|
+
};
|
|
185
|
+
try {
|
|
186
|
+
retVal = await axios({
|
|
187
|
+
url: url
|
|
188
|
+
,method: 'get'
|
|
189
|
+
,headers: headers
|
|
190
|
+
,httpsAgent: this.#httpsAgent
|
|
191
|
+
});
|
|
192
|
+
if (retVal.status !== 200) {
|
|
193
|
+
this.#debug(`Error (MasterProcessBase:#GetLatency): Invalid response from server: [${retVal.status}]`.magenta);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return retVal.data.detail;
|
|
197
|
+
} catch (error: any) {
|
|
198
|
+
this.#debug(`Error (MasterProcessBase:#GetLatency:catch): [${error}]`.red);
|
|
199
|
+
this.#debug(` url: [${url}]`.red);
|
|
200
|
+
if (error.response && error.response.data) {
|
|
201
|
+
this.#debug(` Details: [${JSON.stringify(error.response.data)}]`.red);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#LatencyRequestCompleted = (start: any) => {
|
|
207
|
+
// Update request duration histo data
|
|
208
|
+
let timeInMs = 0;
|
|
209
|
+
const end = process.hrtime(start);
|
|
210
|
+
timeInMs = (end[0]* 1000000000 + end[1]) / 1000000;
|
|
211
|
+
timeInMs = parseFloat(timeInMs.toFixed(4));
|
|
212
|
+
|
|
213
|
+
this.UpdateInstrument(Gauge.LATENCY_HISTOGRAM_GAUGE, {
|
|
214
|
+
val: timeInMs
|
|
215
|
+
} as InstrumentHistogramTelemetry);
|
|
216
|
+
|
|
217
|
+
this.UpdateInstrument(Gauge.LATENCY_GAUGE, {
|
|
218
|
+
val: timeInMs
|
|
219
|
+
} as InstrumentGaugeTelemetry);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
SetupServer = async () =>
|
|
223
|
+
{
|
|
224
|
+
this.SetupInstrumentation();
|
|
225
|
+
setTimeout(() => {
|
|
226
|
+
this.SetupServerEx();
|
|
227
|
+
}, 100);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#_KillWorker = (id: string, signal: NodeJS.Signals, killProcess: boolean): boolean => {
|
|
231
|
+
if (cluster.workers && cluster.workers[id]) {
|
|
232
|
+
const worker = cluster.workers[id] as Worker;
|
|
233
|
+
if (worker.process) {
|
|
234
|
+
this.LogEx(`Sending terminate message `.grey + `(initiated by ${signal})`.yellow + ` for worker PID: ${worker.process.pid}`.grey);
|
|
235
|
+
const command: string = (killProcess ? 'TerminateAndKill' : 'Terminate');
|
|
236
|
+
worker.process.send( { command } );
|
|
237
|
+
return true;
|
|
238
|
+
} else {
|
|
239
|
+
this.LogEx(`Could not kill worker with id: [${id}]. The process does not exists`.red);
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
this.LogEx(`Could not kill worker with id: [${id}]. Worker does not exist within workers collection`.red);
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
249
|
+
KillWorker = (id: string, signal: NodeJS.Signals = "SIGTERM", options: any, killProcess: boolean, allowKillAll: boolean): boolean => {
|
|
250
|
+
if (allowKillAll || Object.keys(cluster.workers as NodeJS.Dict<Worker>).length > 1) {
|
|
251
|
+
if (id.localeCompare('0') === 0) {
|
|
252
|
+
const keys = Object.keys(cluster.workers as NodeJS.Dict<Worker>);
|
|
253
|
+
for (let i=keys.length-1; i > 0; i--) {
|
|
254
|
+
// Kill the last one added (assumed node keeps them in order)
|
|
255
|
+
id = keys[i];
|
|
256
|
+
if (!this.#killWorkers[id]) {
|
|
257
|
+
this.#killWorkers[id] = id;
|
|
258
|
+
// Allow some time for the worker to be terminated before clean-up of the killWorkers array
|
|
259
|
+
setTimeout(() => {
|
|
260
|
+
delete this.#killWorkers[id];
|
|
261
|
+
}, 2000).unref(); //@@
|
|
262
|
+
return this.#_KillWorker(id, signal, killProcess);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
} else {
|
|
267
|
+
return this.#_KillWorker(id, signal, killProcess);
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
this.LogEx(`Not allowed to kill the last worker process.`.yellow);
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
KillWorkers = (signal: NodeJS.Signals): void => {
|
|
276
|
+
try {
|
|
277
|
+
for (const id in cluster.workers) {
|
|
278
|
+
try {
|
|
279
|
+
this.KillWorker(id, signal, null, false, true);
|
|
280
|
+
//cluster.workers[id].process.kill(signal);
|
|
281
|
+
|
|
282
|
+
// Using kill (below) does not fire the events in the worker processes.
|
|
283
|
+
//cluster.workers[id].kill('SIGINT');
|
|
284
|
+
} catch (error) {
|
|
285
|
+
this.LogEx(error);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} catch (error) {
|
|
289
|
+
this.LogEx(error);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
#UpdateWorkersInstrument = (): void => {
|
|
294
|
+
setTimeout(() => {
|
|
295
|
+
this.UpdateInstrument(Gauge.CORE_COUNT_GAUGE, {
|
|
296
|
+
val: this.#workers
|
|
297
|
+
} as InstrumentGaugeTelemetry);
|
|
298
|
+
}, 2000);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
IncWorkers = (): void => {
|
|
302
|
+
this.#workers++;
|
|
303
|
+
this.#debug(` Inc Workers. Total thread count: [${this.#workers}]`);
|
|
304
|
+
this.#UpdateWorkersInstrument();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
DecWorkers = (): void => {
|
|
308
|
+
this.#workers--;
|
|
309
|
+
this.#debug(` Dec Workers. Total thread count: [${this.#workers}]`);
|
|
310
|
+
this.#UpdateWorkersInstrument();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
314
|
+
AddWorker = (options: any): number => {
|
|
315
|
+
const workerId: number = this.#SpawnWorker(options);
|
|
316
|
+
this.#debug(` Spawned worker with id: [${workerId}]`.yellow);
|
|
317
|
+
if (options) {
|
|
318
|
+
this.#debug(` Options: [${JSON.stringify(options)}]`.yellow);
|
|
319
|
+
}
|
|
320
|
+
return workerId;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
#processIPCCommand = async (iPCMessagePayload: IPCMessagePayload): Promise<IPCMessagePayload> => {
|
|
324
|
+
this.#debug(` Processing message command: [${iPCMessagePayload.command}]`.yellow);
|
|
325
|
+
switch (iPCMessagePayload.command) {
|
|
326
|
+
case IPCMessageCommand.AddWorker : {
|
|
327
|
+
const workerId = this.AddWorker(iPCMessagePayload.requestDetail?.options);
|
|
328
|
+
iPCMessagePayload.responseDetail = {
|
|
329
|
+
workerId
|
|
330
|
+
}
|
|
331
|
+
return iPCMessagePayload;
|
|
332
|
+
}
|
|
333
|
+
case IPCMessageCommand.DeleteWorker : {
|
|
334
|
+
const workerId = iPCMessagePayload.requestDetail?.workerId;
|
|
335
|
+
const workerKilled = this.KillWorker(workerId, "SIGTERM", iPCMessagePayload.requestDetail?.options, true, false);
|
|
336
|
+
this.#debug(` Killed worker with id: [${workerId}]`.yellow);
|
|
337
|
+
iPCMessagePayload.responseDetail = {
|
|
338
|
+
workerId,
|
|
339
|
+
workerKilled
|
|
340
|
+
}
|
|
341
|
+
return iPCMessagePayload;
|
|
342
|
+
}
|
|
343
|
+
default : {
|
|
344
|
+
const errorMessage = `Could not process command: [${iPCMessagePayload.command}].`;
|
|
345
|
+
this.#debug(` ${errorMessage}`.red);
|
|
346
|
+
throw new Error(errorMessage);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
352
|
+
#SpawnWorker = (spawnWorkerOptions?: any): number => {
|
|
353
|
+
const workerEnv: any = { };
|
|
354
|
+
workerEnv['STS_GSD_SII'] = JSON.stringify(this.options);
|
|
355
|
+
if (spawnWorkerOptions) {
|
|
356
|
+
workerEnv['STS_GSD_OPTIONS'] = JSON.stringify(spawnWorkerOptions);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// https://nodejs.org/api/cluster.html#clustersetupprimarysettings
|
|
360
|
+
if (this.options.workerExec) {
|
|
361
|
+
cluster.setupPrimary({
|
|
362
|
+
exec: this.options.workerExec,
|
|
363
|
+
silent: true,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const worker = cluster.fork(workerEnv);
|
|
368
|
+
this.IncWorkers();
|
|
369
|
+
|
|
370
|
+
worker.on('exit', (code, signal) => {
|
|
371
|
+
if (signal) {
|
|
372
|
+
this.LogEx(`Worker: ${worker.process.pid} was killed by signal: ${signal}`);
|
|
373
|
+
} else if (code !== 0) {
|
|
374
|
+
this.LogEx(`Worker: ${worker.process.pid} exited with error code: ${code}`);
|
|
375
|
+
} else {
|
|
376
|
+
this.LogEx(`Worker: ${worker.process.pid} exited successfully, code: ${code}, signal: ${signal}`);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
worker.on('message', async (payload) => {
|
|
381
|
+
// Only handle request/response message types here ...
|
|
382
|
+
if (payload.requestResponse) {
|
|
383
|
+
const iPCMessagePayload: IPCMessagePayload = payload as IPCMessagePayload;
|
|
384
|
+
this.#debug(`Received message with id: [${iPCMessagePayload.id}] from worker: [${worker.process.pid}]. Details: [${JSON.stringify(iPCMessagePayload)}]`.yellow);
|
|
385
|
+
const response: IPCMessagePayload = await this.#processIPCCommand(iPCMessagePayload);
|
|
386
|
+
this.#debug(`Sending response message with id: [${iPCMessagePayload.id}] to worker: [${worker.process.pid}]. Details: [${JSON.stringify(response)}]`.green);
|
|
387
|
+
worker.send(response);
|
|
388
|
+
} else {
|
|
389
|
+
this.#WorkerMessageEvent(payload);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
return worker.id;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
MasterStarted(): void { // eslint-disable @typescript-eslint/no-empty-function
|
|
397
|
+
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
override get shuttingDown(): boolean {
|
|
401
|
+
return this.#shuttingDown;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
ProcessTerminating = async (): Promise<void> => {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
SetupServerEx = async () =>
|
|
409
|
+
{
|
|
410
|
+
this.ProcessStartup();
|
|
411
|
+
const LogEx = this.LogEx;
|
|
412
|
+
|
|
413
|
+
LogEx(`Service instance starting. Instance Id: [${this.options.serviceInstanceId}]`);
|
|
414
|
+
|
|
415
|
+
this.LogSystemTelemetry();
|
|
416
|
+
|
|
417
|
+
// socket.io
|
|
418
|
+
// setup connections between the workers
|
|
419
|
+
if (this.options.wssServer === true) {
|
|
420
|
+
if (this.options.useRedisAdaptor) {
|
|
421
|
+
LogEx(`Using Redis for socket.io cluster management (master)`);
|
|
422
|
+
} else {
|
|
423
|
+
LogEx(`Using nodejs cluster mode for socket.io cluster management`);
|
|
424
|
+
setupPrimary();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (this.options.prometheusSupport === true) {
|
|
429
|
+
this.#SetupPrometheusForMaster(LogEx);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const numCPUs = await this.GetNumCPUs();
|
|
433
|
+
for (let i=0; i < numCPUs; i++) {
|
|
434
|
+
this.#SpawnWorker();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
this.#InitCluster(LogEx);
|
|
438
|
+
|
|
439
|
+
cluster.on('listening', (worker, address) =>
|
|
440
|
+
{
|
|
441
|
+
LogEx(`Worker process ${worker.process.pid} is listening at address: ${JSON.stringify(address)}`);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
//Setting up lifecycle event listeners for worker processes
|
|
445
|
+
cluster.on('online', worker =>
|
|
446
|
+
{
|
|
447
|
+
LogEx(`Worker process ${worker.process.pid} is online`);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
cluster.on('exit', (worker, code, signal) =>
|
|
451
|
+
{
|
|
452
|
+
if ((code !== null && code === 0) || (signal === 'SIGINT'))
|
|
453
|
+
{
|
|
454
|
+
LogEx(`Process ${worker.process.pid} terminated gracefully with code: ${code}, signal: ${signal}`.green);
|
|
455
|
+
this.DecWorkers();
|
|
456
|
+
} else if ((code !== null && code === 15) || (signal === 'SIGTERM'))
|
|
457
|
+
{
|
|
458
|
+
this.DecWorkers();
|
|
459
|
+
LogEx(`Process ${worker.process.pid} terminated with code: ${code}, signal: ${signal}`.red);
|
|
460
|
+
} else {
|
|
461
|
+
this.DecWorkers();
|
|
462
|
+
LogEx(`worker ${worker.process.pid} died`.red);
|
|
463
|
+
LogEx(`code: ${code}`.red);
|
|
464
|
+
LogEx(`signal: ${signal}`.red);
|
|
465
|
+
LogEx('process terminated in an error state'.red);
|
|
466
|
+
if (goptions.respawnOnFail === true)
|
|
467
|
+
{
|
|
468
|
+
LogEx(`Attemping to respawn worker`);
|
|
469
|
+
this.#SpawnWorker();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Terminate in order;
|
|
475
|
+
// forked worker threads (send signal)
|
|
476
|
+
// De-Register service
|
|
477
|
+
// systeminformation observers
|
|
478
|
+
// instrument timers (gauge etc.)
|
|
479
|
+
// publisher
|
|
480
|
+
// terminate UI (if loaded)
|
|
481
|
+
const Terminate = async (signal: any) =>
|
|
482
|
+
{
|
|
483
|
+
if (this.#shuttingDown === false)
|
|
484
|
+
{
|
|
485
|
+
this.#shuttingDown = true;
|
|
486
|
+
|
|
487
|
+
if (this.#checkLatency) {
|
|
488
|
+
clearInterval(this.#checkLatency);
|
|
489
|
+
this.#checkLatency = null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
await this.ProcessTerminate();
|
|
493
|
+
|
|
494
|
+
await this.ProcessTerminating();
|
|
495
|
+
|
|
496
|
+
if (this.GetUIController() !== null)
|
|
497
|
+
{
|
|
498
|
+
LogEx('Destroy the user interface controller.');
|
|
499
|
+
this.GetUIController().DestroyUI();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (signal) {
|
|
503
|
+
LogEx(this.GetSignalColour(signal)(`Main Process (masterprocess): ${process.pid} received signal: ${signal}`));
|
|
504
|
+
} else {
|
|
505
|
+
LogEx(this.GetSignalColour(null)(`Main Process (masterprocess): ${process.pid} received Terminate without signal.`));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
LogEx(`De-Registering service.`);
|
|
509
|
+
//@@ De-register here ...
|
|
510
|
+
|
|
511
|
+
if (this.#httpServer !== null) {
|
|
512
|
+
LogEx(`Closing httpServer.`);
|
|
513
|
+
await this.#httpServer.close();
|
|
514
|
+
this.#httpServer = null;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
LogEx(`Stopping instruments.`);
|
|
518
|
+
//@@StopInstruments(this.instruments);
|
|
519
|
+
|
|
520
|
+
//@@endpublish was here (and working ...)
|
|
521
|
+
|
|
522
|
+
LogEx('Killing Workers.');
|
|
523
|
+
this.KillWorkers(signal);
|
|
524
|
+
|
|
525
|
+
if (this.options.useDatabase) {
|
|
526
|
+
LogEx(`Ending database connections and pools.`);
|
|
527
|
+
await this.TerminateDatabase();
|
|
528
|
+
//await this.accessLayer.enddatabase();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Now allow some time for the workers to die and send any remaining messages ...
|
|
532
|
+
//if (this.InstrumentController && this.InstrumentController.Workers.length > 0) {
|
|
533
|
+
if (this.InstrumentController) {
|
|
534
|
+
LogEx(`Ending publisher.`);
|
|
535
|
+
setTimeout(() => {
|
|
536
|
+
if (this.InstrumentController) {
|
|
537
|
+
this.InstrumentController.InstrumentTerminate();
|
|
538
|
+
}
|
|
539
|
+
}, 100);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (this.options.processExitOnTerminate && this.options.processExitOnTerminate === true) {
|
|
543
|
+
setTimeout(() => {
|
|
544
|
+
LogEx(`Performing process.exit(0).`);
|
|
545
|
+
process.exit(0);
|
|
546
|
+
}, this.#childProcessExitTime + this.#masterProcessExitTime); // Give the workers time to terminate gracefully
|
|
547
|
+
} else {
|
|
548
|
+
LogEx(`Performing process.exit(0) - Immediate.`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
process.on('SIGINT', async () =>
|
|
554
|
+
{
|
|
555
|
+
await Terminate('SIGINT');
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
process.on('SIGTERM', async () =>
|
|
559
|
+
{
|
|
560
|
+
await Terminate('SIGTERM');
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
process.on('exit', (code) =>
|
|
564
|
+
{
|
|
565
|
+
if (code === 0)
|
|
566
|
+
{
|
|
567
|
+
LogEx(`Main Process: ${process.pid} terminated gracefully with code: ${code}`.green);
|
|
568
|
+
} else {
|
|
569
|
+
LogEx(`Main Process: ${process.pid} terminated with code: ${code}`.red);
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
if (this.options.useLatency) {
|
|
574
|
+
this.#checkLatency = setInterval(() => {
|
|
575
|
+
this.#CheckLatency();
|
|
576
|
+
}, 1000).unref();
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
this.MasterStarted();
|
|
580
|
+
|
|
581
|
+
LogEx(`Master process:${process.pid} started`.green);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
BroadcastDataToWorkers = (command: any, data: any) => {
|
|
585
|
+
try {
|
|
586
|
+
for (const id in cluster.workers) {
|
|
587
|
+
try {
|
|
588
|
+
//@@LogEx(`Sending message to worker PID: ${cluster.workers[id].process.pid}`.grey);
|
|
589
|
+
(cluster.workers[id] as Worker).process.send( { command: command, data: data } );
|
|
590
|
+
} catch (error) {
|
|
591
|
+
//@@LogEx(error);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
} catch (error) {
|
|
595
|
+
//@@LogEx(error);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|