Skip to content

[SR-15599] Unexpected thread sanitizer issues with actors #57902

@swift-ci

Description

@swift-ci
Previous ID SR-15599
Radar None
Original Reporter Cameron (JIRA User)
Type Bug
Environment

macOS Version 12.1 (21C52)

Xcode Version 13.2 (13C90)

Additional Detail from JIRA
Votes 1
Component/s swift
Labels Bug
Assignee None
Priority Medium

md5: e537527687d15564192ad661b44dddad

Issue Description:

I am seeing what I believe are unexpected thread sanitizer issues with the following actor code.

Here's the actor:

public actor Example<Input: Hashable, Output> {
    private var inProgress = [Input: Task<Output, Error>]()


    private let transform: (Input) async throws -> Output


    public init(_ transform: @escaping (Input) async throws -> Output) {
        self.transform = transform
    }


    public func transform(_ x: Input) async throws -> Output {
        if let task = inProgress[x] {
            return try await task.value
        } else {
            let task = Task {
                try await transform(x) // the races are reported on this line
            }
            inProgress[x] = task
            defer {
                inProgress[x] = nil
            }
            return try await task.value
        }
    }
}

Here's the test code that demonstrates the issues:

import XCTest
import MyLibrary

final class MyLibraryTests: XCTestCase {
    func testExample() async throws {
        let example = Example { (x: Int) -> String in
            String(x)
        }
        let value = try await example.transform(1)
        XCTAssertEqual(value, "1")
    }
}

Here's my testing output:

Test Suite 'All tests' started at 2021-12-15 10:26:19.904
Test Suite 'MyLibraryTests.xctest' started at 2021-12-15 10:26:19.907
Test Suite 'MyLibraryTests' started at 2021-12-15 10:26:19.908
Test Case '-[MyLibraryTests.MyLibraryTests testExample]' started.
==================
WARNING: ThreadSanitizer: data race (pid=83691)
  Read of size 8 at 0x7b6000020458 by thread T3:
    #&#8203;0 (1) suspend resume partial function for closure #&#8203;1 in Example.transform(_:) <null>:2 (MyLibraryTests:x86_64+0x9f1e)
    #&#8203;1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


  Previous write of size 8 at 0x7b6000020458 by thread T1:
    #&#8203;0 closure #&#8203;1 in Example.transform(_:) <null>:2 (MyLibraryTests:x86_64+0x9dc3)
    #&#8203;1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


  Location is heap block of size 1016 at 0x7b6000020400 allocated by thread T1:
    #&#8203;0 malloc <null>:3 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x51d9a)
    #&#8203;1 swift::StackAllocator<1000ul>::getSlabForAllocation(unsigned long) <null>:2 (libswift_Concurrency.dylib:x86_64+0x32769)
    #&#8203;2 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


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


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


SUMMARY: ThreadSanitizer: data race (MyLibraryTests:x86_64+0x9f1e) in (1) suspend resume partial function for closure #&#8203;1 in Example.transform(_:)+0x4e
==================
ThreadSanitizer report breakpoint hit. Use 'thread info -s' to get extended information about the report.
==================
WARNING: ThreadSanitizer: data race (pid=83691)
  Write of size 8 at 0x7b6000020438 by thread T3:
    #&#8203;0 (1) suspend resume partial function for closure #&#8203;1 in Example.transform(_:) <null>:2 (MyLibraryTests:x86_64+0x9f3c)
    #&#8203;1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


  Previous write of size 8 at 0x7b6000020438 by thread T1:
    #&#8203;0 closure #&#8203;1 in Example.transform(_:) <null>:2 (MyLibraryTests:x86_64+0x9e14)
    #&#8203;1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


  Location is heap block of size 1016 at 0x7b6000020400 allocated by thread T1:
    #&#8203;0 malloc <null>:3 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x51d9a)
    #&#8203;1 swift::StackAllocator<1000ul>::getSlabForAllocation(unsigned long) <null>:2 (libswift_Concurrency.dylib:x86_64+0x32769)
    #&#8203;2 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


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


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


SUMMARY: ThreadSanitizer: data race (MyLibraryTests:x86_64+0x9f3c) in (1) suspend resume partial function for closure #&#8203;1 in Example.transform(_:)+0x6c
==================
ThreadSanitizer report breakpoint hit. Use 'thread info -s' to get extended information about the report.
==================
WARNING: ThreadSanitizer: data race (pid=83691)
  Read of size 8 at 0x7b6000020460 by thread T3:
    #&#8203;0 (1) suspend resume partial function for closure #&#8203;1 in Example.transform(_:) <null>:2 (MyLibraryTests:x86_64+0xa03b)
    #&#8203;1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


  Previous write of size 8 at 0x7b6000020460 by thread T1:
    #&#8203;0 closure #&#8203;1 in Example.transform(_:) <null>:2 (MyLibraryTests:x86_64+0x9dae)
    #&#8203;1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


  Location is heap block of size 1016 at 0x7b6000020400 allocated by thread T1:
    #&#8203;0 malloc <null>:3 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x51d9a)
    #&#8203;1 swift::StackAllocator<1000ul>::getSlabForAllocation(unsigned long) <null>:2 (libswift_Concurrency.dylib:x86_64+0x32769)
    #&#8203;2 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


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


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


SUMMARY: ThreadSanitizer: data race (MyLibraryTests:x86_64+0xa03b) in (1) suspend resume partial function for closure #&#8203;1 in Example.transform(_:)+0x16b
==================
ThreadSanitizer report breakpoint hit. Use 'thread info -s' to get extended information about the report.
==================
WARNING: ThreadSanitizer: data race (pid=83691)
  Read of size 8 at 0x7b6000020450 by thread T3:
    #&#8203;0 (1) suspend resume partial function for closure #&#8203;1 in Example.transform(_:) <null>:2 (MyLibraryTests:x86_64+0xa050)
    #&#8203;1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


  Previous write of size 8 at 0x7b6000020450 by thread T1:
    #&#8203;0 closure #&#8203;1 in Example.transform(_:) <null>:2 (MyLibraryTests:x86_64+0x9dd8)
    #&#8203;1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


  Location is heap block of size 1016 at 0x7b6000020400 allocated by thread T1:
    #&#8203;0 malloc <null>:3 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x51d9a)
    #&#8203;1 swift::StackAllocator<1000ul>::getSlabForAllocation(unsigned long) <null>:2 (libswift_Concurrency.dylib:x86_64+0x32769)
    #&#8203;2 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


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


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


SUMMARY: ThreadSanitizer: data race (MyLibraryTests:x86_64+0xa050) in (1) suspend resume partial function for closure #&#8203;1 in Example.transform(_:)+0x180
==================
ThreadSanitizer report breakpoint hit. Use 'thread info -s' to get extended information about the report.
==================
WARNING: ThreadSanitizer: data race (pid=83691)
  Read of size 8 at 0x7b6000020468 by thread T3:
    #&#8203;0 (2) await resume partial function for closure #&#8203;1 in Example.transform(_:) <null>:2 (MyLibraryTests:x86_64+0xa180)
    #&#8203;1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


  Previous write of size 8 at 0x7b6000020468 by thread T1:
    #&#8203;0 closure #&#8203;1 in Example.transform(_:) <null>:2 (MyLibraryTests:x86_64+0x9e73)
    #&#8203;1 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


  Location is heap block of size 1016 at 0x7b6000020400 allocated by thread T1:
    #&#8203;0 malloc <null>:3 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x51d9a)
    #&#8203;1 swift::StackAllocator<1000ul>::getSlabForAllocation(unsigned long) <null>:2 (libswift_Concurrency.dylib:x86_64+0x32769)
    #&#8203;2 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:2 (libswift_Concurrency.dylib:x86_64+0x2d913)


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


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


SUMMARY: ThreadSanitizer: data race (MyLibraryTests:x86_64+0xa180) in (2) await resume partial function for closure #&#8203;1 in Example.transform(_:)+0xe0
==================
ThreadSanitizer report breakpoint hit. Use 'thread info -s' to get extended information about the report.
Test Case '-[MyLibraryTests.MyLibraryTests testExample]' passed (4.684 seconds).
Test Suite 'MyLibraryTests' passed at 2021-12-15 10:26:24.594.
     Executed 1 test, with 0 failures (0 unexpected) in 4.684 (4.686) seconds
Test Suite 'MyLibraryTests.xctest' passed at 2021-12-15 10:26:24.595.
     Executed 1 test, with 0 failures (0 unexpected) in 4.684 (4.688) seconds
Test Suite 'All tests' passed at 2021-12-15 10:26:24.596.
     Executed 1 test, with 0 failures (0 unexpected) in 4.684 (4.692) seconds
ThreadSanitizer: reported 5 warnings
Program ended with exit code: 66

Metadata

Metadata

Assignees

No one assigned

    Labels

    TSanFor issues in the Thread Sanitizer itselfactorFeature → concurrency: `actor` declarationsasync & awaitFeature → concurrency: asynchronous function aka the async/await patternbugA deviation from expected or documented behavior. Also: expected but undesirable behavior.compilerThe Swift compiler itselfconcurrencyFeature: umbrella label for concurrency language featuresfound by tsanFlag: An issue found by the Thread Sanitizerunexpected behaviorBug: Unexpected behavior or incorrect output

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions