Гонка данных в источнике таймера отправки

ThreadSanitizer обнаруживает гонку данных в следующей программе Swift macOS:

import Dispatch


class Foo<T> {
    var value: T?
    let queue = DispatchQueue(label: "Foo syncQueue")
    init(){}
    func complete(value: T) {
        queue.sync {
            self.value = value
        }
    }

    static func completeAfter(_ delay: Double, value: T) -> Foo<T> {
        let returnedFoo = Foo<T>()
        let queue = DispatchQueue(label: "timerEventHandler")
        let timer = DispatchSource.makeTimerSource(queue: queue)
        timer.setEventHandler {
            returnedFoo.complete(value: value)
            timer.cancel()
        }
        timer.scheduleOneshot(deadline: .now() + delay)
        timer.resume()
        return returnedFoo
    }
}



func testCompleteAfter() {

    let foo = Foo<Int>.completeAfter(0.1, value: 1)
    sleep(10)
}

testCompleteAfter()

При работе на iOS Simulator ThreadSanitizer не обнаруживает гонки.

Выход ThreadSanitizer:

WARNING: ThreadSanitizer: data race (pid=71596)
  Read of size 8 at 0x7d0c0000eb48 by thread T2:
    #0 block_destroy_helper.5 main.swift (DispatchTimerSourceDataRace+0x0001000040fb)
    #1 _Block_release <null>:38 (libsystem_blocks.dylib+0x000000000951)

  Previous write of size 8 at 0x7d0c0000eb48 by main thread:
    #0 block_copy_helper.4 main.swift (DispatchTimerSourceDataRace+0x0001000040b0)
    #1 _Block_copy <null>:38 (libsystem_blocks.dylib+0x0000000008b2)
    #2 testCompleteAfter() -> () main.swift:40 (DispatchTimerSourceDataRace+0x000100003981)
    #3 main main.swift:44 (DispatchTimerSourceDataRace+0x000100002250)

  Location is heap block of size 48 at 0x7d0c0000eb20 allocated by main thread:
    #0 malloc <null>:144 (libclang_rt.tsan_osx_dynamic.dylib+0x00000004188a)
    #1 _Block_copy <null>:38 (libsystem_blocks.dylib+0x000000000873)
    #2 testCompleteAfter() -> () main.swift:40 (DispatchTimerSourceDataRace+0x000100003981)
    #3 main main.swift:44 (DispatchTimerSourceDataRace+0x000100002250)

  Thread T2 (tid=3107318, running) created by thread T-1
    [failed to restore the stack]

SUMMARY: ThreadSanitizer: data race main.swift in block_destroy_helper.5

Есть ли что-нибудь подозрительное в коде?

1 ответ

Комментарий @Rob заставил меня снова задуматься о проблеме. Я придумал следующую модификацию для статического функционала completeAfter - каким ThreadSanitizer доволен *):

static func completeAfter(_ delay: Double, value: T) -> Foo<T> {
    let returnedFoo = Foo<T>()
    let queue = DispatchQueue(label: "timerEventHandler")
    queue.async {
        let timer = DispatchSource.makeTimerSource(queue: queue)
        timer.setEventHandler {
            returnedFoo.complete(value: value)
            timer.cancel()
        }
        timer.scheduleOneshot(deadline: .now() + delay)
        timer.resume()
    }
    return returnedFoo
}

Это изменение гарантирует, что все доступы к timer будет выполнен в очереди queue, который пытается синхронизировать таймер таким образом. Хотя это же решение в моем "реальном" коде не работает с этим решением, оно, вероятно, связано с другими внешними факторами.


*) Мы никогда не должны думать, что в нашем коде нет гонок, просто потому, что ThreadSanitizer не обнаруживает их. Могут быть внешние факторы, которые просто "стирают" потенциальную гонку данных (например, диспетчер lib выполняет два блока с конфликтующим доступом в одном потоке - и гонка данных не может произойти)

Другие вопросы по тегам