ThreadSanitizer data race warning in `Task`

565 Views Asked by At

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

0

There are 0 best solutions below