首页 > 解决方案 > 可能的 django 竞争条件

问题描述

@receiver(post_save, sender=MyRequestLog)
def steptwo_launcher(sender, instance, **kwargs):
    GeneralLogging(calledBy='MyRequestLog', logmsg='enter steptwo_launcher').save()  # remember to remove this line
    if instance.stepCode == 100:
       GeneralLogging(calledBy='MyRequestLog', logmsg='step code 100 found. launch next step').save()
       nextStep.delay(instance.requestId,False)

我想我只是目睹了我的代码失去了竞争条件。我的应用程序的后端会更新任务一的状态,并在应该启动下一个任务时将 stepCode 100 写入日志。应用程序的前端轮询以将当前步骤报告给最终用户。

似乎在后端创建了一个带有 stepCode 100 的条目后,前端请求很快就进来了,以至于 if instance.stepCode == 100: 从未被发现为 True。GeneralLogging 仅在可疑碰撞时报告一个条目,不会启动下一步。

我的问题是 1)确认这是可能的,我已经怀疑了。和 2) 一种解决此问题的方法,因此 nextStep 不会由于竞争条件而被跳过。

标签: pythondjangosignals

解决方案


这个问题缺少一堆可能有用的信息(例如缺少代码,缺少输出),但是任何形式的代码

if state == x:
    change_state

当多个控制路径命中此代码时,有潜在的竞争条件。

处理此问题的两种最常见的方法是 (1) 锁:

with some_lock:
    if state:
        change_state

即在我们完成之前阻止其他人点击此代码,并且(2)排队:

queue.push(item_to_be_processed)

# somewhere else
item_to_be_processed = queue.pop()

数据库中的队列/锁实现可以使用select_for_update和使用额外的processed字段,即让“编写器”进程保存模型processed = False并让“读取器”进程执行:

from django.db import transaction
...
with transaction.atomic():
    items = MyRequestLog.objects.select_for_update(skip_locked=True).filter(
        stepCode=100, 
        processed=False
    )
    for item in items:
        do_something_useful(item)  # you might want to pull this outside of the atomic block if your application allows (so you don't keep rows locked for an extended period)
        item.processed = True
        item.save()

ps:检查您的数据库以获得支持(https://docs.djangoproject.com/en/2.0/ref/models/querysets/#select-for-update


推荐阅读