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