J'écris un démon pour surveiller la création de nouveaux objets, ce qui ajoute des lignes à une table de base de données quand il détecte de nouvelles choses. Nous appellerons les objets widgets. Le flux de code est à peu près ceci:en utilisant une transaction pour éviter une course
1: every so often:
2: find newest N widgets (from external source)
3: foreach widget
4: if(widget not yet in database)
5: add rows for widget
Les deux dernières lignes présentent une condition de course, car si deux instances de ce démon sont en cours d'exécution en même temps, ils peuvent à la fois créer une ligne pour un widget X si les lignes de synchronisation en haut La solution la plus évidente consisterait à utiliser une contrainte unique
dans la colonne d'identifiant du widget, mais cela n'est pas possible en raison de la disposition de la base de données (il est en effet possible d'avoir plusieurs lignes pour un widget, mais le daemon devrait ne le ferai jamais automatiquement). Ma prochaine pensée serait d'utiliser une transaction, puisque c'est ce à quoi ils sont destinés. Dans le monde ADO.NET, je crois que je voudrais un niveau d'isolement de Serializable
, mais je ne suis pas positif. Quelqu'un peut me diriger dans la bonne direction?
Mise à jour: J'ai fait quelques expérimentations, et la transaction sérialisée ne semble pas résoudre le problème, ou du moins pas très bien. Le cas intéressant est décrit ci-dessous et suppose qu'une seule table est impliquée. Notez que je ne suis pas positif sur les détails de blocage, mais je pense que je l'ai droit:
Thread A: Executes line 4, acquiring a read lock on the table
Thread B: Executes line 4, acquiring a read lock on the table
Thread A: Tries to execute line 5, which requires upgrading to a write lock
(this requires waiting until Thread B unlocks the table)
Thread B: Tries to execute line 5, again requiring a lock upgrade
(this requires waiting until Thread A unlocks)
Cela nous laisse dans une situation de blocage classique. D'autres chemins de code sont possibles, mais si les threads A et B n'entrelacent pas, il n'y a aucun problème de synchronisation. Le résultat final est qu'une exception SqlException est lancée sur l'un des threads, après que SQL détecte le blocage et termine l'une des instructions. Je peux attraper cette exception et détecter le code d'erreur particulier, mais cela ne me semble pas très propre.
Une autre route que je peux prendre est de créer une deuxième table qui suit les widgets vus par le démon, où je peux utiliser une contrainte unique
. Cela nécessite encore d'attraper et de détecter certains codes d'erreur (dans ce cas, les violations de la contrainte d'intégrité), donc je suis toujours intéressé par une meilleure solution, si quelqu'un peut y penser.
Merci pour les idées. Malheureusement, je pense que cela ne résoudra pas le problème (je vais l'expliquer dans la question initiale). – Charlie
D'un point de vue de la cohérence, cela fonctionne :-) mais bien sûr ce n'est pas bon si cela aboutit à des verrous morts. J'ai peur si je ne peux pas aider beaucoup, mais j'ajoute quelques alternatives à ma réponse. –
Merci encore d'avoir donné tant de réflexion à cela. J'avais aussi considéré l'idée de service, et je pense que ce serait la solution idéale dans mon cas particulier (puisque je peux alors utiliser plus de primitives de verrouillage "standard" comme des mutex ou des moniteurs). Je suis d'accord qu'ayant deux démons comme je l'ai décrit n'est pas une bonne idée. – Charlie