【问题标题】:Race condition in DjangoDjango 中的竞争条件
【发布时间】: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.statuscjob.status 的值是多少?有哪些类型?
  • 这些是单个字符:Job.EXECUTING == 'E'。第 5 行的查询确实排除了状态为“E”的作业(实际上,它只接受状态为“W”或“N”的作业)。

标签: python django race-condition


【解决方案1】:

已通过手动更新事务解决了该问题。自事务开始以来,QuerySet 似乎没有更新。当两个 QuerySet 以某种方式同时启动,并且两个 QuerySet 中都会发生一项工作时,它会破坏运行器。

看了this answer之后,我想出了一个解决方案:就在return True之前,事务被提交了。

【讨论】:

    猜你喜欢
    • 2012-11-24
    • 2023-03-30
    • 2014-04-02
    • 2012-11-05
    • 2016-03-07
    • 2013-02-27
    • 2021-04-16
    • 1970-01-01
    • 2023-04-03
    相关资源
    最近更新 更多