Django会话竞争条件?

Posted

技术标签:

【中文标题】Django会话竞争条件?【英文标题】:Django session race condition? 【发布时间】:2012-11-24 18:00:54 【问题描述】:

总结:Django 会话中是否存在竞争条件,我该如何预防?

我对 Django 会话有一个有趣的问题,我认为这涉及由于同一用户的同时请求而导致的竞争条件。

它发生在同时上传多个文件的脚本中,正在本地主机上进行测试。我认为这很可能会导致来自同一用户的同时请求(由于本地主机的响应时间低,由于文件上传导致的请求时间长)。不过,在本地主机之外的正常请求仍然是可能的,只是可能性较小。

我正在发送几个我认为这样做的(文件发布)请求:

    Django 自动检索用户的会话* 不相关的代码需要一些时间 获取request.session['files'](字典) 将有关当前文件的数据附加到字典中 再次将字典存储在request.session['files']中 检查是否确实已存储 更多不相关的代码需要时间 Django 自动存储用户的会话

这里 6. 处的检查将表明信息确实已存储在会话中。但是,未来的请求表明有时有,有时没有。

我认为正在发生的是其中两个请求(A 和 B)同时发生。请求 A 首先检索request.session['files'],然后 B 执行相同操作,对其进行更改并存储它。当 A 最终完成时,它会覆盖 B 的会话更改。

两个问题:

    真的是这样吗? django开发服务器是多线程的吗?在谷歌上,我正在寻找关于使其成为多线程的页面,这表明默认情况下它不是?否则会是什么问题? 如果这个竞争条件是问题所在,解决它的最佳方法是什么?这是一个不便,但不是安全问题,所以如果机会能大大减少,我已经很高兴了。

在更改之前检索会话数据并在之后立即保存应该会大大降低我认为的机会。但是我还没有找到为request.session 执行此操作的方法,只能使用django.contrib.sessions.backends.db.SessionStore 解决它。但是我认为,如果我以这种方式更改它,Django 只会在请求结束时用request.session 覆盖它。

所以我基本上需要request.session.reload()request.session.commit()

【问题讨论】:

【参考方案1】:

    是的,一个请求可以在另一个请求完成之前开始。您可以通过在视图的开头和结尾打印一些内容并同时启动一堆请求来检查这一点。

    确实,会话在视图之前加载并在视图之后保存。您可以使用request.session = engine.SessionStore(session_key) 重新加载会话并使用request.session.save() 保存它。

但是,重新加载会话会丢弃之前添加到会话中的任何数据(在视图中或之前)。在重新加载之前保存会破坏延迟加载的点。更好的方法是将文件作为新模型保存到数据库中。

答案的精髓在于对 Thomas 的回答的讨论,由于不完整,所以我发布了完整的答案。

【讨论】:

不知道为什么,但会话重新加载不起作用,即使之前有睡眠。然而,数据库解决方案工作得非常好,非常值得额外的表。 您可能需要 db(来自 django.contrib.sessions.backends)而不是引擎。 @JuusoOhtonen 我认为你不应该指定db,因为你可能想切换到另一个会话后端。见docs.djangoproject.com/en/2.0/topics/http/sessions“本节中的示例直接从django.contrib.sessions.backends.db后端导入SessionStore对象。在你自己的代码中,你应该考虑从SESSION_ENGINE指定的会话引擎中导入SessionStore”【参考方案2】:

Mark 搞定了,我只补充一点,就是如何加载该会话:

for key in session.keys():  # if you have potential removals
    del session[key]
session.update(session.load())
session.modified = False  # just making it clean

第一行可选,仅当某些值可能同时从会话中删除时才需要它。

最后一行是可选的,如果你更新会话,那么它并不重要。

【讨论】:

【参考方案3】:

确实如此。您可以通过查看django.contrib.sessions.middleware.SessionMiddleware 来确认。

基本上,request.sessionrequest 到达您的视图之前加载(在process_request 中),并在response 离开您的视图(在process_response 中)后在会话后端更新(如果需要) )。

如果我的意思不清楚,你可能想看看the django documentation for Middleware。


解决问题的最佳方法取决于您试图利用这些信息实现什么目标。如果您提供该信息,我会更新我的答案!

【讨论】:

很清楚。我想让 Django 手动重新加载和存储会话数据,以使竞争条件的时间跨度尽可能小。 (假设没有简单的方法可以完全防止它)。基本上,我想手动执行 Django 在查看代码之前和之后所做的事情。 @Mark 加载会话是通过request.session = engine.SessionStore(session_key) 完成的,并使用request.session.save() 保存它。根据您的会话后端以及您是否使用事务,这可能根本不起作用。如果您想走这条路,我建议您使用其他后端而不是 DB。 这似乎是我正在寻找的。为什么数据库会话不适合这个? (请将解决方案添加到您的答案中,以便我接受)

以上是关于Django会话竞争条件?的主要内容,如果未能解决你的问题,请参考以下文章

Django post_save 信号和 celery 任务之间可能的竞争条件

停止刷新令牌的竞争条件?

条件竞争和恶性条件竞争

条件竞争漏洞测试

竞争条件和解锁写入

Slick Code 中的竞争条件