【发布时间】:2011-07-17 15:41:18
【问题描述】:
在 Django 中,我遇到了一些严重的竞态条件。当两个跑步者尝试同时执行 some_method() 时,麻烦就开始了。创建的日志如下:
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)
以下方法给我带来了麻烦。需要注意的是,方法会重新运行,直到方法返回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]
目前,Django 没有专门的 for_update 方法,并且为了防止使用我们用来确定是否必须运行作业的所有条件来创建查询,我们在简单的 FOR UPDATE 查询之前执行困难查询。
我真的不明白这会如何导致我们看到的麻烦,我们进行查询,然后是当另一个运行程序持有作业锁定时阻塞的语句。锁定仅在作业状态更改后才释放。第二个运行者现在获得了锁,但是作业的状态发生了变化,所以它从方法返回,只是稍后重新进入;但cjob-query 不会再次返回相同的作业,因为它的状态现在已被过滤器排除。
我是否误解了 FOR UPDATE 子句,还是我遗漏了其他内容?
需要注意的是,我使用 MySQL 和 InnoDB,Celery 不适合这个解决方案。
【问题讨论】:
-
job.status和cjob.status的值是多少?有哪些类型? -
这些是单个字符:Job.EXECUTING == 'E'。第 5 行的查询确实排除了状态为“E”的作业(实际上,它只接受状态为“W”或“N”的作业)。
标签: python django race-condition