[Enhancement]Coordinate call leave cleanup through the Call state machine#1124
Conversation
📝 WalkthroughWalkthroughThe Changes
Sequence DiagramsequenceDiagram
participant Caller as Caller
participant Call as Call
participant StateMachine as StateMachine
participant LeavingStage as LeavingStage
participant CallController as CallController
participant StateManager as StreamVideo.State
participant Notifications as NotificationCenter
Caller->>Call: leave(reason:)
Call->>StateMachine: transition to .leaving(input)
StateMachine->>LeavingStage: initialize & execute()
LeavingStage->>CallController: leave(reason:)
LeavingStage->>StateManager: remove call from cache
LeavingStage->>StateManager: clear ringingCall/activeCall
LeavingStage->>StateMachine: transition to .idle
LeavingStage->>Notifications: post CallNotification.callEnded
Notifications->>Caller: notification received
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
SDK Size
|
StreamVideo XCSize
Show 28 more objects
|
d146b3d to
10f9a9b
Compare
Public Interface🚀 No changes affecting the public interface. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
StreamVideoTests/StreamVideo_Tests.swift (1)
111-111: Rename the new test using the given/when/then pattern.This test name is hard to scan and does not clearly follow the repository’s new-test naming convention.
Suggested rename
- func test_streamVideo_activeCallReplacement_postsCallEndedNotificationForPreviousCall() async throws { + func test_activeCallIsReplaced_whenPreviousCallIsLeft_thenPostsCallEnded() + async throws {As per coding guidelines,
**/*Tests.swift: “Name new test methods with the patterntest_<given>_<when>_<then>()” and**/*.swift: “Use 80 characters as the maximum line length”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@StreamVideoTests/StreamVideo_Tests.swift` at line 111, The test function test_streamVideo_activeCallReplacement_postsCallEndedNotificationForPreviousCall does not follow the repository’s test naming convention; rename it to the test_<given>_<when>_<then> pattern (use a given like "activeCall", a when like "replacement", and a then describing the expected outcome) and ensure the new method name stays within 80 characters, then update the test declaration and any references to that method accordingly (look for the function symbol test_streamVideo_activeCallReplacement_postsCallEndedNotificationForPreviousCall to locate the code to rename).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Sources/StreamVideo/CallStateMachine/Stages/Call`+LeavingStage.swift:
- Around line 41-43: The code in Call+LeavingStage.swift returns nil for
previousStage.id == .leaving which causes StreamStateMachine to treat repeated
leaves as invalid transitions; change the behavior so duplicate leave requests
are idempotent: either add a guard inside Call.leave(reason:) to no-op if
current stage is .leaving (check the current stage id and return early) or
modify the leaving stage handler to return the existing leaving stage instead of
nil (keep previousStage when previousStage.id == .leaving) so
StreamStateMachine.transition sees a valid no-op transition; reference:
previousStage.id, .leaving, StreamStateMachine.transition, and
Call.leave(reason:).
---
Nitpick comments:
In `@StreamVideoTests/StreamVideo_Tests.swift`:
- Line 111: The test function
test_streamVideo_activeCallReplacement_postsCallEndedNotificationForPreviousCall
does not follow the repository’s test naming convention; rename it to the
test_<given>_<when>_<then> pattern (use a given like "activeCall", a when like
"replacement", and a then describing the expected outcome) and ensure the new
method name stays within 80 characters, then update the test declaration and any
references to that method accordingly (look for the function symbol
test_streamVideo_activeCallReplacement_postsCallEndedNotificationForPreviousCall
to locate the code to rename).
🪄 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
Run ID: 22c8fa39-1f90-43e6-b503-e02f0c6b326f
📒 Files selected for processing (5)
Sources/StreamVideo/Call.swiftSources/StreamVideo/CallStateMachine/Stages/Call+LeavingStage.swiftSources/StreamVideo/CallStateMachine/Stages/Call+Stage.swiftStreamVideoTests/Call/Call_Tests.swiftStreamVideoTests/StreamVideo_Tests.swift
| switch previousStage.id { | ||
| case .leaving: | ||
| return nil |
There was a problem hiding this comment.
Avoid logging expected duplicate leaves as invalid transitions.
Returning nil here prevents duplicate cleanup, but StreamStateMachine logs nil transitions as InvalidStateMachineTransition. Since repeated leave requests are expected, guard before calling transition or add a silent idempotent path that preserves the current leaving stage.
One possible fix in Call.leave(reason:)
public func leave(reason: String? = nil) {
- stateMachine.transition(
- .leaving(
+ stateMachine.withLock { currentStage, transitionHandler in
+ guard currentStage.id != .leaving else {
+ return
+ }
+
+ transitionHandler(
+ .leaving(
.init(
call: self,
input: .leaving(
@@
- reason: reason
+ reason: reason
+ )
)
)
- )
+ }
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Sources/StreamVideo/CallStateMachine/Stages/Call`+LeavingStage.swift around
lines 41 - 43, The code in Call+LeavingStage.swift returns nil for
previousStage.id == .leaving which causes StreamStateMachine to treat repeated
leaves as invalid transitions; change the behavior so duplicate leave requests
are idempotent: either add a guard inside Call.leave(reason:) to no-op if
current stage is .leaving (check the current stage id and return early) or
modify the leaving stage handler to return the existing leaving stage instead of
nil (keep previousStage when previousStage.id == .leaving) so
StreamStateMachine.transition sees a valid no-op transition; reference:
previousStage.id, .leaving, StreamStateMachine.transition, and
Call.leave(reason:).
|



🔗 Issue Links
N/A
🎯 Goal
Ensure repeated
Call.leave()requests share a single teardown path and make the leave lifecycle safer and easier to reason about.📝 Summary
.leavingstage in theCallstate machineCall.leave()cleanup into the new stage so repeated leave requests are idempotentactiveCall/ringingCallcleanup andcallEndednotification ordering explicitleave()doc comment to describe the cleanup ordering🛠 Implementation
This PR adds a new
LeavingStageto theCall.StateMachineand routesCall.leave()through it instead of performing teardown inline.The stage now owns the leave flow:
CallControllerStreamVideo.State.activeCall/ringingCallCallNotification.callEnded.idleThe repeated-leave case is handled by rejecting
.leaving -> .leavingtransitions, so UI, CallKit, or fallback leave triggers reuse a single cleanup path without double-invoking the controller.Test coverage was added for:
leave()calls only invoke the controller onceactiveCallstill postscallEndedfor the previous call🎨 Showcase
N/A
🧪 Manual Testing Notes
Call_Tests.test_leave_withReason_reasonWasPassedToCallControllerCall_Tests.test_leave_whenCalledRepeatedly_callsCallControllerOnlyOnceStreamVideo_Tests.test_streamVideo_callEndedNotificationIsPostedAfterCleanupStreamVideo_Tests.test_streamVideo_activeCallReplacement_postsCallEndedNotificationForPreviousCallCallNotification.callEnded☑️ Contributor Checklist
Summary by CodeRabbit
Refactor
Bug Fixes
Tests