swift-composable-architecture icon indicating copy to clipboard operation
swift-composable-architecture copied to clipboard

Crash when using @Shared appStorage during certain conditions

Open zachwaugh opened this issue 1 year ago • 5 comments

Description

I'm seeing a crash with a similar backtrace to https://github.com/pointfreeco/swift-composable-architecture/issues/3247, but appears not to be caused by a thread issue.

I'm using a @Shared appStorage key roughly like:

@Shared(.appStorage("key")) var isEnabled: Bool = true

When launching the app it crashes. I'm still working on a simplified repro but having a hard time isolating the exact code causing the problem, but thought I'd post the issue in case anyone else has an idea.

Screenshot 2024-08-26 at 12 24 36 PM

If you look at the backtrace, it appears an NSAttributedString is performing layout (which isn't in my code) which triggers an NSUserDefault update which in turn causes the @Shared storage to update immediately during the same render cycle?The only relevant logs related to the crash are:

precondition failure: setting value during update: 219864
Screenshot 2024-08-26 at 12 25 55 PM

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.
  • [X] This issue hasn't been addressed in an existing GitHub issue or discussion.

Expected behavior

No response

Actual behavior

No response

Steps to reproduce

No response

The Composable Architecture version information

1.13.1

Destination operating system

iOS 17.5

Xcode version information

Xcode 15.4

Swift Compiler version information

No response

zachwaugh avatar Aug 26 '24 19:08 zachwaugh

Hi @zachwaugh, thanks for the report, but unfortunately without a repro it's hard to diagnose. The precondition failure message ("precondition failure: setting value during update") seems to clearly describe the problem: in the act of executing a view's body a value is being mutated. If you do this with @State, SwiftUI will even produce a purple runtime warning letting you know this isn't a good thing to do.

Only one idea comes to mind. In the stack trace we have mainActorNow to execute some work on the main thread as soon as possible, i.e. synchronously if already on the main thread, otherwise DispatchQueue.main.async. If instead we always incurred a thread hop, then that would guarantee the state does not change while in a view body.

Are you in a position to reproduce this precondition easily in your local environment? Would you be willing to try replacing mainActorNow with DispatchQueue.main.async and see if that fixes things?

mbrandonw avatar Aug 29 '24 18:08 mbrandonw

Getting a similarily suspicious crash on writing to FileStorageKey backed Shared. The line open #1 <A><A1, B1>(_:) in Shared.currentValue.getter seems to be same as in the crash above.

We started getting this crash after updating to 1.14.0 from 1.12.1.

#0	(null) in std::__1::__function::__func<_gatherGenericParameters(swift::TargetContextDescriptor<swift::InProcess> const*, __swift::__runtime::llvm::ArrayRef<swift::MetadataOrPack>, swift::TargetMetadata<swift... ()
#1	(null) in swift_getTypeByMangledNameImpl(swift::MetadataRequest, __swift::__runtime::llvm::StringRef, void const* const*, std::__1::function<void const* (unsigned int, unsigned int)>, std::__1::function<swif... ()
#2	(null) in swift_getTypeByMangledName ()
#3	(null) in swift::_checkGenericRequirements(__swift::__runtime::llvm::ArrayRef<swift::TargetGenericRequirementDescriptor<swift::InProcess>>, __swift::__runtime::llvm::SmallVectorImpl<void const*>&, std::__1::... ()
#4	(null) in _gatherGenericParameters(swift::TargetContextDescriptor<swift::InProcess> const*, __swift::__runtime::llvm::ArrayRef<swift::MetadataOrPack>, swift::TargetMetadata<swift::InProcess> const*, __swift:... ()
#5	(null) in (anonymous namespace)::DecodedMetadataBuilder::createBoundGenericType(swift::TargetContextDescriptor<swift::InProcess> const*, __swift::__runtime::llvm::ArrayRef<swift::MetadataOrPack>, swift::Meta... ()
#6	(null) in swift::Demangle::__runtime::TypeDecoder<(anonymous namespace)::DecodedMetadataBuilder>::decodeMangledType(swift::Demangle::__runtime::Node*, unsigned int, bool) ()
#7	(null) in swift_getTypeByMangledNodeImpl(swift::MetadataRequest, swift::Demangle::__runtime::Demangler&, swift::Demangle::__runtime::Node*, void const* const*, std::__1::function<void const* (unsigned int, u... ()
#8	(null) in swift_getTypeByMangledNode ()
#9	(null) in swift_getTypeByMangledNameImpl(swift::MetadataRequest, __swift::__runtime::llvm::StringRef, void const* const*, std::__1::function<void const* (unsigned int, unsigned int)>, std::__1::function<swif... ()
#10	(null) in swift_getTypeByMangledName ()
#11	(null) in swift_getTypeByMangledNameInEnvironment ()
#12	(null) in _resolveKeyPathGenericArgReference(_:genericEnvironment:arguments:) ()
#13	(null) in specialized _walkKeyPathPattern<A>(_:walker:) ()
#14	(null) in _getKeyPathClassAndInstanceSizeFromPattern(_:_:) ()
#15	(null) in _swift_getKeyPath(pattern:arguments:) ()
#16	0x0000000104a61e34 in ValueReference.value.getter at ComposableArchitecture/SharedState/References/ValueReference.swift:355
#17	0x0000000104a66da0 in open #1 <A><A1, B1>(_:) in Shared.currentValue.getter at ComposableArchitecture/SharedState/Shared.swift:260
#18	(null) in Shared._wrappedValue.getter ()

andreyz avatar Aug 30 '24 20:08 andreyz

Hi @andreyz, your crash does not seem to be related to the one discussed here. The one in this issue has stack frames with AG::Graph, which is from AttributeGraph that powers SwiftUI. Your crash is just pure Swift code, and has something to do with opening existentials and/or mangling type names.

And as always, a reproduction is the best way to get help on this.

mbrandonw avatar Aug 30 '24 21:08 mbrandonw

@zachwaugh Can you give #3437 a try and see if it addresses things for you?

stephencelis avatar Oct 21 '24 19:10 stephencelis

Same issue when using @Shared(.appStorage) in multiple features, which causes a crash at startup. I tried the shared-async-subscription branch, and the problem no longer appears on my end.

ajason avatar Oct 23 '24 06:10 ajason

@stephencelis I've removed use of @Shared from my app after hitting a few crashes, so I don't have it in there at the moment to test. But I'll go back to an earlier commit when I have some time and see if it's resolved. Thanks for looking into it!

zachwaugh avatar Oct 25 '24 13:10 zachwaugh

@zachwaugh Curious if you ever found the repro? We'd love to address this bug but want to test the currently open PR as well as an alternative solution.

stephencelis avatar Nov 07 '24 20:11 stephencelis

@stephencelis sorry for the delay, I finally had a chance to test this. I just added back in the @Share(.appStorage) usage and confirmed it still crashes in Xcode 16.1/iOS 18.1 for TCA as of 1.15.2. Updated to the shared-async-subscription branch and it does not crash, at least on launch. I haven't had a chance to do in-depth testing, but I think that branch does fix the crash I reported.

zachwaugh avatar Nov 11 '24 16:11 zachwaugh

@zachwaugh Thanks! I think #3487 has a better fix if you have a chance to test it. We've had @tgrapperon confirm it fixes a crash in his application, and I believe it was the same root cause. We'll be merging and releasing it soon, but if for whatever reason it doesn't fix it as well for you as the other PR we'll use that solution instead.

stephencelis avatar Nov 11 '24 23:11 stephencelis

Updated to 1.16.0 and confirmed the other method did fix the crash as well once I discovered the note about not using . in keys, which I was doing. Once I removed that, it's all good. That's a subtle thing that would be good to call out in the docs somewhere.

zachwaugh avatar Nov 13 '24 13:11 zachwaugh