@3fv/electron-utility-process-manager 1.0.2 → 1.0.4
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 +39 -26
- package/examples/lib-examples/simple/simple-main.js +9 -2
- package/examples/lib-examples/simple/simple-main.js.map +1 -1
- package/examples/lib-examples/simple/simple-renderer.js +1 -1
- package/examples/lib-examples/simple/simple-renderer.js.map +1 -1
- package/lib/cjs/common/types.d.ts +24 -2
- package/lib/cjs/common/types.js.map +1 -1
- package/lib/cjs/main/UPMMainService.d.ts +109 -28
- package/lib/cjs/main/UPMMainService.js +249 -136
- package/lib/cjs/main/UPMMainService.js.map +1 -1
- package/lib/cjs/main/UPMMainServiceManager.d.ts +2 -1
- package/lib/cjs/main/UPMMainServiceManager.js +4 -4
- package/lib/cjs/main/UPMMainServiceManager.js.map +1 -1
- package/lib/mjs/common/types.d.ts +24 -2
- package/lib/mjs/common/types.js.map +1 -1
- package/lib/mjs/main/UPMMainService.d.ts +109 -28
- package/lib/mjs/main/UPMMainService.js +251 -138
- package/lib/mjs/main/UPMMainService.js.map +1 -1
- package/lib/mjs/main/UPMMainServiceManager.d.ts +2 -1
- package/lib/mjs/main/UPMMainServiceManager.js +4 -4
- package/lib/mjs/main/UPMMainServiceManager.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
import { MessagePortMain } from "electron";
|
|
2
2
|
import * as UPM from "../common/index.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Contains & Manages a `Utility/Process`
|
|
5
|
+
* providing simplified messaging & event
|
|
6
|
+
* propagation.
|
|
7
|
+
*/
|
|
7
8
|
export declare class UPMMainService<ReqMap extends UPM.MessageRequestMap = any, MType extends UPM.MessageRequestNames<ReqMap> = UPM.MessageRequestNames<ReqMap>> implements UPM.IMessageClient<ReqMap, MType> {
|
|
8
9
|
readonly serviceName: string;
|
|
9
10
|
readonly entryFile: string;
|
|
11
|
+
/**
|
|
12
|
+
* As this container/wrapper implements
|
|
13
|
+
* `IMessageClient`, it needs to provide
|
|
14
|
+
* a `clientId`, which is constant in the
|
|
15
|
+
* main process
|
|
16
|
+
*/
|
|
10
17
|
readonly clientId: string;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Send an event as `fire-and-forget`
|
|
20
|
+
*
|
|
21
|
+
* @param data
|
|
22
|
+
* @param port
|
|
23
|
+
*/
|
|
24
|
+
sendEvent(data: any, port?: UPM.Port): void;
|
|
25
|
+
/**
|
|
26
|
+
* Send request
|
|
27
|
+
*
|
|
28
|
+
* @param type
|
|
29
|
+
* @param args
|
|
30
|
+
*/
|
|
31
|
+
executeRequest<Type extends MType, R extends UPM.MessageRequestReturnType<ReqMap, Type> = UPM.MessageRequestReturnType<ReqMap, Type>>(type: Type, ...args: UPM.MessageRequestParams<ReqMap, Type>): Promise<R>;
|
|
32
|
+
close(): void;
|
|
20
33
|
/**
|
|
21
34
|
* Resolves when ready
|
|
22
35
|
*
|
|
@@ -27,7 +40,15 @@ export declare class UPMMainService<ReqMap extends UPM.MessageRequestMap = any,
|
|
|
27
40
|
* Return a simple proxy facade wrapping
|
|
28
41
|
* `executeRequest` into individual methods
|
|
29
42
|
*/
|
|
30
|
-
getServiceClient(): IServiceClient<ReqMap, MType>;
|
|
43
|
+
getServiceClient(): UPM.IServiceClient<ReqMap, MType>;
|
|
44
|
+
private readonly config_;
|
|
45
|
+
private readyDeferred_;
|
|
46
|
+
private killDeferred_;
|
|
47
|
+
private messageIdSeq_;
|
|
48
|
+
private childProcess_;
|
|
49
|
+
private pendingMessages_;
|
|
50
|
+
private messageChannels_;
|
|
51
|
+
private onServiceExit;
|
|
31
52
|
/**
|
|
32
53
|
* Returned client side port (for Renderer)
|
|
33
54
|
* @param clientId
|
|
@@ -38,33 +59,93 @@ export declare class UPMMainService<ReqMap extends UPM.MessageRequestMap = any,
|
|
|
38
59
|
* (useful to avoid congestion on default child_process channel)
|
|
39
60
|
*
|
|
40
61
|
* @param {string} clientId
|
|
41
|
-
* @returns {UPM.MessagePortClient
|
|
62
|
+
* @returns {UPM.MessagePortClient}
|
|
42
63
|
*/
|
|
43
64
|
createMainClient(clientId: string): UPM.MessagePortClient<ReqMap, MType>;
|
|
65
|
+
/**
|
|
66
|
+
* Service is still in a healthy/running/runnable state
|
|
67
|
+
*/
|
|
68
|
+
get isRunning(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Stop the service (kill)
|
|
71
|
+
*/
|
|
72
|
+
stop(): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Resource management & cleanup
|
|
75
|
+
*/
|
|
76
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Initialize the service
|
|
79
|
+
*
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
private init;
|
|
83
|
+
/**
|
|
84
|
+
* OnMessage really only handles `results` from
|
|
85
|
+
* `executeRequest(...)`; this may be expanded in
|
|
86
|
+
* the future
|
|
87
|
+
*
|
|
88
|
+
* @param payload
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
private onMessage;
|
|
92
|
+
/**
|
|
93
|
+
* This handler is currently unused, but
|
|
94
|
+
* could be attached to a `MessagePort.on('close')`
|
|
95
|
+
* event
|
|
96
|
+
*
|
|
97
|
+
* @param id
|
|
98
|
+
* @param channel
|
|
99
|
+
* @private
|
|
100
|
+
*/
|
|
44
101
|
private onPortClose;
|
|
45
|
-
private closeMessageChannel;
|
|
46
|
-
close(): void;
|
|
47
102
|
/**
|
|
48
|
-
*
|
|
103
|
+
* Close a message channel
|
|
49
104
|
*
|
|
50
|
-
* @param
|
|
51
|
-
* @param
|
|
105
|
+
* @param id
|
|
106
|
+
* @param channel
|
|
107
|
+
* @private
|
|
52
108
|
*/
|
|
53
|
-
|
|
109
|
+
private closeMessageChannel;
|
|
54
110
|
/**
|
|
55
|
-
*
|
|
111
|
+
* Kill the service
|
|
56
112
|
*
|
|
57
|
-
* @
|
|
58
|
-
* @param args
|
|
113
|
+
* @private
|
|
59
114
|
*/
|
|
60
|
-
executeRequest<Type extends MType, R extends UPM.MessageRequestReturnType<ReqMap, Type> = UPM.MessageRequestReturnType<ReqMap, Type>>(type: Type, ...args: UPM.MessageRequestParams<ReqMap, Type>): Promise<R>;
|
|
61
|
-
stop(): Promise<void>;
|
|
62
115
|
private kill;
|
|
116
|
+
/**
|
|
117
|
+
* Close all message channels
|
|
118
|
+
*
|
|
119
|
+
* @private
|
|
120
|
+
*/
|
|
63
121
|
private removeAllMessageChannels;
|
|
122
|
+
/**
|
|
123
|
+
* Remove all pending messages
|
|
124
|
+
*
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
64
127
|
private cancelAllPendingMessages;
|
|
65
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Remove a pending message from the queue,
|
|
130
|
+
* whether settled or not
|
|
131
|
+
*
|
|
132
|
+
* @param messageId
|
|
133
|
+
* @private
|
|
134
|
+
*/
|
|
66
135
|
private removePendingMessage;
|
|
136
|
+
/**
|
|
137
|
+
* Generate next message id
|
|
138
|
+
*
|
|
139
|
+
* @private
|
|
140
|
+
*/
|
|
67
141
|
private generateMessageId;
|
|
68
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Construct a new service
|
|
144
|
+
*
|
|
145
|
+
* @param serviceName
|
|
146
|
+
* @param entryFile
|
|
147
|
+
* @param options
|
|
148
|
+
*/
|
|
149
|
+
constructor(serviceName: string, entryFile: string, options?: UPM.CreateServiceOptions);
|
|
69
150
|
}
|
|
70
151
|
export default UPMMainService;
|
|
@@ -1,68 +1,66 @@
|
|
|
1
1
|
import { app, MessageChannelMain, utilityProcess } from "electron";
|
|
2
|
-
import { assert, getValue, isString } from "@3fv/guard";
|
|
2
|
+
import { assert, getValue, isNumber, isString } from "@3fv/guard";
|
|
3
3
|
import Fsx from "fs-extra";
|
|
4
4
|
import { Deferred } from "@3fv/deferred";
|
|
5
5
|
import * as UPM from "../common/index.js";
|
|
6
|
-
import { isEmpty, negate } from "lodash";
|
|
6
|
+
import { defaults, isEmpty, negate } from "lodash";
|
|
7
7
|
import Tracer from "tracer";
|
|
8
|
+
import { asOption } from "@3fv/prelude-ts";
|
|
9
|
+
import { match } from "ts-pattern";
|
|
8
10
|
const log = Tracer.colorConsole();
|
|
9
11
|
const isNotEmpty = negate(isEmpty);
|
|
12
|
+
/**
|
|
13
|
+
* Contains & Manages a `Utility/Process`
|
|
14
|
+
* providing simplified messaging & event
|
|
15
|
+
* propagation.
|
|
16
|
+
*/
|
|
10
17
|
export class UPMMainService {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.childProcess_.off("exit", exitListener);
|
|
25
|
-
this.childProcess_.on("message", this.onMessage.bind(this));
|
|
26
|
-
deferred.resolve(this);
|
|
27
|
-
};
|
|
28
|
-
this.childProcess_ = utilityProcess
|
|
29
|
-
.fork(this.entryFile, [], {
|
|
30
|
-
serviceName: this.serviceName,
|
|
31
|
-
env: Object.assign(Object.assign({}, process.env), { IN_CHILD_PROCESS: "true", APP_NAME: app.name })
|
|
32
|
-
})
|
|
33
|
-
.on("spawn", spawnListener)
|
|
34
|
-
.on("exit", exitListener);
|
|
35
|
-
await deferred.promise;
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
deferred.reject(err);
|
|
39
|
-
}
|
|
40
|
-
return deferred.promise;
|
|
18
|
+
/**
|
|
19
|
+
* Send an event as `fire-and-forget`
|
|
20
|
+
*
|
|
21
|
+
* @param data
|
|
22
|
+
* @param port
|
|
23
|
+
*/
|
|
24
|
+
sendEvent(data, port = this.childProcess_) {
|
|
25
|
+
const messageId = this.generateMessageId();
|
|
26
|
+
const payload = {
|
|
27
|
+
channel: UPM.IPCChannel.UPMServiceMessage,
|
|
28
|
+
payload: { messageId, eventData: data, kind: UPM.MessageKind.Event }
|
|
29
|
+
};
|
|
30
|
+
port.postMessage(payload);
|
|
41
31
|
}
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Send request
|
|
34
|
+
*
|
|
35
|
+
* @param type
|
|
36
|
+
* @param args
|
|
37
|
+
*/
|
|
38
|
+
async executeRequest(type, ...args) {
|
|
39
|
+
assert(!!this.childProcess_, "The process is not running");
|
|
40
|
+
const messageId = this.generateMessageId(), pending = {
|
|
41
|
+
deferred: new Deferred(),
|
|
42
|
+
messageId,
|
|
43
|
+
timeoutId: setTimeout(() => this.removePendingMessage(messageId), UPM.Defaults.RequestTimeout)
|
|
44
|
+
};
|
|
45
|
+
this.pendingMessages_.set(messageId, pending);
|
|
46
|
+
const payload = {
|
|
47
|
+
channel: UPM.IPCChannel.UPMServiceMessage,
|
|
48
|
+
payload: { type, messageId, args, kind: UPM.MessageKind.Request }
|
|
49
|
+
};
|
|
50
|
+
this.childProcess_.postMessage(payload);
|
|
44
51
|
try {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (pending.deferred.isSettled()) {
|
|
51
|
-
log.error(`Already settled record ${messageId}`);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
this.removePendingMessage(messageId);
|
|
55
|
-
if (!!error) {
|
|
56
|
-
log.error(error);
|
|
57
|
-
pending.deferred.reject(new Error(error));
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
pending.deferred.resolve(result);
|
|
52
|
+
const result = await pending.deferred.promise;
|
|
53
|
+
log.debug(`Completed utility request (messageId=${messageId})`);
|
|
54
|
+
return result;
|
|
61
55
|
}
|
|
62
56
|
catch (err) {
|
|
63
|
-
log.error(`Failed
|
|
57
|
+
log.error(`Failed utility request (messageId=${messageId})`, err);
|
|
58
|
+
throw err;
|
|
64
59
|
}
|
|
65
60
|
}
|
|
61
|
+
close() {
|
|
62
|
+
log.warn("Close has no effect when called via the main process");
|
|
63
|
+
}
|
|
66
64
|
/**
|
|
67
65
|
* Resolves when ready
|
|
68
66
|
*
|
|
@@ -86,6 +84,22 @@ export class UPMMainService {
|
|
|
86
84
|
}
|
|
87
85
|
});
|
|
88
86
|
}
|
|
87
|
+
onServiceExit(code) {
|
|
88
|
+
log.warn(`Service Exit (code=${code})`);
|
|
89
|
+
let deferred = this.killDeferred_;
|
|
90
|
+
if (deferred) {
|
|
91
|
+
if (!deferred.isSettled()) {
|
|
92
|
+
deferred.resolve(code);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
log.warn(`kill deferred is already settled (code=${code})`);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.killDeferred_ = Deferred.resolve(code);
|
|
100
|
+
this.cancelAllPendingMessages();
|
|
101
|
+
this.removeAllMessageChannels();
|
|
102
|
+
}
|
|
89
103
|
/**
|
|
90
104
|
* Returned client side port (for Renderer)
|
|
91
105
|
* @param clientId
|
|
@@ -98,8 +112,7 @@ export class UPMMainService {
|
|
|
98
112
|
try {
|
|
99
113
|
this.childProcess_.postMessage({
|
|
100
114
|
message: {
|
|
101
|
-
channel: UPM.IPCChannel.UPMServiceNewClient,
|
|
102
|
-
payload: { clientId }
|
|
115
|
+
channel: UPM.IPCChannel.UPMServiceNewClient, payload: { clientId }
|
|
103
116
|
}
|
|
104
117
|
}, [port1]);
|
|
105
118
|
}
|
|
@@ -115,97 +128,162 @@ export class UPMMainService {
|
|
|
115
128
|
* (useful to avoid congestion on default child_process channel)
|
|
116
129
|
*
|
|
117
130
|
* @param {string} clientId
|
|
118
|
-
* @returns {UPM.MessagePortClient
|
|
131
|
+
* @returns {UPM.MessagePortClient}
|
|
119
132
|
*/
|
|
120
133
|
createMainClient(clientId) {
|
|
121
134
|
return new UPM.MessagePortClient(clientId, this.createMessageChannel(clientId));
|
|
122
135
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Service is still in a healthy/running/runnable state
|
|
138
|
+
*/
|
|
139
|
+
get isRunning() {
|
|
140
|
+
return !this.killDeferred_;
|
|
126
141
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
;
|
|
132
|
-
[channel.port1, channel.port2].forEach(p => getValue(() => p.close()));
|
|
142
|
+
/**
|
|
143
|
+
* Stop the service (kill)
|
|
144
|
+
*/
|
|
145
|
+
async stop() {
|
|
146
|
+
await this.kill();
|
|
133
147
|
}
|
|
134
|
-
|
|
135
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Resource management & cleanup
|
|
150
|
+
*/
|
|
151
|
+
async [Symbol.asyncDispose]() {
|
|
152
|
+
await this.stop();
|
|
136
153
|
}
|
|
137
154
|
/**
|
|
138
|
-
*
|
|
155
|
+
* Initialize the service
|
|
139
156
|
*
|
|
140
|
-
* @
|
|
141
|
-
* @param port
|
|
157
|
+
* @private
|
|
142
158
|
*/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
159
|
+
async init() {
|
|
160
|
+
let deferred = this.readyDeferred_;
|
|
161
|
+
if (deferred) {
|
|
162
|
+
return deferred.promise;
|
|
163
|
+
}
|
|
164
|
+
deferred = this.readyDeferred_ = new Deferred();
|
|
165
|
+
try {
|
|
166
|
+
const exitListener = (code) => {
|
|
167
|
+
if (code !== 0) {
|
|
168
|
+
deferred.reject(new Error(`process exited with code ${code} upon starting`));
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const spawnListener = async () => {
|
|
172
|
+
this.childProcess_.off("exit", exitListener);
|
|
173
|
+
this.childProcess_.on("exit", this.onServiceExit.bind(this));
|
|
174
|
+
this.childProcess_.on("message", this.onMessage.bind(this));
|
|
175
|
+
deferred.resolve(this);
|
|
176
|
+
};
|
|
177
|
+
// BUILD ADDITIONAL FORK OPTIONS
|
|
178
|
+
const childProcessOptions = asOption(this.config_.inspect)
|
|
179
|
+
.filter(it => it !== false)
|
|
180
|
+
.map(it => ({
|
|
181
|
+
execArgv: [
|
|
182
|
+
match(it)
|
|
183
|
+
.when(isNumber, port => `--inspect=${port}`)
|
|
184
|
+
.otherwise(({ port, break: shouldBreak }) => {
|
|
185
|
+
const ext = shouldBreak ? "-brk" : "";
|
|
186
|
+
return `--inspect${ext}=${port}`;
|
|
187
|
+
})
|
|
188
|
+
]
|
|
189
|
+
}))
|
|
190
|
+
.getOrElse({});
|
|
191
|
+
// FORK
|
|
192
|
+
this.childProcess_ = utilityProcess
|
|
193
|
+
.fork(this.entryFile, [], Object.assign(Object.assign({}, childProcessOptions), { serviceName: this.serviceName, env: Object.assign(Object.assign({}, process.env), { IN_CHILD_PROCESS: "true", APP_NAME: app.name }) }))
|
|
194
|
+
.on("spawn", spawnListener)
|
|
195
|
+
.on("exit", exitListener);
|
|
196
|
+
await deferred.promise;
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
deferred.reject(err);
|
|
200
|
+
}
|
|
201
|
+
return deferred.promise;
|
|
150
202
|
}
|
|
151
203
|
/**
|
|
152
|
-
*
|
|
204
|
+
* OnMessage really only handles `results` from
|
|
205
|
+
* `executeRequest(...)`; this may be expanded in
|
|
206
|
+
* the future
|
|
153
207
|
*
|
|
154
|
-
* @param
|
|
155
|
-
* @
|
|
208
|
+
* @param payload
|
|
209
|
+
* @private
|
|
156
210
|
*/
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const messageId = this.generateMessageId(), pending = {
|
|
160
|
-
deferred: new Deferred(),
|
|
161
|
-
messageId,
|
|
162
|
-
timeoutId: setTimeout(() => this.removePendingMessage(messageId), UPM.Defaults.RequestTimeout)
|
|
163
|
-
};
|
|
164
|
-
this.pendingMessages_.set(messageId, pending);
|
|
165
|
-
const payload = {
|
|
166
|
-
channel: UPM.IPCChannel.UPMServiceMessage,
|
|
167
|
-
payload: { type, messageId, args, kind: UPM.MessageKind.Request }
|
|
168
|
-
};
|
|
169
|
-
this.childProcess_.postMessage(payload);
|
|
211
|
+
onMessage(payload) {
|
|
212
|
+
const { messageId, result, error } = payload;
|
|
170
213
|
try {
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
214
|
+
const pending = this.pendingMessages_.get(messageId);
|
|
215
|
+
if (!pending) {
|
|
216
|
+
log.info(`Unable to find pending record ${messageId}`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (pending.deferred.isSettled()) {
|
|
220
|
+
log.error(`Already settled record ${messageId}`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.removePendingMessage(messageId);
|
|
224
|
+
if (!!error) {
|
|
225
|
+
log.error(error);
|
|
226
|
+
pending.deferred.reject(new Error(error));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
pending.deferred.resolve(result);
|
|
174
230
|
}
|
|
175
231
|
catch (err) {
|
|
176
|
-
log.error(`Failed
|
|
177
|
-
throw err;
|
|
232
|
+
log.error(`Failed to handle message`, err);
|
|
178
233
|
}
|
|
179
234
|
}
|
|
180
|
-
|
|
181
|
-
|
|
235
|
+
/**
|
|
236
|
+
* This handler is currently unused, but
|
|
237
|
+
* could be attached to a `MessagePort.on('close')`
|
|
238
|
+
* event
|
|
239
|
+
*
|
|
240
|
+
* @param id
|
|
241
|
+
* @param channel
|
|
242
|
+
* @private
|
|
243
|
+
*/
|
|
244
|
+
// noinspection JSUnusedLocalSymbols
|
|
245
|
+
onPortClose(id, channel) {
|
|
246
|
+
log.info(`main side of message channel(id=${id}) closed`);
|
|
247
|
+
this.closeMessageChannel(id, channel);
|
|
182
248
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
249
|
+
/**
|
|
250
|
+
* Close a message channel
|
|
251
|
+
*
|
|
252
|
+
* @param id
|
|
253
|
+
* @param channel
|
|
254
|
+
* @private
|
|
255
|
+
*/
|
|
256
|
+
closeMessageChannel(id, channel) {
|
|
257
|
+
if (this.messageChannels_.has(id)) {
|
|
258
|
+
this.messageChannels_.delete(id);
|
|
190
259
|
}
|
|
191
|
-
|
|
192
|
-
|
|
260
|
+
Array(channel.port1, channel.port2)
|
|
261
|
+
.forEach(p => {
|
|
262
|
+
getValue(() => p.close());
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Kill the service
|
|
267
|
+
*
|
|
268
|
+
* @private
|
|
269
|
+
*/
|
|
270
|
+
async kill() {
|
|
271
|
+
var _a, _b;
|
|
272
|
+
if (!this.readyDeferred_ ||
|
|
273
|
+
!this.readyDeferred_.isSettled() ||
|
|
274
|
+
!this.childProcess_ ||
|
|
275
|
+
this.killDeferred_) {
|
|
276
|
+
log.error("Service is not ready OR the process is not running");
|
|
277
|
+
// noinspection ES6MissingAwait
|
|
278
|
+
return (_b = (_a = this.killDeferred_) === null || _a === void 0 ? void 0 : _a.promise) !== null && _b !== void 0 ? _b : Promise.resolve(0);
|
|
193
279
|
}
|
|
280
|
+
let killDeferred = this.killDeferred_ = new Deferred();
|
|
281
|
+
// GET THE PROCESS
|
|
194
282
|
const process = this.childProcess_;
|
|
195
|
-
|
|
196
|
-
log.error("The process is not running");
|
|
197
|
-
return 0;
|
|
198
|
-
}
|
|
283
|
+
// CANCEL ALL PENDING MESSAGES & REMOVE ALL CHANNELS
|
|
199
284
|
this.cancelAllPendingMessages();
|
|
200
285
|
this.removeAllMessageChannels();
|
|
201
|
-
killDeferred = this.killDeferred_ = new Deferred();
|
|
202
286
|
try {
|
|
203
|
-
process.once("exit", (code) => {
|
|
204
|
-
if (code !== 0) {
|
|
205
|
-
log.error(`Process exited with code ${code} upon stopping`);
|
|
206
|
-
}
|
|
207
|
-
killDeferred.resolve(code);
|
|
208
|
-
});
|
|
209
287
|
process.kill();
|
|
210
288
|
}
|
|
211
289
|
catch (err) {
|
|
@@ -214,33 +292,33 @@ export class UPMMainService {
|
|
|
214
292
|
}
|
|
215
293
|
return await killDeferred.promise;
|
|
216
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Close all message channels
|
|
297
|
+
*
|
|
298
|
+
* @private
|
|
299
|
+
*/
|
|
217
300
|
removeAllMessageChannels() {
|
|
218
301
|
for (const [id, channel] of [...this.messageChannels_.entries()]) {
|
|
219
302
|
this.closeMessageChannel(id, channel);
|
|
220
303
|
}
|
|
221
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Remove all pending messages
|
|
307
|
+
*
|
|
308
|
+
* @private
|
|
309
|
+
*/
|
|
222
310
|
cancelAllPendingMessages() {
|
|
223
311
|
for (const key of [...this.pendingMessages_.keys()]) {
|
|
224
312
|
this.removePendingMessage(key);
|
|
225
313
|
}
|
|
226
314
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.childProcess_ = null;
|
|
235
|
-
this.pendingMessages_ = new Map();
|
|
236
|
-
this.messageChannels_ = new Map();
|
|
237
|
-
//super()
|
|
238
|
-
assert(isString(entryFile) && isNotEmpty(entryFile) && Fsx.existsSync(entryFile), `entry file must be provided (${entryFile})`);
|
|
239
|
-
this.config_ = Object.assign({}, options);
|
|
240
|
-
this.init().catch(err => {
|
|
241
|
-
log.error(`Failed to init utility process`, err);
|
|
242
|
-
});
|
|
243
|
-
}
|
|
315
|
+
/**
|
|
316
|
+
* Remove a pending message from the queue,
|
|
317
|
+
* whether settled or not
|
|
318
|
+
*
|
|
319
|
+
* @param messageId
|
|
320
|
+
* @private
|
|
321
|
+
*/
|
|
244
322
|
removePendingMessage(messageId) {
|
|
245
323
|
const pending = this.pendingMessages_.get(messageId);
|
|
246
324
|
if (!pending) {
|
|
@@ -252,11 +330,46 @@ export class UPMMainService {
|
|
|
252
330
|
}
|
|
253
331
|
this.pendingMessages_.delete(messageId);
|
|
254
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Generate next message id
|
|
335
|
+
*
|
|
336
|
+
* @private
|
|
337
|
+
*/
|
|
255
338
|
generateMessageId() {
|
|
256
|
-
return this.
|
|
339
|
+
return this.messageIdSeq_++;
|
|
257
340
|
}
|
|
258
|
-
|
|
259
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Construct a new service
|
|
343
|
+
*
|
|
344
|
+
* @param serviceName
|
|
345
|
+
* @param entryFile
|
|
346
|
+
* @param options
|
|
347
|
+
*/
|
|
348
|
+
constructor(serviceName, entryFile, options = {}) {
|
|
349
|
+
this.serviceName = serviceName;
|
|
350
|
+
this.entryFile = entryFile;
|
|
351
|
+
/**
|
|
352
|
+
* As this container/wrapper implements
|
|
353
|
+
* `IMessageClient`, it needs to provide
|
|
354
|
+
* a `clientId`, which is constant in the
|
|
355
|
+
* main process
|
|
356
|
+
*/
|
|
357
|
+
this.clientId = "main";
|
|
358
|
+
this.readyDeferred_ = null;
|
|
359
|
+
this.killDeferred_ = null;
|
|
360
|
+
this.messageIdSeq_ = 0;
|
|
361
|
+
this.childProcess_ = null;
|
|
362
|
+
this.pendingMessages_ = new Map();
|
|
363
|
+
this.messageChannels_ = new Map();
|
|
364
|
+
assert(isString(entryFile) &&
|
|
365
|
+
isNotEmpty(entryFile) &&
|
|
366
|
+
Fsx.existsSync(entryFile), `entry file must be provided (${entryFile})`);
|
|
367
|
+
this.config_ = defaults(Object.assign({}, options), {
|
|
368
|
+
serviceName, inspect: false
|
|
369
|
+
});
|
|
370
|
+
this.init().catch(err => {
|
|
371
|
+
log.error(`Failed to init utility process`, err);
|
|
372
|
+
});
|
|
260
373
|
}
|
|
261
374
|
}
|
|
262
375
|
export default UPMMainService;
|