Skip to content

Resource hardening for small hosts colocated with TS6 - #19

Merged
queueeee merged 1 commit into
mainfrom
claude/review-claude-md-iOuUt
May 1, 2026
Merged

queueeee merged 1 commit into
mainfrom
claude/review-claude-md-iOuUt

Conversation

@queueeee

@queueeee queueeee commented May 1, 2026

Copy link
Copy Markdown
Owner

Symptom

On a 4 GB host running the bot next to its TS6 server (loopback connect), the operator reports:

der Bot crasht jedes mal meine TeamSpeak instanz und reliable komme ich auch nicht in den Stream rein

The live log showed why: one viewer at 720p30 / 4608 kbps pushed the bot's RSS past 750 MB and was still climbing. The TS6 server sharing the same 4 GB then started dropping forwarded notifyjoinstreamrequest packets and eventually crashed under memory pressure.

Changes

Lower the encoder defaults (SCREEN_WIDTH=854, SCREEN_HEIGHT=480, SCREEN_FPS=24, STREAM_BITRATE=2500). The reference-frame pool that dominates per-viewer RSS scales roughly with pixels × fps; cutting both halves the per-viewer cost to an expected ~250 MB. Operators who measured headroom can bump it back up in .env.

STREAM_VIEWER_LIMIT default 2 (was 4). With ~250 MB per viewer, two viewers + the TS6 server + the OS fits in 4 GB; four did not.

New ICE_DROP_NETWORKS setting (default 172.16.0.0/12). In network_mode: host, aiortc gathers a host candidate per docker bridge gateway and a matching srflx for each, blowing the offer SDP to ~5 KB / 11 UDP fragments. The TS6 server forwards those fine for tiny offers but visibly struggles with the fragmented case (and our live trace had respondjoinstreamrequest fragments=11 total_bytes=4997). The filter strips bridge-gateway candidates before send_join_response; aiortc keeps them in its own ICE pool, we just don't burden the server with addresses no external viewer can route to anyway.

Test plan

  • pytest — 234 passed, 1 skipped (7 new tests for _filter_sdp_candidates covering: drop network match, non-candidate line preservation, CRLF preservation, empty list passthrough, invalid CIDR safe-skip, no-match passthrough, IPv6 networks).
  • mypy --strict clean (37 files).
  • ruff check clean.
  • Live: with STREAM_VIEWER_LIMIT=2 and SCREEN_HEIGHT=480 in .env, two viewers should stay under ~700 MB combined, the TS6 server should remain alive across multiple join cycles, and stream_publisher.ice_candidates_filtered should appear in the log on each join showing the dropped 172.x candidates.

Operator notes

After pulling and rebuilding, you have two choices for the encoder settings:

  1. Adopt the new defaults — delete SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_FPS, STREAM_BITRATE, STREAM_VIEWER_LIMIT lines from your .env. The bot will pick up the lower defaults.
  2. Keep your current .env — the STREAM_VIEWER_LIMIT=-1 etc. you set previously will still apply. Then this PR only fixes the SDP filter; for the memory issue you'd need to manually set the new lower values.

Either way after pulling:

cd /opt/ts6-stream-bot
git pull
docker compose -f docker-compose.yml -f docker-compose.host.yml down
docker compose -f docker-compose.yml -f docker-compose.host.yml up -d --build
docker compose -f docker-compose.yml -f docker-compose.host.yml logs -f

https://claude.ai/code/session_016DuCjRJK995Tj9aDhhB9at


Generated by Claude Code

Live measurement on a 4 GB host running the bot next to its TS6
server: one viewer at 720p30 / 4608 kbps pushed the bot's RSS past
750 MB and was still climbing. The TS6 server, sharing the same
RAM, started dropping packets - including viewer join requests -
and eventually crashed under memory pressure, taking the whole
stack with it. Operators reported "I can't reliably get into the
stream and the server crashes".

Three changes that together keep the colocated case stable:

* Encoder defaults dropped to 480p24 / 2500 kbps (from 720p30 /
  4608 kbps). Cuts libvpx's reference frame pool roughly in half;
  expected ~250 MB per viewer instead of ~750 MB. Operators with
  a beefier host bump SCREEN_WIDTH/HEIGHT + STREAM_BITRATE
  together in .env.

* `STREAM_VIEWER_LIMIT` default 2 (from 4). With the lower defaults
  that's still 600-700 MB peak for the bot, leaving room for the
  TS6 server + OS on a 4 GB box.

* New `ICE_DROP_NETWORKS` setting (default `172.16.0.0/12`). In
  host-network mode aiortc gathers a host candidate per docker
  bridge gateway plus matching srflx, blowing the offer SDP to
  5 KB / 11 UDP fragments that some TS6 server builds choke on.
  We strip those before send_join_response - aiortc keeps them
  internally, we just don't burden the server with what no
  external viewer can route to anyway.
@queueeee
queueeee merged commit ab2cad2 into main May 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants