From 4f52790f4896ef6b05f3784bdb668ac3f8144aac Mon Sep 17 00:00:00 2001 From: aryajur Date: Fri, 18 Sep 2015 18:20:06 -0700 Subject: Not needed to call configure. It is called automatically with no settings if not called manually --- src/lanes.lua | 967 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 489 insertions(+), 478 deletions(-) (limited to 'src') 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" -- almost everything module() does is done by require() anyway -- -> simply create a table, populate it, return it, and be done local lanes = {} +local lanesMeta = {} +setmetatable(lanes,lanesMeta) -- this function is available in the public interface until it is called, after which it disappears lanes.configure = function( settings_) @@ -52,7 +54,8 @@ lanes.configure = function( settings_) if not string then error( "To use 'lanes', you will also need to have 'string' available.", 2) end - + -- Configure called so remove metatable from lanes + setmetatable(lanes,nil) -- -- Cache globals for code that might run under sandboxing -- @@ -131,548 +134,548 @@ lanes.configure = function( settings_) local core_lane_new = assert( core.lane_new) local max_prio = assert( core.max_prio) -lanes.ABOUT = -{ - author= "Asko Kauppi , Benoit Germain ", - description= "Running multiple Lua states in parallel", - license= "MIT/X11", - copyright= "Copyright (c) 2007-10, Asko Kauppi; (c) 2011-13, Benoit Germain", - version = assert( core.version) -} + lanes.ABOUT = + { + author= "Asko Kauppi , Benoit Germain ", + description= "Running multiple Lua states in parallel", + license= "MIT/X11", + copyright= "Copyright (c) 2007-10, Asko Kauppi; (c) 2011-13, Benoit Germain", + version = assert( core.version) + } --- Making copies of necessary system libs will pass them on as upvalues; --- only the first state doing "require 'lanes'" will need to have 'string' --- and 'table' visible. --- -local function WR(str) - io.stderr:write( str.."\n" ) -end + -- Making copies of necessary system libs will pass them on as upvalues; + -- only the first state doing "require 'lanes'" will need to have 'string' + -- and 'table' visible. + -- + local function WR(str) + io.stderr:write( str.."\n" ) + end -local function DUMP( tbl ) - if not tbl then return end - local str="" - for k,v in pairs(tbl) do - str= str..k.."="..tostring(v).."\n" - end - WR(str) -end + local function DUMP( tbl ) + if not tbl then return end + local str="" + for k,v in pairs(tbl) do + str= str..k.."="..tostring(v).."\n" + end + WR(str) + end ----=== Laning ===--- + ---=== Laning ===--- --- lane_h[1..n]: lane results, same as via 'lane_h:join()' --- lane_h[0]: can be read to make sure a thread has finished (always gives 'true') --- lane_h[-1]: error message, without propagating the error --- --- Reading a Lane result (or [0]) propagates a possible error in the lane --- (and execution does not return). Cancelled lanes give 'nil' values. --- --- lane_h.state: "pending"/"running"/"waiting"/"done"/"error"/"cancelled" --- --- Note: Would be great to be able to have '__ipairs' metamethod, that gets --- called by 'ipairs()' function to custom iterate objects. We'd use it --- for making sure a lane has ended (results are available); not requiring --- the user to precede a loop by explicit 'h[0]' or 'h:join()'. --- --- Or, even better, 'ipairs()' should start valuing '__index' instead --- of using raw reads that bypass it. --- ------ --- lanes.gen( [libs_str|opt_tbl [, ...],] lane_func ) ( [...] ) -> h --- --- 'libs': nil: no libraries available (default) --- "": only base library ('assert', 'print', 'unpack' etc.) --- "math,os": math + os + base libraries (named ones + base) --- "*": all standard libraries available --- --- 'opt': .priority: int (-3..+3) smaller is lower priority (0 = default) --- --- .cancelstep: bool | uint --- false: cancellation check only at pending Linda operations --- (send/receive) so no runtime performance penalty (default) --- true: adequate cancellation check (same as 100) --- >0: cancellation check every x Lua lines (small number= faster --- reaction but more performance overhead) --- --- .globals: table of globals to set for a new thread (passed by value) --- --- .required: table of packages to require --- --- .gc_cb: function called when the lane handle is collected --- --- ... (more options may be introduced later) ... --- --- Calling with a function parameter ('lane_func') ends the string/table --- modifiers, and prepares a lane generator. - -local valid_libs = -{ - ["package"] = true, - ["table"] = true, - ["io"] = true, - ["os"] = true, - ["string"] = true, - ["math"] = true, - ["debug"] = true, - ["bit32"] = true, -- Lua 5.2 only, ignored silently under 5.1 - ["utf8"] = true, -- Lua 5.3 only, ignored silently under 5.1 and 5.2 - -- - ["base"] = true, - ["coroutine"] = true, -- part of "base" in Lua 5.1 - ["lanes.core"] = true -} - -local raise_option_error = function( name_, tv_, v_) - error( "Bad '" .. name_ .. "' option: " .. tv_ .. " " .. string_format( "%q", tostring( v_)), 4) -end + -- lane_h[1..n]: lane results, same as via 'lane_h:join()' + -- lane_h[0]: can be read to make sure a thread has finished (always gives 'true') + -- lane_h[-1]: error message, without propagating the error + -- + -- Reading a Lane result (or [0]) propagates a possible error in the lane + -- (and execution does not return). Cancelled lanes give 'nil' values. + -- + -- lane_h.state: "pending"/"running"/"waiting"/"done"/"error"/"cancelled" + -- + -- Note: Would be great to be able to have '__ipairs' metamethod, that gets + -- called by 'ipairs()' function to custom iterate objects. We'd use it + -- for making sure a lane has ended (results are available); not requiring + -- the user to precede a loop by explicit 'h[0]' or 'h:join()'. + -- + -- Or, even better, 'ipairs()' should start valuing '__index' instead + -- of using raw reads that bypass it. + -- + ----- + -- lanes.gen( [libs_str|opt_tbl [, ...],] lane_func ) ( [...] ) -> h + -- + -- 'libs': nil: no libraries available (default) + -- "": only base library ('assert', 'print', 'unpack' etc.) + -- "math,os": math + os + base libraries (named ones + base) + -- "*": all standard libraries available + -- + -- 'opt': .priority: int (-3..+3) smaller is lower priority (0 = default) + -- + -- .cancelstep: bool | uint + -- false: cancellation check only at pending Linda operations + -- (send/receive) so no runtime performance penalty (default) + -- true: adequate cancellation check (same as 100) + -- >0: cancellation check every x Lua lines (small number= faster + -- reaction but more performance overhead) + -- + -- .globals: table of globals to set for a new thread (passed by value) + -- + -- .required: table of packages to require + -- + -- .gc_cb: function called when the lane handle is collected + -- + -- ... (more options may be introduced later) ... + -- + -- Calling with a function parameter ('lane_func') ends the string/table + -- modifiers, and prepares a lane generator. -local opt_validators = -{ - priority = function( v_) - local tv = type( v_) - return (tv == "number") and v_ or raise_option_error( "priority", tv, v_) - end, - cancelstep = function( v_) - local tv = type( v_) - return (tv == "number") and v_ or (v_ == true) and 100 or (v_ == false) and 0 or raise_option_error( "cancelstep", tv, v_) - end, - globals = function( v_) - local tv = type( v_) - return (tv == "table") and v_ or raise_option_error( "globals", tv, v_) - end, - package = function( v_) - local tv = type( v_) - return (tv == "table") and v_ or raise_option_error( "package", tv, v_) - end, - required = function( v_) - local tv = type( v_) - return (tv == "table") and v_ or raise_option_error( "required", tv, v_) - end, - gc_cb = function( v_) - local tv = type( v_) - return (tv == "function") and v_ or raise_option_error( "gc_cb", tv, v_) - end -} - --- PUBLIC LANES API --- receives a sequence of strings and tables, plus a function -local gen = function( ...) - -- aggregrate all strings together, separated by "," as well as tables - -- the strings are a list of libraries to open - -- the tables contain the lane options - local opt = {} - local libs = nil - - local n = select( '#', ...) - - -- we need at least a function - if n == 0 then - error( "No parameters!", 2) + local valid_libs = + { + ["package"] = true, + ["table"] = true, + ["io"] = true, + ["os"] = true, + ["string"] = true, + ["math"] = true, + ["debug"] = true, + ["bit32"] = true, -- Lua 5.2 only, ignored silently under 5.1 + ["utf8"] = true, -- Lua 5.3 only, ignored silently under 5.1 and 5.2 + -- + ["base"] = true, + ["coroutine"] = true, -- part of "base" in Lua 5.1 + ["lanes.core"] = true + } + + local raise_option_error = function( name_, tv_, v_) + error( "Bad '" .. name_ .. "' option: " .. tv_ .. " " .. string_format( "%q", tostring( v_)), 4) end - -- all arguments but the last must be nil, strings, or tables - for i = 1, n - 1 do - local v = select( i, ...) - local tv = type( v) - if tv == "string" then - libs = libs and libs .. "," .. v or v - elseif tv == "table" then - for k, vv in pairs( v) do - opt[k]= vv - end - elseif v == nil then - -- skip - else - error( "Bad parameter " .. i .. ": " .. tv .. " " .. string_format( "%q", tostring( v)), 2) + local opt_validators = + { + priority = function( v_) + local tv = type( v_) + return (tv == "number") and v_ or raise_option_error( "priority", tv, v_) + end, + cancelstep = function( v_) + local tv = type( v_) + return (tv == "number") and v_ or (v_ == true) and 100 or (v_ == false) and 0 or raise_option_error( "cancelstep", tv, v_) + end, + globals = function( v_) + local tv = type( v_) + return (tv == "table") and v_ or raise_option_error( "globals", tv, v_) + end, + package = function( v_) + local tv = type( v_) + return (tv == "table") and v_ or raise_option_error( "package", tv, v_) + end, + required = function( v_) + local tv = type( v_) + return (tv == "table") and v_ or raise_option_error( "required", tv, v_) + end, + gc_cb = function( v_) + local tv = type( v_) + return (tv == "function") and v_ or raise_option_error( "gc_cb", tv, v_) end - end + } - -- the last argument should be a function or a string - local func = select( n, ...) - local functype = type( func) - if functype ~= "function" and functype ~= "string" then - error( "Last parameter not function or string: " .. functype .. " " .. string_format( "%q", tostring( func)), 2) - end + -- PUBLIC LANES API + -- receives a sequence of strings and tables, plus a function + local gen = function( ...) + -- aggregrate all strings together, separated by "," as well as tables + -- the strings are a list of libraries to open + -- the tables contain the lane options + local opt = {} + local libs = nil + + local n = select( '#', ...) + + -- we need at least a function + if n == 0 then + error( "No parameters!", 2) + end - -- check that the caller only provides reserved library names, and those only once - -- "*" is a special case that doesn't require individual checking - if libs and libs ~= "*" then - local found = {} - for s in string_gmatch(libs, "[%a%d.]+") do - if not valid_libs[s] then - error( "Bad library name: " .. s, 2) + -- all arguments but the last must be nil, strings, or tables + for i = 1, n - 1 do + local v = select( i, ...) + local tv = type( v) + if tv == "string" then + libs = libs and libs .. "," .. v or v + elseif tv == "table" then + for k, vv in pairs( v) do + opt[k]= vv + end + elseif v == nil then + -- skip else - found[s] = (found[s] or 0) + 1 - if found[s] > 1 then - error( "libs specification contains '" .. s .. "' more than once", 2) + error( "Bad parameter " .. i .. ": " .. tv .. " " .. string_format( "%q", tostring( v)), 2) + end + end + + -- the last argument should be a function or a string + local func = select( n, ...) + local functype = type( func) + if functype ~= "function" and functype ~= "string" then + error( "Last parameter not function or string: " .. functype .. " " .. string_format( "%q", tostring( func)), 2) + end + + -- check that the caller only provides reserved library names, and those only once + -- "*" is a special case that doesn't require individual checking + if libs and libs ~= "*" then + local found = {} + for s in string_gmatch(libs, "[%a%d.]+") do + if not valid_libs[s] then + error( "Bad library name: " .. s, 2) + else + found[s] = (found[s] or 0) + 1 + if found[s] > 1 then + error( "libs specification contains '" .. s .. "' more than once", 2) + end end end end - end - -- validate that each option is known and properly valued - for k, v in pairs( opt) do - local validator = opt_validators[k] - if not validator then - error( (type( k) == "number" and "Unkeyed option: " .. type( v) .. " " .. string_format( "%q", tostring( v)) or "Bad '" .. tostring( k) .. "' option"), 2) - else - opt[k] = validator( v) + -- validate that each option is known and properly valued + for k, v in pairs( opt) do + local validator = opt_validators[k] + if not validator then + error( (type( k) == "number" and "Unkeyed option: " .. type( v) .. " " .. string_format( "%q", tostring( v)) or "Bad '" .. tostring( k) .. "' option"), 2) + else + opt[k] = validator( v) + end end - end - local cancelstep, priority, globals, package, required, gc_cb = opt.cancelstep, opt.priority, opt.globals, opt.package or package, opt.required, opt.gc_cb - return function( ...) - -- must pass functions args last else they will be truncated to the first one - return core_lane_new( func, libs, cancelstep, priority, globals, package, required, gc_cb, ...) - end -end -- gen() + local cancelstep, priority, globals, package, required, gc_cb = opt.cancelstep, opt.priority, opt.globals, opt.package or package, opt.required, opt.gc_cb + return function( ...) + -- must pass functions args last else they will be truncated to the first one + return core_lane_new( func, libs, cancelstep, priority, globals, package, required, gc_cb, ...) + end + end -- gen() ----=== Timers ===--- + ---=== Timers ===--- --- PUBLIC LANES API -local timer = function() error "timers are not active" end -local timers = timer -local timer_lane = nil + -- PUBLIC LANES API + local timer = function() error "timers are not active" end + local timers = timer + local timer_lane = nil --- timer_gateway should always exist, even when the settings disable the timers -local timer_gateway = assert( core.timer_gateway) + -- timer_gateway should always exist, even when the settings disable the timers + local timer_gateway = assert( core.timer_gateway) ------ --- = sleep( [seconds_]) --- --- PUBLIC LANES API -local sleep = function( seconds_) - seconds_ = seconds_ or 0.0 -- this causes false and nil to be a valid input, equivalent to 0.0, but that's ok - if type( seconds_) ~= "number" then - error( "invalid duration " .. string_format( "%q", tostring(seconds_))) + ----- + -- = sleep( [seconds_]) + -- + -- PUBLIC LANES API + local sleep = function( seconds_) + seconds_ = seconds_ or 0.0 -- this causes false and nil to be a valid input, equivalent to 0.0, but that's ok + if type( seconds_) ~= "number" then + error( "invalid duration " .. string_format( "%q", tostring(seconds_))) + end + -- receive data on a channel no-one ever sends anything, thus blocking for the specified duration + return timer_gateway:receive( seconds_, "ac100de1-a696-4619-b2f0-a26de9d58ab8") end - -- receive data on a channel no-one ever sends anything, thus blocking for the specified duration - return timer_gateway:receive( seconds_, "ac100de1-a696-4619-b2f0-a26de9d58ab8") -end -if settings.with_timers ~= false then + if settings.with_timers ~= false then --- --- On first 'require "lanes"', a timer lane is spawned that will maintain --- timer tables and sleep in between the timer events. All interaction with --- the timer lane happens via a 'timer_gateway' Linda, which is common to --- all that 'require "lanes"'. --- --- Linda protocol to timer lane: --- --- TGW_KEY: linda_h, key, [wakeup_at_secs], [repeat_secs] --- -local TGW_KEY= "(timer control)" -- the key does not matter, a 'weird' key may help debugging -local TGW_QUERY, TGW_REPLY = "(timer query)", "(timer reply)" -local first_time_key= "first time" - -local first_time = timer_gateway:get( first_time_key) == nil -timer_gateway:set( first_time_key, true) + -- + -- On first 'require "lanes"', a timer lane is spawned that will maintain + -- timer tables and sleep in between the timer events. All interaction with + -- the timer lane happens via a 'timer_gateway' Linda, which is common to + -- all that 'require "lanes"'. + -- + -- Linda protocol to timer lane: + -- + -- TGW_KEY: linda_h, key, [wakeup_at_secs], [repeat_secs] + -- + local TGW_KEY= "(timer control)" -- the key does not matter, a 'weird' key may help debugging + local TGW_QUERY, TGW_REPLY = "(timer query)", "(timer reply)" + local first_time_key= "first time" --- --- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally --- has 'table' always declared) --- -if first_time then + local first_time = timer_gateway:get( first_time_key) == nil + timer_gateway:set( first_time_key, true) - local now_secs = core.now_secs - assert( type( now_secs) == "function") - ----- - -- Snore loop (run as a lane on the background) - -- - -- High priority, to get trustworthy timings. -- - -- We let the timer lane be a "free running" thread; no handle to it - -- remains. + -- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally + -- has 'table' always declared) -- - local timer_body = function() - set_debug_threadname( "LanesTimer") - -- - -- { [deep_linda_lightuserdata]= { [deep_linda_lightuserdata]=linda_h, - -- [key]= { wakeup_secs [,period_secs] } [, ...] }, - -- } - -- - -- Collection of all running timers, indexed with linda's & key. + if first_time then + + local now_secs = core.now_secs + assert( type( now_secs) == "function") + ----- + -- Snore loop (run as a lane on the background) -- - -- Note that we need to use the deep lightuserdata identifiers, instead - -- of 'linda_h' themselves as table indices. Otherwise, we'd get multiple - -- entries for the same timer. + -- High priority, to get trustworthy timings. -- - -- The 'hidden' reference to Linda proxy is used in 'check_timers()' but - -- also important to keep the Linda alive, even if all outside world threw - -- away pointers to it (which would ruin uniqueness of the deep pointer). - -- Now we're safe. + -- We let the timer lane be a "free running" thread; no handle to it + -- remains. -- - local collection = {} - local table_insert = assert( table.insert) - - local get_timers = function() - local r = {} - for deep, t in pairs( collection) do - -- WR( tostring( deep)) - local l = t[deep] - for key, timer_data in pairs( t) do - if key ~= deep then - table_insert( r, {l, key, timer_data}) + local timer_body = function() + set_debug_threadname( "LanesTimer") + -- + -- { [deep_linda_lightuserdata]= { [deep_linda_lightuserdata]=linda_h, + -- [key]= { wakeup_secs [,period_secs] } [, ...] }, + -- } + -- + -- Collection of all running timers, indexed with linda's & key. + -- + -- Note that we need to use the deep lightuserdata identifiers, instead + -- of 'linda_h' themselves as table indices. Otherwise, we'd get multiple + -- entries for the same timer. + -- + -- The 'hidden' reference to Linda proxy is used in 'check_timers()' but + -- also important to keep the Linda alive, even if all outside world threw + -- away pointers to it (which would ruin uniqueness of the deep pointer). + -- Now we're safe. + -- + local collection = {} + local table_insert = assert( table.insert) + + local get_timers = function() + local r = {} + for deep, t in pairs( collection) do + -- WR( tostring( deep)) + local l = t[deep] + for key, timer_data in pairs( t) do + if key ~= deep then + table_insert( r, {l, key, timer_data}) + end end end - end - return r - end -- get_timers() + return r + end -- get_timers() - -- - -- set_timer( linda_h, key [,wakeup_at_secs [,period_secs]] ) - -- - local set_timer = function( linda, key, wakeup_at, period) - assert( wakeup_at == nil or wakeup_at > 0.0) - assert( period == nil or period > 0.0) + -- + -- set_timer( linda_h, key [,wakeup_at_secs [,period_secs]] ) + -- + local set_timer = function( linda, key, wakeup_at, period) + assert( wakeup_at == nil or wakeup_at > 0.0) + assert( period == nil or period > 0.0) - local linda_deep = linda:deep() - assert( linda_deep) + local linda_deep = linda:deep() + assert( linda_deep) - -- Find or make a lookup for this timer - -- - local t1 = collection[linda_deep] - if not t1 then - t1 = { [linda_deep] = linda} -- proxy to use the Linda - collection[linda_deep] = t1 - end - - if wakeup_at == nil then - -- Clear the timer + -- Find or make a lookup for this timer -- - t1[key]= nil + local t1 = collection[linda_deep] + if not t1 then + t1 = { [linda_deep] = linda} -- proxy to use the Linda + collection[linda_deep] = t1 + end + + if wakeup_at == nil then + -- Clear the timer + -- + t1[key]= nil - -- Remove empty tables from collection; speeds timer checks and - -- lets our 'safety reference' proxy be gc:ed as well. - -- - local empty = true - for k, _ in pairs( t1) do - if k ~= linda_deep then - empty = false - break + -- Remove empty tables from collection; speeds timer checks and + -- lets our 'safety reference' proxy be gc:ed as well. + -- + local empty = true + for k, _ in pairs( t1) do + if k ~= linda_deep then + empty = false + break + end + end + if empty then + collection[linda_deep] = nil end + + -- Note: any unread timer value is left at 'linda[key]' intensionally; + -- clearing a timer just stops it. + else + -- New timer or changing the timings + -- + local t2 = t1[key] + if not t2 then + t2= {} + t1[key]= t2 + end + + t2[1] = wakeup_at + t2[2] = period -- can be 'nil' end - if empty then - collection[linda_deep] = nil + end -- set_timer() + + ----- + -- [next_wakeup_at]= check_timers() + -- Check timers, and wake up the ones expired (if any) + -- Returns the closest upcoming (remaining) wakeup time (or 'nil' if none). + local check_timers = function() + local now = now_secs() + local next_wakeup + + for linda_deep,t1 in pairs(collection) do + for key,t2 in pairs(t1) do + -- + if key==linda_deep then + -- no 'continue' in Lua :/ + else + -- 't2': { wakeup_at_secs [,period_secs] } + -- + local wakeup_at= t2[1] + local period= t2[2] -- may be 'nil' + + if wakeup_at <= now then + local linda= t1[linda_deep] + assert(linda) + + linda:set( key, now ) + + -- 'pairs()' allows the values to be modified (and even + -- removed) as far as keys are not touched + + if not period then + -- one-time timer; gone + -- + t1[key]= nil + wakeup_at= nil -- no 'continue' in Lua :/ + else + -- repeating timer; find next wakeup (may jump multiple repeats) + -- + repeat + wakeup_at= wakeup_at+period + until wakeup_at > now + + t2[1]= wakeup_at + end + end + + if wakeup_at and ((not next_wakeup) or (wakeup_at < next_wakeup)) then + next_wakeup= wakeup_at + end + end + end -- t2 loop + end -- t1 loop + + return next_wakeup -- may be 'nil' + end -- check_timers() + + local timer_gateway_batched = timer_gateway.batched + set_finalizer( function( err, stk) + if err and type( err) ~= "userdata" then + WR( "LanesTimer error: "..tostring(err)) + --elseif type( err) == "userdata" then + -- WR( "LanesTimer after cancel" ) + --else + -- WR("LanesTimer finalized") end + end) + while true do + local next_wakeup = check_timers() - -- Note: any unread timer value is left at 'linda[key]' intensionally; - -- clearing a timer just stops it. - else - -- New timer or changing the timings + -- Sleep until next timer to wake up, or a set/clear command -- - local t2 = t1[key] - if not t2 then - t2= {} - t1[key]= t2 + local secs + if next_wakeup then + secs = next_wakeup - now_secs() + if secs < 0 then secs = 0 end end - - t2[1] = wakeup_at - t2[2] = period -- can be 'nil' - end - end -- set_timer() - - ----- - -- [next_wakeup_at]= check_timers() - -- Check timers, and wake up the ones expired (if any) - -- Returns the closest upcoming (remaining) wakeup time (or 'nil' if none). - local check_timers = function() - local now = now_secs() - local next_wakeup - - for linda_deep,t1 in pairs(collection) do - for key,t2 in pairs(t1) do - -- - if key==linda_deep then - -- no 'continue' in Lua :/ + local key, what = timer_gateway:receive( secs, TGW_KEY, TGW_QUERY) + + if key == TGW_KEY then + assert( getmetatable( what) == "Linda") -- 'what' should be a linda on which the client sets a timer + local _, key, wakeup_at, period = timer_gateway:receive( 0, timer_gateway_batched, TGW_KEY, 3) + assert( key) + set_timer( what, key, wakeup_at, period and period > 0 and period or nil) + elseif key == TGW_QUERY then + if what == "get_timers" then + timer_gateway:send( TGW_REPLY, get_timers()) else - -- 't2': { wakeup_at_secs [,period_secs] } - -- - local wakeup_at= t2[1] - local period= t2[2] -- may be 'nil' - - if wakeup_at <= now then - local linda= t1[linda_deep] - assert(linda) - - linda:set( key, now ) - - -- 'pairs()' allows the values to be modified (and even - -- removed) as far as keys are not touched - - if not period then - -- one-time timer; gone - -- - t1[key]= nil - wakeup_at= nil -- no 'continue' in Lua :/ - else - -- repeating timer; find next wakeup (may jump multiple repeats) - -- - repeat - wakeup_at= wakeup_at+period - until wakeup_at > now - - t2[1]= wakeup_at - end - end - - if wakeup_at and ((not next_wakeup) or (wakeup_at < next_wakeup)) then - next_wakeup= wakeup_at - end + timer_gateway:send( TGW_REPLY, "unknown query " .. what) end - end -- t2 loop - end -- t1 loop - - return next_wakeup -- may be 'nil' - end -- check_timers() - - local timer_gateway_batched = timer_gateway.batched - set_finalizer( function( err, stk) - if err and type( err) ~= "userdata" then - WR( "LanesTimer error: "..tostring(err)) - --elseif type( err) == "userdata" then - -- WR( "LanesTimer after cancel" ) - --else - -- WR("LanesTimer finalized") + --elseif secs == nil then -- got no value while block-waiting? + -- WR( "timer lane: no linda, aborted?") + end end - end) - while true do - local next_wakeup = check_timers() + end -- timer_body() + timer_lane = gen( "*", { package= {}, priority = max_prio}, timer_body)() -- "*" instead of "io,package" for LuaJIT compatibility... + end -- first_time - -- Sleep until next timer to wake up, or a set/clear command + ----- + -- = timer( linda_h, key_val, date_tbl|first_secs [,period_secs] ) + -- + -- PUBLIC LANES API + timer = function( linda, key, a, period ) + if getmetatable( linda) ~= "Linda" then + error "expecting a Linda" + end + if a == 0.0 then + -- Caller expects to get current time stamp in Linda, on return + -- (like the timer had expired instantly); it would be good to set this + -- as late as possible (to give most current time) but also we want it + -- to precede any possible timers that might start striking. -- - local secs - if next_wakeup then - secs = next_wakeup - now_secs() - if secs < 0 then secs = 0 end - end - local key, what = timer_gateway:receive( secs, TGW_KEY, TGW_QUERY) - - if key == TGW_KEY then - assert( getmetatable( what) == "Linda") -- 'what' should be a linda on which the client sets a timer - local _, key, wakeup_at, period = timer_gateway:receive( 0, timer_gateway_batched, TGW_KEY, 3) - assert( key) - set_timer( what, key, wakeup_at, period and period > 0 and period or nil) - elseif key == TGW_QUERY then - if what == "get_timers" then - timer_gateway:send( TGW_REPLY, get_timers()) - else - timer_gateway:send( TGW_REPLY, "unknown query " .. what) - end - --elseif secs == nil then -- got no value while block-waiting? - -- WR( "timer lane: no linda, aborted?") + linda:set( key, core.now_secs()) + + if not period or period==0.0 then + timer_gateway:send( TGW_KEY, linda, key, nil, nil ) -- clear the timer + return -- nothing more to do end + a= period end - end -- timer_body() - timer_lane = gen( "*", { package= {}, priority = max_prio}, timer_body)() -- "*" instead of "io,package" for LuaJIT compatibility... -end -- first_time ------ --- = timer( linda_h, key_val, date_tbl|first_secs [,period_secs] ) --- --- PUBLIC LANES API -timer = function( linda, key, a, period ) - if getmetatable( linda) ~= "Linda" then - error "expecting a Linda" - end - if a == 0.0 then - -- Caller expects to get current time stamp in Linda, on return - -- (like the timer had expired instantly); it would be good to set this - -- as late as possible (to give most current time) but also we want it - -- to precede any possible timers that might start striking. - -- - linda:set( key, core.now_secs()) - - if not period or period==0.0 then - timer_gateway:send( TGW_KEY, linda, key, nil, nil ) -- clear the timer - return -- nothing more to do - end - a= period - end - - local wakeup_at= type(a)=="table" and core.wakeup_conv(a) -- given point of time - or (a and core.now_secs()+a or nil) - -- queue to timer - -- - timer_gateway:send( TGW_KEY, linda, key, wakeup_at, period ) -end + local wakeup_at= type(a)=="table" and core.wakeup_conv(a) -- given point of time + or (a and core.now_secs()+a or nil) + -- queue to timer + -- + timer_gateway:send( TGW_KEY, linda, key, wakeup_at, period ) + end ------ --- {[{linda, slot, when, period}[,...]]} = timers() --- --- PUBLIC LANES API -timers = function() - timer_gateway:send( TGW_QUERY, "get_timers") - local _, r = timer_gateway:receive( TGW_REPLY) - return r -end + ----- + -- {[{linda, slot, when, period}[,...]]} = timers() + -- + -- PUBLIC LANES API + timers = function() + timer_gateway:send( TGW_QUERY, "get_timers") + local _, r = timer_gateway:receive( TGW_REPLY) + return r + end -end -- settings.with_timers + end -- settings.with_timers --- avoid pulling the whole core module as upvalue when cancel_error is enough -local cancel_error = assert( core.cancel_error) + -- avoid pulling the whole core module as upvalue when cancel_error is enough + local cancel_error = assert( core.cancel_error) ----=== Lock & atomic generators ===--- + ---=== Lock & atomic generators ===--- --- These functions are just surface sugar, but make solutions easier to read. --- Not many applications should even need explicit locks or atomic counters. + -- These functions are just surface sugar, but make solutions easier to read. + -- Not many applications should even need explicit locks or atomic counters. --- --- [true [, ...]= trues(uint) --- -local function trues( n) - if n > 0 then - return true, trues( n - 1) + -- + -- [true [, ...]= trues(uint) + -- + local function trues( n) + if n > 0 then + return true, trues( n - 1) + end end -end --- --- lock_f = lanes.genlock( linda_h, key [,N_uint=1] ) --- --- = lock_f( +M ) -- acquire M --- ...locked... --- = lock_f( -M ) -- release M --- --- Returns an access function that allows 'N' simultaneous entries between --- acquire (+M) and release (-M). For binary locks, use M==1. --- --- PUBLIC LANES API -local genlock = function( linda, key, N) - -- clear existing data and set the limit - N = N or 1 - if linda:set( key) == cancel_error or linda:limit( key, N) == cancel_error then - return cancel_error - end + -- + -- lock_f = lanes.genlock( linda_h, key [,N_uint=1] ) + -- + -- = lock_f( +M ) -- acquire M + -- ...locked... + -- = lock_f( -M ) -- release M + -- + -- Returns an access function that allows 'N' simultaneous entries between + -- acquire (+M) and release (-M). For binary locks, use M==1. + -- + -- PUBLIC LANES API + local genlock = function( linda, key, N) + -- clear existing data and set the limit + N = N or 1 + if linda:set( key) == cancel_error or linda:limit( key, N) == cancel_error then + return cancel_error + end - -- use an optimized version for case N == 1 - return (N == 1) and - function( M, mode_) - local timeout = (mode_ == "try") and 0 or nil - if M > 0 then - -- 'nil' timeout allows 'key' to be numeric - return linda:send( timeout, key, true) -- suspends until been able to push them - else - local k = linda:receive( nil, key) - -- propagate cancel_error if we got it, else return true or false - return k and ((k ~= cancel_error) and true or k) or false + -- use an optimized version for case N == 1 + return (N == 1) and + function( M, mode_) + local timeout = (mode_ == "try") and 0 or nil + if M > 0 then + -- 'nil' timeout allows 'key' to be numeric + return linda:send( timeout, key, true) -- suspends until been able to push them + else + local k = linda:receive( nil, key) + -- propagate cancel_error if we got it, else return true or false + return k and ((k ~= cancel_error) and true or k) or false + end end - end - or - function( M, mode_) - local timeout = (mode_ == "try") and 0 or nil - if M > 0 then - -- 'nil' timeout allows 'key' to be numeric - return linda:send( timeout, key, trues(M)) -- suspends until been able to push them - else - local k = linda:receive( nil, linda.batched, key, -M) - -- propagate cancel_error if we got it, else return true or false - return k and ((k ~= cancel_error) and true or k) or false + or + function( M, mode_) + local timeout = (mode_ == "try") and 0 or nil + if M > 0 then + -- 'nil' timeout allows 'key' to be numeric + return linda:send( timeout, key, trues(M)) -- suspends until been able to push them + else + local k = linda:receive( nil, linda.batched, key, -M) + -- propagate cancel_error if we got it, else return true or false + return k and ((k ~= cancel_error) and true or k) or false + end end end -end -- @@ -728,6 +731,13 @@ end return lanes end -- lanes.configure +lanesMeta.__index = function(t,k) + -- This is called when some functionality is accessed without calling configure() + lanes.configure() -- Initialize with default settings + -- Access the required key + return lanes[k] +end + -- no need to force calling configure() excepted the first time if core.settings then return lanes.configure() @@ -735,5 +745,6 @@ else return lanes end ---the end -return lanes + +--the end (Unreachable Code) +--return lanes -- cgit v1.2.3-55-g6feb