OpenResty Lua-cosocket API使用问题,在ngx.thread.spawn派生的两个子线程中,无法正确释放资源

117 views
Skip to first unread message

Lieyong Kang

unread,
Mar 9, 2022, 9:34:23 AM3/9/22
to openresty
大家好,在使用OpenResty 提供的cosocket在多线程中使用时,始终无法正确释放连接。
OpenResty版本号: openresty-1.19.9.1

我们的服务大致是这样的:
两个客户端A和B分别连接到OpenResty,OpenResty再将请求转发到后端服务.大致架构如下:

ClientA --
          \
        OpenResty--BackEnd
          /
ClientB --

ClientA和ClientB都在各自内网,他们都作为TCP客户端连接公网OpenResty。
ClientA和B刚开始都进行一些信令交互,完成后开始传输业务(TCP/HTTP)数据。
业务数据完全是经过加密。

ClientA是作为TCP/HTTP客户端
ClientB是作为TCP/HTTP服务器端

(1). 有短连接,一次请求完成后,两端都各自关闭
(2). 短连接,请求完成后,ClientA关闭,然后BackEnd关闭。最终是希望OpenResty连接ClientB的套接字能正常关闭释放。从而ClientB也能释放
(3). 有长连接,一次请求完成后,两端都不关闭,后续接着使用

现在发现第二种情况下始终存在异常。造成ClientB连接OpenResty连接资源无法释放。

请大家帮忙看看这个cosocket API使用哪里不当?还是因为cosocket限制?

Lua 代码如下,在content_by_lua_file 阶段执行。
```
local SOCKET_BUFFER_SIZE = 4096

-- 代理端服务套接字
local clientSock = ngx.req.socket()
clientSock:settimeouts(3000, 36000000, 36000000)

-- 连接后端的套接字
local cloudSock = ngx.socket.tcp()
cloudSock:settimeouts(3000, 36000000, 36000000)
-- 连接后端服务
local ok, err = zrplSock:connect("127.0.0.1", DEFAULT_CLOUD_PORT)

-- 进行一些数据交互操作,读取完后,发送对端,不会篡改数据
-- (1). 读取clientSock一些字节, 提取信息
-- (2). 再将这些字节发给cloudSock
-- (3). cloudSock读取一些字节,提取信息
-- (4). 再将这些字节发给clientSock
-- 数据交互读取操作完成


-- 将从客户端收到的数据,转发给后端
local function Client2Cloud(cloudSock, clientSock)
    bool done = false
    repeat
        local data, err1 = clientSock:receiveany(SOCKET_BUFFER_SIZE)
        if err1 then
            done = true

            -- {
            -- 如果客户端主动关闭连接,这部分能正常处理
            -- socket连接管理这部分逻辑,API无论怎么调整都无法正常关闭,造成TCP连接状态异常,连接泄漏
            if cloudSock.close ~= nil then
                local ok, err3 = cloudSock:close()
                if not ok then
                    cloudSock:shutdown("send")
                end
            else
                cloudSock:shutdown("send")
            end
            -- }            
        else
            local bytes, err2 = clientSock:send(data)
        end
    until done
end

-- 将从后端收到的数据,转发给客户端
local function Cloud2Client(clientSock, cloudSock)
    bool done = false
    repeat
        local data, err1 = cloudSock:receiveany(SOCKET_BUFFER_SIZE)
        if err1 then
            done = true

            -- {
            -- 如果后端主动关闭逻辑,造成TCP连接状态异常,客户端程序始终检测不到连接断开, 而OpenResty的clientSock状态异常
            -- socket连接管理这部分逻辑,API无论怎么调整都无法正常关闭,造成TCP连接状态异常,连接泄漏
            if clientSock.close ~= nil then
                local ok, err3 = clientSock:close()
                if not ok then
                    clientSock:shutdown("send")
                end
            else
                clientSock:shutdown("send")
            end
            -- }
        else
            local bytes, err2 = clientSock:send(data)
        end
    until done
end

-- 创建两个子线程,执行双向数据拷贝操作
local t1 = ngx.thread.spawn(Client2Cloud, cloudSock, clientSock)
local t2 = ngx.thread.spawn(Cloud2Client, clientSock, cloudSock)

-- 当前线程,阻塞等待子线程执行完毕
local ok, result1, result2 = ngx.thread.wait(t1, t2)
```

Junlong li

unread,
Mar 9, 2022, 8:16:24 PM3/9/22
to openresty
建议你能够最小化用例,给出一个大家能够复现的配置。

if clientSock.close ~= nil then 为什么要这么判断呢? clientSock.close这个函数一直是存在的吧

Lieyong Kang

unread,
Mar 11, 2022, 10:15:18 PM3/11/22
to openresty
谢谢。
这一个问题可能是因为我之前对cosocket的理解不当,造成API使用有误造成的。
现在通过修改Lua代码,已经解决这个问题了。
遵循的编程原则是:
1. 两个子线程分别负责:downstream->OpenResty->upstream 以及 upstream->OpenResty->downstream 的数据拷贝,对应两个方向上两个cosocket读取和写入。
2. 对某个cosocket的receive操作和close操作必须在同一个线程中运行,不能分散在两个ngx.thread中。否则会报"busy reading"。
3. 所以最好措施是两个线程共享某一个变量`local connEnd=false`,默认为false。A线程因为cosocket被动关闭(通过recevie操作收到"closed"的error)而退出,退出前需要置 `connEnd=true`。B在读取超时后,需要检查connEnd是否为true, 从而决定是否关闭自身cosocket并退出。
4. receive操作会阻塞,要在receive之后能调用close,必须将receive超时设短点,我设置为1s。
5. 主动调用close()之前,要先判断socket.close成员是否存在。因为如果cosocket被动关闭,该成员已经被置为nil。

通过以上几点,TCP连接资源得以正常释放。
Reply all
Reply to author
Forward
0 new messages