Skip to content

Conversation

kayush0712
Copy link
Contributor

@kayush0712 kayush0712 commented Sep 30, 2025

Summary by CodeRabbit

  • New Features
    • Added a timeout parameter to stop(), enabling graceful, time-bounded shutdowns.
  • Bug Fixes
    • Treats WebRTC “disconnected” as a terminal state to ensure timely session shutdown.
    • Ensures shutdown callbacks run reliably after polling completes.
    • Prevents errors by avoiding close operations on already-closed connections and adds safer error-handled closing.
    • Avoids deadlocks by skipping thread self-join and cleaning up references after stopping.
    • Improves resource cleanup by explicitly stopping background observers and processors.
    • Provides clearer shutdown-related log messages.

Copy link

coderabbitai bot commented Sep 30, 2025

Walkthrough

Refactors SessionShutdownObserver to store a callback, revise polling/stop behavior, and add timeout-aware stopping. Updates WebRTC worker to treat "disconnected" as terminal, guard peer-connection close, stop processors, and explicitly stop and clear the shutdown observer during shutdown.

Changes

Cohort / File(s) Summary
Shutdown observer refactor
`streamlit_webrtc/shutdown.py`
Store callback as `self._callback`; remove passing callback to polling thread; update log texts; ensure stop flag set and callback invoked after loop with error handling; add `stop(timeout: float = 1.0)` with join/timeout behavior and thread-origin guard; clear `polling_thread` after stopping.
WebRTC terminal state & shutdown path
`streamlit_webrtc/webrtc.py`
Treat ICE "disconnected" as terminal like "failed"/"closed"; trigger shutdown, stop processors; guard peer-connection close (skip if already closed; try/except and event-loop aware); enhance `stop()` to unset processors, close safely; explicitly stop and null shutdown observer.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant ICE as ICE State Monitor
  participant PC as RTCPeerConnection
  participant WRK as WebRTC Worker
  participant PROC as Media Processors
  participant OBS as SessionShutdownObserver

  ICE->>WRK: onIceStateChange(state)
  alt state in {failed, closed, disconnected}
    WRK->>PROC: stop()
    WRK->>WRK: stop()
    opt close peer connection
      WRK->>PC: close() (guarded, try/except)
    end
    WRK->>OBS: stop(timeout)
    WRK->>WRK: clear OBS reference
  else other states
    WRK->>WRK: no-op
  end
Loading
sequenceDiagram
  autonumber
  participant OBS as SessionShutdownObserver
  participant TH as Polling Thread
  participant APP as AppSession Registry
  participant CB as Stored Callback

  OBS->>OBS: __init__(callback)->store as _callback
  OBS->>TH: start polling
  loop until stop flag
    TH->>APP: check session presence/requested shutdown
    alt removed or requested shutdown
      TH->>TH: break loop
    else present
      TH->>TH: sleep/poll
    end
  end
  TH->>OBS: set stop flag
  TH->>CB: invoke _callback() (try/except)
  OBS->>TH: stop(timeout) (if external caller)
  alt join within timeout
    OBS->>OBS: clear polling_thread
  else timeout
    OBS->>OBS: warn, clear polling_thread
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A whisker-twitch, a hop, a glide—
I watch the signals ebb and tide.
When ICE goes cold, I call it done,
I tuck the threads in one by one.
With gentle paws, I press “stop” true—
Then bound away to build anew. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly describes the primary change of addressing a thread leak by fixing shutdown and stop logic for polling threads and references the related issue number, making it both specific and aligned with the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b5a274 and 6d6a00d.

⛔ Files ignored due to path filters (1)
  • streamlit_webrtc/frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • streamlit_webrtc/shutdown.py (2 hunks)
  • streamlit_webrtc/webrtc.py (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • streamlit_webrtc/webrtc.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Ruff for Python formatting and linting (ruff format; ruff check --fix)
Run static type checking with mypy for Python code

Files:

  • streamlit_webrtc/shutdown.py
🧬 Code graph analysis (1)
streamlit_webrtc/shutdown.py (1)
streamlit_webrtc/webrtc.py (1)
  • stop (754-773)
🔇 Additional comments (3)
streamlit_webrtc/shutdown.py (3)

18-21: LGTM! Cleaner callback management.

Storing the callback as an instance variable simplifies the threading setup and makes the code more maintainable. Thread-safe since it's set once during initialization and only read from the polling thread.


36-62: LGTM! Well-structured polling logic with proper error handling.

The refactored implementation correctly:

  • Removes the callback parameter from the method signature, consistent with storing it as an instance variable
  • Distinguishes between natural shutdown detection (break → callback invoked) and explicit stop request (return → no callback)
  • Includes exception handling to prevent callback errors from crashing the polling thread

64-78: Excellent fix for the thread leak with proper deadlock prevention.

The implementation correctly addresses the thread leak issue (#2143) by:

  • Providing a clean shutdown mechanism with configurable timeout
  • Preventing deadlock when stop() is called from within the callback (line 69 check)
  • Logging outcomes for debugging (clean stop vs. timeout)

The 1.0-second default timeout aligns well with the polling interval, ensuring the thread responds quickly to the stop signal. Clearing the thread reference (line 78) even in timeout cases is acceptable since the thread is daemon (line 32).


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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
streamlit_webrtc/shutdown.py (1)

66-68: Consider removing redundant is_alive() check.

The is_alive() check before join() is generally safe but potentially introduces a TOCTOU (time-of-check to time-of-use) race. The thread could terminate between the is_alive() check and the join() call. However, calling join() on an already-finished thread is harmless and returns immediately, so this check may be unnecessary.

Consider simplifying to:

-        if self._polling_thread.is_alive():
-            try:
-                self._polling_thread.join(timeout=timeout)
-            except Exception:
-                logger.exception("Failed to join ShutdownPolling thread")
+        try:
+            self._polling_thread.join(timeout=timeout)
+        except Exception:
+            logger.exception("Failed to join ShutdownPolling thread")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57cda0b and 9258295.

📒 Files selected for processing (2)
  • streamlit_webrtc/shutdown.py (1 hunks)
  • streamlit_webrtc/webrtc.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Ruff for Python formatting and linting (ruff format; ruff check --fix)
Run static type checking with mypy for Python code

Files:

  • streamlit_webrtc/webrtc.py
  • streamlit_webrtc/shutdown.py
🧬 Code graph analysis (2)
streamlit_webrtc/webrtc.py (6)
streamlit_webrtc/shutdown.py (1)
  • stop (57-72)
streamlit_webrtc/process.py (2)
  • stop (44-48)
  • stop (211-219)
streamlit_webrtc/mix.py (1)
  • stop (194-202)
streamlit_webrtc/receive.py (1)
  • stop (46-49)
streamlit_webrtc/frontend/src/webrtc/index.ts (2)
  • state (50-85)
  • console (176-187)
streamlit_webrtc/frontend/src/webrtc/reducer.ts (1)
  • state (20-95)
streamlit_webrtc/shutdown.py (1)
streamlit_webrtc/webrtc.py (1)
  • stop (744-759)
🪛 Flake8 (7.2.0)
streamlit_webrtc/webrtc.py

[error] 547-547: indentation is not a multiple of 4

(E111)


[error] 547-547: over-indented

(E117)


[error] 548-548: indentation is not a multiple of 4

(E111)

🔇 Additional comments (2)
streamlit_webrtc/shutdown.py (2)

57-72: LGTM! Clean thread shutdown implementation.

The updated stop() method correctly implements bounded shutdown to prevent thread leaks:

  • Guards against None _polling_thread
  • Sets the stop event before joining
  • Uses a timeout to prevent indefinite blocking
  • Logs exceptions during join
  • Clears the thread reference

This addresses the thread leak issue mentioned in PR #2143.


44-54: Enhanced log messages improve debuggability.

The updated log messages provide clearer context for debugging shutdown behavior. Good improvement for observability.

@kayush0712
Copy link
Contributor Author

can you add hacktoberfest label to it please

@kayush0712
Copy link
Contributor Author

hey @whitphx i did ruff check now can you please approve it with hacktoberfest label
thank you

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
streamlit_webrtc/webrtc.py (1)

543-558: Don’t treat “disconnected” as terminal; it can recover.

Remove “disconnected” from the terminal set (keep “failed”, “closed”), or gate shutdown behind a grace timer.

-            if ice_state in ("disconnected", "failed", "closed"):
+            if ice_state in ("failed", "closed"):
                 logger.debug("ICE state=%s -> stopping WebRTC worker", ice_state)

If you need a fallback, consider starting a timer on “disconnected” and stopping only if it remains non‑connected for N seconds.

🧹 Nitpick comments (2)
streamlit_webrtc/shutdown.py (1)

18-22: Break potential retain cycle; clear callback after use.

Store the callback as Optional and null it after invocation to avoid lingering bound-method references.

-from typing import Callable, Union
+from typing import Callable, Optional, Union
@@
-    def __init__(self, callback: Callback) -> None:
+    def __init__(self, callback: Callback) -> None:
@@
-        self._callback = callback
+        self._callback: Optional[Callback] = callback
@@
-        logger.debug("AppSession shutdown detected, executing callback.")
-        try:
-            self._callback()
-        except Exception as e:
-            logger.exception("Error in shutdown callback: %s", e)
+        logger.debug("AppSession shutdown detected, executing callback.")
+        cb = self._callback
+        self._callback = None
+        if cb:
+            try:
+                cb()
+            except Exception:
+                logger.exception("Error in shutdown callback")

As per coding guidelines (mypy): Optional typing improves safety.

Also applies to: 58-63

streamlit_webrtc/webrtc.py (1)

768-772: Propagate timeout to shutdown observer for consistency.

Use the same timeout passed to WebRtcWorker.stop() to bound the observer’s join as well.

-        if self._session_shutdown_observer:
-            self._session_shutdown_observer.stop()
+        if self._session_shutdown_observer:
+            self._session_shutdown_observer.stop(timeout=timeout if timeout is not None else 1.0)
             self._session_shutdown_observer = None
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9258295 and 9a653f8.

📒 Files selected for processing (2)
  • streamlit_webrtc/shutdown.py (2 hunks)
  • streamlit_webrtc/webrtc.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Ruff for Python formatting and linting (ruff format; ruff check --fix)
Run static type checking with mypy for Python code

Files:

  • streamlit_webrtc/webrtc.py
  • streamlit_webrtc/shutdown.py
🧬 Code graph analysis (2)
streamlit_webrtc/webrtc.py (5)
streamlit_webrtc/shutdown.py (1)
  • stop (64-78)
streamlit_webrtc/mix.py (1)
  • stop (194-202)
streamlit_webrtc/process.py (2)
  • stop (44-48)
  • stop (211-219)
streamlit_webrtc/receive.py (1)
  • stop (46-49)
streamlit_webrtc/frontend/src/webrtc/index.ts (3)
  • state (50-85)
  • console (176-187)
  • pc (80-83)
streamlit_webrtc/shutdown.py (1)
streamlit_webrtc/webrtc.py (2)
  • callback (583-592)
  • stop (752-771)
🔇 Additional comments (1)
streamlit_webrtc/shutdown.py (1)

68-77: Good: avoid self-join deadlock.

Skipping join when called from the polling thread itself prevents deadlock and hangs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
streamlit_webrtc/webrtc.py (1)

546-546: "disconnected" is a transient ICE state, not terminal.

Per WebRTC spec, "disconnected" indicates temporary connectivity loss and may recover, unlike "failed" and "closed" which are terminal. Treating it as terminal can cause premature shutdowns.

Based on past review and WebRTC best practices.

Apply this diff to remove "disconnected" from terminal states:

-            if ice_state in ("failed", "closed", "disconnected"):
+            if ice_state in ("failed", "closed"):
                 logger.debug("ICE state=%s -> scheduling stop of WebRTC worker", ice_state)
🧹 Nitpick comments (2)
streamlit_webrtc/webrtc.py (2)

554-554: Remove redundant exception formatting.

logger.exception() automatically includes the stack trace via exc_info=True, so the %s, e is redundant and could cause confusion.

Apply this diff:

-                    logger.exception("Error while stopping WebRTC worker: %s", e)
+                    logger.exception("Error while stopping WebRTC worker")

765-768: Pass timeout parameter to shutdown observer for consistency.

The stop() method accepts a timeout parameter but doesn't forward it to _session_shutdown_observer.stop(), which also accepts a timeout. For consistency and to respect the caller's timeout intent, pass the timeout through.

Apply this diff:

         # 💡 Explicitly stop shutdown observer here
         if self._session_shutdown_observer:
-            self._session_shutdown_observer.stop()
+            self._session_shutdown_observer.stop(timeout=timeout if timeout else 1.0)
             self._session_shutdown_observer = None
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a653f8 and 5cac8de.

📒 Files selected for processing (1)
  • streamlit_webrtc/webrtc.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Ruff for Python formatting and linting (ruff format; ruff check --fix)
Run static type checking with mypy for Python code

Files:

  • streamlit_webrtc/webrtc.py
🧬 Code graph analysis (1)
streamlit_webrtc/webrtc.py (4)
streamlit_webrtc/shutdown.py (1)
  • stop (64-78)
streamlit_webrtc/mix.py (1)
  • stop (194-202)
streamlit_webrtc/process.py (2)
  • stop (44-48)
  • stop (211-219)
streamlit_webrtc/receive.py (1)
  • stop (46-49)
🔇 Additional comments (1)
streamlit_webrtc/webrtc.py (1)

750-750: Good addition for debugging.

The debug log at the start of stop() helps trace the shutdown flow.

@whitphx
Copy link
Owner

whitphx commented Oct 3, 2025

Thanks, this repo has already tagged with a repo-wide "hacktoberfest" topic so this should be counted as a contribution when merged.
Just let me take time to review.

Copy link
Owner

@whitphx whitphx left a comment

Choose a reason for hiding this comment

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

LGTM!

Plz just run make format to apply formatting, thanks

self._polling_thread.join()

# 🔑 FIX: do not join current thread
if threading.current_thread() is not self._polling_thread:
Copy link
Owner

Choose a reason for hiding this comment

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

Good catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done!
kindly merge it
thank you so much for giving my first open source project

Copy link
Owner

Choose a reason for hiding this comment

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

I meant to run make format and commit the formatted code to this PR so that the CI passes. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i did it

Copy link
Owner

Choose a reason for hiding this comment

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

but there is no new commit in this PR and the CI still fails 🤔

@kayush0712
Copy link
Contributor Author

is it okay now?

@whitphx
Copy link
Owner

whitphx commented Oct 4, 2025

Plz don't add unnecessary changes on the frontend code...
Are you using AI?
You can see what fails on the CI from the log and it's all about Ruff errors.

@kayush0712
Copy link
Contributor Author

sorry for this i am very new for all of this . i am trying to learn

@kayush0712
Copy link
Contributor Author

hey @whitphx can you check it now

@kayush0712
Copy link
Contributor Author

kayush0712 commented Oct 5, 2025

hey @whitphx sorry to ping you but is it okay now ?

@whitphx
Copy link
Owner

whitphx commented Oct 5, 2025

@kayush0712 Thanks, all tests passed! (The remaining CI error is a problem on our side)
Will merge this PR 👍
Thanks for your contribution!

@whitphx whitphx merged commit 8f43cc8 into whitphx:main Oct 5, 2025
22 of 23 checks 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