From e012b7e9ce55079f411943c7a1f6ccbfbd629a5f Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sun, 16 Aug 2026 20:58:08 +0200 Subject: feat(node): the stream capacity is the operator's to set, and a log that says who stopped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bounding the client's read-ahead changed what a transcode slot is. It used to be a burst — the browser took segments as fast as it could append them, so a slot came back within the minute whatever the length of the film. Now it is held for as long as someone is watching, so the cap counts simultaneous viewers, and two of them meant the third was refused for the next hour and a half. The right number depends on the machine, so it belongs to the operator: `[node] max_concurrent_streams` in node.toml, or MESHBAY_MAX_CONCURRENT_STREAMS. Default 8 — one ffmpeg per viewer, remuxing rather than encoding, idle on a pipe for most of the film. Zero, a negative number, a non-number and a bool are refused with a warning naming the setting: `Semaphore(0)` is not "no limit", it is a node where no video ever plays and nothing says why, and TOML `true` would have become 1 by way of `int()`. A stream also ends on the peer's silence now rather than on its stinginess. A viewer buffered well ahead deliberately grants nothing for minutes, and the old budget accumulated over the whole wait, so a keepalive that granted no credit could not keep a paused film alive. The rest is diagnosis, which is what this cost. `client_diag` carries the player's own view — readyState, refused appends, buffered ranges, the video element's error — into the node's log at DEBUG, next to the node's view of the same stream. It is the only window into a phone, and every field is stringified and cut short because all of it is peer-controlled. The node also logs the first keepalive, which distinguishes a paced client from an unpaced one at a glance, and progress every hundred segments, whose last line says where a stream stopped and which side stopped it. --- packages/meshbay-node/tests/test_task_lifetime.py | 26 ++++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) (limited to 'packages/meshbay-node/tests/test_task_lifetime.py') diff --git a/packages/meshbay-node/tests/test_task_lifetime.py b/packages/meshbay-node/tests/test_task_lifetime.py index 8897e71..e1ebfed 100644 --- a/packages/meshbay-node/tests/test_task_lifetime.py +++ b/packages/meshbay-node/tests/test_task_lifetime.py @@ -8,9 +8,10 @@ pending!" and nothing else happens. For `_stream_video` that was expensive. It holds a transcode slot for its whole life with `async with sem`, and a destroyed task never reaches `__aexit__`. The -node allows two, so two abandoned streams left it answering "Server busy" to -every request from then on: videos stopped playing entirely, first try included, -until the daemon was restarted. +node allows only a handful, so a few abandoned streams left it answering +"Server busy" to every request from then on: videos stopped playing entirely, +first try included, until the daemon was restarted. It was two slots at the +time, which is how few it took. Seen in the wild on 2026-08-16 after a viewer switched films mid-stream. """ @@ -86,12 +87,21 @@ def test_closing_a_session_releases_its_tasks(session): assert "gather" in fn, "cancelling without awaiting does not run the exits" -def test_two_slots_is_the_whole_margin(source): - """States the number the failure hinged on, so a change is deliberate.""" +def test_the_slot_count_matches_what_a_slot_now_costs(source): + """States the number, so a change is deliberate rather than drifted into. + + It was two, and two was right while a stream was a burst: the client took + segments as fast as it could append them and the slot came back within the + minute. Bounding the read-ahead to ninety seconds of film (the buffer + ceiling fix) changed what a slot is — it is now held for as long as someone + is watching, so the count is a count of simultaneous viewers. + """ n = int(re.search(r"MAX_CONCURRENT_TRANSCODES\s*=\s*(\d+)", source).group(1)) - assert n == 2, ( - f"the cap is now {n}; the leak above emptied it in {n} abandoned " - "streams, so if this moves the comments explaining it should too") + assert n >= 8, ( + f"the cap is {n}; with a slot held for the length of a film, that is " + f"{n} people watching before the node refuses everyone else") + assert "for as long as the film runs" in source, ( + "the number moved but the comment explaining what a slot costs did not") # ── One viewer, one stream ──────────────────────────────────────────────────── -- cgit v1.2.3