2011-03-17 3 views
0

Dans Django, j'ai rencontré des conditions de course sérieuses. Le problème commence lorsque deux coureurs essaient d'exécuter some_method() en même temps. L'exploitation forestière créée est la suivante:Condition de course dans Django

Job 3: Candidate 
Job 3: Already taken 
Job 3: Candidate 
Job 3: Already taken 
Job 3: Candidate 
Job 3: Already taken 
(et cetera for 18 MB) 

La méthode suivante me donne du mal. Il convient de noter que la méthode est ré-ran jusqu'à ce que la méthode retourne False:

def some_method(): 
    conditions = #(amongst others, excludes jobs with status EXECUTING) 

    try: 
     cjob = Job.objects.filter(conditions).order_by(some_fields)[0] 
    except IndexError: 
     return False 

    print 'Job %s: Candidate' % cjob.id 

    job = cjob.for_update() 

    if cjob.status != job.status: 
     print 'Job %s: Already taken' % cjob.id 
     return True 

    print 'Job %s: Starting...' % job.id 

    job.status = Job.EXECUTING 
    job.save() 
    # Critical section 

# In models.py: 
class Job(models.Model): 
    # ... 

    def for_update(self): 
     return Job.objects.raw('SELECT * FROM `backend_job` WHERE `id` = %s FOR UPDATE', (self.id,))[0] 

Actuellement, Django ne dispose pas d'une méthode for_update dédiée et pour empêcher la création de la requête avec toutes les conditions que nous utilisons pour déterminez si le travail doit être exécuté, nous effectuons la requête difficile avant la requête FOR UPDATE simple.

Je ne vois pas vraiment comment cela pourrait causer le problème que nous voyons, nous faisons la requête, suivie par une instruction qui bloque quand un autre coureur détient le verrou sur le travail. Le verrou n'est libéré qu'une fois l'état du travail modifié. Le deuxième coureur obtient maintenant le verrou, mais le statut du travail a été changé, il revient donc de la méthode, seulement pour le ré-entrer plus tard; mais la requête cjob ne retournera pas le même travail, car son statut est maintenant exclu par le filtre. Est-ce que j'interprète mal la clause FOR UPDATE, ou est-ce que je manque quelque chose d'autre?

Il est à noter que j'utilise MySQL avec InnoDB et que Celery ne correspond pas à cette solution.

+0

? Quels sont les types? – dappawit

+0

Ce sont des caractères uniques: Job.EXECUTING == 'E'. La requête à la ligne 5 exclut les travaux avec le statut «E» (en fait, il n'accepte que ceux avec «W» ou «N»). – ralphje

Répondre

0

Le problème a été résolu en mettant à jour manuellement la transaction. Il semble que le QuerySet n'a pas été mis à jour depuis le début de la transaction. Lorsque deux QuerySets démarreraient en même temps, et qu'un travail se produirait dans les deux QuerySets, les coureurs seraient disloqués. Après avoir lu this answer, j'ai trouvé une solution: juste avant le return True, la transaction est validée. Quelles sont les valeurs de `job.status` et` cjob.status`?

Questions connexes