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