Skip to content

Commit 0b0d797

Browse files
committed
fix(sleep) use interruptible sleep function
1 parent bedfdaa commit 0b0d797

4 files changed

Lines changed: 169 additions & 14 deletions

File tree

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)

docs/index.html

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ <h2><a href="#Functions">Functions</a></h2>
6868
<td class="name" nowrap><a href="#new">new (opts, ...)</a></td>
6969
<td class="summary">Create a new timer.</td>
7070
</tr>
71+
<tr>
72+
<td class="name" nowrap><a href="#sleep">sleep (delay)</a></td>
73+
<td class="summary">A <a href="index.html#sleep">sleep</a> function that exits early on worker exit.</td>
74+
</tr>
7175
</table>
7276

7377
<br/>
@@ -121,11 +125,11 @@ <h3>Usage:</h3>
121125
<li><p><code>recurring</code> : (boolean) set to <code>true</code> to make it a recurring timer</p></li>
122126
<li><p><code>jitter</code> : (optional, number) variable interval to add to the first interval, default 0.
123127
If set to 1 second then the first interval will be set between <code>interval</code> and <code>interval + 1</code>.
124-
This makes sure if large numbers of timers are used, their execution gets randomly
128+
This makes sure if a large number of timers are used, their execution gets randomly
125129
distributed.</p></li>
126130
<li><p><code>immediate</code> : (boolean) will do the first run immediately (the initial
127131
interval will be set to 0 seconds). This option requires the <code>recurring</code> option.
128-
The first run will not include the <code>jitter</code> interval, it will be added to second run.</p></li>
132+
The first run will not include the <code>jitter</code> interval, it will be added to the second run.</p></li>
129133
<li><p><code>detached</code> : (boolean) if set to <code>true</code> the timer will keep running detached, if
130134
set to <code>false</code> the timer will be garbage collected unless anchored
131135
by the user.</p></li>
@@ -147,6 +151,8 @@ <h3>Usage:</h3>
147151
maximum delay could be <code>interval * 2</code> before another worker picks it up. With
148152
this option set, the maximum delay will be <code>interval + sub_interval</code>.
149153
This option requires the <code>immediate</code> and <code>key_name</code> options.</p></li>
154+
<li><p><code>max_use</code> : (optional, number, default 1000) the maximum use count for a
155+
timer context before recreating it.</p></li>
150156
</ul>
151157

152158

@@ -211,6 +217,44 @@ <h3>Usage:</h3>
211217
<span class="keyword">end</span></pre>
212218
</ul>
213219

220+
</dd>
221+
<dt>
222+
<a name = "sleep"></a>
223+
<strong>sleep (delay)</strong>
224+
</dt>
225+
<dd>
226+
A <a href="index.html#sleep">sleep</a> function that exits early on worker exit. The same as <code>ngx.sleep</code>
227+
except that it will be interrupted when the current worker starts exiting.
228+
Calling this function after the worker started exiting will immediately
229+
return (after briefly yielding with a <code>ngx.sleep(0)</code>).
230+
231+
232+
<h3>Parameters:</h3>
233+
<ul>
234+
<li><span class="parameter">delay</span>
235+
same as <code>ngx.sleep()</code>; delay in seconds.
236+
</li>
237+
</ul>
238+
239+
<h3>Returns:</h3>
240+
<ol>
241+
242+
<code>true</code> if finished, <code>false</code> if returned early, or nil+err (under
243+
the hood it will return: "<code>not ngx.worker.exiting()</code>").
244+
</ol>
245+
246+
247+
248+
<h3>Usage:</h3>
249+
<ul>
250+
<pre class="example"><span class="keyword">if</span> <span class="keyword">not</span> sleep(<span class="number">5</span>) <span class="keyword">then</span>
251+
<span class="comment">-- sleep was interrupted, exit now
252+
</span> <span class="keyword">return</span> <span class="keyword">nil</span>, <span class="string">"exiting"</span>
253+
<span class="keyword">end</span>
254+
255+
<span class="comment">-- do stuff</span></pre>
256+
</ul>
257+
214258
</dd>
215259
</dl>
216260

@@ -219,7 +263,7 @@ <h3>Usage:</h3>
219263
</div> <!-- id="main" -->
220264
<div id="about">
221265
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
222-
<i style="float:right;">Last updated 2020-11-07 00:01:15 </i>
266+
<i style="float:right;">Last updated 2021-08-12 14:42:50 </i>
223267
</div> <!-- id="about" -->
224268
</div> <!-- id="container" -->
225269
</body>

docs/topics/README.md.html

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ <h2>Contents</h2>
3232
<li><a href="#Status">Status </a></li>
3333
<li><a href="#Synopsis">Synopsis </a></li>
3434
<li><a href="#Description">Description </a></li>
35+
<li><a href="#Performance_and_optimizations">Performance and optimizations </a></li>
3536
<li><a href="#History">History </a></li>
3637
<li><a href="#Copyright_and_License">Copyright and License </a></li>
3738
</ul>
@@ -53,7 +54,7 @@ <h2>Modules</h2>
5354

5455
<h1>lua-resty-timer</h1>
5556

56-
<p><a href="https://travis-ci.org/Kong/lua-resty-timer/branches"><img src="https://travis-ci.org/Kong/lua-resty-timer.svg?branch=master" alt="Build Status"/></a></p>
57+
<p><a href="https://travis-ci.com/Kong/lua-resty-timer/branches"><img src="https://travis-ci.com/Kong/lua-resty-timer.svg?branch=master" alt="Build Status"/></a></p>
5758

5859
<p>Extended timers for OpenResty. Provided recurring, cancellable, node-wide timers,
5960
beyond what the basic OpenResty timers do.</p>
@@ -86,6 +87,7 @@ <h2>Synopsis</h2>
8687
shm_name = <span class="string">"timer_shm"</span>, <span class="comment">-- shm to use for node-wide timers
8788
</span> key_name = <span class="string">"my_key"</span>, <span class="comment">-- key-name to use for node-wide timers
8889
</span> sub_interval = <span class="number">0.1</span>, <span class="comment">-- max cross worker extra delay
90+
</span> max_use = <span class="number">1000</span>, <span class="comment">-- maximum re-use of timer context
8991
</span> }
9092

9193
<span class="keyword">local</span> object
@@ -118,7 +120,8 @@ <h2>Description</h2>
118120
project.</p>
119121

120122
<ul>
121-
<li><p>recurring timers (supported by OR as well through <code>ngx.timer.every</code>)</p></li>
123+
<li><p>recurring timers (supported by OR as well through <code>ngx.timer.every</code>, but this
124+
implementation will not run overlapping timers)</p></li>
122125
<li><p>immediate first run for recurring timers</p></li>
123126
<li><p>cancellable timers</p></li>
124127
<li><p>cancel callback, called when the timer is cancelled</p></li>
@@ -132,6 +135,37 @@ <h2>Description</h2>
132135
<p>See the <a href="https://kong.github.io/lua-resty-timer/topics/README.md.html">online LDoc documentation</a>
133136
for the complete API.</p>
134137

138+
<p><a name="Performance_and_optimizations"></a></p>
139+
<h2>Performance and optimizations</h2>
140+
141+
<p>This timer implementation is based on "sleeping on a timer-context". This means
142+
that a single timer is created, and in between recurring invocations <code>ngx.sleep</code>
143+
is called as a delay to the next invocation. This as opposed to creating a new
144+
Nginx timer for each invocation. This is configurable however.</p>
145+
146+
<p>Creating a new context is a rather expensive operation. Hence we keep the context
147+
alive and just sleep without the need to recreate it. The downside is that there
148+
is the possibility of a memory leak. Since a timer is implemented in OR as a
149+
request and requests are short-lived, some memory is not released until after the
150+
context is destroyed.</p>
151+
152+
<p>The setting <code>max_use</code> controls the timer behaviour. The default value is <code>1000</code>,
153+
which means that after each <code>1000</code> invocations the timer context is destroyed
154+
and a new one is generated (this happens transparent to the user).</p>
155+
156+
<p>Optimizing this setting (very opinionated/arbitrary!):</p>
157+
158+
<ul>
159+
<li><p>if the timer interval is more than <code>60</code> seconds, then keeping the context
160+
around in idle state for that period is probably more expensive resource wise
161+
than having to recreate the context. So use <code>max_use == 1</code> to drop the
162+
context after each invocation.</p></li>
163+
<li><p>if the timer interval is less than <code>5</code> seconds then reusing the context makes
164+
sense. Assume recycling to be done once per minute, or for very high
165+
frequency timers (and hence higher risk of memory leak), more than once per
166+
minute.</p></li>
167+
</ul>
168+
135169
<p><a name="History"></a></p>
136170
<h2>History</h2>
137171

@@ -150,6 +184,25 @@ <h3>Releasing new versions:</h3>
150184
<li>upload rock to luarocks: <code>luarocks upload rockspecs/[name] --api-key=abc</code></li>
151185
</ul>
152186

187+
<h3>unreleased</h3>
188+
189+
<ul>
190+
<li>Feat: provide a stacktrace upon errors in the timer callback</li>
191+
<li>Feat: add a <code>max_use</code> option. This ensures timer-contexts are recycled to
192+
193+
<pre>
194+
prevent memory leaks.
195+
</pre>
196+
</li>
197+
<li>Feat: adds a new function <a href="../index.html#sleep">sleep</a> similar to <code>ngx.sleep</code> except that it is
198+
199+
<pre>
200+
interrupted on worker exit.
201+
</pre>
202+
</li>
203+
<li>Fix: now accounts for execution time of the handler, when rescheduling.</li>
204+
</ul>
205+
153206
<h3>1.1.0 (6-Nov-2020)</h3>
154207

155208
<ul>
@@ -213,7 +266,7 @@ <h2>Copyright and License</h2>
213266
</div> <!-- id="main" -->
214267
<div id="about">
215268
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
216-
<i style="float:right;">Last updated 2020-11-07 00:01:15 </i>
269+
<i style="float:right;">Last updated 2021-08-12 14:42:50 </i>
217270
</div> <!-- id="about" -->
218271
</div> <!-- id="container" -->
219272
</body>

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)