Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gevent 下的 race condition #69

Closed
zhangclb opened this issue Jul 23, 2016 · 31 comments
Closed

gevent 下的 race condition #69

zhangclb opened this issue Jul 23, 2016 · 31 comments

Comments

@zhangclb
Copy link
Contributor

代码和测试结果在这里: https://gist.github.com/zhangclb/7e173a40ce7dcfef6645c894811593c8 用 uliweb runserver --gevent 跑调试服务器即可重现

@zhangclb
Copy link
Contributor Author

相关搜索到的资料:

http://ju.outofmemory.cn/entry/104736
"[5] 基于 Flask 的 Web 应用可以在 Gevent 或 Eventlet 异步网络库 patch 过的 Python 环境中正常工作。这二者都使用 Greenlet 而不是系统线程作为调度单元,而 Werkzeug 考虑到了这点,在 Greenlet 可用时用 Greenlet ID 代替线程 ID。"

https://github.com/LeicsFrameWork/Leics/issues/1
"werkzeug + gevent 的模式比较冒险,毕竟Werkzeug是Thread Local。
如果实在不行,那么只使用werkzeug的视图部分,调度部分进行重写。"

@zhangclb
Copy link
Contributor Author

http://www.lai18.com/content/1367101.html

from gevent.local import local
from werkzeug.local import LocalProxy

@taogeT
Copy link

taogeT commented Jul 23, 2016

我用FLASK测一下好像没出现类似的问题

# -*- coding: utf-8 -*-
from flask import Flask, request
from gevent import monkey

import gevent

monkey.patch_all()
app = Flask(__name__)


@app.route('/t1')
def t1():
    v = request.values.get("v")
    gevent.sleep(5)
    v_after_sleep = request.values.get("v")
    return 't1: v=%s, v_after_sleep=%s\n'%(v,v_after_sleep)

@app.route('/t2')
def t2():
    v = request.values.get("v")
    gevent.sleep(1)
    v_after_sleep = request.values.get("v")
    return 't2: v=%s, v_after_sleep=%s\n'%(v,v_after_sleep)


if __name__ == '__main__':
    from gevent.wsgi import WSGIServer
    gws = WSGIServer(('127.0.0.1', 5000), app)
    gws.serve_forever()

@limodou
Copy link
Owner

limodou commented Jul 24, 2016

我其实用的是uwsgi的gevent的模式,倒是没有直接使用uliweb gevent,不过在其它的应用中的确用过纯gevent。有什么好办法吗?

@zhangclb
Copy link
Contributor Author

@taogeT 你用的是gevent方式运行的吗?
@limodou 我用uwsgi试一下看看

@taogeT
Copy link

taogeT commented Jul 24, 2016

@zhangclb 是的,你可以看下代码,方式与uliweb类似。

@zhangclb
Copy link
Contributor Author

@limodou 我用 uwsgi 跑测试过了,一样可以重现这个问题
我上面贴的那个是不是就是解决方法:""[5] 基于 Flask 的 Web 应用可以在 Gevent 或 Eventlet 异步网络库 patch 过的 Python 环境中正常工作。这二者都使用 Greenlet 而不是系统线程作为调度单元,而 Werkzeug 考虑到了这点,在 Greenlet 可用时用 Greenlet ID 代替线程 ID。""

是不是新的werkzeug这方面已经做了改进了? 你能不能找到 thread local 这块的代码看看?

@zhangclb
Copy link
Contributor Author

https://github.com/limodou/uliweb/blob/master/uliweb/core/SimpleFrame.py#L1395 这里是获得 local request的地方对吧?

@limodou
Copy link
Owner

limodou commented Jul 25, 2016

from werkzeug import ClosingIterator, Local

On Mon, Jul 25, 2016 at 11:55 AM, zhangclb notifications@github.com wrote:

https://github.com/limodou/uliweb/blob/master/uliweb/core/SimpleFrame.py#L1395
这里是获得 local request的地方对吧?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#69 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAHHQDVVeOX-EpaB8V0bcHwERSd4NGORks5qZDOxgaJpZM4JTXLC
.

I like python!
UliPad <>: http://code.google.com/p/ulipad/
UliWeb <>: https://github.com/limodou/uliweb
My Blog: http://my.oschina.net/limodou

@zhangclb
Copy link
Contributor Author

我现在就想找到不同的thread用不同的env/local是在哪里实现的,还没找到
然后如果flask没有这个问题就是说新版本的werkzeug是没问题的?可以对比看看.

@limodou
Copy link
Owner

limodou commented Jul 27, 2016

确定Werkzeug新版本没有问题吗?那我可以比较一下。

@zhangclb
Copy link
Contributor Author

@limodou 你看 https://github.com/limodou/uliweb/blob/master/uliweb/lib/werkzeug/local.py#L19
本来就有这种处理了,所以为什么没有发生作用呢?

@limodou
Copy link
Owner

limodou commented Jul 28, 2016

也许和我的使用方式有关吧。这块以前没有关注过,毕竟用gevent的情况并不多。我只是单纯的用了local对象,调用方式要比较看一下。

@zhangclb
Copy link
Contributor Author

我之前没认真看,原来那篇文章说的意思是werkzeug对于greenlet是没问题的,因为已经针对处理过了,但是对于uwsgi自己的ugreen是有问题的
我这次也是误打误撞发现了uliweb的这个问题

@limodou
Copy link
Owner

limodou commented Jul 28, 2016

这个问题比较底层,因为我只是直接使用werkzeug的东西,没有特别研究过,看看能不能解决吧。我是用开发服务器发现存在这个问题, uwsgi不知道有没有。

@zhangclb
Copy link
Contributor Author

@limodou uwsgi我试过也是有这个问题的,我用的是 uwsgi --http --gevent方式跑的,具体命令:

../env/bin/uwsgi --http 127.0.0.1:8000 --master --processes 1 --gevent 100 --module wsgi_handler

上面 @taogeT 的 flask代码我也测试过了,确实没有这个问题

@misakar
Copy link

misakar commented Jul 29, 2016

@zhangclb @limodou @taogeT 我觉得问题的原因在于创建Request ThreadLocal环境时获取的这把锁
2016-07-29 9 16 15
首先请求http://localhost:8000/t1?v=1, 获得锁, 第一次调用gevent.sleep[gevent.sleep(5)]的时候会创建gevent hub; 调用gevent.sleep(1)切换到hub运行时, t1线程的锁还没有释放, 所以hub线程中v变量不可变且为1, 所以t2: v_after_sleep=1
flask的Request Context没有加锁
2016-07-29 9 52 20
所以正常.
不确定我的想法有没有问题, 但是在greenlet switch的时候加上线程锁肯定会有影响.

@zhangchunlin
Copy link
Contributor

@neo1218 看不太懂你的解释,因为我对gevent里的概念并没那么清楚
我换一下命令行强制sleep 5秒的t1一定先跑,t2晚一秒跑,那么t1的v就会被改成2,你的解释对于这种情况也是能吻合的吗?

~$ prun "t1: curl -s localhost:8000/t1?v=1" "t2: sh -c 'sleep 1;curl -s localhost:8000/t2?v=2'"
1 t1:  curl -s localhost:8000/t1?v=1
2 t2:  sh -c 'sleep 1;curl -s localhost:8000/t2?v=2'

t2: t2: v=2, v_after_sleep=2
t2: --- end ---
t1: t1: v=1, v_after_sleep=2
t1: --- end ---

@misakar
Copy link

misakar commented Jul 29, 2016

@zhangchunlin 我在2个shell环境中发起curl请求, 一个请求t1, 一个请求t2, 先请求t1: 结果:

先输出t1: v=1, v_after_sleep=1, 然后输出t2: v=2, v_after_sleep=2

测试如图:
2016-07-29 11 44 31

由于我是手动控制时间... 我觉得可能是因为等curl正式请求t2的时候已经过了5s了.

对了, 能看一下你的prun程序吗? 我没搜到这个命令...

@zhangchunlin
Copy link
Contributor

@neo1218 prun在这里: https://gist.github.com/zhangchunlin/05576572b628f5bf9d74
是一个用gevent写的小工具,你可以用用看

@zhangchunlin
Copy link
Contributor

@neo1218 你应该用 uliweb runserver --gevent ,这样才是用 gevent 方式跑

@zhangchunlin
Copy link
Contributor

@limodou 找不到 local 在不同线程或者协程中做特别处理的地方
也不知道 LocalManager 到底是怎么发挥作用的, 另外 get_ident 貌似也没有地方调用到
一头雾水了现在

@zhangchunlin
Copy link
Contributor

我在SimpleFrame.py里改成这样也没用:

from werkzeug.local import get_ident
local_manager = LocalManager([local],ident_func=get_ident)

关键是虽然有 ident_func 但是不知道在哪里调用到

@limodou
Copy link
Owner

limodou commented Aug 1, 2016

先不用研究了,反正用得很少,有时间再说吧

On Mon, Aug 1, 2016 at 5:01 PM, zhangchunlin notifications@github.com
wrote:

我在SimpleFrame.py里改成这样也没用:

from werkzeug.local import get_ident
local_manager = LocalManager([local],ident_func=get_ident)

关键是虽然有 ident_func 但是不知道在哪里调用到


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#69 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAHHQAkc4DN1USWB9ZayewmwLEL7-bZZks5qbbXZgaJpZM4JTXLC
.

I like python!
UliPad <>: http://code.google.com/p/ulipad/
UliWeb <>: https://github.com/limodou/uliweb
My Blog: http://my.oschina.net/limodou

@zhangchunlin
Copy link
Contributor

@limodou 对我来说不少啊,因为我很喜欢用 gevent 来搞异步...
有空我继续研究代码吧

@zhangchunlin
Copy link
Contributor

上面 @neo1218 说是锁的原因,但是我把锁的操作去掉了也是一样的结果,所以应该跟锁没关系吧

@misakar
Copy link

misakar commented Aug 2, 2016

@zhangchunlin 我这两天再来看看, 我先熟悉熟悉uliweb

@zhangchunlin
Copy link
Contributor

@limodou 经过大量的log我有点快接近原因了,我现在在t1 t2 view函数最前面加上一行

from uliweb import request

这样问题就没了,把这个import都去掉,问题就又出现了

所以问题应该出在view函数准备好的request等local变量这里,这些local变量并没有用LocalProxy变量,所以不是协程安全的.

@zhangchunlin
Copy link
Contributor

应该找到直接原因了,把 https://github.com/limodou/uliweb/blob/master/uliweb/core/SimpleFrame.py#L1059 这里改成

local_env['request'] = request

这个问题就没了

zhangchunlin added a commit to zhangchunlin/uliweb that referenced this issue Aug 4, 2016
…lProxy object should be used to prevent race condition in gevent.
limodou added a commit that referenced this issue Aug 8, 2016
Fix issue #69, when preparing the local env for view, the LocalProxy …
@zhangchunlin
Copy link
Contributor

@limodou 这个issue可以关了吧

@limodou
Copy link
Owner

limodou commented Nov 20, 2016

OK

@limodou limodou closed this as completed Nov 20, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants