ReactiveCocoa / ReactiveSwift

Streams of values over time

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

EXC_BAD_ACCESS crash with Lifetime.of(_:NSObject)

iby opened this issue · comments

Can't figure out what exactly is causing the crash but so far the best candidate seems to be Operation with the following extensions:

extension Reactive where Base: Operation {
    internal var cancel: BindingTarget<()> { self.makeBindingTarget(on: ImmediateScheduler(), { operation, _ in if !operation.isFinished && !operation.isCancelled { operation.cancel() } }) }
    internal var didStart: Signal<(), Never> { self.base.reactive.signal(forKeyPath: #keyPath(Operation.isExecuting)).filter({ $0 as? Int == 1 }).void() }
    internal var didFinish: Signal<(), Never> { self.base.reactive.signal(forKeyPath: #keyPath(Operation.isFinished)).filter({ $0 as? Int == 1 }).void() }
    internal var didCancel: Signal<(), Never> { self.base.reactive.signal(forKeyPath: #keyPath(Operation.isCancelled)).filter({ $0 as? Int == 1 }).void() }
}

There are thousands of instances created during the runtime and eventually the app crashes with this stack trace, which really looks like an exceeded recursion depth.

Debugging this in Xcode often gives insane 7000+ stack lines (only 500 in the attached log):

image

Did you see anything like this before? Should something be done differently to avoid this?

Yes, the issue was with observing an Operation. Newly created operations would depend on already running ones. After creating a few thousands of them, when the last one would terminate and get removed from the OperationQueue the entire dependency tree would get released at once. This obviously needs rethinking at my end, but it might be worth looking into why it's actually crashing the Lifetime observer?

The stack trace shows a very abnormal stack trace that looks like a stack overflow. Are these thousands of operations in a chain?

I don't feel like Lifetime is at fault here if it is indeed a stack overflow. Since the stack limit is an invariant, you would still encounter it without Lifetime, only requiring a longer chain of Operations.

You might want to figure out a way to break this deep dealloc call stack apart, either by an iterative algorithm or async scheduling.

The only stack overflow I remember in 2020 starts with http! 😅

That must have been it. Removing all observations and adding a print statement on Operation's deinit crashes on it but takes significantly more time. Cleaning terminated operation dependencies solved the issue.