0

In the following code, which is a simplified version of a more elaborate pipeline, "Done processing" is never called for 2.

Why is that?

I suspect this is a problem due to the demand, but I cannot figure out the cause.

Note that if I remove the combineLatest() or the compactMap(), the value 2 is properly processed (but I need these combineLatest and compactMap for correctness, in my real example they are more involved).

var cancellables = Set<AnyCancellable>([])

func process<T>(_ value: T) -> AnyPublisher<T, Never> {
    return Future<T, Never> { promise in
        print("Starting processing of \(value)")
        DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.1) {
            promise(.success(value))
        }
    }.eraseToAnyPublisher()
}

let s = PassthroughSubject<Int?, Never>()

s
    .print("Combine->Subject")
    .combineLatest(Just(true))
    .print("Compact->Combine")
    .compactMap { value, _ in value }
    .print("Sink->Compact")
    .flatMap(maxPublishers: .max(1)) { process($0) }
    .sink {
        print("Done processing \($0)")
    }
    .store(in: &cancellables)

s.send(nil)

// Give time for flatMap to finish
Thread.sleep(forTimeInterval: 1)
s.send(2)
Kamchatka
  • 3,597
  • 4
  • 38
  • 69
  • `.flatMap(maxPublishers: .max(2)) { process($0) }` will help – Aznix Feb 02 '21 at 09:56
  • @Aznix that only translates the problem by 1. – Kamchatka Feb 02 '21 at 09:57
  • I wonder if this problem isn't similar to https://stackoverflow.com/questions/61143246/why-does-publishers-map-consume-upstream-values-eagerly. – Kamchatka Feb 02 '21 at 09:58
  • Indeed, it is rather odd (maybe even a bug). For some reason, when `combineLatest` receives an additional demand of 1 coming from `compactMap`, it doesn't forward it upstream. This happens only when the demand is received "synchronously" (as per a `print` publisher), when `compactMap` filters out a `nil` value. – New Dev Feb 03 '21 at 04:06
  • It sounds like a bug to me as well. I've submitted a radar. – Kamchatka Feb 03 '21 at 14:50

1 Answers1

0

It sounds like a bug of combineLatest. When a downstream request additional demand "synchronously" (as per-print publisher output), that demand doesn't flow upstream.

One way to overcome this is to wrap the downstream of combineLatest in a flatMap:

s
    .combineLatest(Just(true))
    .flatMap(maxPublishers: .max(1)) {
        Just($0)
          .compactMap { value, _ in value }
          .flatMap { process($0) }
    }
    .sink {
        print("Done processing \($0)")
    }
    .store(in: &cancellables)

The outer flatMap now creates the back pressure, and the inner flatMap doesn't need it anymore.

New Dev
  • 48,427
  • 12
  • 87
  • 129