A scalable timer library for OpenResty.
This library is currently considered experimental.
http {
init_worker_by_lua_block {
local timer_module = require("resty.timerng")
local options = {}
local timer_sys = timer_module.new(options)
local function callback_once(premature, ...)
ngx.log(ngx.ERR, "in timer at")
end
local function callback_every(premature, ...)
ngx.log(ngx.ERR, "in timer every")
end
-- run after 100 ms
local name, err = timer_sys:at(0.1, callback_once)
if not name then
ngx.log(ngx.ERR, err)
end
-- run every 1s
name , err = timer_sys:every(1, callback_every)
if not name then
ngx.log(ngx.ERR, err)
end
}
}
This system is based on the timer wheel algorithm.
which uses the small number of timers
created by OpenResty API ngx.timer.at
to manage a large number of timed tasks.
In other words, it can reduce the number of fake requests.
- Efficiently, create, pause, start and cancel a timer takes O(1) time.
- Concurrency control, you can limit the number of threads.
- Easy to debug
- Get statistics such as maximum, minimum, average, and variance of the runtime for each timer.
- Some information that is useful for debugging, such as where the timer was created and the call stack at that time.
The system records the following information:
- The maximum, minimum, average, and variance of the running time of each timer.
- The location of each timer created, such as call stack.
The following information will be recorded in debug mode.
- The callstack when the timer was created
- The elapsed_time for each timer
- The timers that are running
- Timers that are queued
In debug mode, you can call stats() to get the raw data needed to generate the flamegraph.
-- for resolution, please see `timer_module.new(options?)`
max_delay = resolution
max_interval = resolution
-- for wheel_setting, please see `timer_module.new(options?)`
for _, slots in ipairs(wheel_setting.slots_for_each_level) do
max_delay = max_delay * slots
max_interval = max_interval * slots
end
max_delay = max_delay - 2 -- second
max_interval = max_interval - 2 -- second
Exceeding this range will use OpenResty's timer.
Versioning is strictly based on Semantic Versioning
syntax: timer, err = require("resty.timerng").new(options?)
context: init_by_lua*, 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.*
For example
local timer_module = require("resty.timerng")
local timer_sys = timer_module.new({
-- debug mode
debug = false,
-- 100ms
resolution = 0.1,
restart_thread_after_runs = 5000,
-- automatically adjusts the number of threads according to the load
-- load = (running_timers + pending_timers) / threads
auto_scaling_load_threshold = 5,
min_threads = 32,
max_threads = 256,
-- 0.1 is resolution
-- max_delay/interval = 10 * 60 * 60 * 21 * 0.1 second
wheel_setting = {
level = 4,
slots_for_each_level = { 10, 60, 60, 24 } ,
}
})
syntax: ok, err = timer:start()
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.*
Start the timer system.
syntax: timer:freeze()
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.*
Suspend the timer system and the expiration of each timer will be frozen.
syntax: name_or_false, err = timer:named_at(name, delay, callback, ...)
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.*
Create a once timer. You must call this method after you have called timer:start()
.
If you have called timer:pause()
, you must call this function after you have called timer:start()
.
- name: The name of this timer, or if it is set to
nil
, a random name will be generated. - callback: A callback function will be called when this timer expired,
function callback(premature, ...)
. - delay: The expiration of this timer.
syntax: name_or_false, err = timer:named_every(name, interval, callback, ...)
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.*
Create a recurrent timer. You must call this method after you have called timer:start()
.
If you have called timer:pause()
, you must call this function after you have called timer:start()
.
- name: The name of this timer, or if it is set to
nil
, a random name will be generated. - callback: A callback function will be called when this timer expired,
function callback(premature, ...)
. - interval: The expiration of this timer.
Equivalent to timer:named_at(nil, delay, callback, ...)
Equivalent to timer:named_every(nil, interval, callback, ...)
syntax: ok, err = timer:resume(name)
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.*
Start a timer that has been paused and resets its expiration.
- name: The name of this timer.
syntax: ok, err = timer:pause(name)
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.*
Pause a timer that has been started.
- name: The name of this timer.
syntax: ok, err = timer:cancel(name)
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.*
Cancel a timer.
- name: The name of this timer.
syntax: timer:destroy()
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.*
Destroy all timers and the object is no longer available.
Any operation (except GC) on this object after calling this method is undefined.
syntax: timer:is_managed(name)
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.*
Return true
if the specified timer is managed by this system, and false
otherwise.
name
: name of timer
syntax: timer:set_debug(status)
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.*
Enable or disable debug mode.
status
: If true then debug mode will be enabled and vice versa debug mode will be disabled.
syntax: info, err = timer:stats(options?)
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.*
Get the statistics of the system.
options
:verbose
: Iftrue
, the statistics for each timer will be returned, defualtfalse
.flamegraph
: Iftrue
andverbose == true
, the raw data of flamegraph will be returned, you can runflamegraph.pl <output> > a.svg
to generate flamegraph, defaultfalse
.
For example:
local info, err = timer:stats(true)
if not info then
-- error
end
-- info.sys = {
-- running = [number], number of running timers
-- pending = [number], number of pending timers
-- waiting = [number], number of unexpired timers
-- total = [number], running + pending + waiting
-- }
local sys_info = info.sys
local flamegraph = info.flamegraph
-- flamegraph.running | pending | elapsed_time (second * 1000)
-- is a string, which is fold stacks, like
-- @/lualib/background.lua:32:init();@/lualib/background.lua:64:start_dns_timer() 16
-- @/lualib/background.lua:46:init();@/lualib/background.lua:128:start_cache_timer() 76
-- you can run `flamegraph.pl <output> > a.svg` to generate flamegraph.
-- ref https://github.com/brendangregg/FlameGraph
for timer_name, timer in pairs(info.jobs)
local is_running = timer.is_running
local meta = timer.meta
local stats = timer.stats
local runs = timer.runs -- total number of runs
-- meta.name is an automatically generated string
-- that stores the location where the creation timer was created.
-- Such as 'task.lua:56:start_background_task()'
-- meta.callstack is a string.
stats = {
-- elapsed_time is a table that stores the
-- maximum, minimum, average and variance
-- of the time spent on each run of the timer (second).
elapsed_time = {
max = 100
min = 50
avg = 70
variance = 12
},
-- total number of runs
runs = 0,
-- Number of successful runs
finish = 0,
last_err_msg = "",
}
end
Copyright 2016-2022 Kong Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.