Closed jcampbell05 closed 8 years ago
On further inspection this deadlock happens if the observable is a shareReplay
observable.
Hi @jcampbell05 ,
can you provide some small code snippet that reproduces the issue. I've checked the source code and always see paired _lock.lock(); defer { _lock.unlock() }
, so I'm little confused what's happening here.
@kzaher Sure, I'll try and construct an example product that reproduces it outside of my app.
Hi @jcampbell05 ,
we've talked on Slack regarding you touching UI elements on background thread, and can that cause this.
My suspicion is that maybe that has caused an exception to be thrown that skips defer { _lock.unlock() }
.
Is there maybe any progress on the investigation? Has your issue been resolved?
I would really want to get to the bottom of this as soon as possible, since this is potentially really a serious issue :)
Thnx
@kzaher So as far as I can tell, there shouldn't be an exception as the only thing my code is doing was touching the UI from a non-UI thread.
Currently not resolved I had to remove the use of shareReplay
for now. If an exception is thrown how would I know ? I've added the exception breakpoint but it's not triggered for anything.
Yes, that should detect exceptions as far as I can tell.
Can you paste me some code example with and without shareReplay
so I can try to reproduce something like this?
Sure let me write something up explaining the situation and I'll provide some code samples :)
I basically had a piece of code that listened to a network request and pull down a list of items to show on a view.
For each view that represented that item it had to then load some content for that item (like a profile image). When the user tapped on the view, I presented a view controller which used the shareReplay
so I only made on network request and eventually it would be updated with the new content.
If the data the network request was based on changed (this is a location based app so the items being shown were based on your location) I disposed of all of the old requests to stop them being fired.
If the views has their content changed, they would dispose of the request to load their content, Its at this point it deadlocked.
This is the code that fetched the items to show
networkRequest
.observeOn(MainScheduler.instance)
.subscribeNext {
let items: [Item] = $0.map {
let radarItem = Item(user:$0)
radarItem.userProfile = self.userDatastore.fetchUser(identifier: $0.identifier)
return radarItem
}
//This line below then triggered a bunch of views to be created that were populated with each item from the array above
self.itemsToLoad = items
}.addDisposableTo(refreshDisposeBag)
This is the code in the view:
class UserRadarItemView: RadarItemView {
var disposeBag: DisposeBag!
var userItem: Item? {
didSet {
if let userItem = userItem
{
disposeBag = DisposeBag()
_ = userItem.userProfile?
.observeOn(MainScheduler.instance)
.subscribeNext {
//Code to update view with the downloaded content.
}.addDisposableTo(disposeBag)
}
}
}
}
If the view similarly had its content reset, it would dispose the shareReplay
observable and then deadlock.
Let me know if this is detailed enough.
I believe I would need two things right now:
shareReplay(1)
operator that was causing the deadlock.debug
operator in front of shareReplay
when it deadlocks.Could you maybe provide that?
I think this was caused by a misues of a library :)
Hi @jcampbell05,
can you please explain how did you misuse it so we can fix this?
This shouldn't ever happen.
@kzaher It seemed that I was missing off a line on a tweak I did to a library called Geofire. This caused it to want to wait for some network requests to finish.
In my disposable it would tell this library to start a new request, which would cause it to lock until all of its previous requests were done.
Removing the sharedReplay
worked as it would create a totally new Geofire object which wasn't making any requests. Of course over time it would cause a lot of CPU issues. But its strange this blocking behaviour only happened with shareReplay.
Hi @jcampbell05 ,
yes, this explanation makes sense. That's why you shouldn't be using any of the blocking operators, but only in situations like unit tests or preventing terminal app from exiting before computations are completed.
The same reasoning applies for using any third part blocking code.
I was really surprised when you've reported this, because Rx is designed so it's not possible to cause a deadlock unless you are using it in a wrong way:
defer
blocks when exception is being thrownGlad we sorted this one out :)
I have a DisposeBag property for a UIView, this view (Which acts like a cell) is used in a custom collection type view I've created. When this UIView is reused I dealloc this dispose bag and create another so that any work such as loading remote images is cancelled for the previous setup of the view and new work can begin.
I am running RxSwift 2.1 on iOS 9 for iPhone 6S, when there is 100 of these UIViews I eventually hit deadlock. RxSwift seems to be waiting for a NSRecursiveLock whilst deallocating this dispose bag.
Here is a screenshot of my stack trace.