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
Deadlock #74
Comments
After setting a breakpoint on This is one of my first bigger stabs at RAC4 after coming from RAC2.X. I might be misusing some things. Please let me know! |
Another note: I am using ReactiveCocoa 4.2.2, but this seemed to be a better place to post the issue as it appeared you guys were trying to get the Swift API oriented issues over here. If I need to move this just let me know. |
Hmmm... interesting.... The root of the problem appears to be this code: viewModel.undoEnabled.producer.startWithNext { [unowned self] enabled in
if enabled {
self.viewModel.undoCurrentAction()
}
} If I do this: viewModel.undoEnabled.producer.startWithNext { [unowned self] enabled in
if enabled {
// undoPressed simply calls viewModel.undoCurrentAction()
self.navigationItem.rightBarButtonItem =
UIBarButtonItem(title: "UNDO", style: .Plain, target: self, action: #selector(self.undoPressed))
} else {
self.navigationItem.rightBarButtonItem = nil
}
} Everything works fine. Can anyone elaborate on this? |
It seems you are having an infinite feedback loop. |
This is just a simple example (actually leaking), but as you can see it is not infinite loop, so I feel it makes more sense to make Lock recursive. enum Event {
case a
case b
}
let pipe = Signal<Event, NoError>.pipe()
pipe.output.filterMap { $0 == .a ? Event.b : nil }.observe(pipe.input)
pipe.output.observeValues { event in
print("value: \(event)")
}
pipe.input.send(value: .a) // deadlock What do you think? @andersio @mdiep Related PR that makes Lock recursive #308 Especially when we use RAS with RAC, all events are pretty much UI driven, and sometimes UIKit causes this case unexpectedly. |
Nice example :) |
I think that organization should be discouraged. FRP is all about unidirectional data flow, so I don't think it makes sense to support cycles. |
Let me give you more concrete example: class View {
enum Event {
case textChanged(text: String)
case buttonPressed
}
private let eventIO = Signal<Event, NoError>.pipe() // or view model
private let textField = UITextField()
private let button = UIButton()
private func bind() {
textField.reactive.continuousTextValues
.map { Event.textChanged(text: $0) }
.observe(eventIO.input)
button.reactive.controlEvents(.touchUpInside)
.map { _ in Event.buttonPressed }
.observe(eventIO.input)
}
func observe(action: @escaping (Event) -> Void) {
eventIO.output.observeValues {
action($0)
}
}
}
class ViewController {
let someView = View()
private func bind() {
someView.observe { [weak self] event in
switch event {
case .textChanged(let text):
// do something
case .buttonPressed:
// do something that eventually invoke below
self?.view.endEditing(false)
}
}
}
} When it invokes I do understand What do you think? |
FYI
Since detecting cyclic dependency is hard in general and requires a lot of runtime test, I prefer the approach RxSwift is taking: prompt a message and use RecursiveLock. And IMO "discouraged" only works when the problem is predictable and preventable beforehand. Also, without reentrancy, I think many other potential operators e.g. #308 |
In that particular case, the pipe is an unnecessary level of indirection. This seems like it'd work: class View {
enum Event {
case textChanged(text: String)
case buttonPressed
}
private let textField = UITextField()
private let button = UIButton()
func observe(action: @escaping (Event) -> Void) {
textField.reactive.continuousTextValues
.map { Event.textChanged(text: $0) }
.observe(action)
button.reactive.controlEvents(.touchUpInside)
.map { _ in Event.buttonPressed }
.observe(action)
}
}
class ViewController {
let someView = View()
private func bind() {
someView.observe { [weak self] event in
switch event {
case .textChanged(let text):
// do something
case .buttonPressed:
// do something that eventually invoke below
self?.view.endEditing(false)
}
}
}
} I don't think that the RxSwift behavior of not crashing but violating the contract is better. |
That is right, but as commented in the code, we do have a view model that has some logics inside, so we do need this kind of pipe. Also even if we did not use pipe, if want to do something like below ultimately, same thing will happen. textView.reactive.continuousTextValues
.filter { $0 == "resign" }
.observe { [weak textView] _ in textView?.resignFirstResponder() }
Agree, since sometimes we cannot prevent this happening.. What do you think of providing a way to switch the lock then? |
It should be possible to detect cycles when you're constructing the signal chain. 🤔 |
🤔 textView.reactive.continuousTextValues
.filter { $0 == "resign" }
.observe { [weak textView] _ in textView?.resignFirstResponder() } How can you solve this? |
Oh, good point! 🤔 |
Yeah.. |
FYI here's a flipflop example that won't crash our real world: |
Hello. 👋 Thanks for opening this issue. Due to inactivity, we will soft close the issue. If you feel that it should remain open, please let us know. 😄 |
Just for sharing, we have been patching RAS internally to workaround this. |
@chuganzy do you think you could open a PR with the fix? |
Hi,
I am running into a deadlock issue. I’m not entirely sure why it is happening yet. Here is my setup (dumbed down as this is the only part breaking, can provide more if needed).
Both
commitAction()
andundoCurrentAction()
get called from aUIViewController
that theMyViewModel
is powering and has subscribers toundoEnabled
.Example for testing:
The deadlock seems to be happening when calling
undo
and settingundoable
’svalue
tofalse
.The text was updated successfully, but these errors were encountered: