ReactiveCocoa / ReactiveSwift

Streams of values over time

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Infinite recursion in observeSwitchToLatest()

datwelk opened this issue · comments

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

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)

Hello @datwelk,

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

Thank you.

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