-
Notifications
You must be signed in to change notification settings - Fork 408
Fix thread safety for MainActor property access in MJPEG stream callbacks #4216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix thread safety for MainActor property access in MJPEG stream callbacks #4216
Conversation
Co-authored-by: bgoncal <[email protected]>
Co-authored-by: bgoncal <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR addresses thread safety issues in the CameraMJPEGPlayerViewModel class by ensuring all MainActor-isolated property accesses from background thread callbacks are wrapped in DispatchQueue.main.async. The changes prevent race conditions and potential crashes when accessing @MainActor properties from non-main threads.
Changes:
- Wrapped
startStream()call inDispatchQueue.main.asyncto ensure MainActor-isolated method is called on main thread - Wrapped
errorMessageproperty assignment inDispatchQueue.main.asyncin the error rejection case - Removed redundant
hasStarted = falseassignment from the success path
Comments suppressed due to low confidence (1)
Sources/App/Cameras/CameraPlayer/MJPEG/CameraMJPEGPlayerViewModel.swift:55
- The cleanup block at lines 52-55 sets
hasStarted = falseimmediately after the.pipecallback completes, but thestartStream()call on line 40 is now asynchronous. This creates a race condition wherehasStartedcould be reset tofalsebeforestartStream()even executes on the main thread. This means a subsequent call tostart()could pass the guard check on line 25 and create duplicate streams.
Consider moving the cleanup logic into the completion handlers for both success and error cases, or use a different synchronization mechanism to ensure hasStarted is only reset after the stream has actually started or definitively failed.
DispatchQueue.main.async { [weak self] in
self?.isLoading = false
self?.hasStarted = false
}
| self?.startStream(url, api: api) | ||
| } | ||
| } else { | ||
| Current.Log.error("Failed to get active URL for server \(api.server.info.name)") |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the error path where the active URL cannot be obtained, the code logs an error but doesn't update the UI state. The isLoading flag remains true and hasStarted remains true (until the cleanup block executes), leaving the UI in an inconsistent loading state. Consider setting appropriate error state here similar to the rejected case.
| Current.Log.error("Failed to get active URL for server \(api.server.info.name)") | |
| Current.Log.error("Failed to get active URL for server \(api.server.info.name)") | |
| DispatchQueue.main.async { [weak self] in | |
| self?.errorMessage = StreamError.unableToConnect.localizedDescription | |
| } |
Summary
The
CameraMJPEGPlayerViewModelclass is marked@MainActorbut was accessing MainActor-isolated properties from background thread callbacks without proper synchronization. This creates race conditions and potential crashes.Changes:
startStream()method call inDispatchQueue.main.asyncwhen invoked from background.pipecallbackerrorMessageproperty assignment inDispatchQueue.main.asyncin the rejected casehasStarted = falseassignment that could race with the cleanup blockAll MainActor-isolated members are now accessed exclusively on the main thread.
Screenshots
N/A - Internal thread safety fix with no UI changes
Link to pull request in Documentation repository
N/A - Internal implementation change
Any other notes
This PR merges into #4213 and addresses feedback from #4213 (comment)
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.