Indeterministic behavior in Combine?

Hi, I was able to create an example that does not use any receiveOn or multi-threading/concurrency that shows indeterministic behavior. I am wondering if that is a bug. I already filed as FB9108105 but I am curious to hear your thoughts.

I have two subjects 1 and 2 and marble-style emitting looks like this

A: 1   2   3 
B:        X

I was actually looking for a publisher that combinesLatest but buffers the „missed“ values from A, so that I get (1,X), (2,X), (3,X). Original question here: ios - Elegant way to combineLatest without dropping values and imbalanced publishers in Swift Combine - Stack Overflow

While looking for a solution, I found one that buffers the values of both subjects until the other one emits the first value. However, I am seeing indeterministic behavior. Sometimes, I get (2,X), (3,X) and sometimes (1,X), (2,X), (3,X). Since my solutions does not involve any receiveOn, I would expect to see either or and that consistently.

Here is the minimal example. It will output how often the sink sees 3 values and how often 2.

import UIKit
import Combine

func runExperimentAndReturnNumberOfValuesInSink() -> Int {

    var resultingValues:Int = 0
    
    let subject1 = PassthroughSubject<Int, Never>()
    let subject2 = PassthroughSubject<String, Never>()

    let bufferedSubject1 = Publishers.Concatenate(prefix: Publishers.PrefixUntilOutput(upstream: subject1, other: subject2).collect().flatMap(\.publisher),
                                          suffix: subject1)
    let bufferedSubject2 = Publishers.Concatenate(prefix: Publishers.PrefixUntilOutput(upstream: subject2, other: subject1).collect().flatMap(\.publisher),
                                          suffix: subject2)

    let combined = Publishers.CombineLatest(bufferedSubject1, bufferedSubject2)

    let cancelable = combined.collect()
        .sink(receiveValue: { v in
            resultingValues = v.count
        })

    subject1.send(1)
    subject1.send(2)
    subject2.send("X")
    subject2.send(completion: .finished)
    subject1.send(3)
    subject1.send(completion: .finished)
    
    return resultingValues
    
}

var retrievedValues =  [2: 0, 3:0]

for _ in (1...100) {
    retrievedValues[runExperimentAndReturnNumberOfValuesInSink()]! += 1
}

print("If the behavior was deterministic, we would expect 100 runs with the same output.")
print(retrievedValues)
Terms of Service

Privacy Policy

Cookie Policy