amalgm 0.1.242 → 0.1.244
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/lib/supervisor.js
CHANGED
|
@@ -174,6 +174,8 @@ const SAFE_PROCESS_ENV_KEYS = [
|
|
|
174
174
|
'LANG',
|
|
175
175
|
'LC_ALL',
|
|
176
176
|
'LOGNAME',
|
|
177
|
+
'AMALGM_AUTO_UPDATE',
|
|
178
|
+
'AMALGM_DISABLE_AUTO_UPDATE',
|
|
177
179
|
'AMALGM_NATIVE_NODE_MODULES',
|
|
178
180
|
'AMALGM_SKIP_NATIVE_INSTALL',
|
|
179
181
|
'ELECTRON_RUN_AS_NODE',
|
|
@@ -1343,5 +1345,6 @@ module.exports = {
|
|
|
1343
1345
|
liveDeclaredRuntimePorts,
|
|
1344
1346
|
runtimeStateFilesUnder,
|
|
1345
1347
|
runtimeStateSearchRoots,
|
|
1348
|
+
safeBaseProcessEnv,
|
|
1346
1349
|
},
|
|
1347
1350
|
};
|
package/package.json
CHANGED
|
@@ -269,8 +269,8 @@ test('failed uploads yield the bulk lane to later durable objects', () => {
|
|
|
269
269
|
assert.notEqual(second.sha256, first.sha256);
|
|
270
270
|
const terminal = failUpload(second.resourceId, second.sha256, {
|
|
271
271
|
retryable: false,
|
|
272
|
-
code: '
|
|
273
|
-
message: '
|
|
272
|
+
code: 'invalid_repo_object',
|
|
273
|
+
message: 'this exact object is invalid',
|
|
274
274
|
});
|
|
275
275
|
assert.equal(terminal.changed, true);
|
|
276
276
|
assert.equal(terminal.terminal, true);
|
|
@@ -288,21 +288,35 @@ test('failed uploads yield the bulk lane to later durable objects', () => {
|
|
|
288
288
|
|
|
289
289
|
test('legacy shared-resource uploads can terminate without starving current projects', () => {
|
|
290
290
|
const legacyResourceId = 'shr_legacyobjectqueue0001';
|
|
291
|
-
const
|
|
292
|
-
|
|
291
|
+
const legacyChunks = [
|
|
292
|
+
Buffer.from('first stale object chunk from a legacy rail'),
|
|
293
|
+
Buffer.from('second stale object chunk from a legacy rail'),
|
|
294
|
+
].map((bytes) => ({
|
|
295
|
+
bytes,
|
|
296
|
+
sha256: crypto.createHash('sha256').update(bytes).digest('hex'),
|
|
297
|
+
}));
|
|
293
298
|
const objectRoot = path.join(process.env.AMALGM_DIR, 'state', 'repo-objects');
|
|
294
299
|
const manifests = path.join(objectRoot, 'manifests');
|
|
295
300
|
const chunks = path.join(objectRoot, 'chunks');
|
|
296
|
-
|
|
301
|
+
for (const chunk of legacyChunks) {
|
|
302
|
+
fs.writeFileSync(path.join(chunks, `${chunk.sha256}.bin`), chunk.bytes);
|
|
303
|
+
}
|
|
297
304
|
fs.writeFileSync(path.join(manifests, '000-legacy-shared-resource.json'), JSON.stringify({
|
|
298
305
|
contract: 'git-bundle-chunks@2',
|
|
299
306
|
resourceId: legacyResourceId,
|
|
300
307
|
repoKey: 'legacy-repo',
|
|
301
308
|
oid: '1'.repeat(40),
|
|
302
309
|
ref: `refs/amalgm/exports/${'1'.repeat(40)}`,
|
|
303
|
-
checksum: locatorChecksum(
|
|
304
|
-
|
|
305
|
-
|
|
310
|
+
checksum: locatorChecksum(legacyChunks.map((chunk) => ({
|
|
311
|
+
sha256: chunk.sha256,
|
|
312
|
+
bytes: chunk.bytes.length,
|
|
313
|
+
}))),
|
|
314
|
+
bytes: legacyChunks.reduce((total, chunk) => total + chunk.bytes.length, 0),
|
|
315
|
+
chunks: legacyChunks.map((chunk) => ({
|
|
316
|
+
sha256: chunk.sha256,
|
|
317
|
+
bytes: chunk.bytes.length,
|
|
318
|
+
uploaded: false,
|
|
319
|
+
})),
|
|
306
320
|
createdAt: new Date().toISOString(),
|
|
307
321
|
}));
|
|
308
322
|
|
|
@@ -312,7 +326,7 @@ test('legacy shared-resource uploads can terminate without starving current proj
|
|
|
312
326
|
);
|
|
313
327
|
const legacy = claimUpload();
|
|
314
328
|
assert.equal(legacy.resourceId, legacyResourceId);
|
|
315
|
-
assert.equal(legacy.sha256,
|
|
329
|
+
assert.equal(legacy.sha256, legacyChunks[0].sha256);
|
|
316
330
|
const terminal = failUpload(legacy.resourceId, legacy.sha256, {
|
|
317
331
|
retryable: false,
|
|
318
332
|
code: 'shared_resource_read_forbidden',
|
|
@@ -323,6 +337,9 @@ test('legacy shared-resource uploads can terminate without starving current proj
|
|
|
323
337
|
|
|
324
338
|
const current = claimUpload();
|
|
325
339
|
assert.equal(current.resourceId, PROJECT);
|
|
326
|
-
assert.
|
|
340
|
+
assert.ok(
|
|
341
|
+
legacyChunks.every((chunk) => current.sha256 !== chunk.sha256),
|
|
342
|
+
'a resource-wide rejection skips every remaining chunk from the stale rail',
|
|
343
|
+
);
|
|
327
344
|
acknowledgeUpload(current.resourceId, current.sha256);
|
|
328
345
|
});
|
|
@@ -34,6 +34,12 @@ const REQUESTS = path.join(ROOT, 'requests');
|
|
|
34
34
|
const preparing = new Map();
|
|
35
35
|
const UPLOAD_RETRY_BASE_MS = 1_000;
|
|
36
36
|
const UPLOAD_RETRY_MAX_MS = 60_000;
|
|
37
|
+
const RESOURCE_WIDE_UPLOAD_FAILURES = new Set([
|
|
38
|
+
'invalid_repo_project',
|
|
39
|
+
'private_resource_forbidden',
|
|
40
|
+
'shared_resource_read_forbidden',
|
|
41
|
+
'shared_resource_write_forbidden',
|
|
42
|
+
]);
|
|
37
43
|
|
|
38
44
|
function isProjectId(value) {
|
|
39
45
|
return require('./tree/identity').isUuid(String(value || ''));
|
|
@@ -299,6 +305,10 @@ function failUpload(resourceId, digest, failure = {}) {
|
|
|
299
305
|
ensureDirs();
|
|
300
306
|
const failedAt = new Date().toISOString();
|
|
301
307
|
const retryable = failure?.retryable !== false;
|
|
308
|
+
const failureCode = typeof failure?.code === 'string'
|
|
309
|
+
? failure.code
|
|
310
|
+
: 'repo_object_upload_failed';
|
|
311
|
+
const resourceWide = RESOURCE_WIDE_UPLOAD_FAILURES.has(failureCode);
|
|
302
312
|
let changed = false;
|
|
303
313
|
let terminal = false;
|
|
304
314
|
let retryAfter = null;
|
|
@@ -308,30 +318,38 @@ function failUpload(resourceId, digest, failure = {}) {
|
|
|
308
318
|
const file = path.join(directory, name);
|
|
309
319
|
const manifest = readJson(file);
|
|
310
320
|
if (manifest?.resourceId !== resourceId || !manifest?.chunks) continue;
|
|
311
|
-
const
|
|
312
|
-
entry.
|
|
321
|
+
const failedChunks = manifest.chunks.filter((entry) => (
|
|
322
|
+
!entry.uploaded && (resourceWide || entry.sha256 === digest)
|
|
313
323
|
));
|
|
314
|
-
if (
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
324
|
+
if (failedChunks.length < 1) continue;
|
|
325
|
+
for (const chunk of failedChunks) {
|
|
326
|
+
const chunkAttempts = Number(chunk.uploadAttempts || 0) + 1;
|
|
327
|
+
attempts = Math.max(attempts, chunkAttempts);
|
|
328
|
+
chunk.uploadAttempts = chunkAttempts;
|
|
329
|
+
chunk.uploadLastError = {
|
|
330
|
+
code: failureCode,
|
|
331
|
+
message: typeof failure?.message === 'string'
|
|
332
|
+
? failure.message
|
|
333
|
+
: 'repo object upload failed',
|
|
334
|
+
retryable,
|
|
335
|
+
at: failedAt,
|
|
336
|
+
};
|
|
337
|
+
if (retryable) {
|
|
338
|
+
const delayMs = Math.min(
|
|
339
|
+
UPLOAD_RETRY_MAX_MS,
|
|
340
|
+
UPLOAD_RETRY_BASE_MS * (2 ** Math.min(chunkAttempts - 1, 16)),
|
|
341
|
+
);
|
|
342
|
+
const chunkRetryAfter = new Date(Date.now() + delayMs).toISOString();
|
|
343
|
+
retryAfter = retryAfter && Date.parse(retryAfter) > Date.parse(chunkRetryAfter)
|
|
344
|
+
? retryAfter
|
|
345
|
+
: chunkRetryAfter;
|
|
346
|
+
chunk.uploadRetryAfter = chunkRetryAfter;
|
|
347
|
+
delete chunk.uploadTerminal;
|
|
348
|
+
} else {
|
|
349
|
+
chunk.uploadTerminal = { ...chunk.uploadLastError };
|
|
350
|
+
delete chunk.uploadRetryAfter;
|
|
351
|
+
terminal = true;
|
|
352
|
+
}
|
|
335
353
|
}
|
|
336
354
|
writeJson(file, manifest);
|
|
337
355
|
changed = true;
|