New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Crash in OSSpinLockSynchronization #21
Comments
So it's running in the mainThread and trying to add a block to the completion block of some other Future. |
More importantly, is there any chance that the Future that you are calling onSuccess was somehow being deconstructed? Typically it's tricky to create a Future and deallocate it before it completes, but it's not impossible. |
The future is either grabbed from a cache of previously created futures or created to satisfy the request, and performs its work on a background queue. So it might already be completed. Or it might be in the process of being completed from another thread. It's definitely not being deallocated, though. |
@mishagray: This keeps happening at a low but steady rate in the wild for us. But we now have a reproduction method for it. To reproduce we
In other words, it seems that every future has a small but non-zero risk of triggering this error. Here is a tiny project which reproduces the crash: https://github.com/heiberg/FutureThrasher(run It uses promises and futures to repeatedly increment a counter and show the value in a UILabel: import UIKit
import FutureKit
class ViewController: UIViewController {
@IBOutlet weak var textLabel: UILabel!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.goBananas()
}
private var bananaCounter = 0
private func goBananas() {
let promise = Promise<NSDate>()
Executor.UserInitiated.execute {
promise.completeWithSuccess(NSDate())
}
promise.future.onSuccess(.Main) { _ in
self.textLabel.text = "\(self.bananaCounter++)"
self.goBananas()
}
}
} Running this on my iPhone 6s usually reproduces the crash within 10 seconds or so. The call stack usually ends in |
Further info: Switching the synchronization strategy from
Somehow the method |
ok... There is a moment after the promise completes that we are 'swapping' from the SpinLock to the UnsafeLock. This is supposed to be 'ok' because the promise has effectively become 'immutable' and doesn't need a real lock anymore. But I think the bug is that the swap operation itself is not properly guarded operation. I thought that just using a MemoryGuard around the lock pointer would do it... but I either didn't do it right, or it doesn't work the way I think it does. Which opens up a sort of chicken and egg problem when using the Mutable/Immutable locking strategy. I'm gonna take out the UnsafeSynch and just continue using the SpinLock even after the promise completes. If that's the problem that should solve it. And the optimization is kind of minor. It would be a bigger optimization if we were using NSLock, but OSSpinLocks are already kinda super fast. Thanks! |
That sounds like a good and safer alternative. Thanks for looking into this! I'll keep an eye out for the commit and re-run the test. |
okay! Looks fixed. Deployed a new version 1.2.1, Your 'FutureThrasher' seems to run forever on my devices now. (No more Thrashing..) Thanks! |
This is an unreproducible one-off crash, so I just wanted to leave it here for future reference. It occurred in the wild for a user and was reported via Fabric.io.
From the mangled symbols it looks like it might be the
UnSafeMutableContainer<OSSpinLock>
propertyself.lock
insideOSSpinLockSynchronization
which is causing this issue.The application was calling
.onSuccess(.UserInteractive)
on a Future from the main thread.Device: non-jailbroken iPhone running iOS 9
FutureKit version: 1.2.0
This is the top of the call stack. The rest is application code + system libraries.
The text was updated successfully, but these errors were encountered: