@nosana/node 1.1.43 → 1.1.45-rc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +1 -1
- package/dist/src/NodeManager/node/task/operations/runTaskManagerOperation.js +8 -0
- package/dist/src/NodeManager/provider/ExposedPortHealthCheck.d.ts +3 -0
- package/dist/src/NodeManager/provider/ExposedPortHealthCheck.js +37 -6
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/package.json
CHANGED
|
@@ -392,6 +392,14 @@ export async function runTaskManagerOperation(flow, op, dependent) {
|
|
|
392
392
|
}
|
|
393
393
|
// Loop through each operation that depends on this one
|
|
394
394
|
for (const id of dependent) {
|
|
395
|
+
// A dependency can report itself up more than once, and only an operation
|
|
396
|
+
// that has not started yet may be released.
|
|
397
|
+
const status = this.operationStatus.get(id);
|
|
398
|
+
if (status !== OperationProgressStatuses.PENDING &&
|
|
399
|
+
status !== OperationProgressStatuses.WAITING &&
|
|
400
|
+
status !== OperationProgressStatuses.INIT) {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
395
403
|
const dependencyContext = this.dependecyMap.get(id);
|
|
396
404
|
// Schedule the dependent operation to start, now that its dependency is healthy
|
|
397
405
|
this.currentGroupOperationsPromises.set(id, this.trackGroupOperationPromise(id, this.setUpOperationFunc(flow, id, dependencyContext.dependents)));
|
|
@@ -14,6 +14,9 @@ export declare class ExposedPortHealthCheck {
|
|
|
14
14
|
private healthStatus;
|
|
15
15
|
private jobEmitter;
|
|
16
16
|
private intervals;
|
|
17
|
+
private readonly connectTimeoutSeconds;
|
|
18
|
+
private readonly startupMaxTimeSeconds;
|
|
19
|
+
private readonly continuousMaxTimeSeconds;
|
|
17
20
|
private flowId;
|
|
18
21
|
constructor(flowId: string, frpcContainer: Dockerode.Container, jobEmitter: EventEmitter, containerName: string, startupIntervalMs?: number, continuousIntervalMs?: number);
|
|
19
22
|
addExposedPortsMap(exposedPortMap: Map<string, ExposedPort>): void;
|
|
@@ -6,6 +6,14 @@ export class ExposedPortHealthCheck {
|
|
|
6
6
|
*/
|
|
7
7
|
this.healthStatus = new Map();
|
|
8
8
|
this.intervals = new Map();
|
|
9
|
+
// Every curl is bounded: an unbounded one holds the startup check open and
|
|
10
|
+
// nothing waiting on the service ever starts. The startup budget is generous
|
|
11
|
+
// because a health check can be a real request against a model that is still
|
|
12
|
+
// loading, and checks there never overlap. The continuous budget stays under
|
|
13
|
+
// the interval it runs on, where nothing serialises them.
|
|
14
|
+
this.connectTimeoutSeconds = 2;
|
|
15
|
+
this.startupMaxTimeSeconds = 60;
|
|
16
|
+
this.continuousMaxTimeSeconds = 20;
|
|
9
17
|
this.flowId = flowId;
|
|
10
18
|
this.frpcContainer = frpcContainer;
|
|
11
19
|
this.jobEmitter = jobEmitter;
|
|
@@ -23,8 +31,15 @@ export class ExposedPortHealthCheck {
|
|
|
23
31
|
}
|
|
24
32
|
startStartupHealthCheck(id, exposedPort) {
|
|
25
33
|
let startupCheckInterval = null;
|
|
34
|
+
let checkInFlight = false;
|
|
26
35
|
startupCheckInterval = setInterval(async () => {
|
|
27
|
-
|
|
36
|
+
// Two checks in flight both report the service up.
|
|
37
|
+
if (checkInFlight)
|
|
38
|
+
return;
|
|
39
|
+
checkInFlight = true;
|
|
40
|
+
const success = await this.checkPortHealth(exposedPort, true).finally(() => {
|
|
41
|
+
checkInFlight = false;
|
|
42
|
+
});
|
|
28
43
|
if (success == null) {
|
|
29
44
|
clearInterval(startupCheckInterval);
|
|
30
45
|
this.jobEmitter.emit('healthcheck:url:exposed', {
|
|
@@ -92,29 +107,36 @@ export class ExposedPortHealthCheck {
|
|
|
92
107
|
if (!exposedPort.health_checks || exposedPort.health_checks.length === 0) {
|
|
93
108
|
return null; // Return null to indicate no check was done.
|
|
94
109
|
}
|
|
110
|
+
const maxTimeSeconds = initialRun
|
|
111
|
+
? this.startupMaxTimeSeconds
|
|
112
|
+
: this.continuousMaxTimeSeconds;
|
|
95
113
|
for (const healthCheck of exposedPort.health_checks) {
|
|
96
114
|
if (!initialRun && !healthCheck.continuous) {
|
|
97
115
|
return null;
|
|
98
116
|
}
|
|
99
117
|
if (healthCheck.type === 'http' && typeof exposedPort.port === 'number') {
|
|
100
|
-
const success = await this.runHttpHealthCheck(exposedPort.port, healthCheck);
|
|
118
|
+
const success = await this.runHttpHealthCheck(exposedPort.port, healthCheck, maxTimeSeconds);
|
|
101
119
|
if (!success)
|
|
102
120
|
return false;
|
|
103
121
|
}
|
|
104
122
|
else if (healthCheck.type === 'websocket' &&
|
|
105
123
|
typeof exposedPort.port === 'number') {
|
|
106
|
-
const success = await this.runWebSocketHealthCheck(exposedPort.port, healthCheck);
|
|
124
|
+
const success = await this.runWebSocketHealthCheck(exposedPort.port, healthCheck, maxTimeSeconds);
|
|
107
125
|
if (!success)
|
|
108
126
|
return false;
|
|
109
127
|
}
|
|
110
128
|
}
|
|
111
129
|
return true;
|
|
112
130
|
}
|
|
113
|
-
async runHttpHealthCheck(port, healthCheck) {
|
|
131
|
+
async runHttpHealthCheck(port, healthCheck, maxTimeSeconds) {
|
|
114
132
|
const url = `http://${this.containerName}:${port}${healthCheck.path}`;
|
|
115
133
|
const cmd = [
|
|
116
134
|
'curl',
|
|
117
135
|
'-s',
|
|
136
|
+
'--connect-timeout',
|
|
137
|
+
String(this.connectTimeoutSeconds),
|
|
138
|
+
'--max-time',
|
|
139
|
+
String(maxTimeSeconds),
|
|
118
140
|
'-o',
|
|
119
141
|
'/dev/null',
|
|
120
142
|
'-w',
|
|
@@ -145,8 +167,17 @@ export class ExposedPortHealthCheck {
|
|
|
145
167
|
return false;
|
|
146
168
|
}
|
|
147
169
|
}
|
|
148
|
-
async runWebSocketHealthCheck(port, healthCheck) {
|
|
149
|
-
const cmd = [
|
|
170
|
+
async runWebSocketHealthCheck(port, healthCheck, maxTimeSeconds) {
|
|
171
|
+
const cmd = [
|
|
172
|
+
'curl',
|
|
173
|
+
'--include',
|
|
174
|
+
'--no-buffer',
|
|
175
|
+
'--connect-timeout',
|
|
176
|
+
String(this.connectTimeoutSeconds),
|
|
177
|
+
'--max-time',
|
|
178
|
+
String(maxTimeSeconds),
|
|
179
|
+
`ws://localhost:${port}`,
|
|
180
|
+
];
|
|
150
181
|
try {
|
|
151
182
|
const output = await this.execCommand(cmd);
|
|
152
183
|
return output.includes(healthCheck.expected_response);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nosana/node",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.45-rc",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@nosana/node",
|
|
9
|
-
"version": "1.1.
|
|
9
|
+
"version": "1.1.45-rc",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@coral-xyz/anchor": "^0.28.1-beta.1",
|