@nosana/node 1.1.35 → 1.1.37-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/clients/hostManager/index.js +2 -0
- package/dist/src/NodeManager/clients/retryFetch.d.ts +7 -0
- package/dist/src/NodeManager/clients/retryFetch.js +29 -0
- package/dist/src/NodeManager/errors/HostManagerUnreachableError.d.ts +10 -0
- package/dist/src/NodeManager/errors/HostManagerUnreachableError.js +30 -0
- package/dist/src/NodeManager/node/market/benchmark.js +1 -1
- package/dist/src/NodeManager/node/resource/definition/index.d.ts +1 -1
- package/dist/src/NodeManager/node/resource/definition/index.js +1 -1
- package/dist/src/NodeManager/node/resource/volume/volumeManager.d.ts +7 -3
- package/dist/src/NodeManager/node/resource/volume/volumeManager.js +21 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import createClient from 'openapi-fetch';
|
|
2
2
|
import { getSDK } from '../../sdk/index.js';
|
|
3
3
|
import { configs } from '../../configs/configs.js';
|
|
4
|
+
import { retryFetch } from '../retryFetch.js';
|
|
4
5
|
import { maintenanceMiddleware } from '../maintenanceMiddleware.js';
|
|
5
6
|
let instance = undefined;
|
|
6
7
|
export const hostManagerClientSelector = () => {
|
|
@@ -9,6 +10,7 @@ export const hostManagerClientSelector = () => {
|
|
|
9
10
|
const address = sdk.solana.provider.wallet.publicKey.toString();
|
|
10
11
|
instance = createClient({
|
|
11
12
|
baseUrl: configs().hostManagerBaseUrl,
|
|
13
|
+
fetch: retryFetch,
|
|
12
14
|
headers: {
|
|
13
15
|
'Content-Type': 'application/json',
|
|
14
16
|
},
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom fetch for openapi-fetch clients that retries network-level failures
|
|
3
|
+
* (errors thrown by fetch itself: DNS, connection resets, timeouts) with
|
|
4
|
+
* exponential backoff. HTTP error responses are returned untouched — those
|
|
5
|
+
* are handled by the callers and the maintenance middleware.
|
|
6
|
+
*/
|
|
7
|
+
export declare const retryFetch: (input: Request) => Promise<Response>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { sleep } from '../utils/utils.js';
|
|
3
|
+
import { HostManagerUnreachableError, describeNetworkError, } from '../errors/HostManagerUnreachableError.js';
|
|
4
|
+
const MAX_ATTEMPTS = 6;
|
|
5
|
+
const BASE_DELAY_S = 1;
|
|
6
|
+
/**
|
|
7
|
+
* Custom fetch for openapi-fetch clients that retries network-level failures
|
|
8
|
+
* (errors thrown by fetch itself: DNS, connection resets, timeouts) with
|
|
9
|
+
* exponential backoff. HTTP error responses are returned untouched — those
|
|
10
|
+
* are handled by the callers and the maintenance middleware.
|
|
11
|
+
*/
|
|
12
|
+
export const retryFetch = async (input) => {
|
|
13
|
+
let lastError;
|
|
14
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
15
|
+
try {
|
|
16
|
+
// Fetch a clone so the body is still readable on the next attempt.
|
|
17
|
+
return await fetch(input.clone());
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
lastError = error;
|
|
21
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
22
|
+
const delayS = BASE_DELAY_S * 2 ** (attempt - 1);
|
|
23
|
+
console.warn(chalk.yellow(`Request to the host manager failed (${describeNetworkError(error)}), retrying in ${delayS}s (attempt ${attempt}/${MAX_ATTEMPTS})`));
|
|
24
|
+
await sleep(delayS);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
throw new HostManagerUnreachableError(MAX_ATTEMPTS, lastError);
|
|
29
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class HostManagerUnreachableError extends Error {
|
|
2
|
+
readonly cause: unknown;
|
|
3
|
+
constructor(attempts: number, cause: unknown);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Undici reports every network failure as `TypeError: fetch failed` and hides
|
|
7
|
+
* the actual reason (ECONNRESET, EAI_AGAIN, ...) in the error's cause chain,
|
|
8
|
+
* so surface it in the message.
|
|
9
|
+
*/
|
|
10
|
+
export declare function describeNetworkError(error: unknown): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class HostManagerUnreachableError extends Error {
|
|
2
|
+
constructor(attempts, cause) {
|
|
3
|
+
super(`Could not reach the host manager after ${attempts} attempts: ${describeNetworkError(cause)}. Check your internet connection and firewall, then restart the node.`);
|
|
4
|
+
this.name = 'HostManagerUnreachableError';
|
|
5
|
+
this.cause = cause;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Undici reports every network failure as `TypeError: fetch failed` and hides
|
|
10
|
+
* the actual reason (ECONNRESET, EAI_AGAIN, ...) in the error's cause chain,
|
|
11
|
+
* so surface it in the message.
|
|
12
|
+
*/
|
|
13
|
+
export function describeNetworkError(error) {
|
|
14
|
+
if (!(error instanceof Error))
|
|
15
|
+
return String(error);
|
|
16
|
+
const cause = error.cause;
|
|
17
|
+
if (cause instanceof AggregateError && cause.errors.length > 0) {
|
|
18
|
+
return `${error.message} (${cause.errors.map(describeSingle).join('; ')})`;
|
|
19
|
+
}
|
|
20
|
+
if (cause instanceof Error) {
|
|
21
|
+
return `${error.message} (${describeSingle(cause)})`;
|
|
22
|
+
}
|
|
23
|
+
return describeSingle(error);
|
|
24
|
+
}
|
|
25
|
+
function describeSingle(error) {
|
|
26
|
+
const code = error.code;
|
|
27
|
+
return code && !error.message.includes(code)
|
|
28
|
+
? `${code}: ${error.message}`
|
|
29
|
+
: error.message;
|
|
30
|
+
}
|
|
@@ -57,7 +57,7 @@ export class Benchmark {
|
|
|
57
57
|
const flowResults = this.repository.getFlow(this.benchmarkId);
|
|
58
58
|
if (!flowResults)
|
|
59
59
|
throw new Error(`Cannot find results for flow with id ${this.benchmarkId}`);
|
|
60
|
-
this.repository.deleteflow(this.benchmarkId);
|
|
61
60
|
await HostManager.submitBenchmarkResults(this.benchmarkId, flowResults.state);
|
|
61
|
+
this.repository.deleteflow(this.benchmarkId);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const s3HelperImage = "registry.hub.docker.com/nosana/resource-manager:v1.1.
|
|
1
|
+
export declare const s3HelperImage = "registry.hub.docker.com/nosana/resource-manager:v1.1.14";
|
|
2
2
|
export declare const nosanaBucket = "https://models.nosana.io";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const s3HelperImage = 'registry.hub.docker.com/nosana/resource-manager:v1.1.
|
|
1
|
+
export const s3HelperImage = 'registry.hub.docker.com/nosana/resource-manager:v1.1.14';
|
|
2
2
|
export const nosanaBucket = 'https://models.nosana.io';
|
|
@@ -28,10 +28,14 @@ export declare function findReportedErrors(logs: {
|
|
|
28
28
|
log?: string;
|
|
29
29
|
}[]): string[];
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* The fallback reason for a container that died without reporting through the
|
|
32
|
+
* envelope — a crash before the handlers install, or output lost to the kill.
|
|
33
|
+
* The raw stderr tail is the only account left of what happened, so the last
|
|
34
|
+
* few lines ride along rather than being discarded.
|
|
34
35
|
*/
|
|
36
|
+
export declare function describeUnreportedExit(logs: {
|
|
37
|
+
log?: string;
|
|
38
|
+
}[], statusCode: number): string;
|
|
35
39
|
/**
|
|
36
40
|
* The op a download is running for. Passed only for job resources — a market
|
|
37
41
|
* preload has no op state to write errors to.
|
|
@@ -65,6 +65,26 @@ export function findReportedErrors(logs) {
|
|
|
65
65
|
}
|
|
66
66
|
return messages;
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* The fallback reason for a container that died without reporting through the
|
|
70
|
+
* envelope — a crash before the handlers install, or output lost to the kill.
|
|
71
|
+
* The raw stderr tail is the only account left of what happened, so the last
|
|
72
|
+
* few lines ride along rather than being discarded.
|
|
73
|
+
*/
|
|
74
|
+
export function describeUnreportedExit(logs, statusCode) {
|
|
75
|
+
const tail = logs
|
|
76
|
+
.map((entry) => entry.log?.trim())
|
|
77
|
+
.filter((line) => !!line)
|
|
78
|
+
.slice(-3)
|
|
79
|
+
.join(' | ')
|
|
80
|
+
.slice(-400);
|
|
81
|
+
return `no reason reported (exit code ${statusCode})${tail ? `; stderr tail: ${tail}` : ''}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A resource rendered for an error message. Resources carry credentials (`IAM`,
|
|
85
|
+
* HF `accessToken`), and errors reach the logs and the support channel, so
|
|
86
|
+
* those fields are replaced rather than reported.
|
|
87
|
+
*/
|
|
68
88
|
function describeResource(resource) {
|
|
69
89
|
return JSON.stringify(resource, (key, value) => key === 'IAM' || key === 'accessToken' ? '[hidden]' : value);
|
|
70
90
|
}
|
|
@@ -242,7 +262,7 @@ export class VolumeManager {
|
|
|
242
262
|
// managed to report a reason, so this must throw either way: returning
|
|
243
263
|
// normally here reports success for an unfinished volume, and the run
|
|
244
264
|
// then fails later with the cause lost.
|
|
245
|
-
const reason = reported.at(-1) ??
|
|
265
|
+
const reason = reported.at(-1) ?? describeUnreportedExit(logs, StatusCode);
|
|
246
266
|
throw new Error(`resource download failed for ${name}: ${reason}${describeContainerState(state)}`);
|
|
247
267
|
}
|
|
248
268
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nosana/node",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.37-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.37-rc",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@coral-xyz/anchor": "^0.28.1-beta.1",
|