diff options
author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-02 17:18:38 +0200 |
---|---|---|
committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-02 17:18:38 +0200 |
commit | eb7c7611524ba2dd2324aa3c72142e6973912286 (patch) | |
tree | 0db09627a85f3f9f50b2ce9f37877ffe73c7007f /src | |
parent | 66499a912cbf7ca0205b68a17b430070ef94bc49 (diff) | |
download | lanes-eb7c7611524ba2dd2324aa3c72142e6973912286.tar.gz lanes-eb7c7611524ba2dd2324aa3c72142e6973912286.tar.bz2 lanes-eb7c7611524ba2dd2324aa3c72142e6973912286.zip |
de-megathodize lanes.configure()
Diffstat (limited to 'src')
-rw-r--r-- | src/lanes.lua | 1256 |
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 | ||
14 | Copyright (C) 2007-10 Asko Kauppi <akauppi@gmail.com> | 14 | Copyright (C) 2007-10 Asko Kauppi <akauppi@gmail.com> |
15 | Copyright (C) 2010-13 Benoit Germain <bnt.germain@gmail.com> | 15 | Copyright (C) 2010-24 Benoit Germain <bnt.germain@gmail.com> |
16 | 16 | ||
17 | Permission is hereby granted, free of charge, to any person obtaining a copy | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy |
18 | of this software and associated documentation files (the "Software"), to deal | 18 | of 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 |
43 | local lanesMeta = {} | 43 | local lanesMeta = {} |
44 | local lanes = setmetatable( {}, lanesMeta) | 44 | local lanes = setmetatable({}, lanesMeta) |
45 | 45 | ||
46 | -- this function is available in the public interface until it is called, after which it disappears | 46 | -- ################################################################################################# |
47 | lanes.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) | 53 | local assert = assert(assert) |
54 | local io = assert(io) | ||
55 | local string_gmatch = assert(string.gmatch) | ||
56 | local string_format = assert(string.format) | ||
57 | local select = assert(select) | ||
58 | local setmetatable = assert(setmetatable) | ||
59 | local table_insert = assert(table.insert) | ||
60 | local type = assert(type) | ||
61 | local pairs = assert(pairs) | ||
62 | local tostring = assert(tostring) | ||
63 | local error = assert(error) | ||
64 | |||
65 | -- ################################################################################################# | ||
66 | |||
67 | local 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 | |||
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 | ||
90 | |||
91 | local 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 | |||
130 | local 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) | 160 | end |
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 | ||
164 | local function WR(str) | ||
165 | io.stderr:write(str.."\n" ) | ||
166 | end | ||
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 ) | 170 | local 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) | ||
177 | end | ||
188 | 178 | ||
189 | 179 | -- ################################################################################################# | |
190 | ---=== Laning ===--- | 180 | |
191 | 181 | local 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, | 203 | local 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, | 205 | end |
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 | |
209 | local 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 | ||
276 | local 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 |
346 | end -- 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 | 353 | local timer = function() error "timers are not active" end |
355 | local timers = timer | 354 | local timers = timer |
356 | local timer_lane = nil | 355 | local 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) | 358 | local timer_gateway |
360 | 359 | ||
361 | ----- | 360 | local TGW_KEY = "(timer control)" -- the key does not matter, a 'weird' key may help debugging |
362 | -- <void> = sleep( [seconds_]) | 361 | local 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 | 365 | local 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() | ||
614 | end | ||
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 | ||
623 | local 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") | ||
630 | end -- 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) | 641 | local 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 |
645 | end | ||
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] ) | 648 | local 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 | 661 | local 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 | ||
693 | end -- 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 | 706 | local 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 |
728 | end -- 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 | ||
735 | lanes.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 |
752 | end -- lanes.configure | 790 | end -- lanes.configure |
753 | 791 | ||
754 | lanesMeta.__index = function( t, k) | 792 | -- ################################################################################################# |
793 | |||
794 | lanesMeta.__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_] |
759 | end | 799 | end |
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) |
762 | if core.settings then | 804 | if core.settings then |
763 | return lanes.configure() | 805 | return lanes.configure() |