Skip to content

fix: codec enforcement, publisher stats, stream deletion, and thread shutdown#26

Merged
AlexanderWagnerDev merged 6 commits into
mainfrom
claude/openrtmp-bugs-risks-amf3n6
Jun 30, 2026
Merged

fix: codec enforcement, publisher stats, stream deletion, and thread shutdown#26
AlexanderWagnerDev merged 6 commits into
mainfrom
claude/openrtmp-bugs-risks-amf3n6

Conversation

@AlexanderWagnerDev

@AlexanderWagnerDev AlexanderWagnerDev commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix 2 — allowed_codecs not enforced: ConnState in rtmp_bridge.rs now stores allowed_codecs and stream_id from the DB stream row at on_publish time. on_frame() checks the incoming codec against the comma-separated list (case-insensitive) and returns false to kick the connection if the codec is not permitted. The poll loop in server.rs reads detected_video_codec / detected_audio_codec from librtmp2::session::Conn and calls on_frame() with the detected codec.
  • Fix 3 (double-lock bug): on_play() previously locked self.conns twice sequentially, which deadlocks a non-reentrant Mutex. Fixed by using a single lock guard for both the player insertion and the stream_id assignment.
  • Fix 4 — Publisher stats incomplete: Added update_publisher_stats() to DbRtmpBridge. It calculates bitrate_kbps from bytes delta over elapsed time, rate-limits DB writes to once per second, and updates video_codec / audio_codec / bytes_in in the publisher row. The RTMP poll loop calls this after each poll iteration for publishing connections.
  • Fix 5 — Stream deletion doesn't close active RTMP sessions / thread not joined: Added Arc<Mutex<HashSet<String>>> deleted_streams shared between AppState and the RTMP background thread. handle_stream_delete() inserts the stream ID into the set; the poll loop evicts any connection whose stream_id appears in it. Added Arc<AtomicBool> shutdown signal and JoinHandle::join() so the RTMP thread exits cleanly when the HTTP server shuts down.
  • Cargo.toml: Points librtmp2 dependency at claude/openrtmp-bugs-risks-amf3n6 branch which adds detected_video_codec, detected_audio_codec, and on_connect_cb to Conn, and fixes the TLS transport.

Test plan

  • cargo +stable test — all 28 tests pass
  • cargo +stable build — no errors
  • Publish stream with disallowed codec → connection is closed within one poll cycle
  • /stats endpoint shows non-zero bitrate_kbps and video_codec for a live publisher
  • DELETE /api/v1/streams/:id while a publisher is live → publisher connection dropped within 50 ms
  • SIGTERM → RTMP thread joins cleanly (no zombie thread or fd leak)

Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added coordinated stream-deletion tracking so RTMP connections are evicted when streams are removed.
    • Introduced per-connection codec allow-list enforcement and improved publisher stats reporting (including bitrate updates with rate limiting).
  • Bug Fixes
    • Prevented RTMP publishers/frames using disallowed codecs from being accepted.
    • Improved shutdown and cleanup so streaming stops cleanly after HTTP shutdown.
  • Chores
    • Updated the librtmp2 dependency to use a different git branch.

…down

- rtmp_bridge.rs: Add stream_id and allowed_codecs fields to ConnState.
  on_publish() stores them from the DB stream row. on_frame() enforces
  allowed_codecs list (comma-separated, case-insensitive) and returns
  false to kick connections sending a disallowed codec. Fix double-lock
  bug in on_play() where two sequential Mutex::lock() calls on a
  non-reentrant mutex would deadlock. Add update_publisher_stats() that
  rate-limits DB writes to 1/second and calculates bitrate from bytes
  delta. Add stream_id_for_conn() accessor used by the poll loop.

- server.rs: Thread shutdown via Arc<AtomicBool> + JoinHandle::join()
  after axum::serve completes. Share Arc<Mutex<HashSet<String>>>
  deleted_streams between AppState and the RTMP poll loop. TrackedConn
  gains stream_id, video_codec, audio_codec fields. Per-poll: pick up
  detected codecs from Conn fields, call rtmp_bridge.on_frame() for
  codec enforcement, call update_publisher_stats() for active publishers,
  kick connections whose stream_id appears in the deleted set.

- http.rs: Add deleted_streams Arc<Mutex<HashSet<String>>> to AppState.
  handle_stream_delete() inserts the stream ID into deleted_streams after
  a successful DB delete so the RTMP thread evicts live connections.

- Cargo.toml: Point librtmp2 dependency at the feature branch that
  contains the new Conn fields (detected_video_codec, detected_audio_codec,
  on_connect_cb) and the fixed Transport/TLS implementation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcC3LrUD3aeyyHpa9r7AuE
Copilot AI review requested due to automatic review settings June 30, 2026 17:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@AlexanderWagnerDev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e5e2782-5ad9-4220-8bc0-18080137b3f8

📥 Commits

Reviewing files that changed from the base of the PR and between 2e93de1 and 0c009f5.

📒 Files selected for processing (1)
  • src/rtmp_bridge.rs
📝 Walkthrough

Walkthrough

Adds shared stream-deletion signaling between HTTP and RTMP, enforces per-connection codec allow-lists, tracks stream and codec state per RTMP connection, rate-limits publisher stats updates, coordinates RTMP thread shutdown, and pins the librtmp2 branch.

Changes

Stream eviction and codec enforcement

Layer / File(s) Summary
Shared deletion state and HTTP signaling
src/http.rs, src/server.rs
AppState and ServerApp gain deleted_streams: Arc<Mutex<HashSet<String>>>. The HTTP delete handler records deleted stream IDs, ServerApp::create initializes the set, ServerApp::run wires it into AppState, and the test state initializes it too.
RTMP bridge state and codec policy
src/rtmp_bridge.rs
RtmpEventHandler::on_frame now returns bool and takes conn. Per-connection state stores stream_id, allowed_codecs, and stats fields. on_publish and on_play populate stream_id and allowed_codecs, and on_frame rejects codecs not in the stored allow-list. Tests cover acceptance and rejection.
Bridge accessors and publisher stats
src/rtmp_bridge.rs
Adds stream_id_for_conn, allowed_codecs_for_conn, and update_publisher_stats. Publisher stats updates use elapsed time and byte deltas before writing back to the DB.
RTMP poll loop and shutdown
src/server.rs
The RTMP thread tracks stream_id and codec state per connection, snapshots deleted_streams into deleted_now, rejects unauthorized or codec-disallowed publishers, kicks connections whose stream_id was deleted, updates publisher stats, prunes stale deletion markers, and stops via rtmp_stop before joining on shutdown.
librtmp2 branch pin
Cargo.toml
The librtmp2 git dependency branch changes from main to claude/openrtmp-bugs-risks-amf3n6.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇 I hop through the streams with a twitchy nose,
When one gets deleted, the bright alert glows.
Codec bunnies bounce if their tune is amiss,
But stats keep a beat in the DB’s quiet hiss.
Then the RTMP burrow shuts down with a sigh,
And I nibble my carrots as the cleanup goes by.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main fixes in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/openrtmp-bugs-risks-amf3n6

Comment @coderabbitai help to get the list of available commands.

- Reorder imports alphabetically (atomic before sync)
- Collapse deleted_now chain onto two lines per rustfmt line-length rule
- Collapse single-arg log_warn! onto one line

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcC3LrUD3aeyyHpa9r7AuE

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/server.rs (1)

332-340: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Always stop and join the RTMP thread on HTTP server errors.

The ? on Line 335 returns before Lines 338-340, so an HTTP serve error skips the new RTMP shutdown path and leaves the background thread running.

Proposed shutdown ordering
-        axum::serve(http_listener, app)
+        let http_result = axum::serve(http_listener, app)
             .with_graceful_shutdown(shutdown_signal())
             .await
-            .map_err(|e| format!("HTTP server error: {e}"))?;
+            .map_err(|e| format!("HTTP server error: {e}"));
 
         crate::log_info!("Shutting down...");
         rtmp_stop.store(true, Ordering::Relaxed);
         let _ = rtmp_thread.join();
         crate::log_info!("RTMP thread joined.");
+        http_result?;
         Ok(())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server.rs` around lines 332 - 340, Ensure the RTMP shutdown path in the
HTTP server startup flow always runs, even when axum::serve in the server
entrypoint returns an error. In the code around the serve call and the
subsequent shutdown steps, avoid using ? directly on the result before the RTMP
stop/join logic; instead, capture the error from axum::serve, run the existing
rtmp_stop.store and rtmp_thread.join cleanup in all cases, then return or
propagate the HTTP server error afterward. Reference the server startup/shutdown
flow in src::server and the shutdown_signal/rtmp_thread handling so the
background thread cannot be left running on HTTP failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server.rs`:
- Around line 183-185: The deleted-stream tracking in `src/server.rs` is only
being snapshotted in the poll loop and never cleared, so processed IDs remain
forever and can affect recreated streams. Update the logic around the
`deleted_streams` snapshot and the connection rejection/removal paths to drain
or remove the IDs once they have been handled, using the existing
`deleted_streams` set and the related poll-loop code that checks `deleted_now`
and closes rejected connections. Keep the removal tied to the point where stale
connections are actually dropped so each deletion marker is processed once.
- Around line 170-178: The shutdown path in the RTMP poll loop skips cleanup, so
active entries in tracked remain open in the DB. Update the poll-thread logic in
server.rs to invoke rtmp_bridge.on_close for every still-tracked connection
before breaking when rtmp_stop_clone is set, using the existing tracked
collection and rtmp_bridge handle in the poll loop. Ensure the cleanup runs on
the stop branch before server.stop() exits the loop, and keep the existing
server.poll error handling unchanged.
- Around line 273-286: The codec enforcement in the publishing path only checks
entry.video_codec and skips entry.audio_codec, so disallowed audio can still
pass through. Update the codec-policy block in server::handle_publish (or the
surrounding loop that builds FrameInfo and calls rtmp_bridge.on_frame) to
perform the same allow-list check for audio as for video, using
entry.audio_codec and FrameKind::Audio, and keep the existing
reject_indices/continue flow when on_frame returns false.

---

Outside diff comments:
In `@src/server.rs`:
- Around line 332-340: Ensure the RTMP shutdown path in the HTTP server startup
flow always runs, even when axum::serve in the server entrypoint returns an
error. In the code around the serve call and the subsequent shutdown steps,
avoid using ? directly on the result before the RTMP stop/join logic; instead,
capture the error from axum::serve, run the existing rtmp_stop.store and
rtmp_thread.join cleanup in all cases, then return or propagate the HTTP server
error afterward. Reference the server startup/shutdown flow in src::server and
the shutdown_signal/rtmp_thread handling so the background thread cannot be left
running on HTTP failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cc086a34-52ea-4a9e-aec7-1843b7d0cbb1

📥 Commits

Reviewing files that changed from the base of the PR and between ebed705 and 5554244.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • Cargo.toml
  • src/http.rs
  • src/rtmp_bridge.rs
  • src/server.rs

Comment thread src/server.rs
Comment thread src/server.rs
Comment thread src/server.rs Outdated
- Capture axum::serve result before stopping/joining the RTMP thread so
  HTTP server errors still trigger a clean shutdown instead of leaving
  the background thread running indefinitely.
- After the poll loop exits (stop signal or poll error), call on_close
  for every connection still in the tracked map so the bridge can
  deactivate publishers and players in the database.
- Drain deleted_streams after each eviction cycle: retain only IDs that
  are still referenced by a live connection, preventing the set from
  growing without bound across many create/delete cycles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcC3LrUD3aeyyHpa9r7AuE

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2719884c4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/http.rs
crate::log_info!("Stream deleted: {id}");
// Signal the RTMP poll loop to evict any active connections for
// this stream. The set is drained once no connections remain.
state.deleted_streams.lock().unwrap().insert(id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear deleted stream tombstones before accepting reused IDs

When an operator deletes a stream and later recreates the same stream id, this insertion leaves a permanent tombstone: the RTMP loop only snapshots deleted_streams and never removes ids from it. As soon as the new publisher/player is accepted and entry.stream_id is set, the deletion check in server.rs still matches the old id and immediately kicks every connection for the recreated stream.

Useful? React with 👍 / 👎.

Comment thread src/server.rs Outdated
}

// Enforce codec policy via the bridge (which checks allowed_codecs).
if !entry.video_codec.is_empty() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce the detected audio codec too

For streams whose policy excludes an audio codec, or for audio-only publishers, this branch never checks entry.audio_codec even though detected_audio_codec was read above. That means a publisher can use a disallowed audio codec such as AAC/mp4a and remain connected as long as the video codec is allowed, so allowed_codecs is only partially enforced.

Useful? React with 👍 / 👎.

claude added 3 commits June 30, 2026 17:38
Picks up the transport.rs, client/mod.rs, and session/conn.rs fixes
pushed to librtmp2 claude/openrtmp-bugs-risks-amf3n6.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcC3LrUD3aeyyHpa9r7AuE
Adds /// doc lines to DbRtmpBridge::new and http::router, which were
the only public functions lacking documentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcC3LrUD3aeyyHpa9r7AuE
Fills the remaining gaps in public-API docstring coverage for the
RtmpEventHandler trait.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcC3LrUD3aeyyHpa9r7AuE
@AlexanderWagnerDev AlexanderWagnerDev merged commit a3843e6 into main Jun 30, 2026
2 checks passed
@AlexanderWagnerDev AlexanderWagnerDev deleted the claude/openrtmp-bugs-risks-amf3n6 branch June 30, 2026 18:30
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.

3 participants