TicTacToe random error : Combine bug?

Hello TCA fans,

I've witnessed a bug in the TicTacToe sample application and it looks like a Combine bug :
Sometimes when pressing the login button with 2fa@gmail.com / password as credentials, nothing happens. It's completely random and I can try 20 times without a problem and then it occurs for some reason. I've investigated the issue and realised the action loginResponse never arrived and seemed to be lost in the 1 second delaying. I've managed to reduce the problem to these few lines running in a playground :

import Combine
import Foundation

struct C {
    static var cancellables = [Cancellable]()

func performLoop(_ loopNb: Int) {
    print("loop \(loopNb)")
    var receivedValue = false
        .delay(for: .milliseconds(500), scheduler: DispatchQueue.global())
        .receive(on: DispatchQueue.main)
        .sink(receiveCompletion: { _ in
            if receivedValue {
                performLoop(loopNb + 1)
            } else {
                print("Here is the bug !!!!")
        }, receiveValue: { value in
            print("my value is here !")
            receivedValue = true


After a few loops (totally random can be 1 can be 300), the completion is fired and the value never comes ("Here is the bug" gets printed)
Note that if we comment .receive(on: DispatchQueue.main), the value always arrives but the completion sometimes arrives before the value ...

As it happens on XCode 11.5 and XCode 12 beta, I'm going to file a report to Apple for this.
Have you already seen this kind of behavior from delay operator and perhaps find a way to circumvent it for now ? :sweat_smile:

Thanks for reading this and for your great work ! :slight_smile:

1 Like

Wow thanks for tracking this down @bioche! It's something that @mbrandonw and I have noticed but we haven't spent enough time to figure out what was going on. If it's a Combine bug hopefully it will get fixed! But maybe this is enough to figure out a workaround for the demos. Maybe we can avoid the delay operator (if it is the culprit) and do an asyncAfter instead.

1 Like

@stephencelis @mbrandonw I actually have got a response from Apple on this issue !

Basically the reason behind this weird behavior is that DispatchQueue.global() gives a concurrent queue which puts the events in random order. If instead we use a serial queue we have control over like DispatchQueue(label: "delay-queue") it solves the issue :slight_smile:

I guess it's good to know to avoid any future head scratcher :joy:


@bioche Good find, thanks! We'll get a fix in soon: https://github.com/pointfreeco/swift-composable-architecture/pull/320

Terms of Service

Privacy Policy

Cookie Policy