Skip to content
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

take(1) + share(replay: 1) + zip = deadlock #2653

Open
isaac-weisberg opened this issue Feb 22, 2025 · 4 comments · May be fixed by #2654
Open

take(1) + share(replay: 1) + zip = deadlock #2653

isaac-weisberg opened this issue Feb 22, 2025 · 4 comments · May be fixed by #2654

Comments

@isaac-weisberg
Copy link

Hey, guys. I have uncovered that there is a situation where you can easily cause RxSwift do deadlock.

I have prepared a repo with 100% reproduction rate of this feature.

It is caused by the fact that:

  • take(1) emits .completed
  • zip starts unsubscribing from source when it sees .completed
  • share(replay: 1) synchronizes the unsubscribing under it's own lock
  • BUT also share(replay: 1) replays the stored element under this very same lock
  • which can also be forwarded into .completed event because take(1)
  • which will be received by zip, which will try to unsubscribe from source
  • which is the same share(replay: 1)
  • ???
  • PROFIT

In this deadlock situation, there are always 2 threads that get stuck:

  • one has acquired the lock of zip, and tries to lock on share(replay: 1) to unsub
  • the other has acquired the lock of share(replay: 1) to replay an event, and tries to lock on zip

If one thing happens earlier than the other in a concurrent environment, then they will deadlock.

Here's a link to my repository that reproes this issue, just build, Cmd+R, it's raw Xcodeproj with SPM:

https://github.com/isaac-weisberg/RxSwiftDeadlock

Separate thanks I would like to say to @firecore and specifically this issue: #2525 - one of the problems described in this issue is definitely related!

@danielt1263
Copy link
Collaborator

I'm glad you posted this @isaac-weisberg I've been wanting to explain what's going on since your "lmao" post on another issue that shows effectively the same behavior (and for the same reason.)

RxSwift is not, and has never been, asynchronous by default. If you don't introduce asynchronous behavior, then it will be completely synchronous.

Your sharedSubscription Observable is synchronous. This means that when you call observer.on(.next(3)), the subscriber will immediately receive the Event, before the closure exits, and this is what is causing your issue.

You can solve this by introducing asynchronous behavior on your sharedSubscription, either by dispatching the observe.on(.next(3)) or by adding an .subscribe(on:).

You can also solve this by moving the .take(1) inside the flatMap.

Frankly, I don't run into these sorts of problems because I don't wrap synchronous work in Observable contexts like you did in your sharedSubscription and I recommend that you don't either. (except when testing, where all the work should be synchronous which also solves the problem you are seeing and makes testing much faster.)

@isaac-weisberg
Copy link
Author

Yeah... I mean, what other answer could I have expected here.

This library has some... you know...

@danielt1263
Copy link
Collaborator

It's what makes the library so easily testable. No need for async tests and test expectations. But yea, if you are inconsistent when introducing async behavior you will run into problems.

In production code. There is no need for something like your sharedSubscription Observable. That could easily be just a normal function { _ in 3 }. If you have a function that takes a long time to execute, then you would obviously want to run it in a scheduler (.subscribe(on:) is your friend here), if the function doesn't take long, then just put it in a map closure.

@freak4pc
Copy link
Member

Yeah... I mean, what other answer could I have expected here.

This library has some... you know...

Like your previous post - you show nothing but disrespect for the time and work of people whose help you asked for.

Daniel has spent a massive amount of time and kindness to explain the limitations. If this library doesn't suit your needs, feel free to stop using it. We'll hopefully still be able to afford our daily life after losing a customer 🥲 (of this entirely open-source, volunteer-based framework)

If you are using it and need help, the way you're doing it in your last two posts isn't a way of getting it.

Good luck, and I'm locking this thread, too.

@ReactiveX ReactiveX locked and limited conversation to collaborators Feb 23, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants