feat(agent): global bandwidth cap — the agent can't saturate the desktop's network
One shared TokenBucket (default 8 MB/s; BANDWIDTH_LIMIT_MB_S, 0 = unlimited; live MB/s dial + net readout in the control UI) is charged by every still download (streamed chunk reads) and every ffmpeg video stream (metered from outside via /proc/<pid>/io and SIGSTOP/SIGCONTed into budget). Why: D1 re-measurement 2026-07-02 — the idle link moves ~38 MB/s, but 8 unthrottled downloaders bufferbloated it to ~1-1.5 MB/s PER STREAM (operator's browser included). Capping the aggregate keeps the desktop usable and still beats the collapsed sweep throughput it replaces. Agent build 2026-07-02.4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
@@ -101,15 +101,26 @@ class FcClient:
|
||||
return
|
||||
self._post_quiet("/api/gpu/jobs/release", {"job_ids": job_ids})
|
||||
|
||||
def fetch_image(self, image_url: str) -> bytes:
|
||||
def fetch_image(self, image_url: str, throttle=None) -> bytes:
|
||||
# image_url is a server-relative path ("/images/...").
|
||||
# timeout=(connect, read): the read timeout is BETWEEN-BYTES, not total,
|
||||
# so a large-but-flowing download still completes — but a stuck/dead
|
||||
# connection (curator overloaded) fails in 60s instead of hanging a
|
||||
# downloader for 180s and piling up concurrent stuck requests on curator.
|
||||
r = self.s.get(f"{self.base}{image_url}", timeout=(10, 60))
|
||||
r.raise_for_status()
|
||||
return r.content
|
||||
# With a throttle (the worker's shared TokenBucket), the body is streamed
|
||||
# in chunks and each chunk is charged to the global bandwidth budget —
|
||||
# pausing between reads lets TCP flow control pace curator's send side.
|
||||
with self.s.get(
|
||||
f"{self.base}{image_url}", timeout=(10, 60), stream=throttle is not None
|
||||
) as r:
|
||||
r.raise_for_status()
|
||||
if throttle is None:
|
||||
return r.content
|
||||
buf = bytearray()
|
||||
for chunk in r.iter_content(chunk_size=262_144):
|
||||
throttle.take(len(chunk))
|
||||
buf.extend(chunk)
|
||||
return bytes(buf)
|
||||
|
||||
def is_reachable(self) -> bool:
|
||||
"""Cheap 'is curator responding at all right now?' check. Used to decide,
|
||||
|
||||
Reference in New Issue
Block a user