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