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.22k stars 1.42k forks source link

Crash when using @Shared appStorage during certain conditions #3311

Open zachwaugh opened 2 weeks ago

zachwaugh commented 2 weeks ago

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

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

mbrandonw commented 2 weeks ago

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?

andreyz commented 2 weeks ago

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 ()
mbrandonw commented 2 weeks ago

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.