@torrent-tv/proxy 2.87.11 → 2.87.13

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.
@@ -593,13 +593,13 @@ export class SharedPieceStore {
593
593
  }
594
594
 
595
595
  /**
596
- * Whether the machine's share of memory is smaller than one reader's window.
596
+ * Whether the machine's share is smaller than current read demand.
597
597
  *
598
598
  * The store holds the window anyway — refusing would leave the read it is
599
599
  * serving unable to finish, which is worse — but it is the honest measure of
600
600
  * "this machine cannot take any more", and it is measured rather than
601
- * guessed: it is the last revision's own comparison of what the machine
602
- * allowed against what the widest reader declared.
601
+ * guessed: it is the last revision's comparison of the machine's share
602
+ * against declared windows and distinct pieces pinned by live reads.
603
603
  */
604
604
  get isBeyondTheMachine() {
605
605
  return this.#beyondTheMachine;
@@ -650,15 +650,17 @@ export class SharedPieceStore {
650
650
  * resident piece ends up pinned, the read returns zero bytes and ffmpeg takes
651
651
  * that for the end of the file (field 2026-08-15, roadmap item 9).
652
652
  *
653
- * With no reader declaring anything there is no demand to speak of, so the
654
- * store asks for what the machine allows and the first revision after a read
655
- * begins brings it down to what that read needs.
653
+ * A declared window is not the only evidence of demand: a live reader pins
654
+ * each piece it is waiting on or handing to its consumer. Include those
655
+ * distinct pins as well, because some byte reads do not have a priority-map
656
+ * range to declare.
656
657
  */
657
658
  get wantedBytes() {
658
659
  const demand = this.#lru.demand();
660
+ const pinned = this.#lru.pinnedCount;
659
661
  if (demand.readers > 0) {
660
662
  this.#widestSeenPieces = Math.max(this.#widestSeenPieces, demand.widestPieces);
661
- const pieces = Math.max(MIN_RESIDENT_PIECES, demand.unionPieces, demand.widestPieces);
663
+ const pieces = Math.max(MIN_RESIDENT_PIECES, demand.unionPieces, demand.widestPieces, pinned);
662
664
  // Plus room to absorb what arrives while a write is finishing. Asking for
663
665
  // exactly what the readers want leaves no free place ever, so every
664
666
  // arrival evicts one of them — measured 2026-09-02: `6 reader(s) want 23
@@ -666,9 +668,9 @@ export class SharedPieceStore {
666
668
  // that followed.
667
669
  return (pieces + this.slackPieces()) * this.#chunkLength;
668
670
  }
669
- // WITH NO READER, THE FLOOR IS ONE READER'S WINDOW — the widest this store
670
- // has actually been asked for, which is measured rather than chosen, and
671
- // `MIN_RESIDENT_PIECES` only until it has been asked for anything.
671
+ // WITH NO DECLARED RANGE, the floor is the widest prior window or the
672
+ // distinct pieces pinned by current reads, both measured rather than
673
+ // chosen. `MIN_RESIDENT_PIECES` applies until either has been observed.
672
674
  //
673
675
  // It used to fall to the minimum the moment the last reader went, and the
674
676
  // allowance is re-derived once a minute (`STORE_REPORT_INTERVAL_MS`) while
@@ -677,12 +679,11 @@ export class SharedPieceStore {
677
679
  // to another file of the same torrent and the store was at
678
680
  // `0/3 (0MB of 12MB allowed)` with the machine offering 4.3 GB.
679
681
  //
680
- // Holding one window's worth between readers is not waste: it is what the
681
- // next read will ask for within seconds, and the pieces in it are the ones
682
- // that reader left off at. A store that has never been read keeps nothing
683
- // either — its pieces are arriving for a reader on the way, and they have
684
- // the disk.
685
- return (Math.max(MIN_RESIDENT_PIECES, this.#widestSeenPieces) + this.slackPieces()) * this.#chunkLength;
682
+ // Retaining the last declared window between reads avoids dropping back to
683
+ // the minimum; current pins cover reads that have no declared window. A
684
+ // store that has never been read keeps only the measured minimum.
685
+ const pieces = Math.max(MIN_RESIDENT_PIECES, this.#widestSeenPieces, pinned);
686
+ return (pieces + this.slackPieces()) * this.#chunkLength;
686
687
  }
687
688
 
688
689
  reviseGrowthCeiling(allowedBytes) {
@@ -692,27 +693,33 @@ export class SharedPieceStore {
692
693
  // the machine was full kept a small allowance for its whole life, however
693
694
  // much memory was freed afterwards (roadmap item 2, 2026-09-02).
694
695
  const wanted = Math.floor(Number(allowedBytes) / this.#chunkLength);
695
- // Never below what the live readers together hold, even when the machine's
696
- // share says less. A store that cannot hold what its readers are pinning
697
- // cannot complete any of their reads at all: every resident piece ends up
698
- // pinned, a read returns zero bytes and ffmpeg takes that for the end of
699
- // the file, which killed every encoder on that file in the field on
696
+ // Never below what the live readers together need, even when the machine's
697
+ // share says less. That is the union of declared windows, or the distinct
698
+ // pieces pinned by direct reads that have no declared window. A store that
699
+ // cannot hold what its readers are pinning cannot complete those reads:
700
+ // every resident piece ends up pinned, a read returns zero bytes and
701
+ // ffmpeg takes that for the end of the file, which killed every encoder on
702
+ // that file in the field on
700
703
  // 2026-08-15 (one reader) and again on 2026-09-07 (three readers of one
701
704
  // file at once — picture, sound and the edge-warming read — where this
702
705
  // floor was still computed from only the widest one of them, `wantedBytes`
703
706
  // above already asks for the union and got it right; this is the same
704
- // union, used as the floor instead of only as the ask). Exceeding the share
705
- // is the lesser failure, and the line below says when it happens.
707
+ // union, used as the floor instead of only as the ask). A live read can
708
+ // also pin pieces without a priority-map range, so those measured pins are
709
+ // part of the same minimum. Exceeding the share is the lesser failure, and
710
+ // the line below says when it happens.
706
711
  const demand = this.#lru.demand();
707
- this.#growthCeiling = Math.max(
712
+ const activeDemandPieces = Math.max(
708
713
  MIN_RESIDENT_PIECES,
709
- demand.readers > 0 ? demand.unionPieces : MIN_RESIDENT_PIECES,
714
+ demand.readers > 0 ? demand.unionPieces : 0,
715
+ this.#lru.pinnedCount
716
+ );
717
+ this.#growthCeiling = Math.max(
718
+ activeDemandPieces,
710
719
  Number.isFinite(wanted) ? wanted : MIN_RESIDENT_PIECES
711
720
  );
712
- const belowAWindow = demand.readers > 0
713
- && Number.isFinite(wanted)
714
- && wanted < demand.unionPieces;
715
- this.#beyondTheMachine = belowAWindow;
721
+ const belowActiveDemand = Number.isFinite(wanted) && wanted < activeDemandPieces;
722
+ this.#beyondTheMachine = belowActiveDemand;
716
723
  // The LRU is told too. It was constructed with the store's original
717
724
  // capacity and never revised, so `isFull()` answered against a number that
718
725
  // had not been the limit for some time — dormant only because nothing calls
@@ -754,7 +761,7 @@ export class SharedPieceStore {
754
761
  committedBytes: this.#blocksAllocated * this.#chunkLength,
755
762
  evicted,
756
763
  releasedBlocks,
757
- belowAWindow
764
+ belowActiveDemand
758
765
  };
759
766
  }
760
767
 
@@ -803,6 +810,19 @@ export class SharedPieceStore {
803
810
 
804
811
  pin(index) {
805
812
  this.#lru.pin(index);
813
+ // A piece reader pins before it waits for the bytes to arrive or return
814
+ // from disk. That is the only live record of some reads; the priority map
815
+ // can have no declared range for them (for example, a verified piece that
816
+ // was spilled). Include the measured set of distinct active pins in the
817
+ // immediate floor so a claim does not wait for the next budget pass to get
818
+ // a slot. `wantedBytes` carries the same demand to the machine-wide owner.
819
+ const pinned = this.#lru.pinnedCount;
820
+ if (pinned > this.#growthCeiling) {
821
+ this.#growthCeiling = pinned;
822
+ this.#lru.setCapacity(pinned);
823
+ this.#beyondTheMachine = true;
824
+ this.#wake();
825
+ }
806
826
  }
807
827
 
808
828
  unpin(index) {