You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I noticed my app getting into deadlock on the main thread when using @Shared. I think this is probably the same underlying problem and root cause as my other issue (#3311), but this one I managed to make a repro for.
Checklist
I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
If possible, I've reproduced the issue using the main branch of this package.
Here's a repro similar to my app that I was able to finally reduce into something simpler, but still approximating what happens in my app. There's a lot of things at play and changing some subtle things will cause it not to deadlock, but I was able to reliably deadlock every time with this. Just note if you change something and it works, you may need to delete the app and do a fresh install to trigger it again. I didn't have time at the moment to dig further, but I suspect at least some part of it is due to UserDefaults initializing the first time?
Run the app in a simulator, and you'll see Database init() start log and nothing else and the app will be locked. Hit the pause execution button and you can see where its locked. I think as @mbrandonw suggested in the other issue that changing that mainActorNow to DispatchQueue.main.async would fix it and I'll try that later when I have some more time.
import ComposableArchitecture
import SwiftUI
@mainstructTCATestApp:App{varbody:someScene{WindowGroup{DeadlockView(store:Store(initialState:Deadlock.State(), reducer:{Deadlock()}))}}}structDeadlockView:View{@Bindablevarstore:StoreOf<Deadlock>@Environment(\.scenePhase)privatevarscenePhasevarbody:someView{NavigationSplitView{List(selection: $store.selection){NavigationLink(value:"child"){Text("Go To Child")}}.navigationTitle("Home").listStyle(.sidebar).task{await store.send(.task).finish()}} detail:{NavigationStack{switch store.destination {case.child:iflet store = store.scope(state: \.destination?.child, action: \.destination.child){ChildView(store: store)}default:EmptyView()}}}}}@ReducerstructDeadlock{@Reducer(state:.equatable)enumDestination{case child(Child)}@ObservableStatestructState:Equatable{varselection:String?="child"@Presentsvardestination:Destination.State?=.child(Child.State())@Shared(.appStorage("test"))vartest:Bool=false}enumAction:BindableAction{case task
case destination(PresentationAction<Destination.Action>)case binding(BindingAction<State>)}@Dependency(\.database)vardatabasevarbody:someReducerOf<Self>{BindingReducer()Reduce{ state, action inswitch action {case.task:return.merge(.run { _ inprint("Querying database…")await database.query()print("… query complete")}.cancellable(id:CancelId.one, cancelInFlight:true))case.destination,.binding:return.none
}}.ifLet(\.$destination, action: \.destination)}privateenumCancelId{case one
}}@ReducerstructChild{@ObservableStatestructState:Equatable{@Shared(.appStorage("count"))varcount:Int=0}enumAction{case incrementButtonTapped
}varbody:someReducerOf<Self>{Reduce{ state, action inswitch action {case.incrementButtonTapped:
state.count +=1return.none
}}}}structChildView:View{letstore:StoreOf<Child>varbody:someView{VStack{Text(store.count.formatted()).navigationTitle("Child")Button("Increment"){
store.send(.incrementButtonTapped)}.buttonStyle(.borderedProminent)}}}structDatabase:DependencyKey{init(){print("Database init() start")UserDefaults.standard.set("foo-bar", forKey:"test-key")
// Will never be called, will deadlock on the UserDefaults notification
print("Database init() end")}func query()async{}staticvarliveValue:Database=Database()}extensionDependencyValues{vardatabase:Database{get{self[Database.self]}set{self[Database.self]= newValue }}}
The Composable Architecture version information
1.14.0
Destination operating system
iOS 17.5
Xcode version information
Xcode 15.4
Swift Compiler version information
No response
The text was updated successfully, but these errors were encountered:
Description
I noticed my app getting into deadlock on the main thread when using
@Shared
. I think this is probably the same underlying problem and root cause as my other issue (#3311), but this one I managed to make a repro for.Checklist
main
branch of this package.Expected behavior
No response
Actual behavior
No response
Steps to reproduce
Here's a repro similar to my app that I was able to finally reduce into something simpler, but still approximating what happens in my app. There's a lot of things at play and changing some subtle things will cause it not to deadlock, but I was able to reliably deadlock every time with this. Just note if you change something and it works, you may need to delete the app and do a fresh install to trigger it again. I didn't have time at the moment to dig further, but I suspect at least some part of it is due to
UserDefaults
initializing the first time?Run the app in a simulator, and you'll see
Database init() start
log and nothing else and the app will be locked. Hit the pause execution button and you can see where its locked. I think as @mbrandonw suggested in the other issue that changing thatmainActorNow
toDispatchQueue.main.async
would fix it and I'll try that later when I have some more time.The Composable Architecture version information
1.14.0
Destination operating system
iOS 17.5
Xcode version information
Xcode 15.4
Swift Compiler version information
No response
The text was updated successfully, but these errors were encountered: