fix: avoid unbounded HTTP throttling and make buffer readout opt-in

This commit is contained in:
2026-10-11 11:52:48 +02:00
parent 13554a1574
commit 537e8e2624
6 changed files with 118 additions and 28 deletions
+47 -6
View File
@@ -90,6 +90,7 @@ struct GateState {
bool entered, released;
uint64_t waiting, requests, ranges, bytes, total;
double audioEnd, videoEnd;
uint64_t connections;
};
static GateState gateState(const std::string &base, const std::string &token) {
@@ -98,7 +99,8 @@ static GateState gateState(const std::string &base, const std::string &token) {
[state[@"waiting"] unsignedLongLongValue], [state[@"requests"] unsignedLongLongValue],
[state[@"ranges"] unsignedLongLongValue], [state[@"bytes"] unsignedLongLongValue],
[state[@"total"] unsignedLongLongValue],
[state[@"prefix_ends"][@"audio"] doubleValue], [state[@"prefix_ends"][@"video"] doubleValue]};
[state[@"prefix_ends"][@"audio"] doubleValue], [state[@"prefix_ends"][@"video"] doubleValue],
[state[@"connections"] unsignedLongLongValue]};
}
static void stillBlocked(const std::string &base, const std::string &token) {
@@ -369,6 +371,43 @@ static void cachedWindow(Player *player, const std::string &base) {
std::puts("PASS sliding cache: minute read-ahead/thirty-second history/keyframe replay/cached bidirectional seeks/miss refill/cancel");
}
static void chunkedFetch(Player *player, const std::string &base, bool split) {
const std::string videoToken = split ? "chunked-video" : "chunked-combined";
const std::string audioToken = "chunked-audio";
auto path = [](const std::string &token) {
return "/chunked-media/" + token + "/cache.mp4?after_seconds=2";
};
require(openNetwork(player, base, path(videoToken) + (split ? "" : "&keep_alive=1"),
split ? path(audioToken) : "", 120),
"open recorded media whose CDN throttles oversized requests");
require(player_play(player), "start playback while the minute reserve fills");
until(player, [&](auto s) {
const auto video = gateState(base, videoToken);
const bool throttled = video.entered || (split && gateState(base, audioToken).entered);
require(!(s.position > .5 && s.buffering && throttled),
"oversized HTTP request starves playback before a minute can buffer");
return s.status == PLAYER_PLAYING && buffered(s) && s.buffered_end - s.position >= 59;
}, "finite HTTP requests build a minute of playable reserve without CDN throttling");
require(gateState(base, videoToken).requests >= 2,
"minute read-ahead continues across the first bounded HTTP response");
if (split) require(gateState(base, audioToken).requests >= 2,
"secondary audio also continues bounded HTTP requests");
else require(gateState(base, videoToken).connections == 1,
"minute reserve reuses one HTTP connection across bounded responses");
require(player_pause(player), "pause before an out-of-cache seek");
until(player, [](auto s) { return s.status == PLAYER_PAUSED; }, "pause chunked source");
seekCompleted(player, 119.5, PLAYER_PAUSED);
require(player_play(player), "resume the final partial HTTP range");
until(player, [](auto s) { return s.status == PLAYER_ENDED; }, "bounded final response reaches true media EOF");
require(!gateState(base, videoToken).entered &&
(!split || !gateState(base, audioToken).entered),
"initial fetch, continuation and seek never use CDN-throttled ranges");
require(player_close(player), "close chunked source");
closed(player);
std::puts(split ? "PASS bounded split fetch: minute reserve/secondary audio/range continuation/seek/EOF"
: "PASS bounded combined fetch: minute reserve/range continuation/seek/EOF");
}
static void bufferCoverage(Player *player, const std::string &base) {
const std::string token = "buffer-release";
@@ -436,12 +475,12 @@ static void cancellation(Player *player, const std::string &base, const std::str
}
seekCompleted(player, 28, (splitAudio || splitVideo) ? PLAYER_PLAYING : PLAYER_PAUSED);
stillBlocked(base, token);
auto afterSeek = gateState(base, token);
require(afterSeek.requests > 1 && afterSeek.ranges > 1, "seek replaces stalled read with an actual new byte-range request");
require(player_play(player), "resume at seek target with stale read still blocked");
until(player, [](auto s) {
return s.status == PLAYER_PLAYING && !s.seek_pending && s.position > 28.1 && buffered(s);
}, "retained coverage follows new stalled-read seek target");
until(player, [&](auto s) {
auto afterSeek = gateState(base, token);
return s.status == PLAYER_PLAYING && !s.seek_pending && s.position > 28.1 && buffered(s) &&
afterSeek.requests > 1 && afterSeek.ranges > 1;
}, "new byte-range refill reaches stalled-read seek target");
stillBlocked(base, token);
require(player_pause(player), "pause high-timestamp coverage before backward seek");
until(player, [](auto s) { return s.status == PLAYER_PAUSED && buffered(s); },
@@ -534,6 +573,8 @@ int main(int argc, char **argv) {
styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO];
window.contentView = view;
[window orderBack:nil];
chunkedFetch(player.get(), base, false);
chunkedFetch(player.get(), base, true);
transport(player.get(), view, base, false);
transport(player.get(), view, base, true);
frameSteps(player.get(), view, base);
+24 -11
View File
@@ -24,6 +24,7 @@ class _Gate:
self.requests = 0
self.ranges = 0
self.bytes = 0
self.connections = set()
class StreamingServer:
@@ -60,10 +61,14 @@ class StreamingServer:
pass # Media URLs/headers never appear in logs.
def do_GET(self):
owner._get(self)
def handle(self):
try:
owner._get(self)
super().handle()
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
pass # The engine intentionally cancels active media connections.
# Cancellation can close a body read or an idle keep-alive connection.
self.close_connection = True
self.http = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
self.http.daemon_threads = True
@@ -163,19 +168,20 @@ class StreamingServer:
gate = self.gates.get(parts[2])
if gate is None:
body = {"entered": False, "released": False, "waiting": 0, "requests": 0,
"ranges": 0, "bytes": 0, "total": 0, "prefix_ends": {}}
"ranges": 0, "bytes": 0, "total": 0, "connections": 0, "prefix_ends": {}}
else:
if parts[1] == "release":
gate.release.set()
body = {"entered": gate.entered.is_set(), "released": gate.release.is_set(),
"waiting": gate.waiting, "requests": gate.requests, "ranges": gate.ranges,
"bytes": gate.bytes, "total": gate.total, "prefix_ends": gate.prefix_ends}
"bytes": gate.bytes, "total": gate.total, "connections": len(gate.connections),
"prefix_ends": gate.prefix_ends}
self._reply(request, 200, json.dumps(body).encode())
return
mode = parts[0]
token = None
if len(parts) == 3 and mode in ("stall", "stall-open"):
if len(parts) == 3 and mode in ("stall", "stall-open", "chunked-media"):
token, name = parts[1:]
if not re.fullmatch(r"[A-Za-z0-9_-]+", token):
self._reply(request, 400, b"Invalid gate token", "text/plain")
@@ -223,21 +229,27 @@ class StreamingServer:
query = parse_qs(parsed.query)
try:
gate_at = (self._timestamp_offset(name, float(query["after_seconds"][0]))
if mode == "stall" and "after_seconds" in query else
int(query.get("after", ["131072"])[0]) if mode == "stall" else start)
if mode in ("stall", "chunked-media") and "after_seconds" in query else
int(query.get("after", ["131072"])[0]) if mode in ("stall", "chunked-media") else start)
except (ValueError, OverflowError):
self._reply(request, 400, b"Invalid gate offset", "text/plain")
return
with self.lock:
if token not in self.gates:
prefix = self._prefix_ends(name, gate_at) if mode == "stall" else {}
prefix = self._prefix_ends(name, gate_at) if mode in ("stall", "chunked-media") else {}
self.gates[token] = _Gate(size, prefix)
gate = self.gates[token]
gate.requests += 1
gate.connections.add(request.client_address)
gate.ranges += int(range_header is not None)
# Only the original prefix connection is gated: a seek can create a new
# range request while the old read is still blocked and never released.
should_gate = not gate.claimed and start <= gate_at <= end
# Simulate a CDN that throttles oversized/open-ended ranges, while
# finite requests of at most 10 MiB run at full available speed.
oversized = mode == "chunked-media" and (
range_header is None or not range_header.split("-", 1)[1]
or int(range_header.split("-", 1)[1]) - start + 1 > 10 * 1024 * 1024)
should_gate = (mode != "chunked-media" or oversized) and not gate.claimed and start <= gate_at <= end
if should_gate:
gate.claimed = True
with self.lock:
@@ -249,10 +261,11 @@ class StreamingServer:
request.send_header("Content-Length", str(end - start + 1))
if range_header is not None:
request.send_header("Content-Range", f"bytes {start}-{end}/{size}")
request.send_header("Connection", "close")
persistent = mode == "chunked-media" and parse_qs(parsed.query).get("keep_alive") == ["1"]
request.send_header("Connection", "keep-alive" if persistent else "close")
request.end_headers()
request.wfile.flush()
request.close_connection = True
request.close_connection = not persistent
cursor = start
with path.open("rb") as media:
media.seek(start)
+17
View File
@@ -22,8 +22,20 @@ struct SystemPlaybackChecks {
let suite = "terebi.system-playback-check.\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suite)!
defer { defaults.removePersistentDomain(forName: suite) }
let initialModel = PlayerModel(defaults: defaults)
require(!initialModel.showsBufferInfo, "buffer readout defaults off before playback")
initialModel.setShowsBufferInfo(true)
require(initialModel.showsBufferInfo && initialModel.controlsVisible,
"buffer readout can be enabled before playback and reveals controls")
initialModel.shutdown()
let restoredModel = PlayerModel(defaults: defaults)
require(restoredModel.showsBufferInfo, "enabled buffer readout survives model recreation")
restoredModel.setShowsBufferInfo(false)
require(!restoredModel.showsBufferInfo, "buffer readout can be explicitly disabled")
restoredModel.shutdown()
let model = PlayerModel(defaults: defaults)
defer { model.shutdown() }
require(!model.showsBufferInfo, "explicitly disabled buffer readout survives model recreation")
model.setVolume(0)
let directory = URL(fileURLWithPath: CommandLine.arguments[1], isDirectory: true)
let center = MPNowPlayingInfoCenter.default()
@@ -73,6 +85,11 @@ struct SystemPlaybackChecks {
retainedSnapshot.buffered_end = min(retainedSnapshot.duration, retainedSnapshot.position + 1)
model.applyState(retainedSnapshot)
require(model.bufferedRange != nil, "loaded selection exposes retained media")
let retainedRange = model.bufferedRange
model.setShowsBufferInfo(true)
require(model.bufferedRange == retainedRange, "enabling buffer readout preserves retained media")
model.setShowsBufferInfo(false)
require(model.bufferedRange == retainedRange, "disabling buffer readout preserves retained media")
model.closeFile()
require(model.bufferedRange == nil, "accepted close immediately hides a stale retained-media snapshot")
require(center.nowPlayingInfo == nil && center.playbackState == .stopped,