@torrent-tv/proxy 2.107.1 → 2.107.2
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/package.json
CHANGED
package/services/storage/keep.js
CHANGED
|
@@ -46,3 +46,22 @@
|
|
|
46
46
|
* for reusable torrents, source files, and generated segments.
|
|
47
47
|
*/
|
|
48
48
|
export const IDLE_KEEP_MS = 60 * 60 * 1000;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* How long a torrent keeps its swarm after its last reader has gone, in
|
|
52
|
+
* milliseconds.
|
|
53
|
+
*
|
|
54
|
+
* THE SAME UNKNOWN AS {@link IDLE_KEEP_MS} — whether the viewer comes back —
|
|
55
|
+
* so it is stated here, beside it, and not where it is used. A reload, a tab
|
|
56
|
+
* closed and opened again, or a pause between two transcode sessions is a
|
|
57
|
+
* return within minutes, and a return that finds its connections still open
|
|
58
|
+
* dials nobody.
|
|
59
|
+
*
|
|
60
|
+
* SHORTER THAN THE HOUR because what it holds costs more than disk. A held
|
|
61
|
+
* swarm keeps its connections open and accepts new ones: WebTorrent limits only
|
|
62
|
+
* the peers it dials, and field 2026-09-11 had 249 connected at the start of one
|
|
63
|
+
* viewing and 596 at the end. Five minutes is the owner's figure for the short
|
|
64
|
+
* return (torrent-tv/meta#240); nothing measured derives it either, and the
|
|
65
|
+
* returns recorded by `services/storage/returns.js` are what will.
|
|
66
|
+
*/
|
|
67
|
+
export const SWARM_HOLD_MS = 5 * 60 * 1000;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* download.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { IDLE_KEEP_MS } from "../storage/keep.js";
|
|
11
|
+
import { IDLE_KEEP_MS, SWARM_HOLD_MS } from "../storage/keep.js";
|
|
12
12
|
import dns from "node:dns/promises";
|
|
13
13
|
import os from "node:os";
|
|
14
14
|
import path from "node:path";
|
|
@@ -454,6 +454,76 @@ export function decideUploadLimit(activeTorrents, opts = {}) {
|
|
|
454
454
|
return { bytesPerSec: floor, reason: "active readers, not choke-starved" };
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Swarms kept for a while after their last reader went: the timer that lets
|
|
459
|
+
* each go, and when it was set.
|
|
460
|
+
*
|
|
461
|
+
* @type {Map<object, { timer: ReturnType<typeof setTimeout>, since: number }>}
|
|
462
|
+
*/
|
|
463
|
+
const swarmHolds = new Map();
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Keep the swarm of a torrent whose last reader has just left, for
|
|
467
|
+
* {@link SWARM_HOLD_MS}, and let it go then if nobody has come back.
|
|
468
|
+
*
|
|
469
|
+
* A reader who comes back within it — a reload, a tab opened again — finds every
|
|
470
|
+
* connection still open, the incoming ones too, which the addresses kept by
|
|
471
|
+
* `leaveSwarm` cannot give back. While held, the torrent downloads nothing:
|
|
472
|
+
* it is added with `deselect: true` and nothing is stated for it.
|
|
473
|
+
*
|
|
474
|
+
* The clock runs from the first departure, as the idle timer's does
|
|
475
|
+
* (torrent-tv/meta#95): a second departure while held does not extend it.
|
|
476
|
+
*
|
|
477
|
+
* A torrent held whole is let go at once. It has nothing to fetch, and a whole
|
|
478
|
+
* file with its swarm open is the 596 connections of 2026-09-11.
|
|
479
|
+
*
|
|
480
|
+
* @param {import("webtorrent").Torrent} torrent
|
|
481
|
+
* @param {number} [holdMs]
|
|
482
|
+
* @returns {boolean} Whether the swarm is held.
|
|
483
|
+
*/
|
|
484
|
+
export function holdSwarm(torrent, holdMs = SWARM_HOLD_MS) {
|
|
485
|
+
if (!torrent || torrent.destroyed || torrent.paused === true || swarmHolds.has(torrent)) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
if (torrent.progress === 1) {
|
|
489
|
+
leaveSwarm(torrent);
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
const timer = setTimeout(() => {
|
|
493
|
+
swarmHolds.delete(torrent);
|
|
494
|
+
if (!torrent.destroyed && !isWanted(torrent)) {
|
|
495
|
+
leaveSwarm(torrent);
|
|
496
|
+
}
|
|
497
|
+
}, holdMs);
|
|
498
|
+
timer.unref?.();
|
|
499
|
+
swarmHolds.set(torrent, { timer, since: performance.now() });
|
|
500
|
+
const open = Array.isArray(torrent.wires) ? torrent.wires.length : 0;
|
|
501
|
+
logger.info(
|
|
502
|
+
`torrent-pool: [${String(torrent.infoHash ?? "?").slice(0, 8)}] its last reader went — ` +
|
|
503
|
+
`the swarm is held for ${Math.round(holdMs / 1000)}s, ${open} connection(s) open`
|
|
504
|
+
);
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Stop holding a torrent's swarm, saying nothing: for a torrent that is going.
|
|
510
|
+
*
|
|
511
|
+
* Its timer holds the torrent strongly, as the idle timer did when it kept
|
|
512
|
+
* removed torrents alive (field 2026-10-04).
|
|
513
|
+
*
|
|
514
|
+
* @param {object} torrent
|
|
515
|
+
* @returns {number | null} Milliseconds it was held, or null when it was not.
|
|
516
|
+
*/
|
|
517
|
+
export function releaseSwarmHold(torrent) {
|
|
518
|
+
const held = swarmHolds.get(torrent);
|
|
519
|
+
if (!held) {
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
clearTimeout(held.timer);
|
|
523
|
+
swarmHolds.delete(torrent);
|
|
524
|
+
return performance.now() - held.since;
|
|
525
|
+
}
|
|
526
|
+
|
|
457
527
|
/**
|
|
458
528
|
* Let go of the swarm of a torrent whose last reader has just left.
|
|
459
529
|
*
|
|
@@ -481,6 +551,9 @@ export function decideUploadLimit(activeTorrents, opts = {}) {
|
|
|
481
551
|
* spontaneously: it is a consequence of a departure, and a torrent nobody has
|
|
482
552
|
* read yet has nothing to depart from.
|
|
483
553
|
*
|
|
554
|
+
* It runs when the period {@link holdSwarm} keeps the swarm for has passed with
|
|
555
|
+
* nobody back, or at the departure itself for a torrent held whole.
|
|
556
|
+
*
|
|
484
557
|
* `paused` is the library's own word for this and the path it already checks,
|
|
485
558
|
* so nothing here fights it. The pause alone does not close what is already
|
|
486
559
|
* open, so the peers are let go by hand; the data stays exactly where it is,
|
|
@@ -669,7 +742,21 @@ export function noteAfterRejoin(torrent, kind) {
|
|
|
669
742
|
* @returns {boolean} Whether it had to be taken back.
|
|
670
743
|
*/
|
|
671
744
|
export function rejoinSwarm(torrent) {
|
|
672
|
-
if (!torrent || torrent.destroyed
|
|
745
|
+
if (!torrent || torrent.destroyed) {
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
// Held, not let go: every connection is still open, so there is nothing to
|
|
749
|
+
// resume and nobody to dial.
|
|
750
|
+
const heldMs = releaseSwarmHold(torrent);
|
|
751
|
+
if (heldMs !== null) {
|
|
752
|
+
const open = Array.isArray(torrent.wires) ? torrent.wires.length : 0;
|
|
753
|
+
logger.info(
|
|
754
|
+
`torrent-pool: [${String(torrent.infoHash ?? "?").slice(0, 8)}] a reader came back after ` +
|
|
755
|
+
`${(heldMs / 1000).toFixed(1)}s — the swarm was held, ${open} connection(s) open`
|
|
756
|
+
);
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
if (torrent.paused !== true) {
|
|
673
760
|
return false;
|
|
674
761
|
}
|
|
675
762
|
torrent.resume();
|
|
@@ -2200,11 +2287,11 @@ export class TorrentPool {
|
|
|
2200
2287
|
this.#lastAccess.set(torrent, Date.now());
|
|
2201
2288
|
rejoinSwarm(torrent);
|
|
2202
2289
|
}
|
|
2203
|
-
// A PROXY THAT NEEDS NOTHING FROM A SWARM SHOULD NOT BE IN THAT SWARM
|
|
2204
|
-
//
|
|
2205
|
-
// swarm back.
|
|
2290
|
+
// A PROXY THAT NEEDS NOTHING FROM A SWARM SHOULD NOT BE IN THAT SWARM —
|
|
2291
|
+
// once the viewer has had the time to come back (`holdSwarm`). The data
|
|
2292
|
+
// stays on disk; the next thing that wants a byte of it takes the swarm back.
|
|
2206
2293
|
if (swarm === "let go") {
|
|
2207
|
-
|
|
2294
|
+
holdSwarm(torrent);
|
|
2208
2295
|
}
|
|
2209
2296
|
if (onTheClock) {
|
|
2210
2297
|
this.#scheduleIdleRemoval(torrent);
|
|
@@ -2335,6 +2422,7 @@ export class TorrentPool {
|
|
|
2335
2422
|
*/
|
|
2336
2423
|
#forgetBookkeeping(torrent) {
|
|
2337
2424
|
this.#cancelIdleRemoval(torrent);
|
|
2425
|
+
releaseSwarmHold(torrent);
|
|
2338
2426
|
this.#lastAccess.delete(torrent);
|
|
2339
2427
|
this.#stallSince.delete(torrent);
|
|
2340
2428
|
this.#stallReportedAt.delete(torrent);
|
|
@@ -2510,6 +2598,9 @@ export class TorrentPool {
|
|
|
2510
2598
|
}
|
|
2511
2599
|
this.#idleTimers.clear();
|
|
2512
2600
|
this.#lastAccess.clear();
|
|
2601
|
+
for (const torrent of this.torrents.values()) {
|
|
2602
|
+
releaseSwarmHold(torrent);
|
|
2603
|
+
}
|
|
2513
2604
|
|
|
2514
2605
|
if (!this.client || this.client.destroyed) {
|
|
2515
2606
|
return;
|