We have once again found a deadlock in RxSwift by the same method as before, where we catch main thread being frozen, while it tripped on some RxSwift subject which participates in deadlock.
In these particular stack frames, you will see, one one thread, it's ZipCollectionTypeSink.on(.completed) calling dispose on one of the sources from under its own lock which is trying to unsubscribe from ReplayBufferBase via synchronizedUnsubscribe, while the other thread is holding onto this ReplayBufferBase's lock, replays the initial buffer, trying to push an element into ZipCollectionTypeSink.on.
ZipCollectionTypeSink deadlock.txt
However, just by visually looking at the code, it's clear to me that this lock inversion can happen not only on this path, but in general in the entirety of ZipCollectionTypeSink.on, when it calls self.dispose(), subscriptions[atIndex].dispose(), and even forwardOn...
And on our side, we are going to patch ZipCollectionTypeSink so that it doesn't call anything while retaining its own lock.
I do not have a definitive proof, but it starts to appear to me, that the way operators Sinks call dispose on their sources while retaining its own locks, inversing the propagation of event, going back upstream, is never meant to work in a multithreaded environment...
And the fact that locks everywhere are recursive, it hides this random double acquisition of locks in single threaded environment.
I think RxSwift should really think about a rewrite in which the propagation of events will never be happening with locks being held.
We have once again found a deadlock in RxSwift by the same method as before, where we catch main thread being frozen, while it tripped on some RxSwift subject which participates in deadlock.
In these particular stack frames, you will see, one one thread, it's
ZipCollectionTypeSink.on(.completed)calling dispose on one of the sources from under its own lock which is trying to unsubscribe from ReplayBufferBase via synchronizedUnsubscribe, while the other thread is holding onto this ReplayBufferBase's lock, replays the initial buffer, trying to push an element into ZipCollectionTypeSink.on.ZipCollectionTypeSink deadlock.txt
However, just by visually looking at the code, it's clear to me that this lock inversion can happen not only on this path, but in general in the entirety of ZipCollectionTypeSink.on, when it calls
self.dispose(),subscriptions[atIndex].dispose(), and evenforwardOn...And on our side, we are going to patch ZipCollectionTypeSink so that it doesn't call anything while retaining its own lock.
I do not have a definitive proof, but it starts to appear to me, that the way operators Sinks call dispose on their sources while retaining its own locks, inversing the propagation of event, going back upstream, is never meant to work in a multithreaded environment...
And the fact that locks everywhere are recursive, it hides this random double acquisition of locks in single threaded environment.
I think RxSwift should really think about a rewrite in which the propagation of events will never be happening with locks being held.