Skip to content

Latest commit

 

History

History
207 lines (163 loc) · 12.7 KB

ngxtimerat.md

File metadata and controls

207 lines (163 loc) · 12.7 KB

ngx.timer.at

语法: ok, err = ngx.timer.at(delay, callback, user_arg1, user_arg2, ...)

环境: init_worker_by_lua, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.**

使用一个可选参数的用户回调函数,创建一个 Nginx 定时器。

第一个参数 delay,指定这个定时器的延迟时间,秒为单位。我们可以指定带有小数部分的时间像0.001,这里代表 1 毫秒。当需要立即执行当前挂起的处理,指定延迟为0即可。

第二个参数callback,可以是任何 Lua 函数,在指定的延迟时间之后,将会在一个后台的“轻线程”中被调用。这个调用是 Nginx 内核使用参数 prematureuser_arg1user_arg2等参数自动完成的,premature参数代表这个定时器是否过期的布尔值,user_arg1user_arg2等其他参数是调用ngx.timer.at的其余参数。

当 Nginx 工作进程正在尝试关闭时,定时器会过早失效,例如 HUP 信号触发的 Nginx 配置文件重载或 Nginx 服务关闭。当 Nginx 工作进程正在关闭,是不能调用 ngx.timer.at 来创建新的非零时间延迟定时器,这种情况下ngx.timer.at将返回nil和一个描述这个错误的字符串信息:"process exiting"。

v0.9.3版本开始,当 Nginx 工作进程开始关闭时,是允许创建零延迟定时器的。

当定时器到期,定时器中的 Lua 代码是在一个“请线程”中运行的,它与创造它的原始请求是完全分离的。因此,和创造它的请求有相同生命周期的对象,比如 cosockets,是不能在原始请求和定时器中的回调函数共享使用的。

这是个简单的例子:

 location / {
     ...
     log_by_lua '
         local function push_data(premature, uri, args, status)
             -- push the data uri, args, and status to the remote
             -- via ngx.socket.tcp or ngx.socket.udp
             -- (one may want to buffer the data in Lua a bit to
             -- save I/O operations)
         end
         local ok, err = ngx.timer.at(0, push_data,
                                      ngx.var.uri, ngx.var.args, ngx.header.status)
         if not ok then
             ngx.log(ngx.ERR, "failed to create timer: ", err)
             return
         end
     ';
 }

我们可以创建反复循环使用的定时器,例如,得到一个每5秒触发一次的定时器,可以在定时器中递归调用ngx.timer.at。这里有个这样的例子:

 local delay = 5
 local handler
 handler = function (premature)
     -- do some routine job in Lua just like a cron job
     if premature then
         return
     end
     local ok, err = ngx.timer.at(delay, handler)
     if not ok then
         ngx.log(ngx.ERR, "failed to create the timer: ", err)
         return
     end
 end

 local ok, err = ngx.timer.at(delay, handler)
 if not ok then
     ngx.log(ngx.ERR, "failed to create the timer: ", err)
     return
 end

因为定时调用是在后台运行,并且他们的执行不会增加任何客户端的响应时长,所以它们很容易让服务端累积错误并耗尽系统资源,有可能是 Lua 编程错误也可能仅仅是太多客户端请求。为了防止这种极端的恶果(例如: Nginx 服务崩溃),在 Nginx 工作进程里内建了 "pending timers" 和 "running timers" 两个数量限制。这里的 "pending timers" 代表还没有过期的定时器,"running timers" 代表用户回调函数当前正在运行的定时器。

在 Nginx 进程内 "pending timers" 的最大数控制是 lua_max_pending_timers 指令完成的。 "running timers" 的最大数控制是 lua_max_running_timers 指令完成的。

根据当前实现,每一个 "running timer" ,都将从全局连接列表中占用一个(假)连接,全局列表通过 nginx.conf 的标准指令 worker_connections 配置。所以要确保 worker_connections 指令设置了足够大的值,用来存放真正的连接和定时器需要假连接(受限于 lua_max_running_timers 指令)。

在定时器的回调函数中,很多 Nginx 的 Lua API 是可用的,像数据流/数据报 cosocket 的 (ngx.socket.tcpngx.socket.udp), 共享内存字典 (ngx.shared.DICT), 用户协同程序 (coroutine.*),用户 "轻线程" (ngx.thread.*), ngx.exitngx.now/ngx.timengx.md5/ngx.sha1_bin, 都是允许的。但是子请求 API (如 ngx.location.capture), ngx.req.* , 下游输出 API(如 ngx.sayngx.printngx.flush), 在这个环境是明确被禁用的。

定时器的回调函数可传入大多数标准 Lua 值(空、布尔值、数字、字符串、表、闭包、文件句柄等),无论是明确的用户参数或回调闭包的隐式值。这里有几个例外,通过 coroutine.createngx.thread.spawn 的线程对象,或通过 ngx.socket.tcpngx.socket.udpngx.req.socket 得到的 cosocket 对象,他们都是 不能 作为传入对象的,因为这些对象的生命周期都是绑定到创建定时器的请求环境的,但定时器回调与创建环境是完全隔离的(设计上),并且是在自己的(假)请求环境中运行。如果你尝试在创建请求边界共享线程或 cosocket 对象,你将得到错误信息 "no co ctx found"(对于线程),"bad request"(对于 cosockets)。它是好的,所以,在你的定时器回调中创建所有这些对象。

这个 API 是从v0.8.0首次引入的。

返回目录

English source:

ngx.timer.at

syntax: ok, err = ngx.timer.at(delay, callback, user_arg1, user_arg2, ...)

context: init_worker_by_lua, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.**

Creates an Nginx timer with a user callback function as well as optional user arguments.

The first argument, delay, specifies the delay for the timer, in seconds. One can specify fractional seconds like 0.001 to mean 1 millisecond here. 0 delay can also be specified, in which case the timer will immediately expire when the current handler yields execution.

The second argument, callback, can be any Lua function, which will be invoked later in a background "light thread" after the delay specified. The user callback will be called automatically by the Nginx core with the arguments premature, user_arg1, user_arg2, and etc, where the premature argument takes a boolean value indicating whether it is a premature timer expiration or not, and user_arg1, user_arg2, and etc, are those (extra) user arguments specified when calling ngx.timer.at as the remaining arguments.

Premature timer expiration happens when the Nginx worker process is trying to shut down, as in an Nginx configuration reload triggered by the HUP signal or in an Nginx server shutdown. When the Nginx worker is trying to shut down, one can no longer call ngx.timer.at to create new timers with nonzero delays and in that case ngx.timer.at will return nil and a string describing the error, that is, "process exiting".

Starting from the v0.9.3 release, it is allowed to create zero-delay timers even when the Nginx worker process starts shutting down.

When a timer expires, the user Lua code in the timer callback is running in a "light thread" detached completely from the original request creating the timer. So objects with the same lifetime as the request creating them, like cosockets, cannot be shared between the original request and the timer user callback function.

Here is a simple example:

 location / {
     ...
     log_by_lua '
         local function push_data(premature, uri, args, status)
             -- push the data uri, args, and status to the remote
             -- via ngx.socket.tcp or ngx.socket.udp
             -- (one may want to buffer the data in Lua a bit to
             -- save I/O operations)
         end
         local ok, err = ngx.timer.at(0, push_data,
                                      ngx.var.uri, ngx.var.args, ngx.header.status)
         if not ok then
             ngx.log(ngx.ERR, "failed to create timer: ", err)
             return
         end
     ';
 }

One can also create infinite re-occuring timers, for instance, a timer getting triggered every 5 seconds, by calling ngx.timer.at recursively in the timer callback function. Here is such an example,

 local delay = 5
 local handler
 handler = function (premature)
     -- do some routine job in Lua just like a cron job
     if premature then
         return
     end
     local ok, err = ngx.timer.at(delay, handler)
     if not ok then
         ngx.log(ngx.ERR, "failed to create the timer: ", err)
         return
     end
 end

 local ok, err = ngx.timer.at(delay, handler)
 if not ok then
     ngx.log(ngx.ERR, "failed to create the timer: ", err)
     return
 end

Because timer callbacks run in the background and their running time will not add to any client request's response time, they can easily accumulate in the server and exhaust system resources due to either Lua programming mistakes or just too much client traffic. To prevent extreme consequences like crashing the Nginx server, there are built-in limitations on both the number of "pending timers" and the number of "running timers" in an Nginx worker process. The "pending timers" here mean timers that have not yet been expired and "running timers" are those whose user callbacks are currently running.

The maximal number of pending timers allowed in an Nginx worker is constrolled by the lua_max_pending_timers directive. The maximal number of running timers is controlled by the lua_max_running_timers directive.

According to the current implementation, each "running timer" will take one (fake) connection record from the global connection record list configured by the standard worker_connections directive in nginx.conf. So ensure that the worker_connections directive is set to a large enough value that takes into account both the real connections and fake connections required by timer callbacks (as limited by the lua_max_running_timers directive).

A lot of the Lua APIs for Nginx are enabled in the context of the timer callbacks, like stream/datagram cosockets (ngx.socket.tcp and ngx.socket.udp), shared memory dictionaries (ngx.shared.DICT), user coroutines (coroutine.*), user "light threads" (ngx.thread.*), ngx.exit, ngx.now/ngx.time, ngx.md5/ngx.sha1_bin, are all allowed. But the subrequest API (like ngx.location.capture), the ngx.req.* API, the downstream output API (like ngx.say, ngx.print, and ngx.flush) are explicitly disabled in this context.

You can pass most of the standard Lua values (nils, booleans, numbers, strings, tables, closures, file handles, and etc) into the timer callback, either explicitly as user arguments or implicitly as upvalues for the callback closure. There are several exceptions, however: you cannot pass any thread objects returned by coroutine.create and ngx.thread.spawn or any cosocket objects returned by ngx.socket.tcp, ngx.socket.udp, and ngx.req.socket because these objects' lifetime is bound to the request context creating them while the timer callback is detached from the creating request's context (by design) and runs in its own (fake) request context. If you try to share the thread or cosocket objects across the boundary of the creating request, then you will get the "no co ctx found" error (for threads) or "bad request" (for cosockets). It is fine, however, to create all these objects inside your timer callback.

This API was first introduced in the v0.8.0 release.

Back to TOC