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):
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 Operation
s.
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.