<p>gunicorn 19.x 导致 django-1.7.1 和 gevent 出现问题</p>

创建于 2014-11-04  ·  53评论  ·  资料来源: benoitc/gunicorn

你好,

我在使用gevent工作人员的 gunicorn 19.x (用19.019.1.1测试)上看到了这个错误。 我正在使用django-1.7.1

这是例外:

Traceback:
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  87.                 response = middleware_method(request)
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/contrib/auth/middleware.py" in process_request
  34.         if user and hasattr(user, 'get_session_auth_hash'):
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/utils/functional.py" in inner
  224.             self._setup()
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/utils/functional.py" in _setup
  357.         self._wrapped = self._setupfunc()
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/contrib/auth/middleware.py" in <lambda>
  23.         request.user = SimpleLazyObject(lambda: get_user(request))
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/contrib/auth/middleware.py" in get_user
  11.         request._cached_user = auth.get_user(request)
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/contrib/auth/__init__.py" in get_user
  151.         user_id = request.session[SESSION_KEY]
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/contrib/sessions/backends/base.py" in __getitem__
  49.         return self._session[key]
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/contrib/sessions/backends/base.py" in _get_session
  175.                 self._session_cache = self.load()
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/contrib/sessions/backends/db.py" in load
  21.                 expire_date__gt=timezone.now()
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/db/models/manager.py" in manager_method
  92.                 return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/db/models/query.py" in get
  351.         num = len(clone)
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/db/models/query.py" in __len__
  122.         self._fetch_all()
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/db/models/query.py" in _fetch_all
  966.             self._result_cache = list(self.iterator())
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/db/models/query.py" in iterator
  265.         for row in compiler.results_iter():
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py" in results_iter
  700.         for rows in self.execute_sql(MULTI):
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py" in execute_sql
  784.         cursor = self.connection.cursor()
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/db/backends/__init__.py" in cursor
  163.         self.validate_thread_sharing()
File "/home/sieve/sitroom.sieve.com.br/shared/env/local/lib/python2.7/site-packages/django/db/backends/__init__.py" in validate_thread_sharing
  515.                 % (self.alias, self._thread_ident, thread.get_ident()))

Exception Type: DatabaseError at /
Exception Value: DatabaseWrapper objects created in a thread can only be used in that same thread. The object with alias 'default' was created in thread id 140049505195808 and this is thread id 61130224.

这是我的gunicorn.conf.py

##
# Gunicorn config at 
# Managed by Chef - Local Changes will be Nuked from Orbit (just to be sure)
##

# What ports/sockets to listen on, and what options for them.
bind = "127.0.0.1:9300"

# The maximum number of pending connections
backlog = 2048

# What the timeout for killing busy workers is, in seconds
timeout = 300

# How long to wait for requests on a Keep-Alive connection, in seconds
keepalive = 2

# The maxium number of requests a worker will process before restarting
max_requests = 1024

# Whether the app should be pre-loaded
preload_app = True

# How many worker processes
workers = 16

# Type of worker to use
worker_class = "gevent"

# The granularity of error log outputs.
loglevel = "error"

该错误每次都会发生,只需访问 django 应用程序的任何 URL(当然,这取决于任何模型)会触发此错误。 有任何想法吗?
由于更改为gunicorn-18.0解决了问题,我假设gunicorn-19.x正在做一些不同的事情。

如果您需要任何其他信息,请告诉我,我会在此处发布。

现在我正在使用18.0

非常感谢。

( FeaturWorker FeaturCore ThirdpartGevent Deploy Investigation - Bugs -

最有用的评论

@gukoff提议的修复对我不起作用,我仍然遇到同样的错误 😞

所有53条评论

先问一个问题:取消预加载选项有帮助吗?
我想我记得有人报道过另一个类似的问题。
如果更改该选项有帮助,那么我们一定已经更改了有关 Django 应用程序加载方式的某些内容,这样我们将更容易对其进行跟踪。

我_认为_我们在主进程中导入 django wsgi 应用程序,至少在 django 1.7 上,它会在导入时初始化所有 django 模型。

你好@tilgovi ,谢谢你的时间。 我无法确认使用preload_app = False我可以使用gunicorn-19.1.1django-1.7.1

您是否知道在19.x版本中,gunicorn cloud 对此有何不同之处?

我现在有第二个问题。 我在我的 Django 应用中使用 Raven[1]。 根据 Raven 文档,当使用 WSGU 中间件 [1] 时,可以将 Raven 用作 WSGI 对象(包装原始 django WSGI 应用程序),并且在使用 gunicorn 时(作为外部命令,而不是run_gunicorn django 命令)我们必须向 gunicorn[2] 添加一个钩子。 启用此钩子后,gunicorn 仅在preload_app = True ,然后 gevent 不起作用。 当我在启用钩子的情况下使用preload_app = True时,gunicorn 甚至无法启动,给我和ImportError说它无法导入我的“DJANGO_SETTINGS_MODULE”。

乌鸦是4.2.3

现在我将回到 gunicorn 18.x ,因为它适用于两种配置(gevent 和 raven 作为 WSGI 中间件)
我应该为此打开另一个问题吗?

[1] http://raven.readthedocs.org/en/latest/config/django.html#wsgi -middleware
[2] http://raven.readthedocs.org/en/latest/config/django.html#gunicorn

@daltonmatos在 django 1.7.1 中设置数据库的方式可能已经改变,显然不是线程安全的。 我不知道在这一点上该怎么做。

还有一件事,今天才发现,
django 1.7 中的会话处理与 gunicorn 19.3.0 在几秒钟内到期。

必须切换回 18 才能使其工作。

供参考。

这似乎与https://github.com/benoitc/gunicorn/issues/879相同,后者已关闭但未修复。 它使 gunicorn 19.x。 无法与 Django 一起使用。 也许只适用于 gevent 工作者? 我还没有测试其他工人类型。

@tilgovi我想知道这是否不是由于环境发生了变化。 在 19.x 中,我们将wsgi.multithreadtrue ,而在 18.x 中它是false

https://github.com/benoitc/gunicorn/blob/master/gunicorn/workers/ggevent.py#L99

在 django 中可能会以不同的方式处理此更改。 而且greenlet并不是真正的线程。 想法? 至少也许我们可以尝试恢复它并检查它是否解决了问题。 我们是否有针对此问题的测试或可重现的方法?

哦,我敢打赌这就是问题所在。 好记性,@benoitc。

我仍然不知道为什么。 正如我当时为证明这一变化所说的那样,我无法想象它会造成任何破坏,因为这意味着框架只会在共享数据方面采取更多预防措施。 显然,我可能是错的。

我推了分支fix/927来检查上面的修复程序是否有效。 请测试!

我仍然能够使用带有 django 1.8.1(和 1.7.1)和 gevent 1.0.1 的 fix/927 重现此错误

这让我放心,即使我们仍然不知道原因。

所以我尝试使用示例应用程序并没有重现运行以下命令行的问题:

gunicorn --env DJANGO_SETTINGS_MODULE=testing.settings -k gevent -w3 wsgi:application

谁能给我一个可重现的例子?

我仍然可以像这样调用 gunicorn 重现这个问题:

    gunicorn --env DJANGO_SETTINGS_MODULE=settings -w 3 --max-requests=1000 -b 0.0.0.0:8000 -k gevent --config ogs/gunicorn.py  --pythonpath ogs wsgi:application

您是否需要任何相关详细信息?

@brockhaywood应该是project.settings ,但是是的,我需要有一种方法来重现它,您是否有任何最少的代码可以共享。

查看上面的跟踪,它也可能是猴子修补。 也许线程不应该被猴子修补。 你可以试试下面的补丁吗?

--- a/gunicorn/workers/ggevent.py
+++ b/gunicorn/workers/ggevent.py
@@ -60,9 +60,9 @@ class GeventWorker(AsyncWorker):

         # if the new version is used make sure to patch subprocess
         if gevent.version_info[0] == 0:
-            monkey.patch_all()
+            monkey.patch_all(thread=False)
         else:
-            monkey.patch_all(subprocess=True)
+            monkey.patch_all(subprocess=True, thread=False)

         # monkey patch sendfile to make it none blocking
         patch_sendfile()

使用带有以下 wsgi 的上述补丁不再产生指定的错误:

import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")

from gevent import monkey
monkey.patch_all(thread=False)

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

from django.core.cache.backends.memcached import BaseMemcachedCache
BaseMemcachedCache.close = lambda self, **kwargs: None

我现在看到一个不同的新错误,但不知道它是否相关:

ProgrammingError: close cannot be used while an asynchronous query is underway

这种情况正在发生,10/15 中的 1 个请求正在对模型进行计数。 我怀疑这是我的应用程序中的一个错误,现在已被发现。

@brockhaywood很酷! 顺便说一句,你的 gevent 版本是什么? 对于您的最新错误,它似乎确实与 gunicorn 无关。

嗯,好消息! 在 1.0.1 版的 gevent 上。

@tilgovi所以我想知道我们是否不应该修补 gunicorn 去除线程猴子修补。 也许我们也应该将wsgi.multithreadFalse ? 想法?

@benoitc我能够使用一个非常简单的 Django 应用程序重现我上面引用的新错误,该应用程序只有一个视图,如果我运行多个并发请求,它会执​​行一个简单的模型查询和计数。

您对哪个组件最有可能是罪魁祸首有什么建议吗? 这可能是gevent问题还是psycogreen?

更可能是psycogreen。 你使用这样的钩子来设置它吗?

def def_post_fork(server, worker):
    from psycogreen.gevent import psyco_gevent
    psyco_gevent.make_psycopg_green()
    worker.log.info("Made Psycopg Green")

哦,我实际上正在使用

def post_fork(server, worker):    
    from psycogreen.gevent import patch_psycopg
    patch_psycopg()

我会试试你的建议。

好的,我会尝试用 psycogreen 开票

你可以尝试使用钩子post_worker_init而不是post_fork吗?

在 post_worker_init 中修补 psycogreen 时,我仍然会出现这种情况。

我也向 psycogreen 提交了一张票:
https://bitbucket.org/dvarrazzo/psycogreen/issue/2/databaseerror-used-with-asynchronous-query

@brockhaywood好的:/还找到了这个链接: https : //bitbucket.org/dvarrazzo/psycogreen-hg/issue/1/databaseerror-execute-used-with 。 我不确定它是否可以帮助你。

我确实看过那个,它似乎是同样的问题,但不幸的是我没有看到解决方案。

@brockhaywood你能分享任何可以帮助我重现问题的简单代码示例吗? 我明天去看看。

这是我用来重现的简单项目:
https://github.com/brockhaywood/gunicorn_gevent_issue

谢谢!

2015 年 5 月 17 日星期日晚上 10:34 brockhaywood通知@github.com
写道:

这是我用来重现的简单项目:
https://github.com/brockhaywood/gunicorn_gevent_issue


直接回复此邮件或在 GitHub 上查看
https://github.com/benoitc/gunicorn/issues/927#issuecomment -102852497。

@benoitc 对此有何想法?

@brockhaywood抱歉,我没有时间测试它。 由于我明天被锁定在航班上,无论如何我都会:)

谢谢
2015 年 5 月 26 日下午 1:17,“Benoit Chesneau”通知@ github.com 写道:

@brockhaywood https://github.com/brockhaywood 抱歉我没有时间
测试它。 由于我明天被锁定在航班上,无论如何我都会:)


直接回复此邮件或在 GitHub 上查看
https://github.com/benoitc/gunicorn/issues/927#issuecomment -105652947。

当我看到这个线程时,我刚刚遇到了同样的 DatabaseWrapper 问题。

我可以确认从我的 gunicorn 配置文件中删除preload_app选项可以解决问题。 我正在使用:

  • 姜戈==1.7.7
  • gevent==1.0.2
  • 枪炮==19.3.0
  • psycopg2==2.5.2
  • psycogreen==1.0
  • 由 PgBouncer 包裹的 gunicorn

(跳进来,因为我们一直在看到与此线程中描述的相同的问题)

看起来这些问题是由问题#478修复引起的(不要给 master 打补丁)。

如果您使用的是 gevent 工作线程,那么通过不对 master 进行猴子修补,应用程序预加载从根本上会造成不匹配。 作为预加载的一部分运行的任何应用程序代码都在未打补丁的环境中运行。 在工作程序中运行的任何应用程序代码都在猴子补丁环境中运行。

对我们来说,只需将setup方法恢复到gevent工人类,因为它在 18.x 中定义,以便主服务器被猴子修补,并且在应用程序预加载之前被猴子修补,修复所有我们的问题。 这包括DatabaseWrapper错误,以及我们在 gevent 工作人员会挂起和超时的地方看到的另一个问题。 不需要其他更改(例如,我们不必禁用猴子补丁threads )。

我仍然很犹豫要不要修补主进程,但是如果你想这样做,你可以使用服务器钩子而不是修补 gunicorn 本身。 如果你能做到这一点,我欢迎任何我们可以在比这里更好的地方记录的任何方式。

我认为我们的短期计划是创建一个应用此补丁的(内部)gunicorn 叉。

我将研究将猴子补丁步骤移动到我们的 wsgi 代码中,但这似乎仍然应该在 gunicorn 代码中完成(即使作为可配置选项)。 在预加载的应用程序代码中打补丁会以一种或另一种方式影响 master,重点是保持 worker 的猴子补丁状态与 master 同步。 在应用程序代码中应用 master 猴子补丁意味着依赖此行为的应用程序开发人员将始终追赶 gunicorn 对何时/如何猴子补丁工作人员所做的任何更改。

如果早期修补有效,也许应该将其视为更改。

我不记得是否曾经是这种情况以及是否会导致问题,但是猴子修补总是很棘手。

我们自己的生产 gunicorn 应用程序需要预加载,因此也需要在 master 中进行猴子修补(我们使用 Pyramid/paste 的pserve脚本来启动应用程序并从 ini 文件加载 gunicorn;我们有一个包装器将其包裹起来确保猴子修补尽快发生)。

我是当前的 gevent 维护者。 我很高兴看到 gevent 中使这个场景更好地工作所需的任何问题。 我们预计很快就会推出 1.1,如果这是一个受支持的选项,那就太好了。

我尝试在 wsgi 实现的顶部应用相同的猴子补丁逻辑,我们开始看到DatabaseWrapper问题。 不完全确定为什么 - 查看arbiter.py中的 wsgi 调用,这发生在GeventWorker.setup将被调用的点之后的几行(作为worker_class 分配

如果我有一个 eureka 或者在应用程序代码方面很幸运,我会跟进,但到目前为止,对我们来说,早期在 gunicorn 主代码中打补丁是解决我们所有问题的唯一修复。

@jamadden非常感谢。

@jzupko在任何可能受影响的导入之前进行修补通常很重要。 这就是为什么我建议在服务器挂钩中执行此操作,例如将post_fork函数放入gunicorn.conf.py文件中。 但是,我刚刚意识到应用程序预加载发生在任何服务器挂钩之前,并且不清楚在哪里可以这样做。

@benoitc我们有针对特定于工作人员的配置的任何机制吗?

我将此添加到版本 20 里程碑,并建议我们考虑对 master 进行猴子修补,如果可以在不重新引入 #478 之类的问题的情况下完成。

@tilgovi你有setup工人的方法,我认为可以用它。

在 20.0 发布之前,有人对此有临时修复吗? 或者是在禁用preload_app或降级到 18.0 之间进行选择的唯一解决方案?

@fletom临时修复? 你试过最新的大师吗? 它对你有用吗?

@benoitc我的意思是修复“DatabaseWrapper 对象”错误。

现在的 gunicorn 大师实际上对我来说根本不起作用。 在常规 19.6.0 我得到这个(正常):

[2017-01-05 15:46:32 -0500] [3222] [INFO] Starting gunicorn 19.6.0
[2017-01-05 15:46:32 -0500] [3222] [INFO] Listening at: http://127.0.0.1:8000 (3222)
[2017-01-05 15:46:32 -0500] [3222] [INFO] Using worker: gevent
[2017-01-05 15:46:32 -0500] [3226] [INFO] Booting worker with pid: 3226
[2017-01-05 15:46:32 -0500] [3227] [INFO] Booting worker with pid: 3227
[2017-01-05 15:46:32 -0500] [3228] [INFO] Booting worker with pid: 3228
[2017-01-05 15:46:32 -0500] [3229] [INFO] Booting worker with pid: 3229

在具有 100% 完全相同配置的最新主机上,我得到了这个,据我所知,没有任何端口监听:

[2017-01-05 15:47:19 -0500] [3308] [INFO] Starting gunicorn 19.6.0
[2017-01-05 15:47:19 -0500] [3308] [INFO] Listening at:  (3308)
[2017-01-05 15:47:19 -0500] [3308] [INFO] Using worker: gevent
[2017-01-05 15:47:19 -0500] [3312] [INFO] Booting worker with pid: 3312
[2017-01-05 15:47:19 -0500] [3313] [INFO] Booting worker with pid: 3313
[2017-01-05 15:47:19 -0500] [3314] [INFO] Booting worker with pid: 3314
[2017-01-05 15:47:19 -0500] [3315] [INFO] Booting worker with pid: 3315

好像被窃听了?

@fletom你如何启动 gunicorn?

@benoitc在这种情况下,它是gunicorn -c gunicorn_conf.py <appname>.wsgi

我的gunicorn_conf.py是:

workers = 4

worker_class = 'gevent'

preload_app = False

我在gunicorn==18.0遇到了这个问题。 在 gunicorn_conf.py 我有worker_class = 'gevent'preload_app = True和自定义pre_forkon_startingon_reload函数。

将这些行放在 gunicorn_conf.py 文件的顶部似乎为我解决了这个问题:

import gevent.monkey
gevent.monkey.patch_thread()

更新:
我的 gunicorn.conf 中有这些行。 摆脱它们解决了这个问题,而无需修补。

import django
django.setup()

因为他们 django 在 gunicorn 的猴子补丁之前启动了连接。

@gukoff提议的修复对我不起作用,我仍然遇到同样的错误 😞

我将结束这个讨论,并使用 R20 里程碑上的邮件列表标签展开讨论,以便在 gevent 中修补仲裁器。 如果您在此讨论中遇到与任何相关的问题,并且您认为这可能是 Gunicorn 中的一个错误,请打开一个问题。 如果有助于添加上下文,您可以按编号提及或链接到评论,但在这一点上,这是一张旧票,其中包含有关 Gunicorn 的多个版本和不同症状的并行对话。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

bywangxp picture bywangxp  ·  4评论

lordmauve picture lordmauve  ·  3评论

haolujun picture haolujun  ·  3评论

Abraxos picture Abraxos  ·  4评论

zenglingyu picture zenglingyu  ·  4评论