2017-01-26 1 views
0

ThreadSanitizer détecte une course de données dans l'exécution du programme Swift suivant sur macOS:Race données dans Dispatch timer Source

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() 

Lors de l'exécution sur iOS Simulator, ThreadSanitizer ne détecte pas une course.

sortie 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 

Y at-il quelque chose de suspect avec le code?

+0

Vous créez les choses dans une file d'attente et les libérant dans une autre (à cause du comportement de 'DispatchSourceTimer' pour ne pas se conserver comme [' Timer'] (https://developer.apple.com/reference/foundation/timer), donc ça ne retenu en raison de votre forte référence dans la fermeture). Bien sûr, vous pouvez renvoyer 'timer.cancel()' à la file d'attente principale, mais cela ne semble pas être la solution généralisée. Mais je ne suis pas sûr de savoir comment résoudre ce problème car je ne suis pas sûr de ce que vous essayez de faire ici, pourquoi vous n'utilisez pas 'asyncAfter', pourquoi cette méthode est' static', etc. – Rob

+0

Merci @Rob . Ce 'timer 'sera importé intentionnellement dans le gestionnaire d'événements, car un client n'aura pas de référence. La minuterie sera annulée par d'autres moyens (non représentés). Le but de l'importation de la référence de temporisateur est de maintenir le temporisateur actif jusqu'à ce que le gestionnaire s'exécute. Donc, si ce problème vient du fait que la désinitialisation d'une minuterie accède à des variables qui ont déjà été écrites sur un autre thread sans synchronisation correcte, c'est un peu dommage. J'ai déjà essayé d'inclure tous les accès au timer dans la file d'attente 'queue', mais le problème persiste. – CouchDeveloper

+0

Remarque: l'objet 'foo' sera également affecté au thread A, puis distribué au thread B, modifié sur B (aucune course de données, puisque dispatch insère les barrières de mémoire appropriées), puis désalloué en A. Le désallocation peut potentiellement avoir des données courses aussi bien, sauf si 'deinit' ou le mécanisme ref-count insère des barrières de mémoire appropriées (que nous ne connaissons pas). – CouchDeveloper

Répondre

0

Le commentaire de @Rob m'a fait réfléchir à nouveau sur le problème. Je suis venu avec la modification suivante pour fonc completeAfter statique - qui ThreadSanitizer est satisfait *):

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 
} 

Ce changement garantit que tous les accès à timer seront exécutés dans la file queue, qui essaie de synchroniser l'horloge de cette façon . Même si cette même solution dans mon code «réel» n'a pas fonctionné avec cette solution, c'est probablement dû à d'autres facteurs externes. *) Nous ne devrions jamais penser, notre code n'a pas de races, juste parce que ThreadSanitizer n'en détecte pas. Il peut y avoir des facteurs externes qui viennent "effacer" une course de données potentielle (par exemple, dispatch lib exécute deux blocs avec un accès conflictuel sur le même thread - et aucune course de données ne peut se produire)