Skip to content

Commit 7710e68

Browse files
committed
fix(sleep) use interruptible sleep function
1 parent bedfdaa commit 7710e68

File tree

2 files changed

+66
-8
lines changed

2 files changed

+66
-8
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ The setting `max_use` controls the timer behaviour. The default value is `1000`,
9696
which means that after each `1000` invocations the timer context is destroyed
9797
and a new one is generated (this happens transparent to the user).
9898

99-
Optimizing this setting:
99+
Optimizing this setting (very opinionated/arbitrary!):
100100

101101
* if the timer interval is more than `60` seconds, then keeping the context
102102
around in idle state for that period is probably more expensive resource wise
@@ -128,6 +128,8 @@ Versioning is strictly based on [Semantic Versioning](https://semver.org/)
128128
* Feat: provide a stacktrace upon errors in the timer callback
129129
* Feat: add a `max_use` option. This ensures timer-contexts are recycled to
130130
prevent memory leaks.
131+
* Feat: adds a new function `sleep` similar to `ngx.sleep` except that it is
132+
interrupted on worker exit.
131133
* Fix: now accounts for execution time of the handler, when rescheduling.
132134

133135
### 1.1.0 (6-Nov-2020)

lib/resty/timer.lua

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ local anchor_registry = {}
1414
local gc_registry = setmetatable({},{ __mode = "v" })
1515
local timer_id = 0
1616
local now = ngx.now
17-
local sleep = ngx.sleep
17+
local ngx_sleep = ngx.sleep
1818
local exiting = ngx.worker.exiting
1919

2020
local KEY_PREFIX = "[lua-resty-timer]"
@@ -23,6 +23,64 @@ local CANCEL_GC = "GC"
2323
local CANCEL_SYSTEM = "SYSTEM"
2424
local CANCEL_USER = "USER"
2525

26+
local sleep do
27+
-- create a 10yr timer. Will only be called when the worker exits with
28+
-- `premature` set. The callback will release a global semaphore to wake up
29+
-- sleeping threads
30+
local sema = assert(require("ngx.semaphore").new())
31+
assert(timer_at(10*365*24*60*60, function()
32+
sema:post(math.huge)
33+
34+
-- TODO: remove the sleep(0), it's a hack around semaphores
35+
-- not being released properly, an Openresty bug.
36+
-- See https://github.com/openresty/lua-resty-core/issues/337
37+
ngx_sleep(0)
38+
end))
39+
40+
--- A `sleep` function that exits early on worker exit. The same as `ngx.sleep`
41+
-- except that it will be interrupted when the current worker starts exiting.
42+
-- Calling this function after the worker started exiting will immediately
43+
-- return (after briefly yielding with a `ngx.sleep(0)`).
44+
-- @param delay same as `ngx.sleep()`; delay in seconds.
45+
-- @return `true` if finished, `false` if returned early, or nil+err (under
46+
-- the hood it will return `not ngx.worker.exiting()`).
47+
-- @usage if not sleep(5) then
48+
-- -- sleep was interrupted, exit now
49+
-- return nil, "exiting"
50+
-- end
51+
--
52+
-- -- do stuff
53+
function sleep(delay)
54+
if type(delay) ~= "number" then
55+
error("Bad argument #1, expected number, got " .. type(delay), 2)
56+
end
57+
58+
if delay <= 0 then
59+
-- no delay, just yield and return
60+
ngx_sleep(delay)
61+
return not exiting()
62+
end
63+
64+
local ok, err = sema:wait(delay)
65+
if err == "timeout" then
66+
-- the sleep was finished
67+
return not exiting()
68+
end
69+
70+
-- each call to sleep should at a minimum at least yield, to prevent
71+
-- dead-locks elsewhere, so forcefully yield here.
72+
ngx_sleep(0)
73+
74+
if ok then
75+
-- we're exiting early because resources were posted to the semaphore
76+
return not exiting()
77+
end
78+
79+
ngx.log(ngx.ERR, "waiting for semaphore failed: ", err)
80+
return nil, err
81+
end
82+
end
83+
2684

2785

2886
--- Cancel the timer.
@@ -141,10 +199,7 @@ local function handler(premature, id)
141199
end
142200

143201
-- existing timer recurring, so keep this thread alive and just sleep
144-
if not exiting() then
145-
sleep(next_interval)
146-
end
147-
premature = exiting()
202+
premature = not sleep(next_interval)
148203
end -- while
149204
end
150205

@@ -160,12 +215,12 @@ end
160215
--
161216
-- * `jitter` : (optional, number) variable interval to add to the first interval, default 0.
162217
-- If set to 1 second then the first interval will be set between `interval` and `interval + 1`.
163-
-- This makes sure if large numbers of timers are used, their execution gets randomly
218+
-- This makes sure if a large number of timers are used, their execution gets randomly
164219
-- distributed.
165220
--
166221
-- * `immediate` : (boolean) will do the first run immediately (the initial
167222
-- interval will be set to 0 seconds). This option requires the `recurring` option.
168-
-- The first run will not include the `jitter` interval, it will be added to second run.
223+
-- The first run will not include the `jitter` interval, it will be added to the second run.
169224
--
170225
-- * `detached` : (boolean) if set to `true` the timer will keep running detached, if
171226
-- set to `false` the timer will be garbage collected unless anchored
@@ -340,6 +395,7 @@ end
340395
return setmetatable(
341396
{
342397
new = new,
398+
sleep = sleep,
343399
CANCEL_GC = CANCEL_GC,
344400
CANCEL_SYSTEM = CANCEL_SYSTEM,
345401
CANCEL_USER = CANCEL_USER,

0 commit comments

Comments
 (0)