BUG Report 严重(特别是使用了lua-resty-lock库的服务,有一定概率workers死锁,可重现)

1,217 views
Skip to first unread message

Yun Thanatos

unread,
Sep 25, 2016, 5:43:22 AM9/25/16
to openresty

  本周五对一个由若干个Openresty实例组成的分布式服务进行压力测试,发现基本上大约每隔两个小时,Master Nginx的多个worker一定会死锁,Slaves Nginx偶尔死锁,其死锁频率比Master低很多。

  连续追踪两天,最终今天下午在春哥的帮助下定位出问题,先感谢春哥的耐心帮助 ☺

BUG介绍:

  Openresty提供两套shdict访问接口,一个是lua层面的,一个是ffi接口。
  Bug就是,凡是使用lua层面shdict接口的,并且在lua对象的__gc方法中进行了lua shdict操作的,就有一定概率发生worker死锁(如果所有的workers都会访问shdict,那他们最终全部都会死锁)。

更具体化一点的例子就是:

  凡是使用了lua shdict操作的并且使用了lua-resty-lock库的所有Openresty服务,都会有概率死锁。

BUG重现:

    一般情况下,压力测试一分钟之内,所有的worker都会死锁(前提是没有使用lua-resty-core)。


-------nginx.conf

worker_processes  1; # 
worker_rlimit_core 10000m; # for producing core file
lua_shared_dict test_lock_flaw 10m;
location /flaw{
    content_by_lua '
        local flaw = require"flaw"
        flaw.test_lock_flaw()
    ';
}
location /is_alive{
    content_by_lua '
        ngx.say("not dead lock yet :)")
    ';
}

------- 测试脚本

while true
do
    ab -c 10 -n 100000 127.0.0.1/flaw
    sleep 1
done
-------

while true
do
    ab -c 10 -n 100000 127.0.0.1/is_alive
    sleep 1
done

 ------- flaw.lua 

-- yunth...@gmail.com 25-Sep-16
--
local _M = { _VERSION = '1.2' }
-- debug flag
local ddd_flag = false
local lib_ffi = require'ffi'
local lib_lock = require"resty.lock"
local lib_C = lib_ffi.C
local ffi = lib_ffi
local function anotnil(x)
    assert(type(x)~="nil")
end
anotnil(lib_ffi)
anotnil(lib_lock)

local function traceback_str()
    if ddd_flag == true then
        local striped_body,_=string.gsub(debug.traceback(),"","")
        return(tostring(striped_body))
    else
        return nil
    end
    return 
    --local striped_body,_=string.gsub(debug.traceback(),"","")
    --return(tostring(striped_body))
end

local function notnil(x)
    return type(x)~="nil"
end

local function isnil(x)
    return type(x)=="nil"
end

local function typet(t)
    return type(t)=="table"
end

local function types(s)
    return type(s)=="string"
end

local function typen(n)
    return type(n)=="number"
end
local function typef(f)
    return type(f) == "function"
end
local function typecdata(c)
    return type(c) == "cdata"
end
local function anotnil(x)
    local str = traceback_str()
    assert(type(x)~="nil",str)
end

local function anil(x)
    local str = traceback_str()
    assert(type(x)=="nil",str)
end

local function atypet(t)
    local str = traceback_str()
    assert(type(t)=="table",str)
end

local function atypes(s)
    local str = traceback_str()
    assert(type(s)=="string",str)
end

local function atypen(n)
    local str = traceback_str()
    assert(type(n)=="number",str)
end

local function atypef(f)
    local str = traceback_str()
    assert(type(f)=="function",str)
end
local function acdata(p)
    local str = traceback_str()
    assert(type(p)=="cdata",str)
end
local function acdata_notnil(p)
    local str = traceback_str()
    assert(type(p)=="cdata",str)
    assert(p ~= nil,str)
end

local function csizeof(str)
    atypes(str)
    local sz = ffi.sizeof(str)
    atypen(sz)
    return sz
end
local function ccast(ptr,str)
    --assert(type(ptr) == "cdata")
    --assert(ptr ~= nil)
    atypes(str)
    local p = ffi.cast(str,ptr)
    --assert(type(p) == "cdata")
    --assert(p ~= nil)
    return p
end
atypet(ngx)
local function shd_set_safe(shd_name_str,member_name_str,value)
    --return true or nil,err_str
    local dic=ngx.shared[shd_name_str]
    local success,err=dic:safe_set(member_name_str,value)
    if type(success)~="boolean" or success~=true then
        return nil,tostring(err)
    else 
        return success
    end
end

local function lock_try(shd_name,lock_name,expire)  -- return object lock or nil
    -- new a try lock
    -- auto expire:20s
    if type(expire)~="number" then
        expire=20
    end
    assert(type(lock_name)=="string")
    assert(#lock_name>0)
    atypes(shd_name)
    local lock = lib_lock:new(shd_name,
       {["exptime"]=expire,["timeout"]=0,["step"]=0.001,["ratio"]=2,["max_step"]=0.5})
    -- try_lock
    local elapsed, err = lock:lock(lock_name)
    if type(elapsed) ~= "nil" then
        -- got the lock :)
        -- some stuff :)
        return lock
        -- release the lock
        -- lock:unlock()
    end
    return nil
end

local function lock_release(lock)
    lock:unlock()
end

local function shd_set_safe_with_assert(shd,member,value)
    assert(type(value)~="nil")
    assert(type(shd)=="string")
    assert(#shd>0)
    assert(type(member)=="string")
    assert(#member>0)
    local ret=shd_set_safe(shd,member,value)
    assert(ret==true)
    return ret
end
function _M.test_lock_flaw_prerequisite(pf)
    local ffi_new = ffi.new
    if not typef(pf) then
        pf = ngx.say
    end
    local function gc_fp(cdata)
        acdata_notnil(cdata)
        -- do gc stuff...
        pf("gc_fp:"..tostring(cdata))
    end
    local ctype = ffi.metatype(
        "struct {int key;}",
        { __gc = gc_fp }
    )
    anotnil(ctype)
    pf("type(ctype):"..type(ctype))
    cdata1 = ffi_new(ctype)
    acdata_notnil(cdata1)
    cdata1.key = 1
    --
    cdata1 = nil
    pf("collectgarbage:(are you see some gc outputs?...☺")
    collectgarbage()
    pf("exit,bye :)")
end
function _M.test_lock_flaw(pf)
    local ffi_new = ffi.new
    if not typef(pf) then
        pf = ngx.say
    end
    local function  xpf(str)
        atypes(str)
        pf(str)
        if pf == ngx.say then
            ngx.flush()
        end    
    end
    xpf("enter:")
    local shd_name = "test_lock_flaw"
    local lock_ct = 0
    local lock_t = {}
    local dic = ngx.shared[shd_name]
    shd_set_safe_with_assert(
        shd_name,
        "key:"..tostring(key_ct),
        string.rep("v:"..tostring(key_ct),1)
    )
    while lock_ct < (10000000) do
        local lock = lock_try(shd_name,"lock_name"..tostring(math.random()),1)
        lock = nil
        dic:get("key:0")
        lock_ct = lock_ct + 1    
    end
    local key_ct = 0
    shd_set_safe_with_assert(
        shd_name,
        "key:"..tostring(key_ct),
        string.rep("v:"..tostring(key_ct),1)
    )
    local dic = ngx.shared[shd_name]
    local loop_ct = 0
    while loop_ct < (math.random(1,10000)*100) do
        loop_ct = loop_ct + 1
        dic:get("key:0")
        lock_t = nil
    end
    xpf("exit,bye :)")
    
end
function _M.test_lock_flaw_rand(pf)
    local ffi_new = ffi.new
    if not typef(pf) then
        pf = ngx.say
    end
    local function  xpf(str)
        atypes(str)
        pf(str)
        if pf == ngx.say then
            ngx.flush()
        end    
    end
    xpf("enter:")
    local shd_name = "test_lock_flaw"
    local lock_ct = 0
    local lock_t = {}
    local pid = ngx.pid()
    while lock_ct < 10000 do
        lock_t[lock_ct] = lock_try(shd_name,"lock_name:"..tostring(lock_ct)..":"..tostring(pid)..":"..tostring(math.random()),1)
        anotnil(lock_t[lock_ct])
        lock_ct = lock_ct + 1    
    end
    local key_ct = 0
    shd_set_safe_with_assert(
        shd_name,
        "key:"..tostring(key_ct),
        string.rep("v:"..tostring(key_ct),1000)
    )
    local v = shd_get_safe_with_assert(
        shd_name,
        "key:"..tostring(key_ct)
    )
    assert(v == string.rep("v:"..tostring(key_ct),1000))
    local dic = ngx.shared[shd_name]
    local loop_ct = 0
    while loop_ct < 20000000 do
        loop_ct = loop_ct + 1
        dic:get("key:0")
        lock_t = nil
    end
    xpf("exit,bye :)")
    
end
return _M


最终的死锁现象、原因及解决方法,请参见这个讨论https://groups.google.com/forum/#!topic/openresty/JdeYMWJX-dc




Yun Thanatos

unread,
Sep 25, 2016, 7:12:12 PM9/25/16
to openresty


问题根源:

目前,ngx_http_lua_shdict.c中封装了两类shdict操作接口,一类是lua c接口类型的,另一类是ffi类型。

现以shdict取一个key为string,value亦为string的操作为例(假定能够命中)。

#lua c接口类型的 shdict api

ngx_shmtx_lock(&ctx->shpool->mutex);

rc = ngx_http_lua_shdict_lookup(zone, hash, key.data, key.len, &sd);

if(value_type == LUA_TSTRING){

lua_pushlstring(L, (char *) value.data, value.len);

ngx_shmtx_unlock(&ctx->shpool->mutex);

}

在unlock之前调用了lua_pushlstring,这个函数会将当前的cpu执行权交给luajit vm,这就意味着luajit vm可以接着执行任何它想执行的任务,例如collectgarbage垃圾回收(典型的协程思想)。

假如在这次垃圾回收中,正好有一个lua对象要被回收了,而它的gc函数中定义了一些shdict的操作(这里不管是ffi类型的还是lua c类型的api),都一定会产生死锁,它的过程是:

一个worker的luajit vm垃圾回收调用了shdict访问操作,接着这个shdict访问操作去获取一把它已经获得的锁,于是,自己死锁。

接着,大多数情况下,其他worker的逻辑中也会存在shdict访问(因为shdict本身是用来进行跨worker进程间通信的),于是在接下来一个确定的时间内(取决于程序的固定参数,一般都比较小,秒级别),其他的所有worker一样陷入死锁状态,因为这把锁的拥有者在阻塞地获取这把它已经抢到的锁。

可见,因为此处lua_pushlstring的调用,导致lua c接口类型的shdict api存在死锁隐患,因为从上层来看,这个操作并不是原子的(很多朋友都会认为lua层面的一次shdict操作是原子的,然而,如果用的不是ffi shdict操作,这个编程模型就是错误的,所以会有这个BUG存在)。

#ffi接口类型的 shdict api

ngx_shmtx_lock(&ctx->shpool->mutex);

rc = ngx_http_lua_shdict_lookup(zone, hash, key.data, key.len, &sd);

if(value_type == LUA_TSTRING){
# copy value str to usr's buffer
ngx_shmtx_unlock(&ctx->shpool->mutex);
# return buffer address to user
}

(这种ffi的方法将会产生两次内存copy:value->user_buf->ffi_string)

在luajit vm中看,这个操作是原子的,因为luajit vm无法中断它去执行其它的代码,于是这里就没有死锁条件。

回看lua c接口类型shdict api的设计实现,我想当初春哥设计时也一定想过使用两次内存copy来保证api是原子的,但是因为性能和简洁性的权衡,最终选择了直接使用lua_pushlstring进行值传递的方法。


解法:

方法1.使用lua-resty-core,将默认的shdict访问接口一开始就改写为ffi类型的,通过:

init_by_lua '
    require"resty.core"
';

方法2.一种绝对约束:在lua c类型的shdict api实现中,多加一次内存copy,将这些api的执行逻辑原子化,或者在lua_pushlstring调用前后对vm collectgarbage进行关开。

方法3.最灵活且兼容性强的方法,在lua层,每次调用lua c类型的shdict接口前后对collectgarbage进行关开(效率不用担心,它只是标志位的设置,消耗非常的低),即:

collectgarbage("stop")
--any dict operation
collectgarbage("restart")


总结:

BUG描述:

凡是使用lua c类型shdict接口的,并且在lua对象的__gc方法中进行了lua shdict操作的,就有一定概率发生worker死锁。

一个BUG子集:

凡是使用了lua-resty-lock库并且没有事先使用lua-resty-core进行api替换的Openresty服务,都会有概率死锁。

lua-resty-lock是个非常重要的库,基本上Openresty上所有稍微复杂点的应用都会用到这个基础库,而lua-resty-core大家对它的认识可能仅限于一个ffi接口的封装,并不会说一定会强迫自己使用它对ngx api在init_by_lua阶段进行替换。

例如,我们小组线上的Openresty虽然有安装lua-resty-core库,但是没有人使用它(当然,从今天以后就会不同了),在这么多服务中,也许现在就有一个正在处于死锁状态。

所以,我认为目前线上大部分Openresty应用都可能会有这个隐患,尽管它的出现概率很小,也可以通过检测机制去除这一毛刺,但是肯定会对用户体验产生不利的影响。

使用lua-resty-core ☺

init_by_lua '
    require"resty.core"
';



在 2016年9月25日星期日 UTC+8下午5:43:22,Yun Thanatos写道:

YuanSheng Wang

unread,
Sep 25, 2016, 11:53:03 PM9/25/16
to open...@googlegroups.com
我们这里也碰到了一样的问题,修改方案比较下来,目前觉得这个比较靠谱。

超级感谢你的总结。
 

--
--
邮件来自列表“openresty”,专用于技术讨论!
订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
发言: 请发邮件到 open...@googlegroups.com
退订: 请发邮件至 openresty+unsubscribe@googlegroups.com
归档: http://groups.google.com/group/openresty
官网: http://openresty.org/
仓库: https://github.com/agentzh/ngx_openresty
教程: http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html



--

YuanSheng Wang
---------------------------------------
OpenResty lover ^_^

yun thanatos

unread,
Sep 25, 2016, 11:55:45 PM9/25/16
to openresty
这是一个更加精简版的最小复现:

-------nginx.conf

worker_processes  1; # 
worker_rlimit_core 10000m; # for producing core file
lua_shared_dict test_lock_flaw 10m;
location flaw_simple {
    content_by_lua '
        require "flaw_simple"
    ';
}

location /is_alive{
    content_by_lua '
        ngx.say("not dead lock yet :)")
    ';
}

------- flaw_simple.lua
local lib_lock = require"resty.lock"  -- 0.04 

local function lock_try(shd_name,lock_name,expire)
    if type(expire) ~= "number" then
        expire = 20
    end
    assert(type(lock_name) == "string")
    assert(#lock_name > 0)
    assert(type(shd_name) == "string")
    local lock = lib_lock:new(
        shd_name,
        {
            ["exptime"]=expire,
            ["timeout"]=0,
            ["step"]=0.001,
            ["ratio"]=2,
            ["max_step"]=0.5
        }
    )
    -- try_lock
    local elapsed, err = lock:lock(lock_name)
    if type(elapsed) ~= "nil" then
        -- got the lock :)
        -- some stuff :)
        return lock
        -- release the lock
        -- lock:unlock()
    end
    return nil
end

local function test_lock_flaw(pf)
    if not type(pf) == "function" then
        pf = ngx.say
    end

    local function xpf(str)
        assert(type(str) == "string")
        pf(str)
        if pf == ngx.say then
            ngx.flush()
        end
    end

    xpf("enter:")

    local shd_name = "test_lock_flaw"
    local lock_ct = 0
    local dic = ngx.shared[shd_name]
    dic:set("key:0","v:0")
    while lock_ct < (10000000) do
        local lock = lock_try(
            shd_name,
            "lock_name"..tostring(math.random()),
        1)
        lock = nil
        dic:get("key:0")
        lock_ct = lock_ct + 1
    end

    xpf("exit,bye :)")

end

test_lock_flaw(ngx.say)


------- scripts


--- gdb diagnose

(gdb) bt
#0  sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:85
#1  0x0000000000427438 in ngx_shmtx_lock (mtx=0x7fba9cd7d058) at src/core/ngx_shmtx.c:111
#2  0x00000000004ca8f2 in ngx_http_lua_shdict_set_helper (L=0x41b20050, flags=0) at ../ngx_lua-0.10.5/src/ngx_http_lua_shdict.c:960
#3  0x00007fba9fe3536b in lj_BC_FUNCC () from /usr/local/openresty/luajit/lib/libluajit-5.1.so.2
#4  0x00007fba9fe37564 in gc_call_finalizer (g=g@entry=0x41b113b8, L=L@entry=0x41b20050, mo=mo@entry=0x7ffebd2f5080,
    o=o@entry=0x41420dd8) at lj_gc.c:466
#5  0x00007fba9fe3774a in gc_finalize (L=L@entry=0x41b20050) at lj_gc.c:500
#6  0x00007fba9fe384b3 in gc_onestep (L=L@entry=0x41b20050) at lj_gc.c:650
#7  0x00007fba9fe38c6c in lj_gc_step (L=0x41b20050) at lj_gc.c:680
#8  0x00007fba9fe46cda in lua_pushlstring (L=L@entry=0x41b20050, str=0x7fba9cd8d0d1 "v:0", len=len@entry=3) at lj_api.c:577
#9  0x00000000004c9fce in ngx_http_lua_shdict_get_helper (L=0x41b20050, get_stale=0)
    at ../ngx_lua-0.10.5/src/ngx_http_lua_shdict.c:508
#10 0x00007fba9fe3536b in lj_BC_FUNCC () from /usr/local/openresty/luajit/lib/libluajit-5.1.so.2
#11 0x00007fba9fe950dc in lj_cf_package_require (L=0x41b20050) at lib_package.c:439
#12 0x00007fba9fe3536b in lj_BC_FUNCC () from /usr/local/openresty/luajit/lib/libluajit-5.1.so.2
#13 0x00000000004c2f43 in ngx_http_lua_run_thread (L=L@entry=0x41b11378, r=r@entry=0x7fbaa0862940, ctx=ctx@entry=0x7fbaa0855420,
    nrets=nrets@entry=0) at ../ngx_lua-0.10.5/src/ngx_http_lua_util.c:1015
#14 0x00000000004c6b60 in ngx_http_lua_content_by_chunk (L=0x41b11378, r=0x7fbaa0862940)
    at ../ngx_lua-0.10.5/src/ngx_http_lua_contentby.c:112
#15 0x00000000004c6691 in ngx_http_lua_content_handler (r=0x7fbaa0862940) at ../ngx_lua-0.10.5/src/ngx_http_lua_contentby.c:214
#16 0x0000000000450cdf in ngx_http_core_content_phase (r=0x7fbaa0862940, ph=<optimized out>) at src/http/ngx_http_core_module.c:1381
#17 0x000000000044b3d5 in ngx_http_core_run_phases (r=r@entry=0x7fbaa0862940) at src/http/ngx_http_core_module.c:858
#18 0x000000000044b4c4 in ngx_http_handler (r=r@entry=0x7fbaa0862940) at src/http/ngx_http_core_module.c:841
#19 0x00000000004570b9 in ngx_http_process_request (r=0x7fbaa0862940) at src/http/ngx_http_request.c:1912
#20 0x00000000004576a8 in ngx_http_process_request_headers (rev=rev@entry=0x7fbaa08b82e0) at src/http/ngx_http_request.c:1344
#21 0x0000000000457a66 in ngx_http_process_request_line (rev=0x7fbaa08b82e0) at src/http/ngx_http_request.c:1023
#22 0x0000000000440e39 in ngx_epoll_process_events (cycle=0x7fbaa0851440, timer=<optimized out>, flags=<optimized out>)
    at src/event/modules/ngx_epoll_module.c:822
#23 0x0000000000437527 in ngx_process_events_and_timers (cycle=cycle@entry=0x7fbaa0851440) at src/event/ngx_event.c:242
#24 0x000000000043e805 in ngx_worker_process_cycle (cycle=cycle@entry=0x7fbaa0851440, data=data@entry=0x0)
    at src/os/unix/ngx_process_cycle.c:753
#25 0x000000000043d2d0 in ngx_spawn_process (cycle=cycle@entry=0x7fbaa0851440, proc=proc@entry=0x43e7c0 <ngx_worker_process_cycle>,
    data=data@entry=0x0, name=name@entry=0x4fd975 "worker process", respawn=respawn@entry=-3) at src/os/unix/ngx_process.c:198
#26 0x000000000043ea74 in ngx_start_worker_processes (cycle=cycle@entry=0x7fbaa0851440, n=1, type=type@entry=-3)
    at src/os/unix/ngx_process_cycle.c:358
#27 0x000000000043f7af in ngx_master_process_cycle (cycle=cycle@entry=0x7fbaa0851440) at src/os/unix/ngx_process_cycle.c:130
#28 0x000000000041a5d9 in main (argc=<optimized out>, argv=<optimized out>) at src/core/nginx.c:367

(gdb) lbt
C:ngx_http_lua_shdict_delete
@lua/com/resty/lock.lua:66
C:ngx_http_lua_shdict_get
@lua/flaw_simple.lua:57
@lua/flaw_simple.lua:65
C:lj_cf_package_require
=content_by_lua(nginx.conf:53):2
(gdb)

在 2016年9月26日星期一 UTC+8上午7:12:12,yun thanatos写道:

燕云

unread,
Sep 26, 2016, 12:05:16 AM9/26/16
to open...@googlegroups.com
  YuanSheng客气了 :) 

  这就是玩开源的快乐之处,大家一起交流,一起进步 

  另外,值得一提的是春哥的nginx-gdb-utils调试工具,如果早点用这个工具的话,一定可以省掉很多无谓的追踪时间,这个工具的lbt真是太棒了👍

燕云

unread,
Sep 26, 2016, 12:08:05 AM9/26/16
to open...@googlegroups.com
这些方法都是春哥的,我是借花献佛 ☺

项楠

unread,
Sep 27, 2016, 11:48:31 PM9/27/16
to openresty
感谢分享. 今天才知道lua-resty-core的init_by_lua*阶段的替换用法


在 2016年9月25日星期日 UTC+8下午5:43:22,Yun Thanatos写道:

Yun Thanatos

unread,
Sep 28, 2016, 2:45:50 AM9/28/16
to openresty
这是一个更加精简的测试用例:

nginx.conf

worker_processes  1; # 
worker_rlimit_core 10000m; # for producing core file
lua_shared_dict test_lock_flaw 10m;
location flaw_ordinary {
    content_by_lua '
        require "flaw_ordinary"
    ';
}

location /is_alive{
    content_by_lua '
        ngx.say("not dead lock yet :)")
    ';
}
 
flaw_ordinary.lua

local function test_ordinary_flaw()
    local ffi = require"ffi"
    local ffi_new = ffi.new
    if not (type(pf) == "function") then
        pf = ngx.say
    end

    local function xpf(str)
        assert(type(str) == "string")
        pf(str)
        if pf == ngx.say then
            ngx.flush()
        end
    end

    xpf("enter:")

    local shd_name = "test_lock_flaw"
    local lock_ct = 0
    local dic = ngx.shared[shd_name]

    local function gc_fp(cdata)
        -- do gc stuff...
        dic:set("key:0","v:0")
    end
    local ctype = ffi.metatype(
        "struct {int key;int v;}",
        { __gc = gc_fp }
    )
    xpf("type(ctype):"..type(ctype))
    local cdata1 = ffi_new(ctype)
    cdata1.key = 1
    cdata1.v = 1
    cdata1 = nil
    local shd_name = "test_lock_flaw"
    local lock_ct = 0
    local dic = ngx.shared[shd_name]
    dic:set("key:0","v:0")
    while lock_ct < (10000000) do
        local cdata1 = ffi_new(ctype)
        cdata1.key = 1
        cdata1.v = 1
        cdata1 = nil
        dic:get("key:0")
        lock_ct = lock_ct + 1
    end

    xpf("exit,bye :)")
end

test_ordinary_flaw(ngx.say)

另外,方案三是个不严谨的方案(请考虑若__gc中出现异常的情况) ,推荐使用方法1。

init_by_lua '
    require"resty.core"
';  

在 2016年9月26日星期一 UTC+8上午11:55:45,Yun Thanatos写道:

Yun Thanatos

unread,
Sep 28, 2016, 2:48:45 AM9/28/16
to openresty
客气了 ☺ 

这一点确实较难注意到。

在 2016年9月28日星期三 UTC+8上午11:48:31,项楠写道:

魏海通

unread,
Nov 4, 2016, 5:32:07 AM11/4/16
to openresty
一定要mark下来

在 2016年9月25日星期日 UTC+8下午5:43:22,yunthanatos写道:

Yichun Zhang (agentzh)

unread,
Nov 4, 2016, 10:54:27 PM11/4/16
to openresty
Hello!

On Sun, Sep 25, 2016 at 2:43 AM, Yun Thanatos wrote:
> BUG介绍:
>
> Openresty提供两套shdict访问接口,一个是lua层面的,一个是ffi接口。
> Bug就是,凡是使用lua层面shdict接口的,并且在lua对象的__gc方法中进行了lua shdict操作的,就有一定概率发生worker死锁(如果所有的workers都会访问shdict,那他们最终全部都会死锁)。
>

作为一般的 OpenResty 编程建议,应当避免在 __gc 方法中进行任何复杂操作,比如操作共享内存字典之类。__gc 中应当只进行 C
级别的资源的释放。进行复杂操作的风险很大,因为可能会在任意的上下文中调用。

Regards,
-agentzh

魏海通

unread,
Nov 5, 2016, 12:18:27 AM11/5/16
to openresty
@yunthanatos 
 我用这个配置,在ubuntu14.04 OR1.11.2 跑了半个小时没有出现死锁。


在 2016年9月28日星期三 UTC+8下午2:45:50,yunthanatos写道:

yunthanatos

unread,
Nov 8, 2016, 12:06:31 AM11/8/16
to openresty
抱歉 :)

请确认运行环境中是否有如下情况:
  • 是否使用了lua.resty.core软件包进行了API的替换?
  • 是否关闭了Lua VM的Garbage Step Collect?

yunthanatos

unread,
Nov 8, 2016, 12:13:50 AM11/8/16
to openresty


On Saturday, 5 November 2016 10:54:27 UTC+8, agentzh wrote:

作为一般的 OpenResty 编程建议,应当避免在 __gc 方法中进行任何复杂操作,比如操作共享内存字典之类。__gc 中应当只进行 C
级别的资源的释放。进行复杂操作的风险很大,因为可能会在任意的上下文中调用。

Regards,
-agentzh

嗯☺,赞同春哥的说法,__gc方法会使lua中的执行逻辑非常复杂。

现在还有一个小问题是关于lua-resty-lock库的:

Readme.md:

It is strongly recommended to always call the unlock() method to actively release the lock as soon as possible.
If the unlock() method is never called after this method call, the lock will get released when
  • the current resty.lock object instance is collected automatically by the Lua GC.
  • the exptime for the lock entry is reached.
您看有没有必要将lock库的__gc回收策略去掉呢?因为这里确实有一定概率的竞态隐患 :)

 

yunthanatos

unread,
Nov 8, 2016, 1:02:50 AM11/8/16
to openresty
或者在lock.lua中直接require'resty.core'?

这样会不会有其它的不利影响呢?

Yichun Zhang (agentzh)

unread,
Nov 8, 2016, 3:43:30 PM11/8/16
to openresty
Hello!

2016-11-07 21:13 GMT-08:00 yunthanatos:
> 现在还有一个小问题是关于lua-resty-lock库的:
>
> Readme.md:
>
>> It is strongly recommended to always call the unlock() method to actively release the lock as soon as possible.
>> If the unlock() method is never called after this method call, the lock will get released when
>>
>> the current resty.lock object instance is collected automatically by the Lua GC.
>>
>> the exptime for the lock entry is reached.
>
> 您看有没有必要将lock库的__gc回收策略去掉呢?因为这里确实有一定概率的竞态隐患 :)
>

有必要。欢迎提 pull request!

> 或者在lock.lua中直接require'resty.core'?
>
> 这样会不会有其它的不利影响呢?

lua-resty-lock 库不该强行加载 resty.core,这样副作用太大(倒不是说真会有什么问题)。

Regards,
-agentzh
Reply all
Reply to author
Forward
Message has been deleted
0 new messages