@@ -14,7 +14,7 @@ local anchor_registry = {}
1414local gc_registry = setmetatable ({},{ __mode = " v" })
1515local timer_id = 0
1616local now = ngx .now
17- local sleep = ngx .sleep
17+ local ngx_sleep = ngx .sleep
1818local exiting = ngx .worker .exiting
1919
2020local KEY_PREFIX = " [lua-resty-timer]"
@@ -23,6 +23,64 @@ local CANCEL_GC = "GC"
2323local CANCEL_SYSTEM = " SYSTEM"
2424local 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
149204end
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
340395return 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