diff options
author | Benoit Germain <bnt.germain@gmail.com> | 2015-12-29 10:09:34 +0100 |
---|---|---|
committer | Benoit Germain <bnt.germain@gmail.com> | 2015-12-29 10:09:34 +0100 |
commit | f1b69462a9a7ebf5d761bb8fc3b043b8898969a7 (patch) | |
tree | 4ab132aad136f95bc23c097af9f5b3925e71644f | |
parent | 295ba159356050037419cbdb5ba1d1e3362ce391 (diff) | |
parent | 4f52790f4896ef6b05f3784bdb668ac3f8144aac (diff) | |
download | lanes-f1b69462a9a7ebf5d761bb8fc3b043b8898969a7.tar.gz lanes-f1b69462a9a7ebf5d761bb8fc3b043b8898969a7.tar.bz2 lanes-f1b69462a9a7ebf5d761bb8fc3b043b8898969a7.zip |
Merge pull request #121 from aryajur/master
Improved the lanes.lua file
-rw-r--r-- | src/lanes.lua | 967 |
1 files changed, 489 insertions, 478 deletions
diff --git a/src/lanes.lua b/src/lanes.lua index 5df406a..6cbbd65 100644 --- a/src/lanes.lua +++ b/src/lanes.lua | |||
@@ -41,6 +41,8 @@ local core = require "lanes.core" | |||
41 | -- almost everything module() does is done by require() anyway | 41 | -- almost everything module() does is done by require() anyway |
42 | -- -> simply create a table, populate it, return it, and be done | 42 | -- -> simply create a table, populate it, return it, and be done |
43 | local lanes = {} | 43 | local lanes = {} |
44 | local lanesMeta = {} | ||
45 | setmetatable(lanes,lanesMeta) | ||
44 | 46 | ||
45 | -- this function is available in the public interface until it is called, after which it disappears | 47 | -- this function is available in the public interface until it is called, after which it disappears |
46 | lanes.configure = function( settings_) | 48 | lanes.configure = function( settings_) |
@@ -52,7 +54,8 @@ lanes.configure = function( settings_) | |||
52 | if not string then | 54 | if not string then |
53 | error( "To use 'lanes', you will also need to have 'string' available.", 2) | 55 | error( "To use 'lanes', you will also need to have 'string' available.", 2) |
54 | end | 56 | end |
55 | 57 | -- Configure called so remove metatable from lanes | |
58 | setmetatable(lanes,nil) | ||
56 | -- | 59 | -- |
57 | -- Cache globals for code that might run under sandboxing | 60 | -- Cache globals for code that might run under sandboxing |
58 | -- | 61 | -- |
@@ -131,548 +134,548 @@ lanes.configure = function( settings_) | |||
131 | local core_lane_new = assert( core.lane_new) | 134 | local core_lane_new = assert( core.lane_new) |
132 | local max_prio = assert( core.max_prio) | 135 | local max_prio = assert( core.max_prio) |
133 | 136 | ||
134 | lanes.ABOUT = | 137 | lanes.ABOUT = |
135 | { | 138 | { |
136 | author= "Asko Kauppi <akauppi@gmail.com>, Benoit Germain <bnt.germain@gmail.com>", | 139 | author= "Asko Kauppi <akauppi@gmail.com>, Benoit Germain <bnt.germain@gmail.com>", |
137 | description= "Running multiple Lua states in parallel", | 140 | description= "Running multiple Lua states in parallel", |
138 | license= "MIT/X11", | 141 | license= "MIT/X11", |
139 | copyright= "Copyright (c) 2007-10, Asko Kauppi; (c) 2011-13, Benoit Germain", | 142 | copyright= "Copyright (c) 2007-10, Asko Kauppi; (c) 2011-13, Benoit Germain", |
140 | version = assert( core.version) | 143 | version = assert( core.version) |
141 | } | 144 | } |
142 | 145 | ||
143 | 146 | ||
144 | -- Making copies of necessary system libs will pass them on as upvalues; | 147 | -- Making copies of necessary system libs will pass them on as upvalues; |
145 | -- only the first state doing "require 'lanes'" will need to have 'string' | 148 | -- only the first state doing "require 'lanes'" will need to have 'string' |
146 | -- and 'table' visible. | 149 | -- and 'table' visible. |
147 | -- | 150 | -- |
148 | local function WR(str) | 151 | local function WR(str) |
149 | io.stderr:write( str.."\n" ) | 152 | io.stderr:write( str.."\n" ) |
150 | end | 153 | end |
151 | 154 | ||
152 | local function DUMP( tbl ) | 155 | local function DUMP( tbl ) |
153 | if not tbl then return end | 156 | if not tbl then return end |
154 | local str="" | 157 | local str="" |
155 | for k,v in pairs(tbl) do | 158 | for k,v in pairs(tbl) do |
156 | str= str..k.."="..tostring(v).."\n" | 159 | str= str..k.."="..tostring(v).."\n" |
157 | end | 160 | end |
158 | WR(str) | 161 | WR(str) |
159 | end | 162 | end |
160 | 163 | ||
161 | 164 | ||
162 | ---=== Laning ===--- | 165 | ---=== Laning ===--- |
163 | 166 | ||
164 | -- lane_h[1..n]: lane results, same as via 'lane_h:join()' | 167 | -- lane_h[1..n]: lane results, same as via 'lane_h:join()' |
165 | -- lane_h[0]: can be read to make sure a thread has finished (always gives 'true') | 168 | -- lane_h[0]: can be read to make sure a thread has finished (always gives 'true') |
166 | -- lane_h[-1]: error message, without propagating the error | 169 | -- lane_h[-1]: error message, without propagating the error |
167 | -- | 170 | -- |
168 | -- Reading a Lane result (or [0]) propagates a possible error in the lane | 171 | -- Reading a Lane result (or [0]) propagates a possible error in the lane |
169 | -- (and execution does not return). Cancelled lanes give 'nil' values. | 172 | -- (and execution does not return). Cancelled lanes give 'nil' values. |
170 | -- | 173 | -- |
171 | -- lane_h.state: "pending"/"running"/"waiting"/"done"/"error"/"cancelled" | 174 | -- lane_h.state: "pending"/"running"/"waiting"/"done"/"error"/"cancelled" |
172 | -- | 175 | -- |
173 | -- Note: Would be great to be able to have '__ipairs' metamethod, that gets | 176 | -- Note: Would be great to be able to have '__ipairs' metamethod, that gets |
174 | -- called by 'ipairs()' function to custom iterate objects. We'd use it | 177 | -- called by 'ipairs()' function to custom iterate objects. We'd use it |
175 | -- for making sure a lane has ended (results are available); not requiring | 178 | -- for making sure a lane has ended (results are available); not requiring |
176 | -- the user to precede a loop by explicit 'h[0]' or 'h:join()'. | 179 | -- the user to precede a loop by explicit 'h[0]' or 'h:join()'. |
177 | -- | 180 | -- |
178 | -- Or, even better, 'ipairs()' should start valuing '__index' instead | 181 | -- Or, even better, 'ipairs()' should start valuing '__index' instead |
179 | -- of using raw reads that bypass it. | 182 | -- of using raw reads that bypass it. |
180 | -- | 183 | -- |
181 | ----- | 184 | ----- |
182 | -- lanes.gen( [libs_str|opt_tbl [, ...],] lane_func ) ( [...] ) -> h | 185 | -- lanes.gen( [libs_str|opt_tbl [, ...],] lane_func ) ( [...] ) -> h |
183 | -- | 186 | -- |
184 | -- 'libs': nil: no libraries available (default) | 187 | -- 'libs': nil: no libraries available (default) |
185 | -- "": only base library ('assert', 'print', 'unpack' etc.) | 188 | -- "": only base library ('assert', 'print', 'unpack' etc.) |
186 | -- "math,os": math + os + base libraries (named ones + base) | 189 | -- "math,os": math + os + base libraries (named ones + base) |
187 | -- "*": all standard libraries available | 190 | -- "*": all standard libraries available |
188 | -- | 191 | -- |
189 | -- 'opt': .priority: int (-3..+3) smaller is lower priority (0 = default) | 192 | -- 'opt': .priority: int (-3..+3) smaller is lower priority (0 = default) |
190 | -- | 193 | -- |
191 | -- .cancelstep: bool | uint | 194 | -- .cancelstep: bool | uint |
192 | -- false: cancellation check only at pending Linda operations | 195 | -- false: cancellation check only at pending Linda operations |
193 | -- (send/receive) so no runtime performance penalty (default) | 196 | -- (send/receive) so no runtime performance penalty (default) |
194 | -- true: adequate cancellation check (same as 100) | 197 | -- true: adequate cancellation check (same as 100) |
195 | -- >0: cancellation check every x Lua lines (small number= faster | 198 | -- >0: cancellation check every x Lua lines (small number= faster |
196 | -- reaction but more performance overhead) | 199 | -- reaction but more performance overhead) |
197 | -- | 200 | -- |
198 | -- .globals: table of globals to set for a new thread (passed by value) | 201 | -- .globals: table of globals to set for a new thread (passed by value) |
199 | -- | 202 | -- |
200 | -- .required: table of packages to require | 203 | -- .required: table of packages to require |
201 | -- | 204 | -- |
202 | -- .gc_cb: function called when the lane handle is collected | 205 | -- .gc_cb: function called when the lane handle is collected |
203 | -- | 206 | -- |
204 | -- ... (more options may be introduced later) ... | 207 | -- ... (more options may be introduced later) ... |
205 | -- | 208 | -- |
206 | -- Calling with a function parameter ('lane_func') ends the string/table | 209 | -- Calling with a function parameter ('lane_func') ends the string/table |
207 | -- modifiers, and prepares a lane generator. | 210 | -- modifiers, and prepares a lane generator. |
208 | |||
209 | local valid_libs = | ||
210 | { | ||
211 | ["package"] = true, | ||
212 | ["table"] = true, | ||
213 | ["io"] = true, | ||
214 | ["os"] = true, | ||
215 | ["string"] = true, | ||
216 | ["math"] = true, | ||
217 | ["debug"] = true, | ||
218 | ["bit32"] = true, -- Lua 5.2 only, ignored silently under 5.1 | ||
219 | ["utf8"] = true, -- Lua 5.3 only, ignored silently under 5.1 and 5.2 | ||
220 | -- | ||
221 | ["base"] = true, | ||
222 | ["coroutine"] = true, -- part of "base" in Lua 5.1 | ||
223 | ["lanes.core"] = true | ||
224 | } | ||
225 | |||
226 | local raise_option_error = function( name_, tv_, v_) | ||
227 | error( "Bad '" .. name_ .. "' option: " .. tv_ .. " " .. string_format( "%q", tostring( v_)), 4) | ||
228 | end | ||
229 | 211 | ||
230 | local opt_validators = | 212 | local valid_libs = |
231 | { | 213 | { |
232 | priority = function( v_) | 214 | ["package"] = true, |
233 | local tv = type( v_) | 215 | ["table"] = true, |
234 | return (tv == "number") and v_ or raise_option_error( "priority", tv, v_) | 216 | ["io"] = true, |
235 | end, | 217 | ["os"] = true, |
236 | cancelstep = function( v_) | 218 | ["string"] = true, |
237 | local tv = type( v_) | 219 | ["math"] = true, |
238 | return (tv == "number") and v_ or (v_ == true) and 100 or (v_ == false) and 0 or raise_option_error( "cancelstep", tv, v_) | 220 | ["debug"] = true, |
239 | end, | 221 | ["bit32"] = true, -- Lua 5.2 only, ignored silently under 5.1 |
240 | globals = function( v_) | 222 | ["utf8"] = true, -- Lua 5.3 only, ignored silently under 5.1 and 5.2 |
241 | local tv = type( v_) | 223 | -- |
242 | return (tv == "table") and v_ or raise_option_error( "globals", tv, v_) | 224 | ["base"] = true, |
243 | end, | 225 | ["coroutine"] = true, -- part of "base" in Lua 5.1 |
244 | package = function( v_) | 226 | ["lanes.core"] = true |
245 | local tv = type( v_) | 227 | } |
246 | return (tv == "table") and v_ or raise_option_error( "package", tv, v_) | 228 | |
247 | end, | 229 | local raise_option_error = function( name_, tv_, v_) |
248 | required = function( v_) | 230 | error( "Bad '" .. name_ .. "' option: " .. tv_ .. " " .. string_format( "%q", tostring( v_)), 4) |
249 | local tv = type( v_) | ||
250 | return (tv == "table") and v_ or raise_option_error( "required", tv, v_) | ||
251 | end, | ||
252 | gc_cb = function( v_) | ||
253 | local tv = type( v_) | ||
254 | return (tv == "function") and v_ or raise_option_error( "gc_cb", tv, v_) | ||
255 | end | ||
256 | } | ||
257 | |||
258 | -- PUBLIC LANES API | ||
259 | -- receives a sequence of strings and tables, plus a function | ||
260 | local gen = function( ...) | ||
261 | -- aggregrate all strings together, separated by "," as well as tables | ||
262 | -- the strings are a list of libraries to open | ||
263 | -- the tables contain the lane options | ||
264 | local opt = {} | ||
265 | local libs = nil | ||
266 | |||
267 | local n = select( '#', ...) | ||
268 | |||
269 | -- we need at least a function | ||
270 | if n == 0 then | ||
271 | error( "No parameters!", 2) | ||
272 | end | 231 | end |
273 | 232 | ||
274 | -- all arguments but the last must be nil, strings, or tables | 233 | local opt_validators = |
275 | for i = 1, n - 1 do | 234 | { |
276 | local v = select( i, ...) | 235 | priority = function( v_) |
277 | local tv = type( v) | 236 | local tv = type( v_) |
278 | if tv == "string" then | 237 | return (tv == "number") and v_ or raise_option_error( "priority", tv, v_) |
279 | libs = libs and libs .. "," .. v or v | 238 | end, |
280 | elseif tv == "table" then | 239 | cancelstep = function( v_) |
281 | for k, vv in pairs( v) do | 240 | local tv = type( v_) |
282 | opt[k]= vv | 241 | return (tv == "number") and v_ or (v_ == true) and 100 or (v_ == false) and 0 or raise_option_error( "cancelstep", tv, v_) |
283 | end | 242 | end, |
284 | elseif v == nil then | 243 | globals = function( v_) |
285 | -- skip | 244 | local tv = type( v_) |
286 | else | 245 | return (tv == "table") and v_ or raise_option_error( "globals", tv, v_) |
287 | error( "Bad parameter " .. i .. ": " .. tv .. " " .. string_format( "%q", tostring( v)), 2) | 246 | end, |
247 | package = function( v_) | ||
248 | local tv = type( v_) | ||
249 | return (tv == "table") and v_ or raise_option_error( "package", tv, v_) | ||
250 | end, | ||
251 | required = function( v_) | ||
252 | local tv = type( v_) | ||
253 | return (tv == "table") and v_ or raise_option_error( "required", tv, v_) | ||
254 | end, | ||
255 | gc_cb = function( v_) | ||
256 | local tv = type( v_) | ||
257 | return (tv == "function") and v_ or raise_option_error( "gc_cb", tv, v_) | ||
288 | end | 258 | end |
289 | end | 259 | } |
290 | 260 | ||
291 | -- the last argument should be a function or a string | 261 | -- PUBLIC LANES API |
292 | local func = select( n, ...) | 262 | -- receives a sequence of strings and tables, plus a function |
293 | local functype = type( func) | 263 | local gen = function( ...) |
294 | if functype ~= "function" and functype ~= "string" then | 264 | -- aggregrate all strings together, separated by "," as well as tables |
295 | error( "Last parameter not function or string: " .. functype .. " " .. string_format( "%q", tostring( func)), 2) | 265 | -- the strings are a list of libraries to open |
296 | end | 266 | -- the tables contain the lane options |
267 | local opt = {} | ||
268 | local libs = nil | ||
269 | |||
270 | local n = select( '#', ...) | ||
271 | |||
272 | -- we need at least a function | ||
273 | if n == 0 then | ||
274 | error( "No parameters!", 2) | ||
275 | end | ||
297 | 276 | ||
298 | -- check that the caller only provides reserved library names, and those only once | 277 | -- all arguments but the last must be nil, strings, or tables |
299 | -- "*" is a special case that doesn't require individual checking | 278 | for i = 1, n - 1 do |
300 | if libs and libs ~= "*" then | 279 | local v = select( i, ...) |
301 | local found = {} | 280 | local tv = type( v) |
302 | for s in string_gmatch(libs, "[%a%d.]+") do | 281 | if tv == "string" then |
303 | if not valid_libs[s] then | 282 | libs = libs and libs .. "," .. v or v |
304 | error( "Bad library name: " .. s, 2) | 283 | elseif tv == "table" then |
284 | for k, vv in pairs( v) do | ||
285 | opt[k]= vv | ||
286 | end | ||
287 | elseif v == nil then | ||
288 | -- skip | ||
305 | else | 289 | else |
306 | found[s] = (found[s] or 0) + 1 | 290 | error( "Bad parameter " .. i .. ": " .. tv .. " " .. string_format( "%q", tostring( v)), 2) |
307 | if found[s] > 1 then | 291 | end |
308 | error( "libs specification contains '" .. s .. "' more than once", 2) | 292 | end |
293 | |||
294 | -- the last argument should be a function or a string | ||
295 | local func = select( n, ...) | ||
296 | local functype = type( func) | ||
297 | if functype ~= "function" and functype ~= "string" then | ||
298 | error( "Last parameter not function or string: " .. functype .. " " .. string_format( "%q", tostring( func)), 2) | ||
299 | end | ||
300 | |||
301 | -- check that the caller only provides reserved library names, and those only once | ||
302 | -- "*" is a special case that doesn't require individual checking | ||
303 | if libs and libs ~= "*" then | ||
304 | local found = {} | ||
305 | for s in string_gmatch(libs, "[%a%d.]+") do | ||
306 | if not valid_libs[s] then | ||
307 | error( "Bad library name: " .. s, 2) | ||
308 | else | ||
309 | found[s] = (found[s] or 0) + 1 | ||
310 | if found[s] > 1 then | ||
311 | error( "libs specification contains '" .. s .. "' more than once", 2) | ||
312 | end | ||
309 | end | 313 | end |
310 | end | 314 | end |
311 | end | 315 | end |
312 | end | ||
313 | 316 | ||
314 | -- validate that each option is known and properly valued | 317 | -- validate that each option is known and properly valued |
315 | for k, v in pairs( opt) do | 318 | for k, v in pairs( opt) do |
316 | local validator = opt_validators[k] | 319 | local validator = opt_validators[k] |
317 | if not validator then | 320 | if not validator then |
318 | error( (type( k) == "number" and "Unkeyed option: " .. type( v) .. " " .. string_format( "%q", tostring( v)) or "Bad '" .. tostring( k) .. "' option"), 2) | 321 | error( (type( k) == "number" and "Unkeyed option: " .. type( v) .. " " .. string_format( "%q", tostring( v)) or "Bad '" .. tostring( k) .. "' option"), 2) |
319 | else | 322 | else |
320 | opt[k] = validator( v) | 323 | opt[k] = validator( v) |
324 | end | ||
321 | end | 325 | end |
322 | end | ||
323 | 326 | ||
324 | local cancelstep, priority, globals, package, required, gc_cb = opt.cancelstep, opt.priority, opt.globals, opt.package or package, opt.required, opt.gc_cb | 327 | local cancelstep, priority, globals, package, required, gc_cb = opt.cancelstep, opt.priority, opt.globals, opt.package or package, opt.required, opt.gc_cb |
325 | return function( ...) | 328 | return function( ...) |
326 | -- must pass functions args last else they will be truncated to the first one | 329 | -- must pass functions args last else they will be truncated to the first one |
327 | return core_lane_new( func, libs, cancelstep, priority, globals, package, required, gc_cb, ...) | 330 | return core_lane_new( func, libs, cancelstep, priority, globals, package, required, gc_cb, ...) |
328 | end | 331 | end |
329 | end -- gen() | 332 | end -- gen() |
330 | 333 | ||
331 | ---=== Timers ===--- | 334 | ---=== Timers ===--- |
332 | 335 | ||
333 | -- PUBLIC LANES API | 336 | -- PUBLIC LANES API |
334 | local timer = function() error "timers are not active" end | 337 | local timer = function() error "timers are not active" end |
335 | local timers = timer | 338 | local timers = timer |
336 | local timer_lane = nil | 339 | local timer_lane = nil |
337 | 340 | ||
338 | -- timer_gateway should always exist, even when the settings disable the timers | 341 | -- timer_gateway should always exist, even when the settings disable the timers |
339 | local timer_gateway = assert( core.timer_gateway) | 342 | local timer_gateway = assert( core.timer_gateway) |
340 | 343 | ||
341 | ----- | 344 | ----- |
342 | -- <void> = sleep( [seconds_]) | 345 | -- <void> = sleep( [seconds_]) |
343 | -- | 346 | -- |
344 | -- PUBLIC LANES API | 347 | -- PUBLIC LANES API |
345 | local sleep = function( seconds_) | 348 | local sleep = function( seconds_) |
346 | seconds_ = seconds_ or 0.0 -- this causes false and nil to be a valid input, equivalent to 0.0, but that's ok | 349 | seconds_ = seconds_ or 0.0 -- this causes false and nil to be a valid input, equivalent to 0.0, but that's ok |
347 | if type( seconds_) ~= "number" then | 350 | if type( seconds_) ~= "number" then |
348 | error( "invalid duration " .. string_format( "%q", tostring(seconds_))) | 351 | error( "invalid duration " .. string_format( "%q", tostring(seconds_))) |
352 | end | ||
353 | -- receive data on a channel no-one ever sends anything, thus blocking for the specified duration | ||
354 | return timer_gateway:receive( seconds_, "ac100de1-a696-4619-b2f0-a26de9d58ab8") | ||
349 | end | 355 | end |
350 | -- receive data on a channel no-one ever sends anything, thus blocking for the specified duration | ||
351 | return timer_gateway:receive( seconds_, "ac100de1-a696-4619-b2f0-a26de9d58ab8") | ||
352 | end | ||
353 | 356 | ||
354 | 357 | ||
355 | if settings.with_timers ~= false then | 358 | if settings.with_timers ~= false then |
356 | 359 | ||
357 | -- | 360 | -- |
358 | -- On first 'require "lanes"', a timer lane is spawned that will maintain | 361 | -- On first 'require "lanes"', a timer lane is spawned that will maintain |
359 | -- timer tables and sleep in between the timer events. All interaction with | 362 | -- timer tables and sleep in between the timer events. All interaction with |
360 | -- the timer lane happens via a 'timer_gateway' Linda, which is common to | 363 | -- the timer lane happens via a 'timer_gateway' Linda, which is common to |
361 | -- all that 'require "lanes"'. | 364 | -- all that 'require "lanes"'. |
362 | -- | 365 | -- |
363 | -- Linda protocol to timer lane: | 366 | -- Linda protocol to timer lane: |
364 | -- | 367 | -- |
365 | -- TGW_KEY: linda_h, key, [wakeup_at_secs], [repeat_secs] | 368 | -- TGW_KEY: linda_h, key, [wakeup_at_secs], [repeat_secs] |
366 | -- | 369 | -- |
367 | local TGW_KEY= "(timer control)" -- the key does not matter, a 'weird' key may help debugging | 370 | local TGW_KEY= "(timer control)" -- the key does not matter, a 'weird' key may help debugging |
368 | local TGW_QUERY, TGW_REPLY = "(timer query)", "(timer reply)" | 371 | local TGW_QUERY, TGW_REPLY = "(timer query)", "(timer reply)" |
369 | local first_time_key= "first time" | 372 | local first_time_key= "first time" |
370 | |||
371 | local first_time = timer_gateway:get( first_time_key) == nil | ||
372 | timer_gateway:set( first_time_key, true) | ||
373 | 373 | ||
374 | -- | 374 | local first_time = timer_gateway:get( first_time_key) == nil |
375 | -- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally | 375 | timer_gateway:set( first_time_key, true) |
376 | -- has 'table' always declared) | ||
377 | -- | ||
378 | if first_time then | ||
379 | 376 | ||
380 | local now_secs = core.now_secs | ||
381 | assert( type( now_secs) == "function") | ||
382 | ----- | ||
383 | -- Snore loop (run as a lane on the background) | ||
384 | -- | ||
385 | -- High priority, to get trustworthy timings. | ||
386 | -- | 377 | -- |
387 | -- We let the timer lane be a "free running" thread; no handle to it | 378 | -- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally |
388 | -- remains. | 379 | -- has 'table' always declared) |
389 | -- | 380 | -- |
390 | local timer_body = function() | 381 | if first_time then |
391 | set_debug_threadname( "LanesTimer") | 382 | |
392 | -- | 383 | local now_secs = core.now_secs |
393 | -- { [deep_linda_lightuserdata]= { [deep_linda_lightuserdata]=linda_h, | 384 | assert( type( now_secs) == "function") |
394 | -- [key]= { wakeup_secs [,period_secs] } [, ...] }, | 385 | ----- |
395 | -- } | 386 | -- Snore loop (run as a lane on the background) |
396 | -- | ||
397 | -- Collection of all running timers, indexed with linda's & key. | ||
398 | -- | 387 | -- |
399 | -- Note that we need to use the deep lightuserdata identifiers, instead | 388 | -- High priority, to get trustworthy timings. |
400 | -- of 'linda_h' themselves as table indices. Otherwise, we'd get multiple | ||
401 | -- entries for the same timer. | ||
402 | -- | 389 | -- |
403 | -- The 'hidden' reference to Linda proxy is used in 'check_timers()' but | 390 | -- We let the timer lane be a "free running" thread; no handle to it |
404 | -- also important to keep the Linda alive, even if all outside world threw | 391 | -- remains. |
405 | -- away pointers to it (which would ruin uniqueness of the deep pointer). | ||
406 | -- Now we're safe. | ||
407 | -- | 392 | -- |
408 | local collection = {} | 393 | local timer_body = function() |
409 | local table_insert = assert( table.insert) | 394 | set_debug_threadname( "LanesTimer") |
410 | 395 | -- | |
411 | local get_timers = function() | 396 | -- { [deep_linda_lightuserdata]= { [deep_linda_lightuserdata]=linda_h, |
412 | local r = {} | 397 | -- [key]= { wakeup_secs [,period_secs] } [, ...] }, |
413 | for deep, t in pairs( collection) do | 398 | -- } |
414 | -- WR( tostring( deep)) | 399 | -- |
415 | local l = t[deep] | 400 | -- Collection of all running timers, indexed with linda's & key. |
416 | for key, timer_data in pairs( t) do | 401 | -- |
417 | if key ~= deep then | 402 | -- Note that we need to use the deep lightuserdata identifiers, instead |
418 | table_insert( r, {l, key, timer_data}) | 403 | -- of 'linda_h' themselves as table indices. Otherwise, we'd get multiple |
404 | -- entries for the same timer. | ||
405 | -- | ||
406 | -- The 'hidden' reference to Linda proxy is used in 'check_timers()' but | ||
407 | -- also important to keep the Linda alive, even if all outside world threw | ||
408 | -- away pointers to it (which would ruin uniqueness of the deep pointer). | ||
409 | -- Now we're safe. | ||
410 | -- | ||
411 | local collection = {} | ||
412 | local table_insert = assert( table.insert) | ||
413 | |||
414 | local get_timers = function() | ||
415 | local r = {} | ||
416 | for deep, t in pairs( collection) do | ||
417 | -- WR( tostring( deep)) | ||
418 | local l = t[deep] | ||
419 | for key, timer_data in pairs( t) do | ||
420 | if key ~= deep then | ||
421 | table_insert( r, {l, key, timer_data}) | ||
422 | end | ||
419 | end | 423 | end |
420 | end | 424 | end |
421 | end | 425 | return r |
422 | return r | 426 | end -- get_timers() |
423 | end -- get_timers() | ||
424 | 427 | ||
425 | -- | 428 | -- |
426 | -- set_timer( linda_h, key [,wakeup_at_secs [,period_secs]] ) | 429 | -- set_timer( linda_h, key [,wakeup_at_secs [,period_secs]] ) |
427 | -- | 430 | -- |
428 | local set_timer = function( linda, key, wakeup_at, period) | 431 | local set_timer = function( linda, key, wakeup_at, period) |
429 | assert( wakeup_at == nil or wakeup_at > 0.0) | 432 | assert( wakeup_at == nil or wakeup_at > 0.0) |
430 | assert( period == nil or period > 0.0) | 433 | assert( period == nil or period > 0.0) |
431 | 434 | ||
432 | local linda_deep = linda:deep() | 435 | local linda_deep = linda:deep() |
433 | assert( linda_deep) | 436 | assert( linda_deep) |
434 | 437 | ||
435 | -- Find or make a lookup for this timer | 438 | -- Find or make a lookup for this timer |
436 | -- | ||
437 | local t1 = collection[linda_deep] | ||
438 | if not t1 then | ||
439 | t1 = { [linda_deep] = linda} -- proxy to use the Linda | ||
440 | collection[linda_deep] = t1 | ||
441 | end | ||
442 | |||
443 | if wakeup_at == nil then | ||
444 | -- Clear the timer | ||
445 | -- | 439 | -- |
446 | t1[key]= nil | 440 | local t1 = collection[linda_deep] |
441 | if not t1 then | ||
442 | t1 = { [linda_deep] = linda} -- proxy to use the Linda | ||
443 | collection[linda_deep] = t1 | ||
444 | end | ||
445 | |||
446 | if wakeup_at == nil then | ||
447 | -- Clear the timer | ||
448 | -- | ||
449 | t1[key]= nil | ||
447 | 450 | ||
448 | -- Remove empty tables from collection; speeds timer checks and | 451 | -- Remove empty tables from collection; speeds timer checks and |
449 | -- lets our 'safety reference' proxy be gc:ed as well. | 452 | -- lets our 'safety reference' proxy be gc:ed as well. |
450 | -- | 453 | -- |
451 | local empty = true | 454 | local empty = true |
452 | for k, _ in pairs( t1) do | 455 | for k, _ in pairs( t1) do |
453 | if k ~= linda_deep then | 456 | if k ~= linda_deep then |
454 | empty = false | 457 | empty = false |
455 | break | 458 | break |
459 | end | ||
460 | end | ||
461 | if empty then | ||
462 | collection[linda_deep] = nil | ||
456 | end | 463 | end |
464 | |||
465 | -- Note: any unread timer value is left at 'linda[key]' intensionally; | ||
466 | -- clearing a timer just stops it. | ||
467 | else | ||
468 | -- New timer or changing the timings | ||
469 | -- | ||
470 | local t2 = t1[key] | ||
471 | if not t2 then | ||
472 | t2= {} | ||
473 | t1[key]= t2 | ||
474 | end | ||
475 | |||
476 | t2[1] = wakeup_at | ||
477 | t2[2] = period -- can be 'nil' | ||
457 | end | 478 | end |
458 | if empty then | 479 | end -- set_timer() |
459 | collection[linda_deep] = nil | 480 | |
481 | ----- | ||
482 | -- [next_wakeup_at]= check_timers() | ||
483 | -- Check timers, and wake up the ones expired (if any) | ||
484 | -- Returns the closest upcoming (remaining) wakeup time (or 'nil' if none). | ||
485 | local check_timers = function() | ||
486 | local now = now_secs() | ||
487 | local next_wakeup | ||
488 | |||
489 | for linda_deep,t1 in pairs(collection) do | ||
490 | for key,t2 in pairs(t1) do | ||
491 | -- | ||
492 | if key==linda_deep then | ||
493 | -- no 'continue' in Lua :/ | ||
494 | else | ||
495 | -- 't2': { wakeup_at_secs [,period_secs] } | ||
496 | -- | ||
497 | local wakeup_at= t2[1] | ||
498 | local period= t2[2] -- may be 'nil' | ||
499 | |||
500 | if wakeup_at <= now then | ||
501 | local linda= t1[linda_deep] | ||
502 | assert(linda) | ||
503 | |||
504 | linda:set( key, now ) | ||
505 | |||
506 | -- 'pairs()' allows the values to be modified (and even | ||
507 | -- removed) as far as keys are not touched | ||
508 | |||
509 | if not period then | ||
510 | -- one-time timer; gone | ||
511 | -- | ||
512 | t1[key]= nil | ||
513 | wakeup_at= nil -- no 'continue' in Lua :/ | ||
514 | else | ||
515 | -- repeating timer; find next wakeup (may jump multiple repeats) | ||
516 | -- | ||
517 | repeat | ||
518 | wakeup_at= wakeup_at+period | ||
519 | until wakeup_at > now | ||
520 | |||
521 | t2[1]= wakeup_at | ||
522 | end | ||
523 | end | ||
524 | |||
525 | if wakeup_at and ((not next_wakeup) or (wakeup_at < next_wakeup)) then | ||
526 | next_wakeup= wakeup_at | ||
527 | end | ||
528 | end | ||
529 | end -- t2 loop | ||
530 | end -- t1 loop | ||
531 | |||
532 | return next_wakeup -- may be 'nil' | ||
533 | end -- check_timers() | ||
534 | |||
535 | local timer_gateway_batched = timer_gateway.batched | ||
536 | set_finalizer( function( err, stk) | ||
537 | if err and type( err) ~= "userdata" then | ||
538 | WR( "LanesTimer error: "..tostring(err)) | ||
539 | --elseif type( err) == "userdata" then | ||
540 | -- WR( "LanesTimer after cancel" ) | ||
541 | --else | ||
542 | -- WR("LanesTimer finalized") | ||
460 | end | 543 | end |
544 | end) | ||
545 | while true do | ||
546 | local next_wakeup = check_timers() | ||
461 | 547 | ||
462 | -- Note: any unread timer value is left at 'linda[key]' intensionally; | 548 | -- Sleep until next timer to wake up, or a set/clear command |
463 | -- clearing a timer just stops it. | ||
464 | else | ||
465 | -- New timer or changing the timings | ||
466 | -- | 549 | -- |
467 | local t2 = t1[key] | 550 | local secs |
468 | if not t2 then | 551 | if next_wakeup then |
469 | t2= {} | 552 | secs = next_wakeup - now_secs() |
470 | t1[key]= t2 | 553 | if secs < 0 then secs = 0 end |
471 | end | 554 | end |
472 | 555 | local key, what = timer_gateway:receive( secs, TGW_KEY, TGW_QUERY) | |
473 | t2[1] = wakeup_at | 556 | |
474 | t2[2] = period -- can be 'nil' | 557 | if key == TGW_KEY then |
475 | end | 558 | assert( getmetatable( what) == "Linda") -- 'what' should be a linda on which the client sets a timer |
476 | end -- set_timer() | 559 | local _, key, wakeup_at, period = timer_gateway:receive( 0, timer_gateway_batched, TGW_KEY, 3) |
477 | 560 | assert( key) | |
478 | ----- | 561 | set_timer( what, key, wakeup_at, period and period > 0 and period or nil) |
479 | -- [next_wakeup_at]= check_timers() | 562 | elseif key == TGW_QUERY then |
480 | -- Check timers, and wake up the ones expired (if any) | 563 | if what == "get_timers" then |
481 | -- Returns the closest upcoming (remaining) wakeup time (or 'nil' if none). | 564 | timer_gateway:send( TGW_REPLY, get_timers()) |
482 | local check_timers = function() | ||
483 | local now = now_secs() | ||
484 | local next_wakeup | ||
485 | |||
486 | for linda_deep,t1 in pairs(collection) do | ||
487 | for key,t2 in pairs(t1) do | ||
488 | -- | ||
489 | if key==linda_deep then | ||
490 | -- no 'continue' in Lua :/ | ||
491 | else | 565 | else |
492 | -- 't2': { wakeup_at_secs [,period_secs] } | 566 | timer_gateway:send( TGW_REPLY, "unknown query " .. what) |
493 | -- | ||
494 | local wakeup_at= t2[1] | ||
495 | local period= t2[2] -- may be 'nil' | ||
496 | |||
497 | if wakeup_at <= now then | ||
498 | local linda= t1[linda_deep] | ||
499 | assert(linda) | ||
500 | |||
501 | linda:set( key, now ) | ||
502 | |||
503 | -- 'pairs()' allows the values to be modified (and even | ||
504 | -- removed) as far as keys are not touched | ||
505 | |||
506 | if not period then | ||
507 | -- one-time timer; gone | ||
508 | -- | ||
509 | t1[key]= nil | ||
510 | wakeup_at= nil -- no 'continue' in Lua :/ | ||
511 | else | ||
512 | -- repeating timer; find next wakeup (may jump multiple repeats) | ||
513 | -- | ||
514 | repeat | ||
515 | wakeup_at= wakeup_at+period | ||
516 | until wakeup_at > now | ||
517 | |||
518 | t2[1]= wakeup_at | ||
519 | end | ||
520 | end | ||
521 | |||
522 | if wakeup_at and ((not next_wakeup) or (wakeup_at < next_wakeup)) then | ||
523 | next_wakeup= wakeup_at | ||
524 | end | ||
525 | end | 567 | end |
526 | end -- t2 loop | 568 | --elseif secs == nil then -- got no value while block-waiting? |
527 | end -- t1 loop | 569 | -- WR( "timer lane: no linda, aborted?") |
528 | 570 | end | |
529 | return next_wakeup -- may be 'nil' | ||
530 | end -- check_timers() | ||
531 | |||
532 | local timer_gateway_batched = timer_gateway.batched | ||
533 | set_finalizer( function( err, stk) | ||
534 | if err and type( err) ~= "userdata" then | ||
535 | WR( "LanesTimer error: "..tostring(err)) | ||
536 | --elseif type( err) == "userdata" then | ||
537 | -- WR( "LanesTimer after cancel" ) | ||
538 | --else | ||
539 | -- WR("LanesTimer finalized") | ||
540 | end | 571 | end |
541 | end) | 572 | end -- timer_body() |
542 | while true do | 573 | timer_lane = gen( "*", { package= {}, priority = max_prio}, timer_body)() -- "*" instead of "io,package" for LuaJIT compatibility... |
543 | local next_wakeup = check_timers() | 574 | end -- first_time |
544 | 575 | ||
545 | -- Sleep until next timer to wake up, or a set/clear command | 576 | ----- |
577 | -- = timer( linda_h, key_val, date_tbl|first_secs [,period_secs] ) | ||
578 | -- | ||
579 | -- PUBLIC LANES API | ||
580 | timer = function( linda, key, a, period ) | ||
581 | if getmetatable( linda) ~= "Linda" then | ||
582 | error "expecting a Linda" | ||
583 | end | ||
584 | if a == 0.0 then | ||
585 | -- Caller expects to get current time stamp in Linda, on return | ||
586 | -- (like the timer had expired instantly); it would be good to set this | ||
587 | -- as late as possible (to give most current time) but also we want it | ||
588 | -- to precede any possible timers that might start striking. | ||
546 | -- | 589 | -- |
547 | local secs | 590 | linda:set( key, core.now_secs()) |
548 | if next_wakeup then | 591 | |
549 | secs = next_wakeup - now_secs() | 592 | if not period or period==0.0 then |
550 | if secs < 0 then secs = 0 end | 593 | timer_gateway:send( TGW_KEY, linda, key, nil, nil ) -- clear the timer |
551 | end | 594 | return -- nothing more to do |
552 | local key, what = timer_gateway:receive( secs, TGW_KEY, TGW_QUERY) | ||
553 | |||
554 | if key == TGW_KEY then | ||
555 | assert( getmetatable( what) == "Linda") -- 'what' should be a linda on which the client sets a timer | ||
556 | local _, key, wakeup_at, period = timer_gateway:receive( 0, timer_gateway_batched, TGW_KEY, 3) | ||
557 | assert( key) | ||
558 | set_timer( what, key, wakeup_at, period and period > 0 and period or nil) | ||
559 | elseif key == TGW_QUERY then | ||
560 | if what == "get_timers" then | ||
561 | timer_gateway:send( TGW_REPLY, get_timers()) | ||
562 | else | ||
563 | timer_gateway:send( TGW_REPLY, "unknown query " .. what) | ||
564 | end | ||
565 | --elseif secs == nil then -- got no value while block-waiting? | ||
566 | -- WR( "timer lane: no linda, aborted?") | ||
567 | end | 595 | end |
596 | a= period | ||
568 | end | 597 | end |
569 | end -- timer_body() | ||
570 | timer_lane = gen( "*", { package= {}, priority = max_prio}, timer_body)() -- "*" instead of "io,package" for LuaJIT compatibility... | ||
571 | end -- first_time | ||
572 | 598 | ||
573 | ----- | 599 | local wakeup_at= type(a)=="table" and core.wakeup_conv(a) -- given point of time |
574 | -- = timer( linda_h, key_val, date_tbl|first_secs [,period_secs] ) | 600 | or (a and core.now_secs()+a or nil) |
575 | -- | 601 | -- queue to timer |
576 | -- PUBLIC LANES API | 602 | -- |
577 | timer = function( linda, key, a, period ) | 603 | timer_gateway:send( TGW_KEY, linda, key, wakeup_at, period ) |
578 | if getmetatable( linda) ~= "Linda" then | 604 | end |
579 | error "expecting a Linda" | ||
580 | end | ||
581 | if a == 0.0 then | ||
582 | -- Caller expects to get current time stamp in Linda, on return | ||
583 | -- (like the timer had expired instantly); it would be good to set this | ||
584 | -- as late as possible (to give most current time) but also we want it | ||
585 | -- to precede any possible timers that might start striking. | ||
586 | -- | ||
587 | linda:set( key, core.now_secs()) | ||
588 | |||
589 | if not period or period==0.0 then | ||
590 | timer_gateway:send( TGW_KEY, linda, key, nil, nil ) -- clear the timer | ||
591 | return -- nothing more to do | ||
592 | end | ||
593 | a= period | ||
594 | end | ||
595 | |||
596 | local wakeup_at= type(a)=="table" and core.wakeup_conv(a) -- given point of time | ||
597 | or (a and core.now_secs()+a or nil) | ||
598 | -- queue to timer | ||
599 | -- | ||
600 | timer_gateway:send( TGW_KEY, linda, key, wakeup_at, period ) | ||
601 | end | ||
602 | 605 | ||
603 | ----- | 606 | ----- |
604 | -- {[{linda, slot, when, period}[,...]]} = timers() | 607 | -- {[{linda, slot, when, period}[,...]]} = timers() |
605 | -- | 608 | -- |
606 | -- PUBLIC LANES API | 609 | -- PUBLIC LANES API |
607 | timers = function() | 610 | timers = function() |
608 | timer_gateway:send( TGW_QUERY, "get_timers") | 611 | timer_gateway:send( TGW_QUERY, "get_timers") |
609 | local _, r = timer_gateway:receive( TGW_REPLY) | 612 | local _, r = timer_gateway:receive( TGW_REPLY) |
610 | return r | 613 | return r |
611 | end | 614 | end |
612 | 615 | ||
613 | end -- settings.with_timers | 616 | end -- settings.with_timers |
614 | 617 | ||
615 | -- avoid pulling the whole core module as upvalue when cancel_error is enough | 618 | -- avoid pulling the whole core module as upvalue when cancel_error is enough |
616 | local cancel_error = assert( core.cancel_error) | 619 | local cancel_error = assert( core.cancel_error) |
617 | 620 | ||
618 | ---=== Lock & atomic generators ===--- | 621 | ---=== Lock & atomic generators ===--- |
619 | 622 | ||
620 | -- These functions are just surface sugar, but make solutions easier to read. | 623 | -- These functions are just surface sugar, but make solutions easier to read. |
621 | -- Not many applications should even need explicit locks or atomic counters. | 624 | -- Not many applications should even need explicit locks or atomic counters. |
622 | 625 | ||
623 | -- | 626 | -- |
624 | -- [true [, ...]= trues(uint) | 627 | -- [true [, ...]= trues(uint) |
625 | -- | 628 | -- |
626 | local function trues( n) | 629 | local function trues( n) |
627 | if n > 0 then | 630 | if n > 0 then |
628 | return true, trues( n - 1) | 631 | return true, trues( n - 1) |
632 | end | ||
629 | end | 633 | end |
630 | end | ||
631 | 634 | ||
632 | -- | 635 | -- |
633 | -- lock_f = lanes.genlock( linda_h, key [,N_uint=1] ) | 636 | -- lock_f = lanes.genlock( linda_h, key [,N_uint=1] ) |
634 | -- | 637 | -- |
635 | -- = lock_f( +M ) -- acquire M | 638 | -- = lock_f( +M ) -- acquire M |
636 | -- ...locked... | 639 | -- ...locked... |
637 | -- = lock_f( -M ) -- release M | 640 | -- = lock_f( -M ) -- release M |
638 | -- | 641 | -- |
639 | -- Returns an access function that allows 'N' simultaneous entries between | 642 | -- Returns an access function that allows 'N' simultaneous entries between |
640 | -- acquire (+M) and release (-M). For binary locks, use M==1. | 643 | -- acquire (+M) and release (-M). For binary locks, use M==1. |
641 | -- | 644 | -- |
642 | -- PUBLIC LANES API | 645 | -- PUBLIC LANES API |
643 | local genlock = function( linda, key, N) | 646 | local genlock = function( linda, key, N) |
644 | -- clear existing data and set the limit | 647 | -- clear existing data and set the limit |
645 | N = N or 1 | 648 | N = N or 1 |
646 | if linda:set( key) == cancel_error or linda:limit( key, N) == cancel_error then | 649 | if linda:set( key) == cancel_error or linda:limit( key, N) == cancel_error then |
647 | return cancel_error | 650 | return cancel_error |
648 | end | 651 | end |
649 | 652 | ||
650 | -- use an optimized version for case N == 1 | 653 | -- use an optimized version for case N == 1 |
651 | return (N == 1) and | 654 | return (N == 1) and |
652 | function( M, mode_) | 655 | function( M, mode_) |
653 | local timeout = (mode_ == "try") and 0 or nil | 656 | local timeout = (mode_ == "try") and 0 or nil |
654 | if M > 0 then | 657 | if M > 0 then |
655 | -- 'nil' timeout allows 'key' to be numeric | 658 | -- 'nil' timeout allows 'key' to be numeric |
656 | return linda:send( timeout, key, true) -- suspends until been able to push them | 659 | return linda:send( timeout, key, true) -- suspends until been able to push them |
657 | else | 660 | else |
658 | local k = linda:receive( nil, key) | 661 | local k = linda:receive( nil, key) |
659 | -- propagate cancel_error if we got it, else return true or false | 662 | -- propagate cancel_error if we got it, else return true or false |
660 | return k and ((k ~= cancel_error) and true or k) or false | 663 | return k and ((k ~= cancel_error) and true or k) or false |
664 | end | ||
661 | end | 665 | end |
662 | end | 666 | or |
663 | or | 667 | function( M, mode_) |
664 | function( M, mode_) | 668 | local timeout = (mode_ == "try") and 0 or nil |
665 | local timeout = (mode_ == "try") and 0 or nil | 669 | if M > 0 then |
666 | if M > 0 then | 670 | -- 'nil' timeout allows 'key' to be numeric |
667 | -- 'nil' timeout allows 'key' to be numeric | 671 | return linda:send( timeout, key, trues(M)) -- suspends until been able to push them |
668 | return linda:send( timeout, key, trues(M)) -- suspends until been able to push them | 672 | else |
669 | else | 673 | local k = linda:receive( nil, linda.batched, key, -M) |
670 | local k = linda:receive( nil, linda.batched, key, -M) | 674 | -- propagate cancel_error if we got it, else return true or false |
671 | -- propagate cancel_error if we got it, else return true or false | 675 | return k and ((k ~= cancel_error) and true or k) or false |
672 | return k and ((k ~= cancel_error) and true or k) or false | 676 | end |
673 | end | 677 | end |
674 | end | 678 | end |
675 | end | ||
676 | 679 | ||
677 | 680 | ||
678 | -- | 681 | -- |
@@ -728,6 +731,13 @@ end | |||
728 | return lanes | 731 | return lanes |
729 | end -- lanes.configure | 732 | end -- lanes.configure |
730 | 733 | ||
734 | lanesMeta.__index = function(t,k) | ||
735 | -- This is called when some functionality is accessed without calling configure() | ||
736 | lanes.configure() -- Initialize with default settings | ||
737 | -- Access the required key | ||
738 | return lanes[k] | ||
739 | end | ||
740 | |||
731 | -- no need to force calling configure() excepted the first time | 741 | -- no need to force calling configure() excepted the first time |
732 | if core.settings then | 742 | if core.settings then |
733 | return lanes.configure() | 743 | return lanes.configure() |
@@ -735,5 +745,6 @@ else | |||
735 | return lanes | 745 | return lanes |
736 | end | 746 | end |
737 | 747 | ||
738 | --the end | 748 | |
739 | return lanes | 749 | --the end (Unreachable Code) |
750 | --return lanes | ||