@torrent-tv/proxy 2.9.96 → 2.9.97
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/CHANGELOG.md +4 -0
- package/package.json +1 -1
- package/services/torrent-pool.js +33 -4
- package/test/upload-hurry.test.js +31 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 2.9.97
|
|
2
|
+
|
|
3
|
+
- **Fix**: The generous upload of 2.9.96 did not actually reach the moment it was written for. Only torrents with a registered reader were shown to the upload policy, and the first thing done with a new torrent — fetching the file's head and tail for the codec probe — reads through `createReadStream` without registering one. So for the whole of that wait, 8.36 s of the 11.46 s before playback in the measured session, the torrent looked unused and the upload stayed at the near-silent idle floor, during the exact seconds peers decide whether to serve us. A torrent in a hurry now counts whether or not anything is reading it. The selection is a named function of its own so it can be tested without a live swarm — the fault was in which torrents were considered, not in what was decided about them.
|
|
4
|
+
|
|
1
5
|
## 2.9.96
|
|
2
6
|
|
|
3
7
|
- **New**: The proxy uploads generously at the two moments a viewer is provably waiting — when a torrent is added, and when the viewer seeks — for 25 s, which is two of BitTorrent's unchoke cycles. Peers serve those who serve them: each re-ranks its takers about every 10 s and opens a few slots to whoever uploaded most, plus one at random, so uploading a token 8-50 KB/s means being picked at random, one slot per cycle. Measured on a session where 96 peers were already connected within 2 s: 64 KB/s after 2 s, 1.6 MB/s after 4 s, 4.8 MB/s after 8 s — and the 16 MB the codec probe needs took **8.36 s of the 11.46 s** before playback could start. The existing reciprocity boost could not help, because it waits for the download to be all but dead (below 200 KB/s) with peers visibly choking us, and a ramp is neither: in that same session it first moved the limit 13.3 s after the torrent was added and reached the generous rate at 43.7 s, both after the wait they were meant to shorten. Seeding policy is otherwise unchanged — near-silence when nothing is being watched, a token upload while reading.
|
package/package.json
CHANGED
package/services/torrent-pool.js
CHANGED
|
@@ -115,6 +115,34 @@ const UPLOAD_HURRY_MS = 25_000;
|
|
|
115
115
|
* @param {{ floor?: number, idleFloor?: number, boost?: number, starvingSpeed?: number, chokedThreshold?: number, now?: number }} [opts]
|
|
116
116
|
* @returns {{ bytesPerSec: number, reason: string }}
|
|
117
117
|
*/
|
|
118
|
+
/**
|
|
119
|
+
* The torrents the upload policy is allowed to see.
|
|
120
|
+
*
|
|
121
|
+
* A torrent with a reader qualifies for the obvious reason. A torrent in a
|
|
122
|
+
* HURRY qualifies even without one, and that case is the important one: the
|
|
123
|
+
* first thing done with a new torrent is fetching the file's head and tail for
|
|
124
|
+
* the codec probe, and that read goes straight to `createReadStream` without
|
|
125
|
+
* registering a reader. Judged by readers alone the torrent looks unused for
|
|
126
|
+
* the whole of that wait — 8.36 s of the 11.46 s before playback in the session
|
|
127
|
+
* measured 2026-08-04 — so the upload stayed at the near-silent idle floor
|
|
128
|
+
* during the exact seconds peers were deciding whether to serve us.
|
|
129
|
+
*
|
|
130
|
+
* @param {Iterable<{ hurryUntil?: number }>} torrents
|
|
131
|
+
* @param {Map<object, { size: number }>} usageByTorrent - fileIndex sets, keyed by torrent.
|
|
132
|
+
* @param {number} now
|
|
133
|
+
* @returns {object[]}
|
|
134
|
+
*/
|
|
135
|
+
export function torrentsForUploadPolicy(torrents, usageByTorrent, now) {
|
|
136
|
+
const chosen = [];
|
|
137
|
+
for (const torrent of torrents) {
|
|
138
|
+
const usage = usageByTorrent?.get?.(torrent);
|
|
139
|
+
if ((usage && usage.size > 0) || (torrent?.hurryUntil ?? 0) > now) {
|
|
140
|
+
chosen.push(torrent);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return chosen;
|
|
144
|
+
}
|
|
145
|
+
|
|
118
146
|
export function decideUploadLimit(activeTorrents, opts = {}) {
|
|
119
147
|
const floor = opts.floor ?? UPLOAD_FLOOR_BYTES;
|
|
120
148
|
const idleFloor = opts.idleFloor ?? UPLOAD_IDLE_FLOOR_BYTES;
|
|
@@ -465,10 +493,11 @@ export class TorrentPool {
|
|
|
465
493
|
if (!this.client || this.client.destroyed || typeof this.client.throttleUpload !== "function") {
|
|
466
494
|
return;
|
|
467
495
|
}
|
|
468
|
-
const active =
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
496
|
+
const active = torrentsForUploadPolicy(
|
|
497
|
+
this.torrents.values(),
|
|
498
|
+
this.fileUsageByTorrent,
|
|
499
|
+
Date.now()
|
|
500
|
+
);
|
|
472
501
|
const { bytesPerSec, reason } = decideUploadLimit(active);
|
|
473
502
|
if (bytesPerSec === this.#uploadLimit) {
|
|
474
503
|
return;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import test from "node:test";
|
|
19
19
|
import assert from "node:assert/strict";
|
|
20
|
-
import { decideUploadLimit } from "../services/torrent-pool.js";
|
|
20
|
+
import { decideUploadLimit, torrentsForUploadPolicy } from "../services/torrent-pool.js";
|
|
21
21
|
|
|
22
22
|
const NOW = 1_000_000;
|
|
23
23
|
const healthy = (extra = {}) => ({
|
|
@@ -64,3 +64,33 @@ test("the reciprocity boost still works when no hurry is on", () => {
|
|
|
64
64
|
assert.equal(decision.bytesPerSec, 512 * 1024);
|
|
65
65
|
assert.match(decision.reason, /earn unchoke/);
|
|
66
66
|
});
|
|
67
|
+
|
|
68
|
+
test("a torrent with no reader still reaches the policy while it is in a hurry", () => {
|
|
69
|
+
// The moment that matters: a torrent has just been added and its head and
|
|
70
|
+
// tail are being fetched for the codec probe. That read goes straight to
|
|
71
|
+
// `createReadStream`, so no reader is registered — and the selection only
|
|
72
|
+
// ever kept torrents that had one, which is where the gap was.
|
|
73
|
+
const hurrying = { name: "film.mkv", hurryUntil: NOW + 20_000 };
|
|
74
|
+
const idle = { name: "other.mkv" };
|
|
75
|
+
const usage = new Map();
|
|
76
|
+
|
|
77
|
+
assert.deepEqual(
|
|
78
|
+
torrentsForUploadPolicy([hurrying, idle], usage, NOW),
|
|
79
|
+
[hurrying],
|
|
80
|
+
"a torrent with no reader yet was ignored"
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
assert.deepEqual(
|
|
84
|
+
torrentsForUploadPolicy([{ ...hurrying, hurryUntil: NOW - 1 }, idle], usage, NOW),
|
|
85
|
+
[],
|
|
86
|
+
"and stops counting once the rush is over"
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const read = { name: "watched.mkv" };
|
|
90
|
+
usage.set(read, new Set([0]));
|
|
91
|
+
assert.deepEqual(
|
|
92
|
+
torrentsForUploadPolicy([read], usage, NOW),
|
|
93
|
+
[read],
|
|
94
|
+
"a torrent being read still counts, hurry or not"
|
|
95
|
+
);
|
|
96
|
+
});
|