@torrent-tv/proxy 2.89.17 → 2.89.19
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/README.md +14 -3
- package/package.json +10 -2
- package/server.js +0 -11
- package/utils/client-log-file.js +147 -42
- package/.claude/commands/opsx/apply.md +0 -155
- package/.claude/commands/opsx/archive.md +0 -160
- package/.claude/commands/opsx/explore.md +0 -174
- package/.claude/commands/opsx/propose.md +0 -109
- package/.claude/commands/opsx/sync.md +0 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +0 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +0 -117
- package/.claude/skills/openspec-explore/SKILL.md +0 -289
- package/.claude/skills/openspec-propose/SKILL.md +0 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +0 -147
- package/.dockerignore +0 -5
- package/AGENTS.md +0 -363
- package/CHANGELOG.md +0 -2377
- package/Dockerfile +0 -30
- package/biome.json +0 -412
- package/corpus.tmp.json +0 -1
- package/docs/browser-proxy-contract.md +0 -155
- package/docs/container-architecture.md +0 -273
- package/docs/download-architecture.md +0 -261
- package/docs/encode-architecture.md +0 -765
- package/docs/encode-run-state.md +0 -96
- package/docs/logs.md +0 -151
- package/docs/playback-start-forecast.md +0 -113
- package/docs/socks5-egress-research.md +0 -114
- package/docs/storage-architecture.md +0 -273
- package/knip.json +0 -14
- package/main2.cpuprofile +0 -1
- package/openspec/changes/adaptive-bitrate/.openspec.yaml +0 -0
- package/openspec/changes/adaptive-bitrate/design.md +0 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +0 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +0 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +0 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +0 -2
- package/openspec/changes/binary-distribution/proposal.md +0 -53
- package/openspec/changes/chunked-request-bodies/design.md +0 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +0 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +0 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +0 -40
- package/openspec/changes/cold-start/design.md +0 -98
- package/openspec/changes/cold-start/proposal.md +0 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +0 -47
- package/openspec/changes/cold-start/tasks.md +0 -48
- package/openspec/changes/disk-cap/.openspec.yaml +0 -2
- package/openspec/changes/disk-cap/proposal.md +0 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +0 -25
- package/openspec/changes/disk-cap/tasks.md +0 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +0 -2
- package/openspec/changes/proxy-observability/design.md +0 -38
- package/openspec/changes/proxy-observability/proposal.md +0 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +0 -33
- package/openspec/changes/proxy-observability/tasks.md +0 -21
- package/openspec/changes/seek-debounce/.openspec.yaml +0 -0
- package/openspec/changes/seek-debounce/design.md +0 -118
- package/openspec/changes/seek-debounce/proposal.md +0 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +0 -41
- package/openspec/changes/seek-debounce/tasks.md +0 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +0 -2
- package/openspec/changes/subtitle-language/proposal.md +0 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +0 -34
- package/openspec/changes/subtitle-language/tasks.md +0 -26
- package/openspec/changes/track-selection/.openspec.yaml +0 -2
- package/openspec/changes/track-selection/design.md +0 -47
- package/openspec/changes/track-selection/proposal.md +0 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +0 -41
- package/openspec/changes/track-selection/tasks.md +0 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +0 -2
- package/openspec/changes/transcode-quality/proposal.md +0 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +0 -139
- package/openspec/changes/transcode-quality/tasks.md +0 -63
- package/openspec/config.yaml +0 -38
- package/public/.well-known/appspecific/com.chrome.devtools.json +0 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +0 -329
- package/research/double-spawn-2026-09-10.md +0 -171
- package/research/handover-2026-09-13.md +0 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +0 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +0 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +0 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +0 -225
- package/research/valgrind-replica-2026-08-27/logs.tar.gz +0 -0
- package/scripts/build-audio-samples.mjs +0 -31
- package/scripts/render-run-graph.js +0 -173
- package/test/asset-paths.test.js +0 -28
- package/test/audio-buffer-continuity.test.js +0 -77
- package/test/audio-calibration.test.js +0 -23
- package/test/audio-inventory.test.js +0 -212
- package/test/audio-track-bitrate.test.js +0 -40
- package/test/audio-warm-recorded.test.js +0 -63
- package/test/auto-quality-step.test.js +0 -762
- package/test/available-share.test.js +0 -76
- package/test/background-fill.test.js +0 -190
- package/test/client-log-file.test.js +0 -126
- package/test/client-logs-route.test.js +0 -34
- package/test/coalesce.test.js +0 -39
- package/test/completed-files.test.js +0 -115
- package/test/concurrent-cost.test.js +0 -148
- package/test/container-media-info.test.js +0 -228
- package/test/contention.test.js +0 -76
- package/test/core-dumps.test.js +0 -57
- package/test/coverage-follows-the-disk.test.js +0 -233
- package/test/coverage-map.test.js +0 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +0 -58
- package/test/cut-times-timeline.test.js +0 -65
- package/test/cuts-follow-published-grid.test.js +0 -109
- package/test/data-channel-frame.test.js +0 -63
- package/test/data-channel-viewer-lifecycle.test.js +0 -193
- package/test/dead-torrent-handle.test.js +0 -52
- package/test/declared-rates.test.js +0 -116
- package/test/declared-tracks.test.js +0 -43
- package/test/decode-cost-fit.test.js +0 -202
- package/test/decode-cost.test.js +0 -475
- package/test/delivery-probe-field.test.js +0 -173
- package/test/delivery-probe.test.js +0 -382
- package/test/demand-register.test.js +0 -206
- package/test/dht-bootstrap.test.js +0 -72
- package/test/drain-threat.test.js +0 -65
- package/test/duplicate-add.test.js +0 -48
- package/test/encode-admission.test.js +0 -170
- package/test/encode-cost.test.js +0 -275
- package/test/encode-exit.test.js +0 -111
- package/test/encode-orchestrator.test.js +0 -426
- package/test/encode-plan-viewers.test.js +0 -725
- package/test/encode-plan.test.js +0 -545
- package/test/encode-run-state.test.js +0 -314
- package/test/encode-run.test.js +0 -248
- package/test/encode-slope.test.js +0 -46
- package/test/encoded-output.test.js +0 -39
- package/test/encoder-kinds.test.js +0 -122
- package/test/encoder-readings.test.js +0 -68
- package/test/episode-naming.test.js +0 -131
- package/test/every-claimant-is-told-its-share.test.js +0 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +0 -354
- package/test/failed-start-breaker.test.js +0 -202
- package/test/fastest-wires.test.js +0 -143
- package/test/ffmpeg-stream-counts.test.js +0 -101
- package/test/file-edges.test.js +0 -191
- package/test/files-in-use.test.js +0 -34
- package/test/fixtures/audio-frame-boundary.json +0 -1
- package/test/fixtures/refused-audio-20261002.json +0 -10
- package/test/fmp4-presentation-clock.test.js +0 -148
- package/test/fmp4-self-contained.test.js +0 -80
- package/test/fmp4-timestamps.test.js +0 -140
- package/test/fmp4-track-completeness.test.js +0 -41
- package/test/format-keyed-outputs.test.js +0 -78
- package/test/health-metrics.test.js +0 -36
- package/test/held-read-ownership.test.js +0 -77
- package/test/held-request-width.test.js +0 -157
- package/test/helpers/encode-run.js +0 -248
- package/test/helpers/held-file.js +0 -77
- package/test/helpers/manager.js +0 -62
- package/test/helpers/matroska-file.js +0 -296
- package/test/helpers/output-spec.js +0 -63
- package/test/host-load.test.js +0 -68
- package/test/index-accuracy-is-about-the-file.test.js +0 -128
- package/test/init-compat.test.js +0 -219
- package/test/input-lost-quiets-the-plan.test.js +0 -261
- package/test/input-unavailable.test.js +0 -48
- package/test/installation-id.test.js +0 -39
- package/test/keeping-period.test.js +0 -83
- package/test/keyframe-index-accuracy.test.js +0 -188
- package/test/keyframe-table-budget.test.js +0 -123
- package/test/keyframes-belong-to-the-file.test.js +0 -249
- package/test/learned-median.test.js +0 -37
- package/test/letting-go-asks-who-is-watching.test.js +0 -191
- package/test/limit-row-by-area.test.js +0 -60
- package/test/link-budget.test.js +0 -111
- package/test/link-probe.test.js +0 -56
- package/test/local-encoding-data.test.js +0 -273
- package/test/logger-repeats.test.js +0 -134
- package/test/look-ahead-coverage.test.js +0 -56
- package/test/manager-surface.test.js +0 -291
- package/test/matroska-blocks.test.js +0 -0
- package/test/matroska-cluster-discovery.test.js +0 -323
- package/test/matroska-cues-track.test.js +0 -192
- package/test/media-time.test.js +0 -62
- package/test/memory-budget.test.js +0 -390
- package/test/move-cost.test.js +0 -216
- package/test/mp4-composition-times.test.js +0 -0
- package/test/mp4-subtitles.test.js +0 -173
- package/test/one-authority.test.js +0 -285
- package/test/one-budget.test.js +0 -149
- package/test/one-output-one-session.test.js +0 -273
- package/test/one-resource-divided.test.js +0 -185
- package/test/one-serving-output-per-height.test.js +0 -458
- package/test/orchestrator-wired.test.js +0 -207
- package/test/output-catalog.test.js +0 -39
- package/test/output-shape.test.js +0 -21
- package/test/output-spec.test.js +0 -251
- package/test/output-speed.test.js +0 -92
- package/test/outputs-being-prepared.test.js +0 -40
- package/test/packet-witness-ring.test.js +0 -236
- package/test/packet-witness.test.js +0 -148
- package/test/piece-completeness.test.js +0 -78
- package/test/piece-disk-store.test.js +0 -401
- package/test/piece-from-whole-file.test.js +0 -129
- package/test/piece-lru.test.js +0 -319
- package/test/piece-reader.test.js +0 -162
- package/test/piece-selection-offset.test.js +0 -85
- package/test/piece-store-eviction.test.js +0 -595
- package/test/piece-store-never-refuses.test.js +0 -153
- package/test/piece-store-reservations.test.js +0 -403
- package/test/piece-store-slow-disk.test.js +0 -159
- package/test/piece-tail.test.js +0 -97
- package/test/plan-host-timings.test.js +0 -68
- package/test/plan-is-steady.test.js +0 -225
- package/test/playback-readiness.test.js +0 -518
- package/test/playback-start-regressions.test.js +0 -101
- package/test/priority-map-download.test.js +0 -174
- package/test/priority-map-emptied.test.js +0 -204
- package/test/priority-map-per-output.test.js +0 -215
- package/test/priority-map.test.js +0 -163
- package/test/probe-wedge-certainty.test.js +0 -60
- package/test/produced-copy-choice.test.js +0 -213
- package/test/published-timeline.test.js +0 -83
- package/test/quality-offer.test.js +0 -160
- package/test/quality-variants.test.js +0 -1274
- package/test/rate-control-in-the-output.test.js +0 -181
- package/test/read-survives-withdrawal.test.js +0 -164
- package/test/read-window.test.js +0 -365
- package/test/reads-not-yet-arrived.test.js +0 -218
- package/test/resume-warm.test.js +0 -39
- package/test/run-graph-drift.test.js +0 -37
- package/test/run-inputs.test.js +0 -80
- package/test/run-intervals.test.js +0 -100
- package/test/run-position-follows-published-grid.test.js +0 -89
- package/test/run-progress.test.js +0 -64
- package/test/same-height-switch.test.js +0 -663
- package/test/seek-frees-the-viewers-requests.test.js +0 -217
- package/test/seek-landing.test.js +0 -109
- package/test/seek-target-not-superseded.test.js +0 -195
- package/test/segment-closed-on-both-branches.test.js +0 -114
- package/test/segment-cut-times.test.js +0 -63
- package/test/segment-demand.test.js +0 -80
- package/test/segment-hold-supersede.test.js +0 -91
- package/test/segment-judged-by-its-own-header.test.js +0 -222
- package/test/segment-serve-wiring.test.js +0 -481
- package/test/segment-store-eviction.test.js +0 -247
- package/test/segment-store.test.js +0 -352
- package/test/segment-true-start.test.js +0 -86
- package/test/segments-are-shared.test.js +0 -176
- package/test/send-chunk-bytes.test.js +0 -86
- package/test/serving-existing-output.test.js +0 -111
- package/test/serving-output.test.js +0 -102
- package/test/shared-piece-store.test.js +0 -310
- package/test/sidecar-naming.test.js +0 -142
- package/test/soundtrack-rate-bound.test.js +0 -289
- package/test/source-file.test.js +0 -133
- package/test/source-files-route.test.js +0 -113
- package/test/source-registry.test.js +0 -18
- package/test/split-a-stretch.test.js +0 -139
- package/test/stale-request-after-seek.test.js +0 -158
- package/test/startup-readings.test.js +0 -124
- package/test/stream-route.test.js +0 -237
- package/test/subtitle-cue-framing.test.js +0 -200
- package/test/subtitle-cue-source.test.js +0 -102
- package/test/subtitle-cue-walk.test.js +0 -385
- package/test/subtitle-cursor.test.js +0 -63
- package/test/subtitle-defaults.test.js +0 -118
- package/test/subtitle-language.test.js +0 -252
- package/test/subtitle-metadata.test.js +0 -15
- package/test/subtitle-plan-waits.test.js +0 -125
- package/test/subtitle-track-numbering.test.js +0 -391
- package/test/supply-margin.test.js +0 -190
- package/test/swarm-follows-readers.test.js +0 -174
- package/test/swarm-reach.test.js +0 -130
- package/test/swarm-selection.test.js +0 -220
- package/test/tail-duplication.test.js +0 -167
- package/test/the-channel-has-both-ends.test.js +0 -91
- package/test/the-torrent-thread-serves-bytes.test.js +0 -107
- package/test/thread-pool.test.js +0 -53
- package/test/timeline.test.js +0 -84
- package/test/torrent-contents.test.js +0 -235
- package/test/torrent-cost.test.js +0 -71
- package/test/torrent-files.test.js +0 -221
- package/test/torrent-naming.test.js +0 -255
- package/test/tracks-begin-together.test.js +0 -209
- package/test/tunnel-renewal.test.js +0 -167
- package/test/two-viewers-one-picture.test.js +0 -508
- package/test/upload-hurry.test.js +0 -186
- package/test/usrsctp-state.test.js +0 -131
- package/test/video-facts.test.js +0 -102
- package/test/viewer-assignments.test.js +0 -143
- package/test/viewer-outputs.test.js +0 -285
- package/test/viewer-presence.test.js +0 -116
- package/test/viewer-requests-admission.test.js +0 -92
- package/test/viewer-starts-on-map.test.js +0 -27
- package/test/viewer-states.test.js +0 -192
- package/test/viewer-subtitle-subscription.test.js +0 -96
- package/test/viewer.test.js +0 -139
- package/test/viewing-generations.test.js +0 -390
- package/test/visible-rung.test.js +0 -37
- package/test/wait-ledger.test.js +0 -100
- package/test/webrtc-shim.test.js +0 -149
- package/test/wedge-certainty.test.js +0 -131
- package/test/whole-file-held-read.test.js +0 -30
- package/test/withdraw-piece-claim.test.js +0 -212
- package/test/worker-channel.test.js +0 -158
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
# Передача: клин доставки, стенд и werift — 2026-09-13
|
|
2
|
-
|
|
3
|
-
Написано для того, кто продолжит. Всё, что ниже, либо прочитано из журналов и
|
|
4
|
-
кода, либо помечено как непроверенное.
|
|
5
|
-
|
|
6
|
-
## 1. Что установила сессия 2026-09-12 (прокси 2.83.5)
|
|
7
|
-
|
|
8
|
-
Показ встал на **521,2 с фильма** и не возобновился. Причина — клин доставки,
|
|
9
|
-
и он снят на проводе с обеих сторон. Разбор:
|
|
10
|
-
`research/delivery-wedge-on-the-wire-2026-09-12.md`.
|
|
11
|
-
|
|
12
|
-
Цепочка, каждое звено с числом из журнала:
|
|
13
|
-
|
|
14
|
-
1. 21:50:39.658 — последнее, что браузер получил. Его счётчик канала
|
|
15
|
-
`proxy=12112msg/479898176B` не сдвинулся больше ни разу;
|
|
16
|
-
2. прокси продолжал отдавать: `sent=485243979 → 485263428`, очереди
|
|
17
|
-
`queued[proxy:0B proxy-control:0B proxy-fast:0B]`, `rtt=24ms`,
|
|
18
|
-
`pc=connected ice=completed`. Обратное направление работало весь клин;
|
|
19
|
-
3. через ассоциацию прошло **485 МБ**. Прежние эпизоды: 783, 813, 825, 2294 МБ.
|
|
20
|
-
Порога по объёму нет;
|
|
21
|
-
4. обрыв пойман посреди отдачи одного куска: `bytes=10657210 sendMs=360
|
|
22
|
-
bufferedAtEnd=0` — usrsctp принял всё, очередь пуста; на проводе за
|
|
23
|
-
следующие три секунды ушло 6 727 961 байт (≈63 %), и в середине четвёртой —
|
|
24
|
-
ноль, без ретрансмиссий и без утоньшения подтверждений.
|
|
25
|
-
|
|
26
|
-
**Лечение было измерено и выброшено.** Браузер поднял вторую ассоциацию к тому
|
|
27
|
-
же пиру, прокачал `GET /api/delivery-sink?bytes=4194304` за 140 мс, записал
|
|
28
|
-
`the fault is held in the wedged association` — и **закрыл её через 0,45 с**.
|
|
29
|
-
Показ остался на мёртвой.
|
|
30
|
-
|
|
31
|
-
**Почему зритель сидит навсегда, а не минуту:** лестница пересоединения не
|
|
32
|
-
запускается. В клиентском журнале нет ни `transport lost`, ни `reconnect
|
|
33
|
-
attempt`, потому что заклинивший канал остаётся `state=connected
|
|
34
|
-
ice=connected`, и объявлять его потерянным нечему. В тот же вечер в 20:03 на
|
|
35
|
-
ЗАКРЫТОМ канале та же лестница отработала штатно.
|
|
36
|
-
|
|
37
|
-
## 2. Две поправки к роадмапу, сделанные чтением кода
|
|
38
|
-
|
|
39
|
-
1. **`waitForBufferDrain` УЖЕ починен** (`services/data-channel-handler.js:1199`,
|
|
40
|
-
выпущено в 2.80.6): он ждёт нижней отметки с опросом каждые 50 мс,
|
|
41
|
-
постоянная `DC_BUFFER_DRAIN_TIMEOUT_MS` удалена. Запись в роадмапе
|
|
42
|
-
(«fails open», 2026-09-06) устарела.
|
|
43
|
-
2. **И он всё равно не защищает.** В поле 2026-09-12 `bufferedAtEnd=0` и
|
|
44
|
-
`drainMs=0` на каждой строке: наша очередь пуста, потому что libdatachannel
|
|
45
|
-
отдал байты в usrsctp, а держит их usrsctp. `bufferedAmount()` слеп к этому
|
|
46
|
-
по построению — прокси отдал 10,6 МБ в тишину и не узнал.
|
|
47
|
-
|
|
48
|
-
**Что на месте этого предложено и НЕ построено:** backpressure по тому, что
|
|
49
|
-
зритель ПОДТВЕРДИЛ. Браузер уже сообщает счётчики каждого канала
|
|
50
|
-
(`proxy=12112msg/479898176B` в строке `[dc-far]`, раз в 500 мс). Отправка
|
|
51
|
-
сверяется с ними, а не только с `bufferedAmount()`. Порог не выбирать — вывести
|
|
52
|
-
из того, сколько байт эта связь держит в полёте при здоровой работе.
|
|
53
|
-
|
|
54
|
-
## 3. Обновление зависимости — проверено, не лечит
|
|
55
|
-
|
|
56
|
-
Пин `^0.32.0`, в npm **0.33.4**. Внутри libdatachannel 0.24.2 → 0.24.5.
|
|
57
|
-
Подмодуль usrsctp сверен по четырём тегам (v0.24.2, .3, .4, .5):
|
|
58
|
-
`fec583d54493f879d2ae44a743423bf8a04371ab` во всех. **От клина не лечит.**
|
|
59
|
-
Запись роадмапа «upstream fix waiting нет» была верна на 26.08; теперь
|
|
60
|
-
перепроверена и по-прежнему верна по существу, но libdatachannel ушёл вперёд
|
|
61
|
-
на три версии (там исправление прерывистых отказов DTLS-рукопожатия).
|
|
62
|
-
|
|
63
|
-
## 4. werift — исследовано, читая сам пакет
|
|
64
|
-
|
|
65
|
-
werift 0.24.4, MIT, обновлён 2026-08-10, один автор. 4,2 МБ, 9 зависимостей.
|
|
66
|
-
`ice/`, `dtls/`, `sctp/`, `rtp/`, `webrtc/` — полная реализация на TypeScript.
|
|
67
|
-
|
|
68
|
-
**За:**
|
|
69
|
-
1. usrsctp исчезает целиком, а с ним и подозреваемый;
|
|
70
|
-
2. ни одной нативной зависимости (`@noble/curves`, `tweetnacl`,
|
|
71
|
-
`@peculiar/x509` — чистый JS). Ни prebuilt, ни musl/aarch64, ни сборки в
|
|
72
|
-
дополнении. Для проекта с одним форком нативного модуля и девятью смертями
|
|
73
|
-
процесса это существенно;
|
|
74
|
-
3. `bufferedAmount`, `bufferedAmountLow`, `bufferedAmountLowThreshold` есть —
|
|
75
|
-
наш backpressure переносится.
|
|
76
|
-
|
|
77
|
-
**Против:**
|
|
78
|
-
1. **Мультиплексирования одного UDP-порта нет.** `lib/common/src/transport.js:130`
|
|
79
|
-
создаёт `dgram.createSocket` на соединение; `icePortRange` — диапазон, а не
|
|
80
|
-
общий сокет. У нас `IceUdpMuxListener` держит один порт 9090 на все сессии,
|
|
81
|
-
и ровно он замаплен UPnP — шаг 3 удалённого доступа (2.9.18). На werift
|
|
82
|
-
каждая сессия берёт свой порт, которого в маппинге нет;
|
|
83
|
-
2. **Цена в процессоре не измерена и может быть запретительной.**
|
|
84
|
-
`lib/sctp/src/sctp.js:16` — `USERDATA_MAX_LENGTH = 1200`. Сообщение в 65 КБ
|
|
85
|
-
режется на 55 SCTP-кусков, каждый собирается в JS, CRC32c через
|
|
86
|
-
`Buffer.concat` на пакет (`chunk.js:763`), поверх — DTLS-шифрование на JS.
|
|
87
|
-
При 8 Мбит/с ≈830 пакетов/с; на измеренных 24.08 пиках 150-330 Мбит/с —
|
|
88
|
-
за 30 000/с. На CM4, который кодирует 1080p на 1,96x.
|
|
89
|
-
|
|
90
|
-
**Вывод:** werift снимает подозреваемого и платит сломанным единым портом и
|
|
91
|
-
неизвестной производительностью. Назвать его решением до замера нельзя.
|
|
92
|
-
Замер дешёвый: поднять werift-приёмник, прокачать гигабайт, сравнить загрузку
|
|
93
|
-
процессора с сегодняшней.
|
|
94
|
-
|
|
95
|
-
## 5. Стенд — построен наполовину, упёрся в выбор прокси
|
|
96
|
-
|
|
97
|
-
Цель: измерить, ЗАВИСИТ ЛИ КЛИН ОТ РАЗМЕРА ПОРЦИИ. Это гейт на всё остальное,
|
|
98
|
-
и он не снят ни разу — стенд 26.08 прокачал 1,0 ГБ без отказов, но его
|
|
99
|
-
приёмник был libdatachannel, а нужен dcSCTP браузера.
|
|
100
|
-
|
|
101
|
-
**Что работает:** `chrome-devtools` MCP подключён к Chrome 155 на 9222
|
|
102
|
-
(`list_pages` отвечает). Локальный прокси поднимается и регистрируется:
|
|
103
|
-
|
|
104
|
-
```
|
|
105
|
-
node bin/cli.js --server-url https://webauth.courses --port 9091 \
|
|
106
|
-
--delivery-sink --name wedge-stand --log-file <path>
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
**Что мешает:** браузер его не выбирает. Дважды подряд, включая после
|
|
110
|
-
перезагрузки страницы:
|
|
111
|
-
|
|
112
|
-
```
|
|
113
|
-
selectedId: f3013ad3 (полевой, хост дополнения)
|
|
114
|
-
candidates: wedge-stand score=0.6075, proxy-f3013ad3 score=0.4466
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Стенд имеет ЛУЧШИЙ счёт и не выбран. Отличие одно: `reachable: false` —
|
|
118
|
-
сервер дозванивается снаружи и до машины разработчика не достаёт. Значит
|
|
119
|
-
нагрузка пошла бы на рабочий узел, чего делать нельзя.
|
|
120
|
-
|
|
121
|
-
**Два выхода, решение за пользователем:** сделать стенд достижимым (проброс
|
|
122
|
-
порта) или добавить способ назначить прокси явно (правка продукта ради
|
|
123
|
-
стенда).
|
|
124
|
-
|
|
125
|
-
**Оговорка, которую надо держать при любом прогоне:** клин наступал при 485,
|
|
126
|
-
783, 813, 825 и 2294 МБ — разброс впятеро. Одна прогонка на значение не
|
|
127
|
-
докажет ничего; нужно по нескольку, и это часы прокачки. Если разброс внутри
|
|
128
|
-
одного размера порции останется таким же, честный ответ — «не установлено», а
|
|
129
|
-
не число.
|
|
130
|
-
|
|
131
|
-
**Механика автономного прогона, продуманная и не построенная:** фоновый
|
|
132
|
-
процесс через `run_in_background`; цикл в странице ставится ОДНИМ вызовом
|
|
133
|
-
`evaluate_script` и дальше живёт сам; результат выходит через `client-logger`
|
|
134
|
-
на сервер, а не через канал данных, — этот путь HTTP и переживает клин, чего
|
|
135
|
-
сегодня не хватило (пункт 85: браузер замолчал ровно на клине).
|
|
136
|
-
|
|
137
|
-
## 6. Что делать дальше, по порядку
|
|
138
|
-
|
|
139
|
-
1. снять препятствие со стендом и взять замер размера порции — это гейт;
|
|
140
|
-
2. backpressure по подтверждённому зрителем (§2), целиком наш код;
|
|
141
|
-
3. замер werift на процессоре, если §1 не даст ответа;
|
|
142
|
-
4. сообщить наверх: у нас есть то, чего нет ни у кого — два захвата с обеих
|
|
143
|
-
сторон и доказательство, что вторая ассоциация к тому же пиру работает в ту
|
|
144
|
-
же секунду. Ни в libdatachannel, ни в usrsctp вопрос не открыт.
|
|
145
|
-
|
|
146
|
-
**Чего делать НЕ надо:** прямой HTTPS (шаги 6-7) как ответ на этот клин.
|
|
147
|
-
Сказано пользователем 2026-09-12: это не решение проблемы.
|
|
148
|
-
|
|
149
|
-
## 7. Что осталось незакрытым от прошлых сессий и всплыло сегодня
|
|
150
|
-
|
|
151
|
-
1. ложное срабатывание 21:13:34 при 76 МБ прокачанных — 180 с захвата и файлы
|
|
152
|
-
кольца на показ, который шёл дальше ещё 37 минут;
|
|
153
|
-
2. `--delivery-sink` включён на полевом прокси. Он отдаёт ответ любого размера
|
|
154
|
-
и существует только для стенда.
|
|
155
|
-
|
|
156
|
-
## 8. СОСТОЯНИЕ НА КОНЕЦ 2026-09-12: дополнение ОСТАНОВЛЕНО, на его месте стенд
|
|
157
|
-
|
|
158
|
-
**Смотреть нельзя, пока это не свёрнуто.** Дополнение остановлено намеренно
|
|
159
|
-
(`ha apps stop b34a1737_torrent_tv_proxy`), и его место на порту 9090 занял
|
|
160
|
-
контейнер `ttv-wedge-stand`, запущенный из того же образа
|
|
161
|
-
(`b34a1737/aarch64-addon-torrent_tv_proxy:0.74.4`) с пропатченными
|
|
162
|
-
`services/data-channel-handler.js` и `bin/cli.js`.
|
|
163
|
-
|
|
164
|
-
**Свернуть:**
|
|
165
|
-
|
|
166
|
-
```
|
|
167
|
-
ssh ha 'sudo docker rm -f ttv-wedge-stand'
|
|
168
|
-
ssh ha 'sudo docker exec hassio_cli ha apps start b34a1737_torrent_tv_proxy'
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
**Почему свой контейнер, а не правка живого.** Пробовал патчить контейнер
|
|
172
|
-
дополнения — не выживает: `ha apps restart` пересоздаёт его из образа, а
|
|
173
|
-
убийство процесса внутри поднимает watchdog, который тоже пересоздаёт. Дважды
|
|
174
|
-
проверено по дате создания контейнера. Свой контейнер watchdog не трогает.
|
|
175
|
-
|
|
176
|
-
**Что построено в продукте** (в npm НЕ опубликовано, в git НЕ закоммичено):
|
|
177
|
-
`--send-chunk-bytes <N>` — размер одного сообщения канала данных;
|
|
178
|
-
`bodySender` — чистая функция нарезки и склейки, восемь проверок
|
|
179
|
-
(`test/send-chunk-bytes.test.js`), одна проверена на красноту поломкой склейки
|
|
180
|
-
через границу чтения; `msgBytes=` в строке `[net-debug]`; `chunks=` теперь
|
|
181
|
-
считает сообщения, а не чтения тела. Запись в CHANGELOG на 2.83.7.
|
|
182
|
-
|
|
183
|
-
**Как сменить размер** (стенд перезапускается изнутри своего контейнера):
|
|
184
|
-
|
|
185
|
-
```
|
|
186
|
-
ssh ha 'sudo docker exec ttv-wedge-stand pkill -f cli.js'
|
|
187
|
-
ssh ha 'sudo docker exec -d ttv-wedge-stand sh -c "node /usr/local/lib/node_modules/@torrent-tv/proxy/bin/cli.js --server-url https://webauth.courses --host 0.0.0.0 --port 9090 --ffmpeg-bin /usr/bin/ffmpeg --state-dir /standdata --delivery-sink --send-chunk-bytes 16384 --name wedge-stand > /standdata/stand.log 2>&1"'
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
**Нагрузка** ставится одним вызовом `evaluate_script` в открытой на
|
|
191
|
-
`webauth.courses` вкладке Chrome (9222, chrome-devtools MCP): цикл поднимает
|
|
192
|
-
СВОЁ соединение `new WebRtcProxy(id)` — рабочее соединение страницы не
|
|
193
|
-
трогается, — тянет `GET /api/delivery-sink?bytes=4194304` подряд и пишет отчёты
|
|
194
|
-
через `POST /api/client-logs`, то есть по HTTP, мимо заклинивающего канала.
|
|
195
|
-
Состояние лежит в `window.__WEDGE_STAND__`, остановка — `.stopped = true`.
|
|
196
|
-
|
|
197
|
-
**Читать результат:**
|
|
198
|
-
|
|
199
|
-
```
|
|
200
|
-
ssh do 'docker logs --tail 400 infra-server-1' | grep wedge-stand
|
|
201
|
-
ssh ha 'sudo docker exec ttv-wedge-stand grep msgBytes /standdata/stand.log | tail -5'
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
**Ловушка, стоившая одного прогона:** локальный стенд на машине разработчика
|
|
205
|
-
назывался тем же именем `wedge-stand`, и цикл подключился к нему вместо ХА.
|
|
206
|
-
Целиться по id, не по имени. Локальный остановлен.
|
|
207
|
-
|
|
208
|
-
**Опорный прогон запущен 2026-09-12 23:36 UTC** на `f312d1e6`, размер `asread`
|
|
209
|
-
(65 382 байта), 4 МБ на запрос, 190-325 Мбит/с. Результата на момент записи нет.
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# Handover: the priority map is the only thing that says a file is wanted
|
|
2
|
-
|
|
3
|
-
Written 2026-09-12, after the instruction that reversed the work in the tree.
|
|
4
|
-
|
|
5
|
-
## The instruction, in the user's own terms
|
|
6
|
-
|
|
7
|
-
The priority map answers ONE question: how much is this stretch of this file
|
|
8
|
-
wanted. Nothing anywhere asks WHO is reading a torrent. There are no reader
|
|
9
|
-
counters. Everything built around counting readers comes out.
|
|
10
|
-
|
|
11
|
-
The map is rebuilt when viewers come and go, so it already shows the current
|
|
12
|
-
state of affairs. A second bookkeeping of the same thing is what has to go.
|
|
13
|
-
|
|
14
|
-
## What is in the working tree right now, and why it is all wrong
|
|
15
|
-
|
|
16
|
-
HEAD is `79e802a`. On top of it, UNCOMMITTED:
|
|
17
|
-
|
|
18
|
-
| file | what was done to it |
|
|
19
|
-
|---|---|
|
|
20
|
-
| `services/torrent/ReaderClaims.js` | NEW, untracked. The whole class is the mistake. |
|
|
21
|
-
| `services/torrent-pool.js` | `fileUsageByTorrent` replaced by `this.readers = new ReaderClaims()`; a listener on it calls `rejoinSwarm`, `leaveSwarm` and `#scheduleIdleRemoval`; `torrentsForUploadPolicy` changed from taking a usage map to taking an `isRead` predicate. |
|
|
22
|
-
| `services/torrent-worker/worker.js` | `warmActiveFiles` (`:842`) and `keepWholeFiles` (`:1024`) read `pool.readers.of(torrent)`. |
|
|
23
|
-
| `test/swarm-follows-readers.test.js` | the announcement test rewritten around `ReaderClaims`. |
|
|
24
|
-
| `test/upload-hurry.test.js` | rewritten around the `isRead` predicate. |
|
|
25
|
-
|
|
26
|
-
All of it counts readers. All of it comes out. Two further facts about this
|
|
27
|
-
tree, both of which the next agent needs:
|
|
28
|
-
|
|
29
|
-
1. A bulk text replacement earlier in that session DESTROYED three methods of
|
|
30
|
-
`TorrentPool` — `#scheduleIdleRemoval`, `#cancelIdleRemoval` and
|
|
31
|
-
`#forgetDeadTorrent` — which were then called but not defined. They were
|
|
32
|
-
restored BY HAND from HEAD and adapted, not diffed line by line against the
|
|
33
|
-
original. One log line was changed deliberately: the idle-timer message no
|
|
34
|
-
longer prints a reader count. **Diff the restored bodies against
|
|
35
|
-
`git show HEAD:services/torrent-pool.js` before trusting them**, or discard
|
|
36
|
-
the tree and start from HEAD.
|
|
37
|
-
2. Discarding the uncommitted changes is probably the cheapest start, since
|
|
38
|
-
every one of them is being reversed. That is a destructive action and is the
|
|
39
|
-
USER'S CALL — ask before running anything that throws work away.
|
|
40
|
-
|
|
41
|
-
## What already exists and answers the question without counting anybody
|
|
42
|
-
|
|
43
|
-
Read these before designing. Line numbers are of the working tree.
|
|
44
|
-
|
|
45
|
-
**The map is built from viewers and published.** `services/priority/PriorityOrchestrator.js`
|
|
46
|
-
builds one map per film+file, and a second per output, from where the viewers
|
|
47
|
-
are. `services/priority/PriorityMap.js` holds the scale and the words for
|
|
48
|
-
reading it — `isAtAWatchingViewer`, `isBehindEverybody`, `isNobodyComingNow`.
|
|
49
|
-
|
|
50
|
-
**The map reaches the torrent thread as a command.** `Command.PRIORITY_MAP` in
|
|
51
|
-
`services/torrent-worker/worker.js:539` calls `pool.applyPriorityMap(torrent,
|
|
52
|
-
fileIndex, zones, durationSeconds)`.
|
|
53
|
-
|
|
54
|
-
**`applyPriorityMap` (`services/torrent-pool.js:1191`) is where seconds become
|
|
55
|
-
bytes**, and it writes the result into the demand register:
|
|
56
|
-
`demandFor(torrent).register.state({ claimant, fileIndex, byteStart, byteEnd,
|
|
57
|
-
urgency })`, with the claimant named `${MAP_CLAIMANT}:${fileIndex}:${index}`
|
|
58
|
-
(`:1257`). It WITHDRAWS its own stale claimants for that file afterwards
|
|
59
|
-
(`:1266-1273`), so a map that shrinks does shrink the register. **Whether a map
|
|
60
|
-
that wants NOTHING leaves the register empty for that file is not established —
|
|
61
|
-
check it, do not assume it.**
|
|
62
|
-
|
|
63
|
-
**The register is therefore the existing statement of what is wanted.**
|
|
64
|
-
`services/demand/DemandRegister.js` offers `size`, `files()`, `windows()`,
|
|
65
|
-
`at(urgency)`, `union(fileIndex)`, `mostUrgent()`, `withdraw(claimant)`,
|
|
66
|
-
`clear()`. `services/download/registry.js` offers `demandFor(torrent)`,
|
|
67
|
-
`hasUnmetDemand(torrent)`, `forgetTorrent(torrent)`, `reconcileAll()`.
|
|
68
|
-
|
|
69
|
-
## THE TRAP — read this before writing a line
|
|
70
|
-
|
|
71
|
-
**Not every claimant in the register is a viewer, and the ones that are not are
|
|
72
|
-
exactly what a naive rule kills.**
|
|
73
|
-
|
|
74
|
-
Measured claimants other than the map:
|
|
75
|
-
|
|
76
|
-
- `services/torrent-worker/piece-reader.js:670` — a read that is STOPPED on a
|
|
77
|
-
piece right now, stated at the BLOCKED level. Only a read can say this; the
|
|
78
|
-
map deliberately never states BLOCKED (see the comment at
|
|
79
|
-
`torrent-pool.js:1204`).
|
|
80
|
-
- `services/torrent-pool.js:1147`, `:1159`, `:1162` — the background fill and
|
|
81
|
-
the tail.
|
|
82
|
-
|
|
83
|
-
And several reads exist that no viewer has yet asked for: the codec probe
|
|
84
|
-
reading the file's head and tail, the keyframe table, the subtitle cluster
|
|
85
|
-
walk, a sidecar soundtrack being fetched whole.
|
|
86
|
-
|
|
87
|
-
**This is not hypothetical.** On 2026-09-11 a rule of exactly this shape shipped
|
|
88
|
-
as proxy 2.83.1: a torrent nobody had stated anything about left its swarm. A
|
|
89
|
-
torrent that has just been added has stated nothing yet, because its edges are
|
|
90
|
-
still being read and its playback plan is still being built. Field record:
|
|
91
|
-
added 20:57:17, first peer 20:57:18, out of the swarm 20:57:21 with 741
|
|
92
|
-
connections let go, and nothing ever rejoined it — rejoining waited for a
|
|
93
|
-
reader, and the reader was waiting for the header the swarm had been fetching.
|
|
94
|
-
`peers=0 connected of 0 known (94 queued, 5 tracker(s) offered up to 683
|
|
95
|
-
seeders)` for as long as the viewer would wait. Playback never started.
|
|
96
|
-
|
|
97
|
-
So "the map wants nothing, therefore let the swarm go" is WRONG as written. The
|
|
98
|
-
honest question is whether ANYTHING in the register wants bytes of this torrent
|
|
99
|
-
— the map's zones and every other claimant together — and that question has to
|
|
100
|
-
be asked of the register, which already knows, rather than of a counter.
|
|
101
|
-
|
|
102
|
-
## What has to change, and what each site should ask instead
|
|
103
|
-
|
|
104
|
-
Each of these currently asks "who is reading". Work out, per site, what it
|
|
105
|
-
actually needs from the register.
|
|
106
|
-
|
|
107
|
-
1. **`TorrentPool.acquireFile`** (`torrent-pool.js:1770`) and its release
|
|
108
|
-
function. Callers: `routes/stream/get.js:211`,
|
|
109
|
-
`routes/api/transcode-sessions/post.js:47`,
|
|
110
|
-
`services/controllers/SubtitleController.js:54`,
|
|
111
|
-
`services/torrent-worker/worker.js:251` and `:366`,
|
|
112
|
-
`services/torrent-worker/pool-adapter.js:88`,
|
|
113
|
-
`services/torrent-worker/client.js:327`. Note it also does two things that
|
|
114
|
-
are NOT counting: it cancels a pending idle removal and stamps
|
|
115
|
-
`#lastAccess`, which the disk-cap LRU reads. Decide where those two belong
|
|
116
|
-
before deleting the method.
|
|
117
|
-
2. **Leaving and rejoining the swarm.** `leaveSwarm` and `rejoinSwarm` are
|
|
118
|
-
exported from `torrent-pool.js:465` and `:497`. Today the trigger is the
|
|
119
|
-
first claim taken and the last released. It becomes a fact of the register.
|
|
120
|
-
3. **Idle removal.** `#scheduleIdleRemoval` fires on the last reader leaving and
|
|
121
|
-
re-checks for readers when the timer fires.
|
|
122
|
-
4. **The upload policy.** `torrentsForUploadPolicy` (`torrent-pool.js:349`) sets
|
|
123
|
-
`torrent.hasActiveReader` and `torrent.hasUnmetDemand`. The second already
|
|
124
|
-
comes from the register (`hasUnmetDemand`); the first is the counter.
|
|
125
|
-
5. **`#reportStalledDownloads`** (`:1311`) and **`#adjustUploadLimit`** (`:1354`)
|
|
126
|
-
skip a torrent with no readers.
|
|
127
|
-
6. **The disk-cap LRU** (`:1411`) picks torrents with no readers.
|
|
128
|
-
7. **`#removeTorrent`** (`:1886`) prints a refcount for diagnostics.
|
|
129
|
-
8. **`warmActiveFiles`** (`worker.js:842`) walks "the files being read" to keep
|
|
130
|
-
subtitle cues current. `register.files()` says which files anything wants.
|
|
131
|
-
9. **`keepWholeFiles`** (`worker.js:1024`) refuses to write a completed file out
|
|
132
|
-
while it is being read, so that the write does not take the disk from a
|
|
133
|
-
viewer. This one genuinely needs "is anybody reading THIS file right now" and
|
|
134
|
-
is the site most likely to need a different answer from the register than a
|
|
135
|
-
plain "is it wanted".
|
|
136
|
-
|
|
137
|
-
## Rules that hold whatever the design turns out to be
|
|
138
|
-
|
|
139
|
-
- **Plan first, approval before any edit.** No exceptions, including when told
|
|
140
|
-
to get on with it.
|
|
141
|
-
- **Never add a line to `services/hls-session-manager.js`.** It is being taken
|
|
142
|
-
apart. Deletion only; `git diff --numstat` must show more removed than added.
|
|
143
|
-
- **All code, comments and documents in the repositories are English.**
|
|
144
|
-
- **Do not run the whole test suite.** Run the tests of the files changed.
|
|
145
|
-
Relevant here: `test/swarm-follows-readers.test.js`, `test/upload-hurry.test.js`,
|
|
146
|
-
`test/file-claims.test.js`, `test/background-fill.test.js`,
|
|
147
|
-
`test/priority-map-download.test.js`, `test/read-window.test.js`,
|
|
148
|
-
`test/piece-reader.test.js`, `test/dead-torrent-handle.test.js`.
|
|
149
|
-
- **A CHANGELOG entry is part of the change**, written at the version
|
|
150
|
-
`npm run patch` will produce — the current `package.json` version plus one
|
|
151
|
-
patch. Never edit the version in `package.json` by hand.
|
|
152
|
-
- **Bumping the proxy means bumping the addon**, `ha-addon/torrent_tv_proxy/config.yaml`,
|
|
153
|
-
with its own CHANGELOG entry, and the proxy is published FIRST.
|
|
154
|
-
- **Publishing is pre-authorised.** Destructive actions are not.
|
|
155
|
-
- Refer to work by what it is, never by a list number.
|
|
156
|
-
|
|
157
|
-
## State of the tree as measured, 2026-09-12
|
|
158
|
-
|
|
159
|
-
- `node --check` passes on the three touched source files.
|
|
160
|
-
- Biome is clean over `services/`, `test/` and `routes/` — 282 files.
|
|
161
|
-
- Tests of the changed files: 131 of 131 pass.
|
|
162
|
-
- Nothing has been verified in the field; the released proxy is 2.83.3 with
|
|
163
|
-
addon 0.74.0 on the host.
|
|
164
|
-
|
|
165
|
-
None of that argues for keeping the work. It is recorded so that a failure
|
|
166
|
-
after the removal is not mistaken for a failure that was already there.
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
# A film played 80 seconds and then stood still for 92 minutes — 2026-09-12
|
|
2
|
-
|
|
3
|
-
Proxy 2.83.4, addon 0.74.1, one viewer, `Reacher.S04E07.1080p.rus.LostFilm.TV.mkv`
|
|
4
|
-
(2 363 497 962 bytes). Reported by the user as "the film only managed to play
|
|
5
|
-
one minute nineteen seconds".
|
|
6
|
-
|
|
7
|
-
Every claim below is tagged by where it comes from: **[log]** read out of a
|
|
8
|
-
captured log, **[code]** read out of the source, **[measured]** computed from
|
|
9
|
-
the captured logs, **[derived]** reasoned from two of the above.
|
|
10
|
-
|
|
11
|
-
## 1. What the viewer saw
|
|
12
|
-
|
|
13
|
-
**[log, browser]** Every one of the 1112 `decode` readings in the captured
|
|
14
|
-
window is identical: `t=80.0s size=1920x1080 frames=1920 dropped=0
|
|
15
|
-
readyState=2`. 1920 frames at 24 fps is 80.0 s, so the picture advanced 80
|
|
16
|
-
seconds and then never moved again. The page stayed open until 18:52.
|
|
17
|
-
|
|
18
|
-
**[log, browser]** From then on the player asked for one segment and was
|
|
19
|
-
refused, about once a minute for 92 minutes:
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
18:30:47 non-fatal: fragLoadError currentTime=79.97 msReadyState=open
|
|
23
|
-
frag=segment-00008.mp4 sn=8 action=5 error=Error: HTTP Error 503 …
|
|
24
|
-
18:41:15 fatal: fragLoadError … sn=8 … HTTP Error 503
|
|
25
|
-
18:41:17 recovered fatal networkError, resuming at 80.0s
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
`msReadyState=open` throughout, so this is **not** the closed-MediaSource
|
|
29
|
-
failure of item 86. **[log]** The transport was healthy the whole time —
|
|
30
|
-
`rtt=4ms`, `state=connected ice=connected`, every counter advancing — so it is
|
|
31
|
-
not item 10 either.
|
|
32
|
-
|
|
33
|
-
## 2. What the proxy answered, and why
|
|
34
|
-
|
|
35
|
-
**[code]** 503 in `routes/transcode/session-file/get.js:143` is the
|
|
36
|
-
`warming-up` branch: the segment is still being produced. It was never produced.
|
|
37
|
-
|
|
38
|
-
**[log]** 239 times:
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
transcode 17ec5953 holding segment-00008.mp4: the file is not on disk
|
|
42
|
-
(runs from #8, viewer at #8, encoder alive, index #8, produced 0.0s at 2.23x
|
|
43
|
-
— the encoder has not made it)
|
|
44
|
-
[hold] segment-00008.mp4 warming-up after 60400ms (the map wants it now, rank 69 of 69)
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**[log]** The encoder could not open its input, 5712 times:
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
encode-run #8..#247 lost its input ([http @ …] Error reading HTTP response: End of file)
|
|
51
|
-
Error opening input file http://127.0.0.1:9090/stream?sourceKey=94dda59f… (8514×)
|
|
52
|
-
Error opening input files: End of file (5712×)
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
**[log]** And `/stream` said exactly what was wrong, 2884 times:
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
stream: read of "Reacher.S04E07.1080p.rus.LostFilm.TV.mkv" bytes 0-2363497961
|
|
59
|
-
ended after 0 of 2363497962 bytes: Piece 0 is verified but absent from the store.
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Zero bytes of 2.36 GB. ffmpeg reads an empty body as the end of the file.
|
|
63
|
-
|
|
64
|
-
## 3. The cause
|
|
65
|
-
|
|
66
|
-
**[log]** The torrent believed it had the whole file:
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
[stats] 94dda59f 5d276be5 peers=54 connected … file=100.0% header=8388608/8388608B
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**[log]** The store did not:
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
piece-store "Reacher.S04E07…": resident=27/42 (108MB of 168MB allowed)
|
|
76
|
-
committed=108MB blocks=27 on-disk=26MB pinned=0 spilled=7
|
|
77
|
-
reads=5932 (73.8% from memory) spills=571 revivals=41
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
**[measured]** 571 spills at a 4 MB piece is about 2284 MB written to disk;
|
|
81
|
-
26 MB remained. So roughly 565 pieces had been removed. **[log]** It was not
|
|
82
|
-
the disk cap: `disk: 93634MB free; spilled pieces 26MB of 46656MB` — the
|
|
83
|
-
ceiling never came near binding, and 93 GB were free.
|
|
84
|
-
|
|
85
|
-
**[code]** That leaves exactly one remover: `PieceDiskStore.forgetBehind`,
|
|
86
|
-
called from `SharedPieceStore.reviseSpillCeiling`, which drops every piece
|
|
87
|
-
lying behind the earliest read head. **[log]** The encoder had reached
|
|
88
|
-
`proxyProcessed=725.643`, twelve minutes of film, so piece 0 — the start of the
|
|
89
|
-
file and its Matroska header — was far behind every reader and was dropped.
|
|
90
|
-
That is the rule working as designed.
|
|
91
|
-
|
|
92
|
-
**[code]** `piece-reader.js:151` then asks `torrent.bitfield.get(index)`. The
|
|
93
|
-
library still answered yes, because it had downloaded and hashed that piece
|
|
94
|
-
once. **Nothing ever told it otherwise:** `bitfield.set` does not occur
|
|
95
|
-
anywhere in `services/piece-store/`, `services/torrent-pool.js` or
|
|
96
|
-
`services/torrent-worker/`. So the read concluded the piece was had, asked the
|
|
97
|
-
store, was told it was absent, and threw.
|
|
98
|
-
|
|
99
|
-
**[derived]** And it was never fetched again, because the library does not
|
|
100
|
-
download a piece it believes it owns. The comment on `forgetBehind` states the
|
|
101
|
-
bargain in as many words — "a seek back re-downloads it, which is the same
|
|
102
|
-
bargain this tier makes whenever it drops a piece for room" — and that bargain
|
|
103
|
-
had never once been honoured.
|
|
104
|
-
|
|
105
|
-
**[derived]** The trigger is ordinary: an encoder restart re-opens its input at
|
|
106
|
-
byte 0 (`bytes 0-2363497961` in the log), which is precisely the region
|
|
107
|
-
`forgetBehind` removes first.
|
|
108
|
-
|
|
109
|
-
This is the "un-have problem" the parent `CLAUDE.md` names in the Level 2 disk
|
|
110
|
-
design — "mark the piece incomplete in the completion store → it re-downloads on
|
|
111
|
-
the next read". The eviction shipped; the un-have did not.
|
|
112
|
-
|
|
113
|
-
## 4. Two amplifiers, each a defect of its own
|
|
114
|
-
|
|
115
|
-
### The delay after a lost input did not bind
|
|
116
|
-
|
|
117
|
-
**[code]** `hls-session-manager.js` timed a retry against the DEAD RUN, doubling
|
|
118
|
-
2 s → 15 s. **[code]** `EncodeRun.isAlive` is false in `RETRY_WAIT`, and the
|
|
119
|
-
orchestrator plans from live runs only — so the plan saw the stretch as free and
|
|
120
|
-
placed a new run at once, on every event.
|
|
121
|
-
|
|
122
|
-
**[measured]** 2432 `restart for` lines in 23 minutes: one every 0.57 s, for 61
|
|
123
|
-
minutes, while the delay printed `retrying in 15s`. The comment beside that
|
|
124
|
-
timer predicted this exactly and the code did not prevent it.
|
|
125
|
-
|
|
126
|
-
### The log destroyed the record of its own failure
|
|
127
|
-
|
|
128
|
-
**[measured]** 76 385 lines in 23 minutes and 159 000 in the previous 38 — about
|
|
129
|
-
55 lines a second, of which **52 567 of 76 385 (68.8 %) are byte-identical
|
|
130
|
-
repeats**. **[code]** `MAX_FILE_BYTES` was 32 MiB with one previous turn kept, so
|
|
131
|
-
the file turned over twice inside the session: 61 minutes of 92 survived, and the
|
|
132
|
-
onset — everything before 17:51 — was overwritten by the second rotation and is
|
|
133
|
-
gone. **[log]** `/data` had 91.4 GB free.
|
|
134
|
-
|
|
135
|
-
## 5. What was built (proxy 2.83.5)
|
|
136
|
-
|
|
137
|
-
1. the store announces when it can no longer produce a piece at all — not
|
|
138
|
-
resident, not on disk, not in a file held whole — and the pool withdraws the
|
|
139
|
-
library's claim with `_markUnverified`. The disk tier states only its own
|
|
140
|
-
loss; the store decides whether that is a loss;
|
|
141
|
-
2. a read whose piece is withdrawn under it goes back one step and takes the
|
|
142
|
-
whole ordinary path again instead of throwing. Once per piece;
|
|
143
|
-
3. the delay lives in the orchestrator, per output, and the plan places nothing
|
|
144
|
-
there until it is over — unless something on that output is still producing.
|
|
145
|
-
It is lifted only by a run that actually produced;
|
|
146
|
-
4. an established fact is logged once, then at doubling intervals to a minute,
|
|
147
|
-
saying how many were held back. Matched verbatim; digits are deliberately not
|
|
148
|
-
normalised, because that would merge the memory series;
|
|
149
|
-
5. the log file may reach 1 GB before turning over.
|
|
150
|
-
|
|
151
|
-
## 6. What this does NOT establish
|
|
152
|
-
|
|
153
|
-
1. **Nothing here has been seen in the field.** The next session is the proof.
|
|
154
|
-
What should appear: `withdrawn=` climbing on the piece-store line while reads
|
|
155
|
-
keep succeeding, `its input was not there (attempt N) — placing nothing for
|
|
156
|
-
Xs`, and `[said N more time(s) …]` instead of a flood. What should disappear:
|
|
157
|
-
`Piece N is verified but absent from the store`.
|
|
158
|
-
2. **The onset was not read.** Both log turns begin with the failure already
|
|
159
|
-
established, so which piece failed FIRST, and whether the encoder's first
|
|
160
|
-
input loss had the same cause, is not known. The mechanism is established;
|
|
161
|
-
its first instance is not.
|
|
162
|
-
3. **A consequence, stated rather than discovered later:** a torrent whose
|
|
163
|
-
pieces have been withdrawn is no longer `done`, so a film watched to the end
|
|
164
|
-
is not assembled into a whole file. That is the honest state — those bytes
|
|
165
|
-
are not here — and the previous behaviour was worse than it looked, since the
|
|
166
|
-
assembly reads the file through the store and would have waited for pieces
|
|
167
|
-
the library wrongly believed it had. What is NOT measured is how often a film
|
|
168
|
-
now fails to be assembled, and whether keeping the pieces of a nearly-finished
|
|
169
|
-
file would be worth the disk.
|
|
170
|
-
4. **The re-download cost is not measured.** A seek back past the read heads now
|
|
171
|
-
waits for pieces to be fetched again instead of failing. How long that takes
|
|
172
|
-
on a real swarm, and whether the viewer notices, needs a session.
|
|
173
|
-
5. **`peers=54 connected of -4599 known (…, -1128 queued)`** — the negative
|
|
174
|
-
figures come from WebTorrent's own `_peersLength`/`_numQueued`, read for the
|
|
175
|
-
log only. Not investigated, not touched.
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# Does the preset benchmark's pixels/sec figure hold at a different size? (2026-08-22)
|
|
2
|
-
|
|
3
|
-
Roadmap item 4(e): the software-preset benchmark (`benchmarkSoftwarePresets`,
|
|
4
|
-
`services/hwaccel.js`) measures encoder throughput at one fixed reference size —
|
|
5
|
-
640×360 — and every later use of that figure (deciding which quality rungs this
|
|
6
|
-
host can sustain) scales it by pixel count, assuming cost per pixel is constant
|
|
7
|
-
regardless of frame size. This was never checked against a second size.
|
|
8
|
-
|
|
9
|
-
## Measurement
|
|
10
|
-
|
|
11
|
-
Same clip (`cal-h264-1080-hi.mp4`), same `libx264` presets, same measurement
|
|
12
|
-
method as the shipped benchmark (slope between two `-progress` reports,
|
|
13
|
-
excluding startup) — run once at the existing 640×360 reference and once at
|
|
14
|
-
1280×720 (4× the pixel count), on the developer's desktop:
|
|
15
|
-
|
|
16
|
-
| preset | 640×360 | 1280×720 | ratio (should be ~1.0) |
|
|
17
|
-
|-----------|----------------|----------------|-------------------------|
|
|
18
|
-
| ultrafast | 1322.8 Mpx/s | 588.6 Mpx/s | 0.445 |
|
|
19
|
-
| fast | 176.6 Mpx/s | 128.1 Mpx/s | 0.725 |
|
|
20
|
-
|
|
21
|
-
Script: ad-hoc, not committed (reused the benchmark's own ffmpeg arguments at a
|
|
22
|
-
second `-vf scale=`/`-s` size; available on request if this needs re-running).
|
|
23
|
-
|
|
24
|
-
## Reading
|
|
25
|
-
|
|
26
|
-
The assumption does not hold. Cost per pixel RISES with frame size on this
|
|
27
|
-
host — by 2.25× for `ultrafast`, 1.38× for `fast`, at 4× the pixel count — and
|
|
28
|
-
the effect is preset-dependent, not a single constant. A quality ladder priced
|
|
29
|
-
from the 640×360 figure alone over-states what this host can sustain at 1080p,
|
|
30
|
-
worse for the faster presets that the ladder falls back to under load.
|
|
31
|
-
|
|
32
|
-
## Why this does not become a code change yet
|
|
33
|
-
|
|
34
|
-
One host, one clip, two sizes. [[feedback_pool_diversity]] — a correction fitted
|
|
35
|
-
to this desktop and applied to every proxy in the pool would be exactly the
|
|
36
|
-
mistake that memory exists to prevent: the pool spans ARM boards (HA Yellow /
|
|
37
|
-
CM4) with different cache sizes and memory bandwidth, where a size penalty of
|
|
38
|
-
this shape could be smaller, larger, or absent. [[feedback_no_fudged_numbers]] —
|
|
39
|
-
publishing 0.445/0.725 as universal constants is fabricating a number for hosts
|
|
40
|
-
that were never measured.
|
|
41
|
-
|
|
42
|
-
The benchmark already measures per-host (item 4(f), 4(h)); a size-scaling
|
|
43
|
-
correction belongs there too — the benchmark itself gains a second, cheap
|
|
44
|
-
measurement point (one preset, one second size) and derives its own exponent
|
|
45
|
-
from that host's two readings, the same way `fitDecodeCost` derives its terms
|
|
46
|
-
from clips rather than from a stated formula. That is the remaining work under
|
|
47
|
-
item 4(e); this note is the measurement that justifies doing it, not the fix
|
|
48
|
-
itself.
|
|
49
|
-
|
|
50
|
-
## What is verified
|
|
51
|
-
|
|
52
|
-
The measurement method matches the shipped benchmark exactly (real footage,
|
|
53
|
-
raw frames decoded once, slope between two progress reports) — the only
|
|
54
|
-
variable changed was the target size, so this is not comparing two different
|
|
55
|
-
methodologies.
|