@decentnetwork/peer 0.1.54 → 0.1.55
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/compat/filetransfer.js +39 -43
- package/package.json +1 -1
|
@@ -138,7 +138,7 @@ export class FileTransferManager {
|
|
|
138
138
|
const req = encodeFileSendRequest(fileNumber, opts.kind ?? FILEKIND.DATA, BigInt(data.length), fileId, opts.name);
|
|
139
139
|
const st = {
|
|
140
140
|
fileNumber, fileId, name: opts.name, data, size: data.length,
|
|
141
|
-
acked: 0,
|
|
141
|
+
acked: 0, nextSend: 0, pumping: false, started: false, req, startMs: nowMs(),
|
|
142
142
|
lastProgressAcked: 0, lastAckAdvanceMs: nowMs(), lastResendMs: 0,
|
|
143
143
|
};
|
|
144
144
|
this.#map(this.#sending, friendId).set(fileNumber, st);
|
|
@@ -369,23 +369,27 @@ export class FileTransferManager {
|
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
else if (maxRecv > st.acked + MAX_FILE_DATA_SIZE && nowMs() - st.lastResendMs >= FAST_RT_MIN_MS) {
|
|
372
|
-
// Duplicate ack WITH a gap → the chunk at `acked` was lost.
|
|
373
|
-
// the
|
|
374
|
-
// dup-
|
|
375
|
-
// is treated as random, not congestion.
|
|
372
|
+
// Duplicate ack WITH a gap → the chunk at `acked` was lost. Rewind the send
|
|
373
|
+
// cursor to the ack point so the loop re-sends the hole (rate-limited to
|
|
374
|
+
// ~1 RTT so dup-ack bursts for one loss don't storm). No window backoff:
|
|
375
|
+
// file-path loss is treated as random, not congestion.
|
|
376
376
|
st.lastResendMs = nowMs();
|
|
377
|
-
|
|
377
|
+
if (st.nextSend > st.acked)
|
|
378
|
+
st.nextSend = st.acked;
|
|
378
379
|
}
|
|
379
380
|
if (st.acked >= st.size) {
|
|
380
381
|
this.#completeSend(friendId, st);
|
|
381
382
|
return;
|
|
382
383
|
}
|
|
383
|
-
this.#pump(friendId, st);
|
|
384
|
+
void this.#pump(friendId, st);
|
|
384
385
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
386
|
+
// Single send loop. Awaits each send so net_crypto / the UDP socket applies
|
|
387
|
+
// backpressure (fire-and-forget blasted the whole window into the macOS UDP
|
|
388
|
+
// buffer at once → systematic overflow/drops the retransmit couldn't dig out
|
|
389
|
+
// of → a big file "failed"). Sends from `nextSend`, which the ack handler and
|
|
390
|
+
// watchdog rewind to `acked` to retransmit a hole (go-back-N). The fixed
|
|
391
|
+
// window bounds in-flight bytes; throughput self-clocks to the path.
|
|
392
|
+
async #pump(friendId, st) {
|
|
389
393
|
if (!st.timer) {
|
|
390
394
|
let stalledSinceMs = nowMs();
|
|
391
395
|
st.timer = setInterval(() => {
|
|
@@ -408,44 +412,36 @@ export class FileTransferManager {
|
|
|
408
412
|
return;
|
|
409
413
|
}
|
|
410
414
|
st.lastAckAdvanceMs = nowMs();
|
|
411
|
-
st.highestSent = st.acked; // go-back-N: re-send from the ack point
|
|
412
415
|
st.lastResendMs = nowMs();
|
|
413
|
-
|
|
416
|
+
if (st.nextSend > st.acked)
|
|
417
|
+
st.nextSend = st.acked; // go-back-N
|
|
418
|
+
void this.#pump(friendId, st);
|
|
414
419
|
}, WATCHDOG_MS);
|
|
415
420
|
if (typeof st.timer.unref === "function")
|
|
416
421
|
st.timer.unref();
|
|
417
422
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
423
|
+
if (st.pumping)
|
|
424
|
+
return;
|
|
425
|
+
st.pumping = true;
|
|
426
|
+
try {
|
|
427
|
+
while (this.#map(this.#sending, friendId).has(st.fileNumber) && st.acked < st.size) {
|
|
428
|
+
const windowEnd = Math.min(st.acked + WINDOW_CHUNKS * MAX_FILE_DATA_SIZE, st.size);
|
|
429
|
+
if (st.nextSend >= windowEnd)
|
|
430
|
+
break; // window full / all sent — wait for acks
|
|
431
|
+
const off = st.nextSend;
|
|
432
|
+
const end = Math.min(off + MAX_FILE_DATA_SIZE, st.size);
|
|
433
|
+
try {
|
|
434
|
+
await this.send(friendId, PACKET_ID_FILE_DATA, encodeFileData(st.fileNumber, off, st.data.subarray(off, end)));
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
break; // transport down; the watchdog re-kicks
|
|
438
|
+
}
|
|
439
|
+
if (st.nextSend === off)
|
|
440
|
+
st.nextSend = end; // unless a rewind moved it
|
|
441
|
+
}
|
|
434
442
|
}
|
|
435
|
-
|
|
436
|
-
st.
|
|
437
|
-
}
|
|
438
|
-
// Re-send the chunks in [from, to) — the gap region the receiver is missing.
|
|
439
|
-
// Already-received chunks are idempotent (placed by offset on the receiver).
|
|
440
|
-
#resendRange(friendId, st, from, to) {
|
|
441
|
-
let off = from;
|
|
442
|
-
const end0 = Math.min(to, st.size);
|
|
443
|
-
while (off < end0) {
|
|
444
|
-
if (!this.#map(this.#sending, friendId).has(st.fileNumber))
|
|
445
|
-
return;
|
|
446
|
-
const end = Math.min(off + MAX_FILE_DATA_SIZE, st.size);
|
|
447
|
-
void this.send(friendId, PACKET_ID_FILE_DATA, encodeFileData(st.fileNumber, off, st.data.subarray(off, end))).catch(() => undefined);
|
|
448
|
-
off = end;
|
|
443
|
+
finally {
|
|
444
|
+
st.pumping = false;
|
|
449
445
|
}
|
|
450
446
|
}
|
|
451
447
|
#completeSend(friendId, st) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.55",
|
|
4
4
|
"description": "Pure TypeScript port of Elastos Carrier (toxcore-derived) P2P messaging. DHT, onion routing, TCP relay, FlatBuffers app payloads, Express offline relay. Wire-compatible with iOS Beagle and the Carrier C SDK.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|