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

Infinite recursion in observeSwitchToLatest() #877

Closed
datwelk opened this issue Jul 13, 2023 · 3 comments
Closed

Infinite recursion in observeSwitchToLatest() #877

datwelk opened this issue Jul 13, 2023 · 3 comments

Comments

@datwelk
Copy link
Contributor

datwelk commented Jul 13, 2023

I occasionally receive crash reports with an infinite recursion in observeSwitchToLatest().

0   libswiftCore.dylib                   0x00000001cc435978 getCache(swift::TargetTypeContextDescriptor<swift::InProcess> const&) + 8
1   LLLLLLLL                             0x00000001027def08 __swift_instantiateGenericMetadata (<compiler-generated>:0)
2   LLLLLLLL                             0x000000010281c038 ReactiveSwift.Signal.(Core in _6DF632AE8A9288C3EAD8EFDF3D3AF99E).send(ReactiveSwift.Signal<A, B>.Event) -> () (<compiler-generated>:0)
3   LLLLLLLL                             0x000000010281b064 ReactiveSwift.Signal.Observer.send(ReactiveSwift.Signal<A, B>.Event) -> () (Signal.Observer.swift:111)
4   LLLLLLLL                             0x00000001027f9780 generic partial specialization <Signature = @escaping @convention(thin) @convention(method) <A, B><A1 where B: Swift.Error, A1 == ReactiveSwift.Lock> (@in_guaranteed ReactiveSwift.Signal<A, B>.Event, @guaranteed ReactiveSwift.Signal<A, B>.(Core in _6DF632AE8A9288C3EAD8EFDF3D3AF99E)<ReactiveSwift.Lock>) -> ()> of ReactiveSwift.Signal.(Core in _6DF632AE8A9288C3EAD8EFDF3D3AF99E).send(ReactiveSwift.Signal<A, B>.Event) -> () (Signal.swift:124)
5   LLLLLLLL                             0x000000010281b064 ReactiveSwift.Signal.Observer.send(ReactiveSwift.Signal<A, B>.Event) -> () (Signal.Observer.swift:111)
6   LLLLLLLL                             0x00000001027f2db8 closure #3 (ReactiveSwift.Signal<A.Value, B>.Event) -> () in closure #1 (ReactiveSwift.Signal<A.Value, B>, ReactiveSwift.Disposable) -> () in closure #1 (ReactiveSwift.Signal<A, B>.Event) -> () in (extension in ReactiveSwift):ReactiveSwift.Signal< where A: ReactiveSwift.SignalProducerConvertible, B == A.Error>.(observeSwitchToLatest in _6345D8752C3E65AA1118F4C784F9873D)(ReactiveSwift.Signal<A.Value, B>.Observer, ReactiveSwift.SerialDisposable) -> ReactiveSwift.Disposable? (Flatten.swift:677)
7   LLLLLLLL                             0x000000010281b064 ReactiveSwift.Signal.Observer.send(ReactiveSwift.Signal<A, B>.Event) -> () (Signal.Observer.swift:111)
8   LLLLLLLL                             0x00000001027f9780 generic partial specialization <Signature = @escaping @convention(thin) @convention(method) <A, B><A1 where B: Swift.Error, A1 == ReactiveSwift.Lock> (@in_guaranteed ReactiveSwift.Signal<A, B>.Event, @guaranteed ReactiveSwift.Signal<A, B>.(Core in _6DF632AE8A9288C3EAD8EFDF3D3AF99E)<ReactiveSwift.Lock>) -> ()> of ReactiveSwift.Signal.(Core in _6DF632AE8A9288C3EAD8EFDF3D3AF99E).send(ReactiveSwift.Signal<A, B>.Event) -> () (Signal.swift:124)
9   LLLLLLLL                             0x000000010281b064 ReactiveSwift.Signal.Observer.send(ReactiveSwift.Signal<A, B>.Event) -> () (Signal.Observer.swift:111)

From looking at the source code of ReactiveSwift, this seems only possible from using flatten(.latest) or flatMap(.latest).

In my codebase, I have identified three places that use flatten(.latest). The primary suspect has been refactored to using Combine. Unfortunately this did not solve the problem. The other two suspects do not seem suspicious to me:

  1. Uses flatMapError to return a new producer, up to 3 times, after a delay of 10 seconds:
Foo.produce().flatMapError {
  if retry < 3 {
    return SignalProducer.timer(interval: .seconds(10), on: QueueScheduler.main)
    .take(first: 1)
    .flatMap(.latest) {
      return Foo.produce()
    }
  }
}
  1. Similarly, recursively returns a new producer instance until a condition is met:
func recursiveProducer() -> {
  return Foo.producer()
  .flatMap(.latest) {
    return recursiveProducer()
  }
}

The condition is checked inside Foo.producer(), if the condition is met the returned producer will complete immediately without sending a value. Before the condition is met, a producer is returned that emits one value and completes, or an error.

I am using ReactiveSwift 6.6.1. The stack trace is attached. Is either of the use-cases above the culprit, or am I missing something else?

crash_report.txt

@datwelk
Copy link
Contributor Author

datwelk commented Jul 14, 2023

I'm starting to think that a Swift compiler optimization (bug) might be causing this. Never seen the bug in debug builds (no optimization), but I do see it in release builds (-Os optimization)

@akshaybdv7
Copy link

Hello @datwelk,

Have you got any solution for this issue? I am also facing same kind of issue in our project.

Thank you.

@datwelk
Copy link
Contributor Author

datwelk commented Feb 2, 2024

Yes the issue arises when using a recursive signal without delay in between.

@datwelk datwelk closed this as completed Feb 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants