aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <bnt period germain arrobase gmail period com>2016-11-21 14:29:49 +0100
committerBenoit Germain <bnt period germain arrobase gmail period com>2016-11-21 14:29:49 +0100
commitae22961167e0ec553e4f28cd7c5e2dc75b23e812 (patch)
treed378f4402aac83d5a118e545f1808357eaf6ab9f
parenteec92f309d2b0c24946e0aebffabd80a91594840 (diff)
parentf1b69462a9a7ebf5d761bb8fc3b043b8898969a7 (diff)
downloadlanes-ae22961167e0ec553e4f28cd7c5e2dc75b23e812.tar.gz
lanes-ae22961167e0ec553e4f28cd7c5e2dc75b23e812.tar.bz2
lanes-ae22961167e0ec553e4f28cd7c5e2dc75b23e812.zip
Merge branch 'master' of https://github.com/LuaLanes/lanes
-rw-r--r--src/lanes.lua967
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
43local lanes = {} 43local lanes = {}
44local lanesMeta = {}
45setmetatable(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
46lanes.configure = function( settings_) 48lanes.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
134lanes.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 --
148local function WR(str) 151 local function WR(str)
149 io.stderr:write( str.."\n" ) 152 io.stderr:write( str.."\n" )
150end 153 end
151 154
152local 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)
159end 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
209local 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
226local raise_option_error = function( name_, tv_, v_)
227 error( "Bad '" .. name_ .. "' option: " .. tv_ .. " " .. string_format( "%q", tostring( v_)), 4)
228end
229 211
230local 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
260local 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
329end -- gen() 332 end -- gen()
330 333
331---=== Timers ===--- 334 ---=== Timers ===---
332 335
333-- PUBLIC LANES API 336 -- PUBLIC LANES API
334local timer = function() error "timers are not active" end 337 local timer = function() error "timers are not active" end
335local timers = timer 338 local timers = timer
336local 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
339local 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
345local 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")
352end
353 356
354 357
355if 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 --
367local 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
368local TGW_QUERY, TGW_REPLY = "(timer query)", "(timer reply)" 371 local TGW_QUERY, TGW_REPLY = "(timer query)", "(timer reply)"
369local first_time_key= "first time" 372 local first_time_key= "first time"
370
371local first_time = timer_gateway:get( first_time_key) == nil
372timer_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--
378if 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...
571end -- 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 --
577timer = 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 )
601end
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
607timers = 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
611end 614 end
612 615
613end -- 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
616local 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 --
626local 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
630end
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
643local 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
675end
676 679
677 680
678 -- 681 --
@@ -728,6 +731,13 @@ end
728 return lanes 731 return lanes
729end -- lanes.configure 732end -- lanes.configure
730 733
734lanesMeta.__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]
739end
740
731-- no need to force calling configure() excepted the first time 741-- no need to force calling configure() excepted the first time
732if core.settings then 742if core.settings then
733 return lanes.configure() 743 return lanes.configure()
@@ -735,5 +745,6 @@ else
735 return lanes 745 return lanes
736end 746end
737 747
738--the end 748
739return lanes 749--the end (Unreachable Code)
750--return lanes