amalgm 0.1.207 → 0.1.208
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.
|
@@ -17,6 +17,7 @@ const CLAIM_LIMIT = 16;
|
|
|
17
17
|
const CLAIM_LEASE_MS = 30_000;
|
|
18
18
|
const RETRY_MS = 1_000;
|
|
19
19
|
const REAUTHORIZE_MS = 30_000;
|
|
20
|
+
const REPO_OBJECT_REQUEST_TIMEOUT_MS = 2_000;
|
|
20
21
|
|
|
21
22
|
function createSharedRealtimeTunnel({
|
|
22
23
|
send,
|
|
@@ -24,6 +25,7 @@ function createSharedRealtimeTunnel({
|
|
|
24
25
|
localPort,
|
|
25
26
|
localHeaders,
|
|
26
27
|
warn,
|
|
28
|
+
repoObjectRequestTimeoutMs = REPO_OBJECT_REQUEST_TIMEOUT_MS,
|
|
27
29
|
}) {
|
|
28
30
|
const mutationInflight = new Map();
|
|
29
31
|
const promotionInflight = new Map();
|
|
@@ -277,8 +279,17 @@ function createSharedRealtimeTunnel({
|
|
|
277
279
|
repoObjectTimer.unref?.();
|
|
278
280
|
}
|
|
279
281
|
|
|
282
|
+
function expireRepoObjectRequests(now = Date.now()) {
|
|
283
|
+
for (const inflight of [repoUploadInflight, repoDownloadInflight]) {
|
|
284
|
+
for (const [requestId, object] of inflight) {
|
|
285
|
+
if (now - object.sentAt >= repoObjectRequestTimeoutMs) inflight.delete(requestId);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
280
290
|
async function drainRepoObjects() {
|
|
281
291
|
if (!enabled) return;
|
|
292
|
+
expireRepoObjectRequests();
|
|
282
293
|
if (repoUploadInflight.size === 0) {
|
|
283
294
|
const result = await localStateJson('POST', '/state/repo-objects/uploads/claim', {});
|
|
284
295
|
const upload = result?.upload;
|
|
@@ -295,6 +306,7 @@ function createSharedRealtimeTunnel({
|
|
|
295
306
|
repoUploadInflight.set(requestId, {
|
|
296
307
|
resourceId: upload.resourceId,
|
|
297
308
|
sha256: upload.sha256,
|
|
309
|
+
sentAt: Date.now(),
|
|
298
310
|
});
|
|
299
311
|
}
|
|
300
312
|
}
|
|
@@ -313,6 +325,7 @@ function createSharedRealtimeTunnel({
|
|
|
313
325
|
repoDownloadInflight.set(requestId, {
|
|
314
326
|
resourceId: download.resourceId,
|
|
315
327
|
sha256: download.sha256,
|
|
328
|
+
sentAt: Date.now(),
|
|
316
329
|
});
|
|
317
330
|
}
|
|
318
331
|
}
|
package/package.json
CHANGED
|
@@ -122,3 +122,59 @@ test('repo object chunks travel through the shared tunnel one at a time', async
|
|
|
122
122
|
server.close();
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
|
+
|
|
126
|
+
test('repo object chunks retry when an acknowledgement is lost', async () => {
|
|
127
|
+
const uploadBytes = Buffer.from('retry this content-addressed chunk');
|
|
128
|
+
const uploadSha = crypto.createHash('sha256').update(uploadBytes).digest('hex');
|
|
129
|
+
const resourceId = 'shr_cp1abcdefghijklmnopqrstuvwx';
|
|
130
|
+
const server = http.createServer((req, res) => {
|
|
131
|
+
req.resume();
|
|
132
|
+
req.on('end', () => {
|
|
133
|
+
res.setHeader('content-type', 'application/json');
|
|
134
|
+
if (req.url === '/state/shared/replicas') {
|
|
135
|
+
res.end('{"replicas":[]}');
|
|
136
|
+
} else if (req.url.startsWith('/state/stream')) {
|
|
137
|
+
res.writeHead(200, { 'content-type': 'text/event-stream' });
|
|
138
|
+
res.write('\n');
|
|
139
|
+
} else if (req.url === '/state/repo-objects/uploads/claim') {
|
|
140
|
+
res.end(JSON.stringify({
|
|
141
|
+
upload: {
|
|
142
|
+
resourceId,
|
|
143
|
+
sha256: uploadSha,
|
|
144
|
+
bytes: uploadBytes.length,
|
|
145
|
+
dataBase64: uploadBytes.toString('base64'),
|
|
146
|
+
},
|
|
147
|
+
}));
|
|
148
|
+
} else if (req.url === '/state/repo-objects/downloads/claim') {
|
|
149
|
+
res.end('{"download":null}');
|
|
150
|
+
} else {
|
|
151
|
+
res.end('{}');
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
await new Promise((resolve) => { server.listen(0, '127.0.0.1', resolve); });
|
|
156
|
+
|
|
157
|
+
const frames = [];
|
|
158
|
+
const tunnel = createSharedRealtimeTunnel({
|
|
159
|
+
send: (frame) => { frames.push(frame); return true; },
|
|
160
|
+
sendLarge: (frame) => { frames.push(frame); return true; },
|
|
161
|
+
localPort: () => server.address().port,
|
|
162
|
+
localHeaders: (headers) => headers,
|
|
163
|
+
warn: () => {},
|
|
164
|
+
repoObjectRequestTimeoutMs: 25,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
tunnel.enable(['shared.mutation.v1']);
|
|
169
|
+
await until(
|
|
170
|
+
() => frames.filter((frame) => frame.type === 'repo.object.put').length >= 2,
|
|
171
|
+
'the unacknowledged upload retry',
|
|
172
|
+
);
|
|
173
|
+
const puts = frames.filter((frame) => frame.type === 'repo.object.put');
|
|
174
|
+
assert.notEqual(puts[0].request_id, puts[1].request_id);
|
|
175
|
+
assert.equal(puts[0].sha256, puts[1].sha256);
|
|
176
|
+
} finally {
|
|
177
|
+
tunnel.stop();
|
|
178
|
+
server.close();
|
|
179
|
+
}
|
|
180
|
+
});
|
|
@@ -120,6 +120,39 @@ test('only a value different from both the materialized baseline and the rail is
|
|
|
120
120
|
branches: [{ name: 'main', oid: oidC }],
|
|
121
121
|
};
|
|
122
122
|
assert.equal(hasLocalContribution(local, remoteAdvance, baseline), false);
|
|
123
|
+
assert.equal(hasLocalContribution(
|
|
124
|
+
{
|
|
125
|
+
head: { branch: 'main', oid: oidB, detached: false },
|
|
126
|
+
refs: [{ name: 'main', oid: oidB }],
|
|
127
|
+
},
|
|
128
|
+
remoteAdvance,
|
|
129
|
+
{
|
|
130
|
+
branch: 'main',
|
|
131
|
+
baseOid: oidB,
|
|
132
|
+
branches: [{ name: 'main', oid: oidB }],
|
|
133
|
+
},
|
|
134
|
+
null,
|
|
135
|
+
{
|
|
136
|
+
head: { branch: 'main', oid: oidA, detached: false },
|
|
137
|
+
branches: [{ name: 'main', oid: oidA }],
|
|
138
|
+
},
|
|
139
|
+
), false);
|
|
140
|
+
assert.equal(hasLocalContribution(
|
|
141
|
+
local,
|
|
142
|
+
plan,
|
|
143
|
+
{
|
|
144
|
+
...baseline,
|
|
145
|
+
branches: [...baseline.branches, { name: 'feature', oid: null }],
|
|
146
|
+
},
|
|
147
|
+
null,
|
|
148
|
+
{
|
|
149
|
+
head: { branch: 'main', oid: oidA, detached: false },
|
|
150
|
+
branches: [
|
|
151
|
+
{ name: 'main', oid: oidA },
|
|
152
|
+
{ name: 'feature', oid: oidB },
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
), true);
|
|
123
156
|
assert.equal(hasLocalContribution(local, plan, baseline, {
|
|
124
157
|
checkpoint: 'repo-checkpoint@7',
|
|
125
158
|
machine: 'machine-a',
|
|
@@ -245,11 +245,13 @@ function hasLocalContribution(card, plan, baseline, checkpoint = null, previousC
|
|
|
245
245
|
const current = currentRefs.get(name) ?? null;
|
|
246
246
|
const shared = railRefs.get(name) ?? null;
|
|
247
247
|
const previous = baselineRefs.get(name) ?? null;
|
|
248
|
-
const
|
|
249
|
-
|
|
248
|
+
const published = previousRefs.get(name) ?? null;
|
|
249
|
+
const reversesPublishedSharedValue = previousRefs.has(name)
|
|
250
|
+
&& published === shared
|
|
251
|
+
&& current !== published;
|
|
250
252
|
if (
|
|
251
253
|
current !== shared
|
|
252
|
-
&& (!baselineRefs.has(name) || current !== previous ||
|
|
254
|
+
&& (!baselineRefs.has(name) || current !== previous || reversesPublishedSharedValue)
|
|
253
255
|
) return true;
|
|
254
256
|
}
|
|
255
257
|
|