Hello!
On Mon, Oct 15, 2012 at 8:29 PM, 朱茂海 wrote:
> js="aaaa"
> local handle = io.popen("echo "..js)
> local result = handle:read("*a")
> 这样会出错:
> stdin:1: attempt to index global 'handle' (a nil value)
> stack traceback:
> stdin:1: in main chunk
> [C]: ?
>
> 如何解决,谢谢!
>
首先,我们应该对 io.popen 和 io.read 这些操作进行恰当地错误处理:
location /echo {
default_type text/html;
content_by_lua '
local js = "aaaa"
local handle, err = io.popen("echo " .. js)
if not handle then
ngx.log(ngx.ERR, "failed to open: ", err)
return
end
local result, err = handle:read("*a")
if not result then
ngx.log(ngx.ERR, "failed to read: ", err)
return
end
ngx.print("xxx" .. result)
';
}
然后我们以 ab -c10 对这个 /echo 接口施加压力,便可以在 nginx 的错误日志文件中看到类似这样的错误:
[error] 27462#0: *6937 [lua] [string "content_by_lua"]:10: failed
to read: Interrupted system call, ...
显然 io.read() 操作失败了,而且是系统调用被信号这样的东西给中断了。接下来,使用下面的 systemtap 追踪发送给当前
nginx worker 进程的所有信号:
# signal.stp
probe begin {
println("Start tracing...\nHit Ctrl-C to end.")
}
probe signal.send {
if (sig_pid == target()) {
printf("%s was sent to %s (pid:%d) by %s uid:%d\n",
sig_name, pid_name, sig_pid, execname(),
uid())
}
}
执行之(同时请求 /echo 接口):
$ stap -x 27462 signal.stp
Start tracing...
Hit Ctrl-C to end.
SIGCHLD was sent to nginx (pid:27462) by sh uid:1000
SIGCHLD was sent to nginx (pid:27462) by sh uid:1000
...
假设这里只有一个 nginx worker 进程,并且其 pid 是 27462.
从这个 systemtap 脚本的输出可以清楚地看到,io.popen 确实创建了新的 sh 子进程,而当 sh
子进程退出时,便会自动向它的父进程,也就是调用 io.popen 的 nginx worker 进程发送 SIGCHLD 信号。而如果此时
nginx 正阻塞在 io.read() 发起的 read 系统调用上时,则该系统调用就会被中断,并返回错误“Interrupted
system call”. 一个直接的解决办法是,总是显式地调用 io.close() 方法关闭当前的 pipe 文件句柄:
location /echo {
default_type text/html;
content_by_lua '
local js = "aaaa"
local handle, err = io.popen("echo " .. js)
if not handle then
ngx.log(ngx.ERR, "failed to open: ", err)
return
end
local result, err = handle:read("*a")
handle:close()
if not result then
ngx.log(ngx.ERR, "failed to read: ", err)
return
end
ngx.print("xxx" .. result)
';
}
这样可以降低 Nginx 已经在处理下一个请求时才收到当前 shell 子进程的 SIGCHLD
信号。不过,正如上面的分析所暗示的,io.popen 和 io.read/io.write 这些都会发起系统调用都是阻塞
I/O,也就是说,使用它们的 ngx_lua 接口都只有最多 1 并发每 nginx worker 进程。所以是应当慎用的。
我有计划自己在 ngx_lua 中以非阻塞方式重新实现 io.popen 以及对应的 io.read/io.write
方法。虽然系统管道天然不可能是 C10K 的,但至少读写不要阻塞 nginx worker 进程 :)
另外,值得一提的是,在上面这个例子中,即使显式调用了 handle:close(),我们仍然会在 nginx 错误日志中得到类似下面这样的错误信息:
[alert] 17237#0: waitpid() failed (10: No child processes)
这是 Nginx worker 进程注册的 SIGCHLD 信号处理程序打印的无害的错误。更多细节可以参见静龙同学早前发的这一篇贴子:
http://groups.google.com/group/openresty/msg/6f353536526441c3
Best regards,
-agentzh