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?
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
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
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