0

I am running Unit tests with Thread Sanitizer enabled and I am getting a Data Race warning when executing the statement

try? await Task.sleep(nanoseconds: 10_000_000)

Update

I was able to reproduce the data race warning with only Swift Combine and Swift Concurrency, i.e. my own code is not the culprit, and so I removed it.

Instead, below is a self running XCTest. Note, that you need to enable "Thread Sanitizer" in the "Diagnostics" Section of the Test Scheme.

The test cancels the task after 10 seconds, and the above statement will be called every 0.01 seconds in order to provoke a data race. See below:

import XCTest
import Combine

extension Publisher {
    func asyncMap<T>(
        _ transform: @escaping @Sendable (Output) async -> T
    ) -> Publishers.FlatMap<Future<T, Never>, Self> where Output: Sendable {
        flatMap { value in
            Future { promise in
                Task {
                    let output = await transform(value)
                    promise(.success(output))
                }
            }
        }
    }
}

class ThreadSafetyTests: XCTestCase {

    func testExample() throws {

        let expectCompletion = expectation(description: "completion")
        expectCompletion.isInverted = true

        let cancellable = ["a", "b", "b"].publisher
        .asyncMap { (string: String) -> String  in
            while !Task.isCancelled {
                try? await Task.sleep(nanoseconds: 10_000_000)  // 0.01 second
            }
            return string.uppercased()
        }
        .sink { completion in
            expectCompletion.fulfill()  // it should never fullfill!
        } receiveValue: { string in
        }

        wait(for: [expectCompletion], timeout: 10)
        cancellable.cancel()
    }
}

Detailed Console Log:

WARNING: ThreadSanitizer: data race (pid=40024)
  Read of size 8 at 0x7b6400040d30 by thread T3:
    #0 (1) await resume partial function for closure #1 in ThreadSafetyTests.testExample() ThreadSafetyTests.swift:29 (OakTests:x86_64+0x6846a)
    #1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2be53)

  Previous write of size 8 at 0x7b6400040d30 by thread T1:
    #0 (1) await resume partial function for closure #1 in ThreadSafetyTests.testExample() ThreadSafetyTests.swift:29 (OakTests:x86_64+0x6857e)
    #1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2be53)

  Location is heap block of size 1032 at 0x7b6400040b00 allocated by thread T6:
    #0 malloc <null>:3 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x532ec)
    #1 swift::StackAllocator<1000ul, &(swift::TaskAllocatorSlabMetadata)>::getSlabForAllocation(unsigned long) <null>:2 (libswift_Concurrency.dylib:x86_64+0x30c9a)
    #2 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2be53)

  Thread T3 (tid=4063077, running) is a GCD worker thread

  Thread T1 (tid=4063076, running) is a GCD worker thread

  Thread T6 (tid=4063081, running) is a GCD worker thread

SUMMARY: ThreadSanitizer: data race ThreadSafetyTests.swift:29 in (1) await resume partial function for closure #1 in ThreadSafetyTests.testExample()

So, probably there is no solution for us to fix it - and I hope this is a false positive. Comments appreciated ;)

Updated

deleted

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • Do you experience the TSAN issue in a normal app? I’ve seen [report](https://stackoverflow.com/questions/72009251/threadsanitizer-vs-async-await-in-xctest) of TSAN issues with Swift concurrency in XCTest. I wonder if it is Swift concurrency within Combine, or just Swift concurrency within XCTest. – Rob Jun 03 '22 at 12:41
  • 1
    Not an official answer, but I believe TSan just isn't aware of the concurrency model yet: https://forums.swift.org/t/tsan-with-task-concurrency-warnings/57775 It may be curious to see how things develop with WWDC upcoming, with potentially improved support. – Itai Ferber Jun 03 '22 at 13:10
  • @Rob I haven't tried it yet. So, I can't tell. This is a swift package. Chances are higher to get data races in Unit tests than in Apps. Maybe it's only `Task.sleep` which has an issue. I will keep an eye on this pattern, and when used in my current app, I try it with TSan. As Itai Ferber pointed out with his link (thanks, Itai! ;) ), Swift might not be ready yet to reliable use TSan. – CouchDeveloper Jun 03 '22 at 15:03

0 Answers0