pointfreeco / swift-composable-architecture

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.
https://www.pointfree.co/collections/composable-architecture
MIT License
12.35k stars 1.44k forks source link

Crash in Store.send(_:originatingFrom:) #1742

Closed ehyche closed 1 year ago

ehyche commented 1 year ago

Description

We are seeing a crash in Store.send(_:originatingFrom:), as shown by this Crashlytics call stack:

Screen Shot 2022-12-13 at 10 56 57 AM Screen Shot 2022-12-13 at 10 57 41 AM

It's difficult to see how this could happen, given that self.bufferedActions is protected by an !self.bufferedActions.isEmpty check here:

      if !self.bufferedActions.isEmpty {
        if let task = self.send(
          self.bufferedActions.removeLast(), originatingFrom: originatingAction
        ) {
          tasks.wrappedValue.append(task)
        }
      }

We are using swift-composable-architecture v0.45.0, although it appears the code from v0.45.0 is the same as the code in the main branch.

Checklist

Expected behavior

No crash.

Actual behavior

See the Crashlytics call stacks above.

Steps to reproduce

It is intermittent, and does not happen very often.

The Composable Architecture version information

0.45.0

Destination operating system

No response

Xcode version information

Xcode 14.1

Swift Compiler version information

ehyche in ~/src/github/noom/coach-ios (ehyche/ARM-778-cached-or-fresh) > xcrun swiftc --version
swift-driver version: 1.62.15 Apple Swift version 5.7.1 (swiftlang-5.7.1.135.3 clang-1400.0.29.51)
Target: arm64-apple-macosx12.0
stephencelis commented 1 year ago

It's difficult to see how this could happen, given that self.bufferedActions is protected by an !self.bufferedActions.isEmpty check

That's strange indeed! Do you have a reliable way of reproducing the problem, or do you only have a Crashlytics trace?

Do you think there's a chance that your store is being interacted with on more than one thread at the same time? That's not allowed, but is the most probable way I could see this happening (two threads sending actions at the same time, and mutating the same storage at the same time).

ehyche commented 1 year ago

@stephencelis : we don't have a reliable way of reproducing - only a Crashlytics stack trace.

We may indeed be sending actions in from multiple threads. I'll check on that.

ilyap7o commented 1 year ago

To add some context, we're using TCA with UIKit, which is likely a factor. I've added thread logging but won't have results for a couple of weeks. Will update this issue when we have new findings.

stephencelis commented 1 year ago

Without a reproduction we're not sure there's much we can actionably do here. Does anyone maybe have an update? Or should I convert this to a discussion for now?

ilyap7o commented 1 year ago

Not yet; we haven't released the version with the new instrumentation yet. I will update this thread when we have some data on the issue and had a chance to analyze it, which will still be a couple of weeks.

damirstuhec commented 1 year ago

two threads sending actions at the same time, and mutating the same storage at the same time

@stephencelis any advice on how to catch these situations? We are seeing the same as OP.

kaishin commented 1 year ago

@stephencelis any advice on how to catch these situations? We are seeing the same as OP.

I would add that in the case we're dealing with, the issue disappears on release build with optimizations. This all started as we added a new leaf domain with a couple of subdomains.

stephencelis commented 1 year ago

The times we've seen this kind of access in the past is when somewhat dangerous things were being done, like having a ViewStore or send closure injected into a reducer via action/environment. If you are interacting with the store in such a way we strongly recommend refactoring away from this kind of pattern. Ideally a reducer has no way of interfacing with its backing store directly.

As an aside, please let us know if newer versions of TCA help. We're constantly fixing small bugs, including a recent release-only one: #1881.

Kondamon commented 1 year ago

@stephencelis I think I'm receiving similar crashes using TCA 0.50.1. Sometimes I receive them multiple times in a row and after doing a clean build the crash goes away. It always crashes after clicking on a specific button.

I can reproduce the issue in my existing project. I have to create a new State variable in the to be opened Reducer after button click, e.g. var newVar = false and after building the project, the crash happens on button click to open the sheet with the Reducer having the new variable. The crash goes away after doing a clean build. So, it seems in my case the crash is related to an Xcode build issue...

Crash 1:

#0  0x000000018c01c20c in swift_retain ()
#1  0x000000018c057edc in swift_bridgeObjectRetain ()
#2  0x0000000102d9b4e8 in outlined init with copy of CalendarCollectionOverview.State ()
#3  0x0000000102dea5f0 in closure #1 in CalendarCollectionOverviewSUI.body.getter at /Users/User/pgm/apps/af/Calendars/Frontend/Calendars/CalendarCollectionOverview/CalendarCollectionOverviewSUI.swift:167
#4  0x00000001035b1140 in closure #3 in ScopedReducer.rescope<τ_0_0, τ_0_1, τ_0_2, τ_0_3>(_:state:action:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:672
#5  0x00000001035b161c in partial apply for closure #3 in ScopedReducer.rescope<τ_0_0, τ_0_1, τ_0_2, τ_0_3>(_:state:action:) ()
#6  0x000000019baa28e4 in Subscribers.Sink.receive(_:) ()
#7  0x000000019baa2f04 in protocol witness for Subscriber.receive(_:) in conformance Subscribers.Sink<τ_0_0, τ_0_1> ()
#8  0x000000019bb59b4c in Publishers.Drop.Inner.receive(_:) ()
#9  0x000000019bb59e74 in protocol witness for Subscriber.receive(_:) in conformance Publishers.Drop<τ_0_0>.Inner<τ_1_0> ()
#10 0x000000019bb10418 in CurrentValueSubject.Conduit.offer(_:) ()
#11 0x000000019bb11250 in partial apply for closure #1 in CurrentValueSubject.send(_:) ()
#12 0x000000019bae570c in partial apply for thunk for @callee_guaranteed (@guaranteed ConduitBase<τ_0_0, τ_0_1>) -> (@error @owned Error) ()
#13 0x000000018bddb588 in Sequence.forEach(_:) ()
#14 0x000000019bae5124 in ConduitList.forEach(_:) ()
#15 0x000000019bb0f65c in CurrentValueSubject.send(_:) ()
#16 0x000000019bb11214 in specialized CurrentValueSubject.value.setter ()
#17 0x000000019bb0f550 in CurrentValueSubject.value.setter ()
#18 0x00000001035aa714 in $defer #1 <τ_0_0, τ_0_1>() in Store.send(_:originatingFrom:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:358
#19 0x00000001035aa478 in Store.send(_:originatingFrom:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:354
#20 0x00000001035afc0c in ScopedReducer.reduce(into:action:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:634
#21 0x00000001035b01c0 in protocol witness for ReducerProtocol.reduce(into:action:) in conformance ScopedReducer<τ_0_0, τ_0_1, τ_0_2, τ_0_3> ()
#22 0x00000001035a9950 in Store.send(_:originatingFrom:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:374
#23 0x0000000103660e00 in closure #1 in ViewStore.init(_:removeDuplicates:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/ViewStore.swift:203
#24 0x0000000103661520 in ViewStore.send(_:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/ViewStore.swift:281
#25 0x0000000102ebab4c in closure #1 in closure #2 in closure #1 in closure #2 in closure #1 in CalendarCollectionHeaderSUI.body.getter at /Users/User/pgm/apps/af/Calendars/Frontend/Calendars/CalendarCollectionOverview/CalendarCollectionHeaderSUI.swift:100
#26 0x000000010ea658f0 in ___lldb_unnamed_symbol115858 ()
#27 0x000000010f1f485c in ___lldb_unnamed_symbol179593 ()
#28 0x000000010ef84ec0 in ___lldb_unnamed_symbol159540 ()
#29 0x000000010ef84edc in ___lldb_unnamed_symbol159541 ()
#30 0x000000010ef84ec0 in ___lldb_unnamed_symbol159540 ()
#31 0x000000010eced894 in ___lldb_unnamed_symbol139917 ()
#32 0x000000010eced34c in ___lldb_unnamed_symbol139916 ()
#33 0x000000010ee3ce50 in ___lldb_unnamed_symbol148748 ()
#34 0x000000010f5823d4 in ___lldb_unnamed_symbol206503 ()
#35 0x000000010f580c74 in ___lldb_unnamed_symbol206486 ()
#36 0x000000010f580d4c in ___lldb_unnamed_symbol206489 ()
#37 0x000000010acc9b34 in -[UIGestureRecognizer _componentsEnded:withEvent:] ()
#38 0x000000010b1994f4 in -[UITouchesEvent _sendEventToGestureRecognizer:] ()
#39 0x000000010acbedc4 in -[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] ()
#40 0x000000010acbebb4 in -[UIGestureEnvironment _updateForEvent:window:] ()
#41 0x000000010b155d20 in -[UIWindow sendEvent:] ()
#42 0x0000000108d471b4 in __smartlook_method ()
#43 0x000000010b133fe0 in -[UIApplication sendEvent:] ()
#44 0x000000010b1b196c in __dispatchPreprocessedEventFromEventQueue ()
#45 0x000000010b1b35d0 in __processEventQueue ()
#46 0x000000010b1ac4ac in __eventFetcherSourceCallback ()
#47 0x000000018037318c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#48 0x00000001803730d4 in __CFRunLoopDoSource0 ()
#49 0x0000000180372844 in __CFRunLoopDoSources0 ()
#50 0x000000018036ceb0 in __CFRunLoopRun ()
#51 0x000000018036c7a4 in CFRunLoopRunSpecific ()
#52 0x0000000188ff7c98 in GSEventRunModal ()
#53 0x000000010b11a37c in -[UIApplication _run] ()
#54 0x000000010b11e374 in UIApplicationMain ()
#55 0x000000010f53d0d4 in ___lldb_unnamed_symbol204132 ()
#56 0x000000010f53cf7c in ___lldb_unnamed_symbol204130 ()
#57 0x000000010eca2b60 in static App.main() ()
#58 0x0000000102d64af8 in static MainApp.main() at /Users/User/pgm/apps/af/Calendars/Supporting Files/MainApp.swift:13
#59 0x0000000102d64b78 in static MainApp.$main() at /Users/User/pgm/apps/af/Calendars/Supporting Files/MainApp.swift:10
#60 0x0000000102d64b90 in main ()
#61 0x000000010814dfa0 in start_sim ()
#62 0x000000010824508c in start ()

Crash2: Thread 1: EXC_BAD_ACCESS (code=1, address=0x41c4cabc0478f5a9)

#0  0x000000018c01c20c in swift_retain ()
#1  0x00000001045c4ba0 in outlined init with copy of CalendarCollectionOverview.State ()
#2  0x00000001046150e0 in implicit closure #2 in implicit closure #1 in closure #1 in closure #1 in closure #2 in CalendarCollectionOverviewSUI.body.getter ()
#3  0x00000001046151ac in thunk for @escaping @callee_guaranteed (@in_guaranteed CalendarCollectionOverview.State) -> (@owned CalendarCollectionHeader.State) ()
#4  0x0000000104ddc534 in closure #3 in ScopedReducer.rescope<τ_0_0, τ_0_1, τ_0_2, τ_0_3>(_:state:action:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:672
#5  0x0000000104ddca10 in partial apply for closure #3 in ScopedReducer.rescope<τ_0_0, τ_0_1, τ_0_2, τ_0_3>(_:state:action:) ()
#6  0x000000019baa28e4 in Subscribers.Sink.receive(_:) ()
#7  0x000000019baa2f04 in protocol witness for Subscriber.receive(_:) in conformance Subscribers.Sink<τ_0_0, τ_0_1> ()
#8  0x000000019bb59b4c in Publishers.Drop.Inner.receive(_:) ()
#9  0x000000019bb59e74 in protocol witness for Subscriber.receive(_:) in conformance Publishers.Drop<τ_0_0>.Inner<τ_1_0> ()
#10 0x000000019bb10418 in CurrentValueSubject.Conduit.offer(_:) ()
#11 0x000000019bb11250 in partial apply for closure #1 in CurrentValueSubject.send(_:) ()
#12 0x000000019bae570c in partial apply for thunk for @callee_guaranteed (@guaranteed ConduitBase<τ_0_0, τ_0_1>) -> (@error @owned Error) ()
#13 0x000000018bddb588 in Sequence.forEach(_:) ()
#14 0x000000019bae5124 in ConduitList.forEach(_:) ()
#15 0x000000019bb0f65c in CurrentValueSubject.send(_:) ()
#16 0x000000019bb11214 in specialized CurrentValueSubject.value.setter ()
#17 0x000000019bb0f550 in CurrentValueSubject.value.setter ()
#18 0x0000000104dd5b08 in $defer #1 <τ_0_0, τ_0_1>() in Store.send(_:originatingFrom:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:358
#19 0x0000000104dd586c in Store.send(_:originatingFrom:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:354
#20 0x0000000104ddb000 in ScopedReducer.reduce(into:action:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:634
#21 0x0000000104ddb5b4 in protocol witness for ReducerProtocol.reduce(into:action:) in conformance ScopedReducer<τ_0_0, τ_0_1, τ_0_2, τ_0_3> ()
#22 0x0000000104dd4d44 in Store.send(_:originatingFrom:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/Store.swift:374
#23 0x0000000104e8c1f4 in closure #1 in ViewStore.init(_:removeDuplicates:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/ViewStore.swift:203
#24 0x0000000104e8c914 in ViewStore.send(_:) at /Users/User/Library/Developer/Xcode/DerivedData/Calendars-bujhypvdwqviozgftiyfphwceywp/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitecture/ViewStore.swift:281
#25 0x00000001046e50b8 in closure #1 in closure #2 in closure #1 in closure #2 in closure #1 in CalendarCollectionHeaderSUI.body.getter at /Users/User/pgm/apps/af/Calendars/Frontend/Calendars/CalendarCollectionOverview/CalendarCollectionHeaderSUI.swift:100
#26 0x00000001103158f0 in ___lldb_unnamed_symbol115858 ()
#27 0x0000000110aa485c in ___lldb_unnamed_symbol179593 ()
#28 0x0000000110834ec0 in ___lldb_unnamed_symbol159540 ()
#29 0x0000000110834edc in ___lldb_unnamed_symbol159541 ()
#30 0x0000000110834ec0 in ___lldb_unnamed_symbol159540 ()
#31 0x000000011059d894 in ___lldb_unnamed_symbol139917 ()
#32 0x000000011059d34c in ___lldb_unnamed_symbol139916 ()
#33 0x00000001106ece50 in ___lldb_unnamed_symbol148748 ()
#34 0x0000000110e323d4 in ___lldb_unnamed_symbol206503 ()
#35 0x0000000110e30c74 in ___lldb_unnamed_symbol206486 ()
#36 0x0000000110e30d4c in ___lldb_unnamed_symbol206489 ()
#37 0x000000010c579b34 in -[UIGestureRecognizer _componentsEnded:withEvent:] ()
#38 0x000000010ca494f4 in -[UITouchesEvent _sendEventToGestureRecognizer:] ()
#39 0x000000010c56edc4 in -[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] ()
#40 0x000000010c56ebb4 in -[UIGestureEnvironment _updateForEvent:window:] ()
#41 0x000000010ca05d20 in -[UIWindow sendEvent:] ()
#42 0x000000010a5f71b4 in __smartlook_method ()
#43 0x000000010c9e3fe0 in -[UIApplication sendEvent:] ()
#44 0x000000010ca6196c in __dispatchPreprocessedEventFromEventQueue ()
#45 0x000000010ca635d0 in __processEventQueue ()
#46 0x000000010ca5c4ac in __eventFetcherSourceCallback ()
#47 0x000000018037318c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#48 0x00000001803730d4 in __CFRunLoopDoSource0 ()
#49 0x0000000180372844 in __CFRunLoopDoSources0 ()
#50 0x000000018036ceb0 in __CFRunLoopRun ()
#51 0x000000018036c7a4 in CFRunLoopRunSpecific ()
#52 0x0000000188ff7c98 in GSEventRunModal ()
#53 0x000000010c9ca37c in -[UIApplication _run] ()
#54 0x000000010c9ce374 in UIApplicationMain ()
#55 0x0000000110ded0d4 in ___lldb_unnamed_symbol204132 ()
#56 0x0000000110decf7c in ___lldb_unnamed_symbol204130 ()
#57 0x0000000110552b60 in static App.main() ()
#58 0x000000010458e080 in static MainApp.main() at /Users/User/pgm/apps/af/Calendars/Supporting Files/MainApp.swift:13
#59 0x000000010458e100 in static MainApp.$main() at /Users/User/pgm/apps/af/Calendars/Supporting Files/MainApp.swift:10
#60 0x000000010458e118 in main ()
#61 0x0000000109855fa0 in start_sim ()
#62 0x000000010995508c in start ()
maxkazakov commented 1 year ago

I'm locally reproducing this bug. You can look at LocationTracker.swift line 22. The crash happens in case this line is not commented out. https://github.com/maxkazakov/Intervals/commit/b7e256bfe4fbbf7e41657a0c34664cb40d6ab02f

To test: open the app, click on any row (e.g. Workout plan 1), then click the "Start workout" button at the bottom. The app immediately crashes.

tgrapperon commented 1 year ago

Hey @maxkazakov! I've just tested, on simulator and on device, in debug and release mode, and I'm not able to reproduce the crash on my iPhone Xs under iOS 16.2. Have you tried to rebuild your app from a clean slate? (I think it misses a dependency to Utils in FinishedWorkoutCore)?

maxkazakov commented 1 year ago

@tgrapperon the crash disappeared after cleaning and building. That's weird. Anyway, thank you, I'll keep you posted if it comes back. P.S. I use Xcode 14.0 and I had no error messages regarding Utils dependency in FinishedWorkoutCore, either way, I fixed it.

tgrapperon commented 1 year ago

This kind of crash is usually caused by bugs in incremental compilation. The Utils dependency issue is something that can be spotted from a clean slate, but once you build this dependency for another reason (for another module), the build product is available and Swift fishes it. It can thus depend on the compilation order, so it can be easy to miss, even after cleaning out things.

maxkazakov commented 1 year ago

Yes, I reproduce the crash again after incremental build after adding a new field to FinishedWorkoutCore.State. Scenario: clean, build, run, tap on "Workout plan 1", then tap "Start workout" — no crash. Then go to FinishedWorkoutCore and add new field to the State. Run, tap on "Workout plan 1", then tap "Start workout" — crash.

image
tgrapperon commented 1 year ago

And if you clean and rebuild from this state, you still have a crash?

maxkazakov commented 1 year ago

No

tgrapperon commented 1 year ago

That's rather unfortunate for your overall experience. There must be something in your project that Xcode doesn't like when building incrementally. You should probably file a Feedback. I'm sorry that I can't help more with the issue.

maxkazakov commented 1 year ago

Thank you for such quick feedback. I'm downloading Xcode 14.2. Will see if it fixes the issue.

Kondamon commented 1 year ago

Thank you for such quick feedback. I'm downloading Xcode 14.2. Will see if it fixes the issue.

Unfortunately not, I‘m having the exact same issue with incremental builds.

stephencelis commented 1 year ago

Since the original issue still can't be reproduced in a reliable way that can point to a bug in the library, I'm going to close this for now. If anyone encounters this crash in the future with a repro we can work with, please open a new issue!