Skip to content
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

Support for complete strict concurrency and Swift 6 migration #456

Draft
wants to merge 25 commits into
base: develop
Choose a base branch
from

Conversation

martinmitrevski
Copy link
Collaborator

🔗 Issue Links

Resolves https://stream-io.atlassian.net/browse/PBE-4992?atlOrigin=eyJpIjoiZTIyOWY1OWRkN2JlNDcxMWFjOGE1ZDdiMjRlNjQ0NzMiLCJwIjoiaiJ9.

🎯 Goal

Enable complete strict concurrency and make the migration to Swift 6 seamless.

The goal is to have no issues on both Xcodes. The checks and the language capabilities are not the same on both Xcodes, which means we need some #if language checks.

Current state:

  • Code compiles in both Xcodes for Swift 5 and 6.
  • Things are also working - we need to test extensively for some assertions (had to fix few crashes because of this).

Things yet to fix:

  • warnings
  • haven't checked the tests yet

As soon as we're happy, we can merge with the Swift 5 version support - it anyway fixes some strict checking warnings (there's no need to wait for Xcode 16).

📝 Summary

  • Complete strict concurrency checking
  • Swift 6 migration (can only be turned on in Xcode 16 beta)

We should discuss some of the fixes taken there.

🛠 Implementation

There are few types of issues, did similar things as described here: https://developer.apple.com/wwdc24/10169.

🎨 Showcase

Add relevant screenshots and/or videos/gifs to easily see what this PR changes, if applicable.

Before After
img img

🧪 Manual Testing Notes

We should test on both Xcodes. Touch as many features as we can, since there might be some wrong queue assertions we need to fix (that are causing crashes).

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change follows zero ⚠️ policy (required)
  • This change should receive manual QA
  • Changelog is updated with client-facing changes
  • New code is covered by unit tests
  • Comparison screenshots added for visual changes
  • Affected documentation updated (Docusaurus, tutorial, CMS)

🎁 Meme

Alt Text

@martinmitrevski martinmitrevski requested a review from a team as a code owner July 4, 2024 08:26
@martinmitrevski martinmitrevski marked this pull request as draft July 4, 2024 08:26
Copy link

github-actions bot commented Jul 4, 2024

1 Message
📖 Skipping Danger since the Pull Request is classed as Draft/Work In Progress

Generated by 🚫 Danger

Copy link
Collaborator

@ipavlidakis ipavlidakis left a comment

Choose a reason for hiding this comment

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

Do you think there is any way we can make the injectionkey nconsidered nonisolated unsafe to avoid the changes (and also the access on di values only from the main actor)

@@ -173,10 +173,15 @@ final class LocalParticipantSnapshotViewModel: NSObject, AVCapturePhotoCaptureDe
}

/// Provides the default value of the `LocalParticipantSnapshotViewModel` class.
#if swift(>=6.0)
struct LocalParticipantSnapshotViewModelKey: @preconcurrency InjectionKey {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm wondering how the environmenrkey on SwiftUI handles that? Is it assigned on MainActor?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, this part is ugly, we have to find a better way

Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's meet and dicsuccs

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

maybe we can create a small package with the DI capabilities. Reuse it in both SwiftUI and Video, with the @preconcurrency import. That way, we can probably avoid this stuff.

@@ -34,6 +32,8 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
/// Provides access to the speaker.
public let speaker: SpeakerManager

@MainActor public internal(set) lazy var state = CallState()
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the purpose of having it as lazy?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the init is not on the main actor, so it was not possible to init the state like this anymore.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we simply mark the init as @mainactor, instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

you mean the Call object? I don't think we should enforce that, calls can be created from any thread - only the state should be accessed from the main actor.

StreamVideo.xcodeproj/project.pbxproj Show resolved Hide resolved
@@ -51,8 +51,8 @@ jobs:
run: bundle exec fastlane test device:"${{ env.IOS_SIMULATOR_DEVICE }}"
timeout-minutes: 40
env:
XCODE_VERSION: "15.2" # the most stable pair of Xcode
IOS_SIMULATOR_DEVICE: "iPhone 15 Pro (17.2)" # and iOS
XCODE_VERSION: "15.4" # the most stable pair of Xcode
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Temp to see if the CI passes - we might have a problem if nonisolated(unsafe) doesn't work on 15.2.

Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the outcome on this one?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we still have a problem here, need to dig into this

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I spent the whole day on this one - there's no good way. nonisolated(unsafe) was introduced in Swift 5.10. Tried with macros and property wrappers, but no luck. Let's discuss it if you have an idea.

Copy link
Collaborator Author

@martinmitrevski martinmitrevski Jul 30, 2024

Choose a reason for hiding this comment

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

If there's no solution, we would have to drop 5.9.

Sources/StreamVideo/WebRTC/AudioSession.swift Outdated Show resolved Hide resolved
app?.endBackgroundTask(activeTask)
activeBackgroundTask = nil
executeOnMain {
self.app?.endBackgroundTask(activeTask)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shall we change that to weak self?

@@ -51,8 +51,8 @@ jobs:
run: bundle exec fastlane test device:"${{ env.IOS_SIMULATOR_DEVICE }}"
timeout-minutes: 40
env:
XCODE_VERSION: "15.2" # the most stable pair of Xcode
IOS_SIMULATOR_DEVICE: "iPhone 15 Pro (17.2)" # and iOS
XCODE_VERSION: "15.4" # the most stable pair of Xcode
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the outcome on this one?

DispatchQueue.global(qos: .userInteractive).async {
self.captureSession?.startRunning()
}
self.captureSession?.startRunning()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did we remove the background async?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good catch

DispatchQueue.global(qos: .userInteractive).async {
self.captureSession?.stopRunning()
}
captureSession?.stopRunning()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did we remove the background async? Can we replace it instead with a simple task to the GlobalActor?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 603 to 606
let audioRecorder = self.audioRecorder
Task {
await audioRecorder.stopRecording()
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you thin that may work?

Suggested change
let audioRecorder = self.audioRecorder
Task {
await audioRecorder.stopRecording()
}
Task { @MainActor in
await audioRecorder.stopRecording()
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

same as here: #456 (comment).

@@ -87,7 +88,10 @@ struct LobbyContentView: View {
HStack {
Spacer()
Button {
Task { await microphoneChecker.stopListening() }
let microphoneChecker = self.microphoneChecker
Task {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we dispatch to @mainactor and simplify the changeset?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

unfortunately not

@@ -59,6 +59,7 @@ struct LobbyView_iOS13: View {
onCloseLobby: onCloseLobby
)
.onReceive(callViewModel.$callSettings) { newValue in
let microphoneChecker = self.microphoneChecker
Task {
Copy link
Collaborator

Choose a reason for hiding this comment

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

suggesstion: @mainactor maybe to simplify the changeset?

trackSize = .init(width: Int(frame.width), height: Int(frame.height))
nonisolated func renderFrame(_ frame: RTCVideoFrame?) {
nonisolated(unsafe) let unsafeFrame = frame
Task { @MainActor [weak self] in
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure that running the decoding part on MainActor is wise. What was the part that was complaining? Can we reduce the MainActor calling and move to the background for the transformation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

there were some things, but I made them nonisolated.

@@ -5,7 +5,6 @@
@testable import StreamVideo
import XCTest

@MainActor
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wasn't that sufficient?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nope, since the base class is not a main actor.

@Stream-SDK-Bot
Copy link
Collaborator

Stream-SDK-Bot commented Jul 30, 2024

SDK Size

title develop branch diff status
StreamVideo 7.11MB 7.16MB 0.05MB 🟢
StreamVideoSwiftUI 2.18MB 2.2MB 0.02MB 🟢
StreamVideoUIKit 2.32MB 2.33MB 0.01MB 🟢

Copy link

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