@nosana/node 1.1.31 → 1.1.32-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
CHANGED
|
@@ -43,6 +43,14 @@ export type ResourceHistory = {
|
|
|
43
43
|
};
|
|
44
44
|
export type VolumeResource = ResourceHistory & {
|
|
45
45
|
volume: string;
|
|
46
|
+
/**
|
|
47
|
+
* True from the moment the Docker volume is created until its download
|
|
48
|
+
* completes. The entry is written before the download starts so that a
|
|
49
|
+
* crash mid-download cannot orphan the volume: a pending entry is resynced
|
|
50
|
+
* when its resource is requested again, and expired like any other entry
|
|
51
|
+
* otherwise. Absent on entries written before this field existed.
|
|
52
|
+
*/
|
|
53
|
+
pending?: boolean;
|
|
46
54
|
};
|
|
47
55
|
export declare class DB {
|
|
48
56
|
db: LowSync<NodeDb>;
|
|
@@ -75,7 +75,10 @@ export class VolumeManager {
|
|
|
75
75
|
const controller = new AbortController();
|
|
76
76
|
const savedVolumes = this.repository.getVolumesResources();
|
|
77
77
|
for (const resource of this.market_required_volumes) {
|
|
78
|
-
|
|
78
|
+
// A pending entry is a download that never finished — the volume exists
|
|
79
|
+
// but its contents can't be trusted, so it must be synced, not skipped.
|
|
80
|
+
const saved = savedVolumes[createResourceName(resource)];
|
|
81
|
+
if (!saved || saved.pending) {
|
|
79
82
|
await this.createRemoteVolume(resource, controller);
|
|
80
83
|
}
|
|
81
84
|
}
|
|
@@ -93,14 +96,20 @@ export class VolumeManager {
|
|
|
93
96
|
if (!volumeName) {
|
|
94
97
|
sync = false;
|
|
95
98
|
const response = await this.containerOrchestration.createVolume();
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
// PODMAN returns `name`, Docker returns `Name`.
|
|
100
|
+
volumeName = response.name ?? response.Name;
|
|
101
|
+
// Recorded before the download starts, marked pending. Recording only
|
|
102
|
+
// after success left every crash mid-download with a full-size volume
|
|
103
|
+
// the DB had never heard of, which nothing ever swept. A pending entry
|
|
104
|
+
// is resynced when the resource is requested again and expires like any
|
|
105
|
+
// other entry if it never is.
|
|
106
|
+
this.repository.updateVolumeResource(resourceName, {
|
|
107
|
+
volume: volumeName,
|
|
108
|
+
required: this.market_required_volumes.some((vol) => createResourceName(vol) === resourceName),
|
|
109
|
+
lastUsed: new Date(),
|
|
110
|
+
usage: 0,
|
|
111
|
+
pending: true,
|
|
112
|
+
});
|
|
104
113
|
}
|
|
105
114
|
switch (resource.type) {
|
|
106
115
|
case 'S3':
|
|
@@ -153,68 +162,81 @@ export class VolumeManager {
|
|
|
153
162
|
}
|
|
154
163
|
async runResourceManagerContainer(volume, name, args, controller, syncing = false) {
|
|
155
164
|
const container = await this.containerOrchestration.runContainer(args);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
start
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
165
|
+
try {
|
|
166
|
+
const logStream = await container.logs({
|
|
167
|
+
stdout: true,
|
|
168
|
+
stderr: false,
|
|
169
|
+
follow: true,
|
|
170
|
+
abortSignal: liveAbortSignal(controller?.signal),
|
|
171
|
+
});
|
|
172
|
+
let start = false;
|
|
173
|
+
let formatSize = 'kb';
|
|
174
|
+
logStream.on('data', (logBuffer) => {
|
|
175
|
+
try {
|
|
176
|
+
const logString = logBuffer.toString('utf8');
|
|
177
|
+
const logJSON = JSON.parse(logString.slice(8, logString.length - 1));
|
|
178
|
+
if (!start && logJSON.event === 'status') {
|
|
179
|
+
start = true;
|
|
180
|
+
const { value, format } = convertFromBytes(logJSON.size.total);
|
|
181
|
+
formatSize = format;
|
|
182
|
+
this.progressBarReporter.start(`${syncing ? 'Syncing' : 'Downloading'} resource ${name}`, {
|
|
183
|
+
format: `{bar} {percentage}% | {value}/{total}${format} | {valueFiles}/{totalFiles} files`,
|
|
184
|
+
}, value, 0, {
|
|
185
|
+
valueFiles: 0,
|
|
186
|
+
totalFiles: logJSON.count.total,
|
|
187
|
+
}, Presets.shades_classic);
|
|
188
|
+
}
|
|
189
|
+
else if (logJSON.event === 'status') {
|
|
190
|
+
const { value } = convertFromBytes(logJSON.size.current, formatSize);
|
|
191
|
+
this.progressBarReporter.update(value, {
|
|
192
|
+
valueFiles: logJSON.count.current,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
184
195
|
}
|
|
196
|
+
catch (error) { }
|
|
197
|
+
});
|
|
198
|
+
const { StatusCode } = await container.wait({
|
|
199
|
+
condition: 'not-running',
|
|
200
|
+
abortSignal: liveAbortSignal(controller?.signal),
|
|
201
|
+
});
|
|
202
|
+
if (StatusCode !== 0) {
|
|
203
|
+
const errrorBuffer = await container.logs({
|
|
204
|
+
follow: false,
|
|
205
|
+
stdout: false,
|
|
206
|
+
stderr: true,
|
|
207
|
+
tail: 24999,
|
|
208
|
+
timestamps: true,
|
|
209
|
+
});
|
|
210
|
+
const { logs } = extractLogsAndResultsFromLogBuffer(errrorBuffer, undefined);
|
|
211
|
+
// Read the state before the catch removes it: a killed process logs
|
|
212
|
+
// nothing, so the runtime's own record is the only account of why.
|
|
213
|
+
const state = await container
|
|
214
|
+
.inspect()
|
|
215
|
+
.then(({ State }) => State)
|
|
216
|
+
.catch(() => undefined);
|
|
217
|
+
// A non-zero exit is a failed download whether or not the container
|
|
218
|
+
// managed to report a reason, so this must throw either way: returning
|
|
219
|
+
// normally here reports success for an unfinished volume, and the run
|
|
220
|
+
// then fails later with the cause lost.
|
|
221
|
+
const reason = findReportedError(logs) ??
|
|
222
|
+
`resource download failed (exit code ${StatusCode})`;
|
|
223
|
+
throw new Error(`${reason}${describeContainerState(state)}`);
|
|
185
224
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const { StatusCode } = await container.wait({
|
|
189
|
-
condition: 'not-running',
|
|
190
|
-
abortSignal: liveAbortSignal(controller?.signal),
|
|
191
|
-
});
|
|
192
|
-
// If download failed, remove volume
|
|
193
|
-
if (StatusCode !== 0) {
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
194
227
|
this.progressBarReporter.stop(`${syncing ? 'Syncing' : 'Downloading'} resource ${name} stopped`);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
.then(({ State }) => State)
|
|
208
|
-
.catch(() => undefined);
|
|
209
|
-
await container.remove({ force: true });
|
|
210
|
-
await this.containerOrchestration.deleteVolume(volume);
|
|
211
|
-
// A non-zero exit is a failed download whether or not the container
|
|
212
|
-
// managed to report a reason, so this must throw either way: returning
|
|
213
|
-
// normally here reports success for a volume already deleted, and the run
|
|
214
|
-
// then fails later with the cause lost.
|
|
215
|
-
const reason = findReportedError(logs) ??
|
|
216
|
-
`resource download failed (exit code ${StatusCode})`;
|
|
217
|
-
throw new Error(`${reason}${describeContainerState(state)}`);
|
|
228
|
+
// Force-removal also stops a download an abort left running.
|
|
229
|
+
await container.remove({ force: true }).catch(() => { });
|
|
230
|
+
// The volume goes with the failed download, and its entry goes with the
|
|
231
|
+
// volume. If the deletion itself fails, the entry must stay: a pending
|
|
232
|
+
// entry over a leftover volume is resynced or expired later, which is
|
|
233
|
+
// the point of writing it up front.
|
|
234
|
+
try {
|
|
235
|
+
await this.containerOrchestration.deleteVolume(volume);
|
|
236
|
+
this.repository.deleteVolumeResource(name);
|
|
237
|
+
}
|
|
238
|
+
catch { }
|
|
239
|
+
throw error;
|
|
218
240
|
}
|
|
219
241
|
this.progressBarReporter.stop(`${syncing ? 'Synced' : 'Downloaded'} resource ${name} completed`);
|
|
220
242
|
await this.containerOrchestration.stopAndDeleteContainer(container.id);
|
|
@@ -226,6 +248,8 @@ export class VolumeManager {
|
|
|
226
248
|
required: this.market_required_volumes.some((vol) => createResourceName(vol) === bucket),
|
|
227
249
|
lastUsed: new Date(),
|
|
228
250
|
usage: volumeObj?.usage + 1 || 1,
|
|
251
|
+
// Only a completed sync reaches here, which is what pending means.
|
|
252
|
+
pending: false,
|
|
229
253
|
});
|
|
230
254
|
}
|
|
231
255
|
async hasVolume(resource) {
|
|
@@ -278,7 +302,10 @@ export class VolumeManager {
|
|
|
278
302
|
if (hoursSinceLastUsed > 24) {
|
|
279
303
|
try {
|
|
280
304
|
await this.containerOrchestration.deleteVolume(volume);
|
|
281
|
-
|
|
305
|
+
// Keyed by resource name — `volume` is the Docker volume ID, which
|
|
306
|
+
// this map is not keyed by, so passing it left the entry behind
|
|
307
|
+
// pointing at a deleted volume.
|
|
308
|
+
this.repository.deleteVolumeResource(resource);
|
|
282
309
|
}
|
|
283
310
|
catch (err) {
|
|
284
311
|
const message = err.json.message;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nosana/node",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.32-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.32-rc",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@coral-xyz/anchor": "^0.28.1-beta.1",
|