@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.
@@ -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, highestSent: 0, started: false, req, startMs: nowMs(),
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. Fast-retransmit
373
- // the hole region immediately (rate-limited to ~1 RTT so a burst of
374
- // dup-acks for one loss doesn't storm). No window backoff: file-path loss
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
- this.#resendRange(friendId, st, st.acked, maxRecv);
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); // send new data
384
+ void this.#pump(friendId, st);
384
385
  }
385
- #pump(friendId, st) {
386
- // Watchdog: if the ack hasn't advanced within WATCHDOG_MS, a chunk (or the
387
- // fast-retransmit) was lostgo-back-N resend from the ack point. Backstop
388
- // to the dup-ack fast-retransmit above.
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 oncesystematic 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
- this.#pumpFrom(friendId, st, st.acked);
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
- // Send only NEW data, from the high-water mark up to the fixed window edge.
419
- this.#pumpFrom(friendId, st, Math.max(st.acked, st.highestSent));
420
- }
421
- // Fire chunks from `from` up to the window edge. Fire-and-forget: the fixed
422
- // window bounds in-flight bytes and the 4 MB socket buffer absorbs the burst,
423
- // so we don't serialize on a per-chunk await (which capped throughput at one
424
- // chunk per event-loop turn → minutes for a big file).
425
- #pumpFrom(friendId, st, from) {
426
- const windowEnd = Math.min(st.acked + WINDOW_CHUNKS * MAX_FILE_DATA_SIZE, st.size);
427
- let off = from;
428
- while (off < windowEnd) {
429
- if (!this.#map(this.#sending, friendId).has(st.fileNumber))
430
- return; // cancelled
431
- const end = Math.min(off + MAX_FILE_DATA_SIZE, st.size);
432
- void this.send(friendId, PACKET_ID_FILE_DATA, encodeFileData(st.fileNumber, off, st.data.subarray(off, end))).catch(() => undefined);
433
- off = end;
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
- if (off > st.highestSent)
436
- st.highestSent = off;
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.54",
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",