aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <bnt.germain@gmail.com>2025-05-07 15:43:01 +0200
committerBenoit Germain <bnt.germain@gmail.com>2025-05-07 15:43:01 +0200
commit074a7157b6bd3867b60d04f685cdede6063e6e3c (patch)
tree21f58c5c739fadaaa57b214e468524efbbe26cbb
parentd0dd3b644b36bac119aa9e9da40c3cfe38a6e234 (diff)
downloadlanes-master.tar.gz
lanes-master.tar.bz2
lanes-master.zip
Thread priority reworkHEADmaster
* thread priorities can now be set using the native range of values, if desired. * thread API errors cause a Lua error instead of aborting the program. * new function lanes.thread_priority_range(), to query the valid range of priorities. * unit tests for all of the above
-rw-r--r--CHANGES3
-rw-r--r--Makefile2
-rw-r--r--docs/index.html21
-rw-r--r--src/lane.cpp4
-rw-r--r--src/lane.hpp2
-rw-r--r--src/lanes.cpp62
-rw-r--r--src/lanes.lua42
-rw-r--r--src/threading.cpp146
-rw-r--r--src/threading.hpp14
-rw-r--r--src/universe.hpp5
-rw-r--r--unit_tests/lane_tests.cpp217
11 files changed, 347 insertions, 171 deletions
diff --git a/CHANGES b/CHANGES
index 4dc9d2d..65077f7 100644
--- a/CHANGES
+++ b/CHANGES
@@ -16,6 +16,7 @@ CHANGE 2: BGe 27-Nov-24
16 - new function lanes.finally(). Installs a function that gets called at Lanes shutdown after attempting to terminate all lanes. 16 - new function lanes.finally(). Installs a function that gets called at Lanes shutdown after attempting to terminate all lanes.
17 If some lanes still run after the finalizer, Universe::__gc with raise an error or freeze, depending on its return value. 17 If some lanes still run after the finalizer, Universe::__gc with raise an error or freeze, depending on its return value.
18 - new function lanes.collectgarbage(), to force a full GC cycle in the keeper states. 18 - new function lanes.collectgarbage(), to force a full GC cycle in the keeper states.
19 - new function lanes.thread_priority_range(), to query the valid range of priorities.
19 - Configuration settings: 20 - Configuration settings:
20 - Boolean parameters only accept boolean values. 21 - Boolean parameters only accept boolean values.
21 - allocator provider function is called with a string hint to distinguish internal allocations, lane and keeper states. 22 - allocator provider function is called with a string hint to distinguish internal allocations, lane and keeper states.
@@ -26,6 +27,8 @@ CHANGE 2: BGe 27-Nov-24
26 - verbose_errors removed. Use lane error_trace_level instead. 27 - verbose_errors removed. Use lane error_trace_level instead.
27 - with_timers is false by default. 28 - with_timers is false by default.
28 - Non-deep full userdata are processed during module registration just like ordinary module C functions, making them valid transferable (up)values (for example: io.stdin). 29 - Non-deep full userdata are processed during module registration just like ordinary module C functions, making them valid transferable (up)values (for example: io.stdin).
30 - thread API errors cause a Lua error instead of aborting the program.
31 - thread priorities can now be set using the native range of values, if desired.
29 - Lanes: 32 - Lanes:
30 - Can no longer be "killed" by hard-stopping their thread without any resource cleanup (see lane:cancel()). 33 - Can no longer be "killed" by hard-stopping their thread without any resource cleanup (see lane:cancel()).
31 - lanes.gen() settings: 34 - lanes.gen() settings:
diff --git a/Makefile b/Makefile
index 98fa1ac..ea4f1d3 100644
--- a/Makefile
+++ b/Makefile
@@ -80,7 +80,7 @@ build_DUE:
80run_unit_tests: build_lanes build_unit_tests build_DUE 80run_unit_tests: build_lanes build_unit_tests build_DUE
81 @echo ========================================================================================= 81 @echo =========================================================================================
82 $(_PREFIX) $(_UNITTEST_TARGET) --list-tests 82 $(_PREFIX) $(_UNITTEST_TARGET) --list-tests
83 $(_PREFIX) $(_UNITTEST_TARGET) --rng-seed 0 -s scripted_tests.lane.tasking_cancelling 83 $(_PREFIX) $(_UNITTEST_TARGET) --rng-seed 0
84 84
85debug_unit_tests: build_lanes build_unit_tests build_DUE 85debug_unit_tests: build_lanes build_unit_tests build_DUE
86 @echo ========================================================================================= 86 @echo =========================================================================================
diff --git a/docs/index.html b/docs/index.html
index e3fbd0b..be8ad7f 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -71,7 +71,7 @@
71 </p> 71 </p>
72 72
73 <p> 73 <p>
74 This document was revised on 23-Apr-25, and applies to version <tt>4.0.0</tt>. 74 This document was revised on 07-May-25, and applies to version <tt>4.0.0</tt>.
75 </p> 75 </p>
76 </font> 76 </font>
77 </center> 77 </center>
@@ -106,7 +106,7 @@
106 <li>Threads can be given priorities.</li> 106 <li>Threads can be given priorities.</li>
107 <li>Lanes are cancellable, with proper cleanup.</li> 107 <li>Lanes are cancellable, with proper cleanup.</li>
108 <li>No Lua-side application level locking - ever!</li> 108 <li>No Lua-side application level locking - ever!</li>
109 <li>Several totally independant Lanes universes may coexist in an application, one per "master" Lua state.</li> 109 <li>Several totally independent Lanes universes may coexist in an application, one per "master" Lua state.</li>
110 </ul> 110 </ul>
111 111
112 112
@@ -117,7 +117,7 @@
117 <li>Sharing full userdata between states needs special C side preparations (-&gt; <A HREF="#deep_userdata">deep userdata</A> and -&gt; <A HREF="#clonable_userdata">clonable userdata</A>).</li> 117 <li>Sharing full userdata between states needs special C side preparations (-&gt; <A HREF="#deep_userdata">deep userdata</A> and -&gt; <A HREF="#clonable_userdata">clonable userdata</A>).</li>
118 <li>Network level parallelism not included.</li> 118 <li>Network level parallelism not included.</li>
119 <li>Multi-CPU is done with OS threads, not processes. A lane is a Lua full userdata, therefore it will exist only as long as the Lua state that created it still exists. Therefore, a lane won't continue execution after the main program's termination.</li> 119 <li>Multi-CPU is done with OS threads, not processes. A lane is a Lua full userdata, therefore it will exist only as long as the Lua state that created it still exists. Therefore, a lane won't continue execution after the main program's termination.</li>
120 <li>Just like independant Lua states, Lanes universes cannot communicate together.</li> 120 <li>Just like independent Lua states, Lanes universes cannot communicate together.</li>
121 </ul> 121 </ul>
122</p> 122</p>
123 123
@@ -190,6 +190,7 @@
190 <li><tt>lanes.genlock()</tt>: obtain an atomic-like data stack</li> 190 <li><tt>lanes.genlock()</tt>: obtain an atomic-like data stack</li>
191 <li><tt>lanes.linda()</tt>: create a Linda</li> 191 <li><tt>lanes.linda()</tt>: create a Linda</li>
192 <li><tt>lanes.nameof()</tt>: find where a value exists</li> 192 <li><tt>lanes.nameof()</tt>: find where a value exists</li>
193 <li><tt>lanes.thread_priority_range()</tt>: obtain the valid range of thread priorities</li>
193 <li><tt>lanes.now_secs()</tt>: obtain the current clock value</li> 194 <li><tt>lanes.now_secs()</tt>: obtain the current clock value</li>
194 <li><tt>lanes.register()</tt>: scan modules so that functions using them can be transferred</li> 195 <li><tt>lanes.register()</tt>: scan modules so that functions using them can be transferred</li>
195 <li><tt>lanes.set_thread_priority()</tt>: change thread priority</li> 196 <li><tt>lanes.set_thread_priority()</tt>: change thread priority</li>
@@ -863,12 +864,13 @@
863 </tr> 864 </tr>
864 <tr valign=top> 865 <tr valign=top>
865 <td> 866 <td>
866 <code>.priority</code> 867 <code>.priority</code><br />
868 <code>.native_priority</code>
867 </td> 869 </td>
868 <td>integer</td> 870 <td>integer</td>
869 <td> 871 <td>
870 The priority of lanes generated in the range -3..+3 (default is 0). 872 <tt>priority</tt>: The priority of lanes in the range <tt>[-3,+3]</tt> (default is 0). These values are a mapping over the actual priority range of the underlying implementation.<br />
871 These values are a mapping over the actual priority range of the underlying implementation.<br /> 873 <tt>native_priority</tt>: The priority of lanes in a platform-dependent the range. Use <a href="#priority"><tt>lanes.thread_priority_range()</tt></a> to query said range.
872 Implementation and dependability of priorities varies by platform. Especially Linux kernel 2.6 is not supporting priorities in user mode.<br /> 874 Implementation and dependability of priorities varies by platform. Especially Linux kernel 2.6 is not supporting priorities in user mode.<br />
873 A lane can also change its own thread priority dynamically with <a href="#priority"><tt>lanes.set_thread_priority()</tt></a>. 875 A lane can also change its own thread priority dynamically with <a href="#priority"><tt>lanes.set_thread_priority()</tt></a>.
874 </td> 876 </td>
@@ -957,14 +959,17 @@
957 <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"> 959 <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%">
958 <tr> 960 <tr>
959 <td> 961 <td>
960 <pre> lanes.set_thread_priority(prio)</pre> 962 <pre> prio_min, prio_max = lanes.thread_priority_range(prio [,"native"])</pre>
963 <pre> lanes.set_thread_priority(prio [,"native"])</pre>
961 </td> 964 </td>
962 </tr> 965 </tr>
963 </table> 966 </table>
964<p> 967<p>
965 Besides setting a default priority in the generator <a href="#generator_settings">settings</a>, each thread can change its own priority at will. This is also true for the main Lua state. 968 Besides setting a default priority in the generator <a href="#generator_settings">settings</a>, each thread can change its own priority at will. This is also true for the main Lua state.
966 <br /> 969 <br />
967 The priority must be in the range <tt>[-3,+3]</tt>. 970 <tt>lanes.thread_priority_range()</tt> returns the range of acceptable mapped values. If nothing is specified, should be <tt>[-3,3]</tt> or <tt>[0,3]</tt>, depending on the threading implementation.
971 <br />
972 <tt>lanes.thread_priority_range('native')</tt> returns the range of acceptable native values. The actual values are threading implementation dependent. And some implementations can only accept some values inside that range. YMMV.
968</p> 973</p>
969 974
970 975
diff --git a/src/lane.cpp b/src/lane.cpp
index 26ddebd..65a776e 100644
--- a/src/lane.cpp
+++ b/src/lane.cpp
@@ -1169,11 +1169,11 @@ void Lane::securizeDebugName(lua_State* const L_)
1169 1169
1170// ################################################################################################# 1170// #################################################################################################
1171 1171
1172void Lane::startThread(int const priority_) 1172void Lane::startThread(lua_State* const L_, int const priority_, NativePrioFlag native_)
1173{ 1173{
1174 thread = std::thread([this]() { lane_main(this); }); 1174 thread = std::thread([this]() { lane_main(this); });
1175 if (priority_ != kThreadPrioDefault) { 1175 if (priority_ != kThreadPrioDefault) {
1176 THREAD_SET_PRIORITY(thread, priority_, U->sudo); 1176 THREAD_SET_PRIORITY(L_, thread, priority_, native_, U->sudo);
1177 } 1177 }
1178} 1178}
1179 1179
diff --git a/src/lane.hpp b/src/lane.hpp
index 5fe36b6..917606f 100644
--- a/src/lane.hpp
+++ b/src/lane.hpp
@@ -204,7 +204,7 @@ class Lane final
204 [[nodiscard]] 204 [[nodiscard]]
205 bool selfdestructRemove(); 205 bool selfdestructRemove();
206 void securizeDebugName(lua_State* L_); 206 void securizeDebugName(lua_State* L_);
207 void startThread(int priority_); 207 void startThread(lua_State* L_, int priority_, NativePrioFlag native_);
208 void storeDebugName( std::string_view const& name_); 208 void storeDebugName( std::string_view const& name_);
209 [[nodiscard]] 209 [[nodiscard]]
210 int storeResults(lua_State* L_); 210 int storeResults(lua_State* L_);
diff --git a/src/lanes.cpp b/src/lanes.cpp
index d1a353b..0eaeb3e 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -137,13 +137,15 @@ LUAG_FUNC(set_singlethreaded)
137LUAG_FUNC(set_thread_priority) 137LUAG_FUNC(set_thread_priority)
138{ 138{
139 lua_Integer const _prio{ luaL_checkinteger(L_, 1) }; 139 lua_Integer const _prio{ luaL_checkinteger(L_, 1) };
140 NativePrioFlag const _native{ std::string_view{ "native" } == luaL_optstring(L_, 2, "mapped") };
140 // public Lanes API accepts a generic range -3/+3 141 // public Lanes API accepts a generic range -3/+3
141 // that will be remapped into the platform-specific scheduler priority scheme 142 // that will be remapped into the platform-specific scheduler priority scheme
142 // On some platforms, -3 is equivalent to -2 and +3 to +2 143 // On some platforms, -3 is equivalent to -2 and +3 to +2
143 if (_prio < kThreadPrioMin || _prio > kThreadPrioMax) { 144 if (!_native && (_prio < kThreadPrioMin || _prio > kThreadPrioMax)) {
144 raise_luaL_error(L_, "priority out of range: %d..+%d (%d)", kThreadPrioMin, kThreadPrioMax, _prio); 145 raise_luaL_error(L_, "priority out of range: %d..+%d (%d)", kThreadPrioMin, kThreadPrioMax, _prio);
145 } 146 }
146 THREAD_SET_PRIORITY(static_cast<int>(_prio), Universe::Get(L_)->sudo); 147
148 THREAD_SET_PRIORITY(L_, static_cast<int>(_prio), _native, Universe::Get(L_)->sudo);
147 return 0; 149 return 0;
148} 150}
149 151
@@ -155,7 +157,8 @@ LUAG_FUNC(set_thread_affinity)
155 if (_affinity <= 0) { 157 if (_affinity <= 0) {
156 raise_luaL_error(L_, "invalid affinity (%d)", _affinity); 158 raise_luaL_error(L_, "invalid affinity (%d)", _affinity);
157 } 159 }
158 THREAD_SET_AFFINITY(static_cast<unsigned int>(_affinity)); 160
161 THREAD_SET_AFFINITY(L_, static_cast<unsigned int>(_affinity));
159 return 0; 162 return 0;
160} 163}
161 164
@@ -236,9 +239,26 @@ int lanes_register(lua_State* const L_)
236 239
237// ################################################################################################# 240// #################################################################################################
238 241
242LUAG_FUNC(thread_priority_range)
243{
244 NativePrioFlag const _native{ std::string_view{ "native" } == luaL_optstring(L_, 1, "mapped") };
245 if (_native) {
246 auto const [_prio_min, _prio_max] = THREAD_NATIVE_PRIOS();
247 lua_pushinteger(L_, _prio_min);
248 lua_pushinteger(L_, _prio_max);
249 } else {
250 lua_pushinteger(L_, kThreadPrioMin);
251 lua_pushinteger(L_, kThreadPrioMax);
252 }
253 return 2;
254}
255
256// #################################################################################################
257
239//--- [] means can be nil 258//--- [] means can be nil
240// lane_ud = lane_new( function 259// lane_ud = lane_new( function
241// , [libs_str] 260// , [libs_str]
261// , [prio_is_native_bool]
242// , [priority_int] 262// , [priority_int]
243// , [globals_tbl] 263// , [globals_tbl]
244// , [package_tbl] 264// , [package_tbl]
@@ -255,15 +275,16 @@ LUAG_FUNC(lane_new)
255{ 275{
256 static constexpr StackIndex kFuncIdx{ 1 }; 276 static constexpr StackIndex kFuncIdx{ 1 };
257 static constexpr StackIndex kLibsIdx{ 2 }; 277 static constexpr StackIndex kLibsIdx{ 2 };
258 static constexpr StackIndex kPrioIdx{ 3 }; 278 static constexpr StackIndex kPrinIdx{ 3 };
259 static constexpr StackIndex kGlobIdx{ 4 }; 279 static constexpr StackIndex kPrioIdx{ 4 };
260 static constexpr StackIndex kPackIdx{ 5 }; 280 static constexpr StackIndex kGlobIdx{ 5 };
261 static constexpr StackIndex kRequIdx{ 6 }; 281 static constexpr StackIndex kPackIdx{ 6 };
262 static constexpr StackIndex kGcCbIdx{ 7 }; 282 static constexpr StackIndex kRequIdx{ 7 };
263 static constexpr StackIndex kNameIdx{ 8 }; 283 static constexpr StackIndex kGcCbIdx{ 8 };
264 static constexpr StackIndex kErTlIdx{ 9 }; 284 static constexpr StackIndex kNameIdx{ 9 };
265 static constexpr StackIndex kAsCoro{ 10 }; 285 static constexpr StackIndex kErTlIdx{ 10 };
266 static constexpr StackIndex kFixedArgsIdx{ 10 }; 286 static constexpr StackIndex kAsCoro{ 11 };
287 static constexpr StackIndex kFixedArgsIdx{ 11 };
267 288
268 int const _nargs{ lua_gettop(L_) - kFixedArgsIdx }; 289 int const _nargs{ lua_gettop(L_) - kFixedArgsIdx };
269 LUA_ASSERT(L_, _nargs >= 0); 290 LUA_ASSERT(L_, _nargs >= 0);
@@ -400,21 +421,22 @@ LUAG_FUNC(lane_new)
400 // public Lanes API accepts a generic range -3/+3 421 // public Lanes API accepts a generic range -3/+3
401 // that will be remapped into the platform-specific scheduler priority scheme 422 // that will be remapped into the platform-specific scheduler priority scheme
402 // On some platforms, -3 is equivalent to -2 and +3 to +2 423 // On some platforms, -3 is equivalent to -2 and +3 to +2
403 int const _priority{ 424 auto const [_priority, _native] {
404 std::invoke([L = L_]() { 425 std::invoke([L = L_]() {
426 NativePrioFlag const _native{ static_cast<bool>(lua_toboolean(L, kPrinIdx)) };
405 StackIndex const _prio_idx{ lua_isnoneornil(L, kPrioIdx) ? kIdxNone : kPrioIdx }; 427 StackIndex const _prio_idx{ lua_isnoneornil(L, kPrioIdx) ? kIdxNone : kPrioIdx };
406 if (_prio_idx == 0) { 428 if (_prio_idx == kIdxNone) {
407 return kThreadPrioDefault; 429 return std::make_pair(kThreadPrioDefault, _native);
408 } 430 }
409 int const _priority{ static_cast<int>(lua_tointeger(L, _prio_idx)) }; 431 int const _priority{ static_cast<int>(lua_tointeger(L, _prio_idx)) };
410 if ((_priority < kThreadPrioMin || _priority > kThreadPrioMax)) { 432 if (!_native && (_priority < kThreadPrioMin || _priority > kThreadPrioMax)) {
411 raise_luaL_error(L, "Priority out of range: %d..+%d (%d)", kThreadPrioMin, kThreadPrioMax, _priority); 433 raise_luaL_error(L, "Priority out of range: %d..+%d (%d)", kThreadPrioMin, kThreadPrioMax, _priority);
412 } 434 }
413 return _priority; 435 return std::make_pair(_priority, _native);
414 }) 436 })
415 }; 437 };
416 438
417 _lane->startThread(_priority); 439 _lane->startThread(L_, _priority, _native);
418 440
419 STACK_GROW(_L2, _nargs + 3); 441 STACK_GROW(_L2, _nargs + 3);
420 STACK_GROW(L_, 3); 442 STACK_GROW(L_, 3);
@@ -658,6 +680,7 @@ namespace {
658 { Universe::kFinally, Universe::InitializeFinalizer }, 680 { Universe::kFinally, Universe::InitializeFinalizer },
659 { "linda", LG_linda }, 681 { "linda", LG_linda },
660 { "nameof", LG_nameof }, 682 { "nameof", LG_nameof },
683 { "thread_priority_range", LG_thread_priority_range },
661 { "now_secs", LG_now_secs }, 684 { "now_secs", LG_now_secs },
662 { "register", lanes_register }, 685 { "register", lanes_register },
663 { "set_singlethreaded", LG_set_singlethreaded }, 686 { "set_singlethreaded", LG_set_singlethreaded },
@@ -753,9 +776,6 @@ LUAG_FUNC(configure)
753 ); // L_: settings M VERSION 776 ); // L_: settings M VERSION
754 lua_setfield(L_, -2, "version"); // L_: settings M 777 lua_setfield(L_, -2, "version"); // L_: settings M
755 778
756 lua_pushinteger(L_, kThreadPrioMax); // L_: settings M kThreadPrioMax
757 lua_setfield(L_, -2, "max_prio"); // L_: settings M
758
759 kCancelError.pushKey(L_); // L_: settings M kCancelError 779 kCancelError.pushKey(L_); // L_: settings M kCancelError
760 lua_setfield(L_, -2, "cancel_error"); // L_: settings M 780 lua_setfield(L_, -2, "cancel_error"); // L_: settings M
761 781
diff --git a/src/lanes.lua b/src/lanes.lua
index 3ee959c..c5b3315 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -280,6 +280,10 @@ local opt_validators =
280 local tv = type(v_) 280 local tv = type(v_)
281 return (tv == "string") and v_ or raise_option_error("name", tv, v_) 281 return (tv == "string") and v_ or raise_option_error("name", tv, v_)
282 end, 282 end,
283 native_priority = function(v_)
284 local tv = type(v_)
285 return (tv == "number") and v_ or raise_option_error("native_priority", tv, v_)
286 end,
283 package = function(v_) 287 package = function(v_)
284 local tv = type(v_) 288 local tv = type(v_)
285 return (tv == "table") and v_ or raise_option_error("package", tv, v_) 289 return (tv == "table") and v_ or raise_option_error("package", tv, v_)
@@ -295,7 +299,7 @@ local opt_validators =
295} 299}
296 300
297-- ############################################################################################# 301-- #############################################################################################
298-- ##################################### lanes.gen() ########################################### 302-- ################################### lanes.gen/coro() ########################################
299-- ############################################################################################# 303-- #############################################################################################
300 304
301local process_gen_opt = function(...) 305local process_gen_opt = function(...)
@@ -367,9 +371,16 @@ local process_gen_opt = function(...)
367 opt[k] = validator(v) 371 opt[k] = validator(v)
368 end 372 end
369 end 373 end
374
375 -- special case: can't have priority and native_priority at the same time
376 if opt.priority and opt.native_priority then
377 error "priority and native_priority cannot be specified together"
378 end
370 return func, libs, opt 379 return func, libs, opt
371end -- process_gen_opt 380end -- process_gen_opt
372 381
382-- #################################################################################################
383
373-- lane_h[1..n]: lane results, same as via 'lane_h:join()' 384-- lane_h[1..n]: lane results, same as via 'lane_h:join()'
374-- lane_h[0]: can be read to make sure a thread has finished (gives the number of available results) 385-- lane_h[0]: can be read to make sure a thread has finished (gives the number of available results)
375-- lane_h[negative]: error message, without propagating the error 386-- lane_h[negative]: error message, without propagating the error
@@ -408,25 +419,28 @@ end -- process_gen_opt
408-- Calling with a function argument ('lane_func') ends the string/table 419-- Calling with a function argument ('lane_func') ends the string/table
409-- modifiers, and prepares a lane generator. 420-- modifiers, and prepares a lane generator.
410 421
411-- receives a sequence of strings and tables, plus a function 422local make_generator = function(is_coro_, ...)
412local gen = function(...)
413 local func, libs, opt = process_gen_opt(...) 423 local func, libs, opt = process_gen_opt(...)
414 local core_lane_new = assert(core.lane_new) 424 local core_lane_new = assert(core.lane_new)
415 local priority, globals, package, required, gc_cb, name, error_trace_level = opt.priority, opt.globals, opt.package or package, opt.required, opt.gc_cb, opt.name, error_trace_levels[opt.error_trace_level] 425 local prio_is_native = opt.native_priority and true or false
426 local priority, globals, package, required, gc_cb, name, error_trace_level = opt.priority or opt.native_priority, opt.globals, opt.package or package, opt.required, opt.gc_cb, opt.name, error_trace_levels[opt.error_trace_level]
416 return function(...) 427 return function(...)
417 -- must pass functions args last else they will be truncated to the first one 428 -- must pass functions args last else they will be truncated to the first one
418 return core_lane_new(func, libs, priority, globals, package, required, gc_cb, name, error_trace_level, false, ...) 429 return core_lane_new(func, libs, prio_is_native, priority, globals, package, required, gc_cb, name, error_trace_level, is_coro_, ...)
419 end 430 end
431end -- make_generator
432
433-- #################################################################################################
434
435-- receives a sequence of strings and tables, plus a function
436local gen = function(...)
437 return make_generator(false, ...)
420end -- gen() 438end -- gen()
421 439
440-- #################################################################################################
441
422local coro = function(...) 442local coro = function(...)
423 local func, libs, opt = process_gen_opt(...) 443 return make_generator(true, ...)
424 local core_lane_new = assert(core.lane_new)
425 local priority, globals, package, required, gc_cb, name, error_trace_level = opt.priority, opt.globals, opt.package or package, opt.required, opt.gc_cb, opt.name, error_trace_levels[opt.error_trace_level]
426 return function(...)
427 -- must pass functions args last else they will be truncated to the first one
428 return core_lane_new(func, libs, priority, globals, package, required, gc_cb, name, error_trace_level, true, ...)
429 end
430end -- coro() 444end -- coro()
431 445
432-- ################################################################################################# 446-- #################################################################################################
@@ -656,7 +670,8 @@ local configure_timers = function()
656 end 670 end
657 end 671 end
658 end -- timer_body() 672 end -- timer_body()
659 timer_lane = gen("lanes_core,table", { name = "LanesTimer", package = {}, priority = core.max_prio }, timer_body)() 673 local min_prio, max_prio = core.thread_priority_range()
674 timer_lane = gen("lanes_core,table", { name = "LanesTimer", package = {}, priority = max_prio }, timer_body)()
660 end -- first_time 675 end -- first_time
661 676
662 ----- 677 -----
@@ -876,6 +891,7 @@ local configure = function(settings_)
876 lanes.set_thread_affinity = core.set_thread_affinity 891 lanes.set_thread_affinity = core.set_thread_affinity
877 lanes.set_thread_priority = core.set_thread_priority 892 lanes.set_thread_priority = core.set_thread_priority
878 lanes.sleep = core.sleep 893 lanes.sleep = core.sleep
894 lanes.thread_priority_range = core.thread_priority_range
879 lanes.threads = core.threads or function() error "lane tracking is not available" end -- core.threads isn't registered if settings.track_lanes is false 895 lanes.threads = core.threads or function() error "lane tracking is not available" end -- core.threads isn't registered if settings.track_lanes is false
880 896
881 lanes.gen = gen 897 lanes.gen = gen
diff --git a/src/threading.cpp b/src/threading.cpp
index 483a228..efca7eb 100644
--- a/src/threading.cpp
+++ b/src/threading.cpp
@@ -49,6 +49,7 @@ THE SOFTWARE.
49 49
50#endif // __linux__ 50#endif // __linux__
51 51
52#include "compat.hpp"
52#include "threading.hpp" 53#include "threading.hpp"
53 54
54#if !defined(PLATFORM_XBOX) && !defined(PLATFORM_WIN32) && !defined(PLATFORM_POCKETPC) 55#if !defined(PLATFORM_XBOX) && !defined(PLATFORM_WIN32) && !defined(PLATFORM_POCKETPC)
@@ -82,25 +83,42 @@ THE SOFTWARE.
82#pragma warning(disable : 4054) 83#pragma warning(disable : 4054)
83#endif 84#endif
84 85
86static constexpr std::string_view StripFuncName(std::string_view const& where_)
87{
88 std::string_view funcname_{ where_ };
89
90 auto _args_pos{ funcname_.find_first_of('(') };
91 funcname_ = funcname_.substr(0, _args_pos);
92 auto _name_pos{ funcname_.find_last_of(' ') };
93 funcname_.remove_prefix(_name_pos + 1);
94 return funcname_;
95}
96
85/* 97/*
86 * FAIL is for unexpected API return values - essentially programming 98 * FAIL is for unexpected API return values - essentially programming
87 * error in _this_ code. 99 * error in _this_ code.
88 */ 100 */
89#if HAVE_WIN32 101#if HAVE_WIN32
90static void FAIL(char const* funcname_, DWORD const rc_) 102
103template <typename F, typename... ARGS>
104void Win32Invoke(lua_State* const L_, std::string_view const& where_, F& f_, ARGS... args_)
91{ 105{
106 auto const _ret{ std::invoke(f_, std::forward<ARGS>(args_)...) };
107 if (!_ret) {
108 auto const _rc{ GetLastError() };
109 std::string_view const _funcname{ StripFuncName(where_) };
110
92#if defined(PLATFORM_XBOX) 111#if defined(PLATFORM_XBOX)
93 fprintf(stderr, "%s() failed! (%d)\n", funcname_, rc_); 112 luaG_pushstring(L_, "%s() failed with code %d", _funcname.data(), _rc);
94#else // PLATFORM_XBOX 113#else // PLATFORM_XBOX
95 char buf[256]; 114 char _buf[256];
96 FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, rc_, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 256, nullptr); 115 FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, _rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), _buf, 256, nullptr);
97 fprintf(stderr, "%s() failed! [GetLastError() -> %lu] '%s'", funcname_, rc_, buf); 116 luaG_pushstring(L_, "%s() failed with code %d '%s'", _funcname.data(), _rc, _buf);
98#endif // PLATFORM_XBOX 117#endif // PLATFORM_XBOX
99#ifdef _MSC_VER 118 raise_lua_error(L_);
100 __debugbreak(); // give a chance to the debugger! 119 }
101#endif // _MSC_VER
102 abort();
103} 120}
121
104#endif // HAVE_WIN32 122#endif // HAVE_WIN32
105 123
106/*---=== Threading ===---*/ 124/*---=== Threading ===---*/
@@ -121,33 +139,35 @@ static int const gs_prio_remap[] = {
121 139
122// ################################################################################################# 140// #################################################################################################
123 141
124void THREAD_SET_PRIORITY(int prio_, [[maybe_unused]] bool sudo_) 142std::pair<int, int> THREAD_NATIVE_PRIOS()
125{ 143{
126 // prio range [-3,+3] was checked by the caller 144 return std::make_pair(THREAD_PRIORITY_IDLE, THREAD_PRIORITY_TIME_CRITICAL);
127 if (!SetThreadPriority(GetCurrentThread(), gs_prio_remap[prio_ + 3])) {
128 FAIL("THREAD_SET_PRIORITY", GetLastError());
129 }
130} 145}
131 146
132// ################################################################################################# 147// #################################################################################################
133 148
134void THREAD_SET_PRIORITY(std::thread& thread_, int prio_, [[maybe_unused]] bool sudo_) 149[[nodiscard]]
150void THREAD_SET_PRIORITY(lua_State* const L_, int const prio_, NativePrioFlag const native_, [[maybe_unused]] SudoFlag const sudo_)
135{ 151{
136 // prio range [-3,+3] was checked by the caller 152 // mapped prio range [-3,+3] was checked by the caller
137 // for some reason when building for mingw, native_handle() is an unsigned long long, but HANDLE is a void* 153 return Win32Invoke(L_, std::source_location::current().function_name(), SetThreadPriority, GetCurrentThread(), native_ ? prio_ : gs_prio_remap[prio_ + 3]);
138 // -> need a strong cast to make g++ happy
139 if (!SetThreadPriority(thread_.native_handle(), gs_prio_remap[prio_ + 3])) {
140 FAIL("THREAD_SET_PRIORITY", GetLastError());
141 }
142} 154}
143 155
144// ################################################################################################# 156// #################################################################################################
145 157
146void THREAD_SET_AFFINITY(unsigned int aff_) 158[[nodiscard]]
159void THREAD_SET_PRIORITY(lua_State* const L_, std::thread& thread_, int const prio_, NativePrioFlag const native_, [[maybe_unused]] SudoFlag const sudo_)
147{ 160{
148 if (!SetThreadAffinityMask(GetCurrentThread(), aff_)) { 161 // mapped prio range [-3,+3] was checked by the caller
149 FAIL("THREAD_SET_AFFINITY", GetLastError()); 162 return Win32Invoke(L_, std::source_location::current().function_name(), SetThreadPriority, thread_.native_handle(), native_ ? prio_ : gs_prio_remap[prio_ + 3]);
150 } 163}
164
165// #################################################################################################
166
167[[nodiscard]]
168void THREAD_SET_AFFINITY(lua_State* const L_, unsigned int aff_)
169{
170 return Win32Invoke(L_, std::source_location::current().function_name(), SetThreadAffinityMask, GetCurrentThread(), aff_);
151} 171}
152 172
153// ################################################################################################# 173// #################################################################################################
@@ -215,24 +235,24 @@ static int pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy)
215#endif // pthread_attr_setschedpolicy() 235#endif // pthread_attr_setschedpolicy()
216#endif // defined(__MINGW32__) || defined(__MINGW64__) 236#endif // defined(__MINGW32__) || defined(__MINGW64__)
217 237
218static void _PT_FAIL(int rc, const char* name, const char* file, int line) 238template <typename F, typename... ARGS>
239void PthreadInvoke(lua_State* const L_, std::string_view const& where_, F& f_, ARGS... args_)
219{ 240{
220 const char* why = (rc == EINVAL) ? "EINVAL" 241 auto const _rc{ std::invoke(f_, std::forward<ARGS>(args_)...) };
221 : (rc == EBUSY) ? "EBUSY" 242 if (_rc) {
222 : (rc == EPERM) ? "EPERM" 243 std::string_view const _funcname{ StripFuncName(where_) };
223 : (rc == ENOMEM) ? "ENOMEM" 244
224 : (rc == ESRCH) ? "ESRCH" 245 char const* _why = (_rc == EINVAL) ? "EINVAL"
225 : (rc == ENOTSUP) ? "ENOTSUP" 246 : (_rc == EBUSY) ? "EBUSY"
226 : "<UNKNOWN>"; 247 : (_rc == EPERM) ? "EPERM"
227 fprintf(stderr, "%s %d: %s failed, %d %s\n", file, line, name, rc, why); 248 : (_rc == ENOMEM) ? "ENOMEM"
228 abort(); 249 : (_rc == ESRCH) ? "ESRCH"
229} 250 : (_rc == ENOTSUP) ? "ENOTSUP"
230#define PT_CALL(call) \ 251 : "<UNKNOWN>";
231 { \ 252
232 int rc = call; \ 253 raise_luaL_error(L_, "%s() failed with code %s", _funcname.data(), _why);
233 if (rc != 0) \
234 _PT_FAIL(rc, #call, __FILE__, __LINE__); \
235 } 254 }
255}
236 256
237// array of 7 thread priority values, hand-tuned by platform so that we offer a uniform [-3,+3] public priority range 257// array of 7 thread priority values, hand-tuned by platform so that we offer a uniform [-3,+3] public priority range
238static int const gs_prio_remap[] = { 258static int const gs_prio_remap[] = {
@@ -357,7 +377,18 @@ static int const gs_prio_remap[] = {
357#endif // _PRIO_0 377#endif // _PRIO_0
358}; 378};
359 379
360void THREAD_SET_PRIORITY(int prio_, [[maybe_unused]] bool sudo_) 380// #################################################################################################
381
382std::pair<int, int> THREAD_NATIVE_PRIOS()
383{
384 int const _prio_min{ sched_get_priority_min(_PRIO_MODE) };
385 int const _prio_max{ sched_get_priority_max(_PRIO_MODE) };
386 return std::make_pair(_prio_min, _prio_max);
387}
388
389// #################################################################################################
390
391void THREAD_SET_PRIORITY(lua_State* const L_, int const prio_, NativePrioFlag const native_, [[maybe_unused]] SudoFlag const sudo_)
361{ 392{
362#ifdef PLATFORM_LINUX 393#ifdef PLATFORM_LINUX
363 if (!sudo_) // only root-privileged process can change priorities 394 if (!sudo_) // only root-privileged process can change priorities
@@ -366,13 +397,13 @@ void THREAD_SET_PRIORITY(int prio_, [[maybe_unused]] bool sudo_)
366 397
367 struct sched_param sp; 398 struct sched_param sp;
368 // prio range [-3,+3] was checked by the caller 399 // prio range [-3,+3] was checked by the caller
369 sp.sched_priority = gs_prio_remap[prio_ + 3]; 400 sp.sched_priority = native_ ? prio_ : gs_prio_remap[prio_ + 3];
370 PT_CALL(pthread_setschedparam(pthread_self(), _PRIO_MODE, &sp)); 401 PthreadInvoke(L_, std::source_location::current().function_name(), pthread_setschedparam, pthread_self(), _PRIO_MODE, &sp);
371} 402}
372 403
373// ################################################################################################# 404// #################################################################################################
374 405
375void THREAD_SET_PRIORITY(std::thread& thread_, int prio_, [[maybe_unused]] bool sudo_) 406void THREAD_SET_PRIORITY(lua_State* const L_, std::thread& thread_, int const prio_, NativePrioFlag const native_, [[maybe_unused]] SudoFlag const sudo_)
376{ 407{
377#ifdef PLATFORM_LINUX 408#ifdef PLATFORM_LINUX
378 if (!sudo_) // only root-privileged process can change priorities 409 if (!sudo_) // only root-privileged process can change priorities
@@ -381,28 +412,26 @@ void THREAD_SET_PRIORITY(std::thread& thread_, int prio_, [[maybe_unused]] bool
381 412
382 struct sched_param sp; 413 struct sched_param sp;
383 // prio range [-3,+3] was checked by the caller 414 // prio range [-3,+3] was checked by the caller
384 sp.sched_priority = gs_prio_remap[prio_ + 3]; 415 sp.sched_priority = native_ ? prio_ : gs_prio_remap[prio_ + 3];
385 PT_CALL(pthread_setschedparam(thread_.native_handle(), _PRIO_MODE, &sp)); 416 PthreadInvoke(L_, std::source_location::current().function_name(), pthread_setschedparam, thread_.native_handle(), _PRIO_MODE, &sp);
386} 417}
387 418
388// ################################################################################################# 419// #################################################################################################
389 420
390#ifdef __PROSPERO__ 421#ifdef __PROSPERO__
391 422
392void THREAD_SET_AFFINITY(unsigned int aff_) 423void THREAD_SET_AFFINITY(lua_State* const L_, unsigned int aff_)
393{ 424{
394 scePthreadSetaffinity(scePthreadSelf(), aff_); 425 PthreadInvoke(L_, std::source_location::current().function_name(), scePthreadSetaffinity, scePthreadSelf(), aff_);
395} 426}
396 427
397#else // __PROSPERO__ 428#else // __PROSPERO__
398 429
399void THREAD_SET_AFFINITY(unsigned int aff_) 430void THREAD_SET_AFFINITY(lua_State* const L_, unsigned int aff_)
400{ 431{
401#if HAVE_WIN32 // "hybrid": Win32 API is available, and pthread too 432#if HAVE_WIN32 // "hybrid": Win32 API is available, and pthread too
402 // since pthread_setaffinity_np can be missing (for example mingw), use win32 api instead 433 // since pthread_setaffinity_np can be missing (for example mingw), use win32 api instead
403 if (!SetThreadAffinityMask(GetCurrentThread(), aff_)) { 434 Win32Invoke(L_, std::source_location::current().function_name(), SetThreadAffinityMask, GetCurrentThread(), aff_);
404 FAIL("THREAD_SET_AFFINITY", GetLastError());
405 }
406#else // pure pthread 435#else // pure pthread
407 int bit = 0; 436 int bit = 0;
408#ifdef __NetBSD__ 437#ifdef __NetBSD__
@@ -422,12 +451,13 @@ void THREAD_SET_AFFINITY(unsigned int aff_)
422 aff_ >>= 1; 451 aff_ >>= 1;
423 } 452 }
424#ifdef __ANDROID__ 453#ifdef __ANDROID__
425 PT_CALL(sched_setaffinity(pthread_self(), sizeof(cpu_set_t), &cpuset)); 454
455 PthreadInvoke(L_, std::source_location::current().function_name(), sched_setaffinity, pthread_self(), sizeof(cpu_set_t), &cpuset);
426#elif defined(__NetBSD__) 456#elif defined(__NetBSD__)
427 PT_CALL(pthread_setaffinity_np(pthread_self(), cpuset_size(cpuset), cpuset)); 457 PthreadInvoke(L_, std::source_location::current().function_name(), pthread_setaffinity_np, pthread_self(), cpuset_size(cpuset), cpuset);
428 cpuset_destroy(cpuset); 458 cpuset_destroy(cpuset);
429#else 459#else
430 PT_CALL(pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset)); 460 PthreadInvoke(L_, std::source_location::current().function_name(), pthread_setaffinity_np, pthread_self(), sizeof(cpu_set_t), &cpuset);
431#endif 461#endif
432#endif // PLATFORM_MINGW 462#endif // PLATFORM_MINGW
433} 463}
@@ -447,7 +477,7 @@ void THREAD_SETNAME(std::string_view const& name_)
447 477
448void THREAD_SETNAME(std::string_view const& name_) 478void THREAD_SETNAME(std::string_view const& name_)
449{ 479{
450 // exact API to set the thread name is platform-dependant 480 // exact API to set the thread name is platform-dependent
451 // if you need to fix the build, or if you know how to fill a hole, tell me (bnt.germain@gmail.com) so that I can submit the fix in github. 481 // if you need to fix the build, or if you know how to fill a hole, tell me (bnt.germain@gmail.com) so that I can submit the fix in github.
452#if defined PLATFORM_MINGW 482#if defined PLATFORM_MINGW
453 pthread_setname_np(pthread_self(), name_.data()); 483 pthread_setname_np(pthread_self(), name_.data());
diff --git a/src/threading.hpp b/src/threading.hpp
index 912c28f..07c1ab3 100644
--- a/src/threading.hpp
+++ b/src/threading.hpp
@@ -1,6 +1,7 @@
1#pragma once 1#pragma once
2 2
3#include "platform.h" 3#include "platform.h"
4#include "unique.hpp"
4 5
5#define THREADAPI_WINDOWS 1 6#define THREADAPI_WINDOWS 1
6#define THREADAPI_PTHREAD 2 7#define THREADAPI_PTHREAD 2
@@ -73,8 +74,15 @@ static constexpr int kThreadPrioMax{ +3 };
73// ################################################################################################# 74// #################################################################################################
74// ################################################################################################# 75// #################################################################################################
75 76
77DECLARE_UNIQUE_TYPE(SudoFlag, bool);
78DECLARE_UNIQUE_TYPE(NativePrioFlag, bool);
79
80std::pair<int, int> THREAD_NATIVE_PRIOS();
81
76void THREAD_SETNAME(std::string_view const& name_); 82void THREAD_SETNAME(std::string_view const& name_);
77void THREAD_SET_PRIORITY(int prio_, bool sudo_);
78void THREAD_SET_AFFINITY(unsigned int aff_);
79 83
80void THREAD_SET_PRIORITY(std::thread& thread_, int prio_, bool sudo_); 84void THREAD_SET_PRIORITY(lua_State* L_, int prio_, NativePrioFlag native_, SudoFlag sudo_);
85
86void THREAD_SET_AFFINITY(lua_State* L_, unsigned int aff_);
87
88void THREAD_SET_PRIORITY(lua_State* L_, std::thread& thread_, int prio_, NativePrioFlag native_, SudoFlag sudo_);
diff --git a/src/universe.hpp b/src/universe.hpp
index fac5f50..ab49f86 100644
--- a/src/universe.hpp
+++ b/src/universe.hpp
@@ -4,6 +4,7 @@
4#include "cancel.hpp" 4#include "cancel.hpp"
5#include "keeper.hpp" 5#include "keeper.hpp"
6#include "lanesconf.h" 6#include "lanesconf.h"
7#include "threading.hpp"
7#include "tracker.hpp" 8#include "tracker.hpp"
8#include "uniquekey.hpp" 9#include "uniquekey.hpp"
9 10
@@ -70,9 +71,9 @@ class Universe final
70 71
71#ifdef PLATFORM_LINUX 72#ifdef PLATFORM_LINUX
72 // Linux needs to check, whether it's been run as root 73 // Linux needs to check, whether it's been run as root
73 bool const sudo{ geteuid() == 0 }; 74 SudoFlag const sudo{ geteuid() == 0 };
74#else 75#else
75 bool const sudo{ false }; 76 SudoFlag const sudo{ false };
76#endif // PLATFORM_LINUX 77#endif // PLATFORM_LINUX
77 78
78 // for verbose errors 79 // for verbose errors
diff --git a/unit_tests/lane_tests.cpp b/unit_tests/lane_tests.cpp
index 1367ae5..3e5da2b 100644
--- a/unit_tests/lane_tests.cpp
+++ b/unit_tests/lane_tests.cpp
@@ -1,5 +1,7 @@
1#include "_pch.hpp" 1#include "_pch.hpp"
2
2#include "shared.h" 3#include "shared.h"
4#include "lanes/src/threading.hpp"
3 5
4// ################################################################################################# 6// #################################################################################################
5// ################################################################################################# 7// #################################################################################################
@@ -33,6 +35,76 @@ TEST_CASE("lanes.nameof")
33// ################################################################################################# 35// #################################################################################################
34// ################################################################################################# 36// #################################################################################################
35 37
38TEST_CASE("lanes.thread_priority_range")
39{
40 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
41 S.requireSuccess("lanes = require 'lanes'.configure()");
42
43 S.requireSuccess("a, b = lanes.thread_priority_range(); print(a, b)");
44 S.requireSuccess("assert(type(a) == 'number' and type(b) == 'number' and b > a)");
45 S.requireSuccess("c, d = lanes.thread_priority_range('native'); print(c, d)");
46 S.requireSuccess("assert(type(c) == 'number' and type(d) == 'number' and d > c)");
47
48 // can't really test the range of values from pthread as they are platform-dependent
49 if constexpr (THREADAPI == THREADAPI_WINDOWS) {
50 // windows constants THREAD_PRIORITY_IDLE and THREAD_PRIORITY_TIME_CRITICAL
51 S.requireSuccess("assert(c == -15 and d == 15)");
52 }
53}
54
55// #################################################################################################
56// #################################################################################################
57
58TEST_CASE("lanes.set_thread_priority")
59{
60 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
61 S.requireSuccess("lanes = require 'lanes'.configure()");
62
63 SECTION("mapped priorities")
64 {
65 std::string_view const _script{
66 " min_prio, max_prio = lanes.thread_priority_range()"
67 " for prio = min_prio, max_prio do"
68 " lanes.set_thread_priority(prio)"
69 " end"
70 };
71 S.requireSuccess(_script);
72
73 S.requireFailure("lanes.set_thread_priority(min_prio - 1)");
74 S.requireFailure("lanes.set_thread_priority(max_prio + 1)");
75 }
76
77 SECTION("native priorities")
78 {
79 S.requireSuccess("min_prio, max_prio = lanes.thread_priority_range('native')");
80 if constexpr (THREADAPI == THREADAPI_WINDOWS) {
81 // Win32 range is -15 to 15, but only some values are accepted
82 S.requireSuccess("lanes.set_thread_priority(-15, 'native')"); // THREAD_PRIORITY_IDLE
83 S.requireFailure("lanes.set_thread_priority(-3, 'native')");
84 S.requireSuccess("lanes.set_thread_priority(-2, 'native')"); // THREAD_PRIORITY_LOWEST
85 S.requireSuccess("lanes.set_thread_priority(-1, 'native')"); // THREAD_PRIORITY_BELOW_NORMAL
86 S.requireSuccess("lanes.set_thread_priority(0, 'native')"); // THREAD_PRIORITY_NORMAL
87 S.requireSuccess("lanes.set_thread_priority(1, 'native')"); // THREAD_PRIORITY_ABOVE_NORMAL
88 S.requireSuccess("lanes.set_thread_priority(2, 'native')"); // THREAD_PRIORITY_HIGHEST
89 S.requireFailure("lanes.set_thread_priority(3, 'native')");
90 S.requireSuccess("lanes.set_thread_priority(-15, 'native')"); // THREAD_PRIORITY_TIME_CRITICAL
91 } else {
92 // until proven otherwise, the full set of values is supported by pthread
93 std::string_view const _script{
94 " for prio = min_prio, max_prio do"
95 " lanes.set_thread_priority(prio, 'native')"
96 " end"
97 };
98 S.requireSuccess(_script);
99 }
100 S.requireFailure("lanes.set_thread_priority(min_prio - 1)");
101 S.requireFailure("lanes.set_thread_priority(max_prio + 1)");
102 }
103}
104
105// #################################################################################################
106// #################################################################################################
107
36TEST_CASE("lanes.sleep.argument validation") 108TEST_CASE("lanes.sleep.argument validation")
37{ 109{
38 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; 110 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
@@ -97,88 +169,109 @@ TEST_CASE("lanes.sleep.interactions with timers")
97// ################################################################################################# 169// #################################################################################################
98// ################################################################################################# 170// #################################################################################################
99 171
100TEST_CASE("lanes.gen") 172TEST_CASE("lanes.gen.argument_checks")
101{ 173{
102 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; 174 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
103 S.requireSuccess("lanes = require 'lanes'.configure()"); 175 S.requireSuccess("lanes = require 'lanes'.configure()");
104 176
105 // --------------------------------------------------------------------------------------------- 177 // ---------------------------------------------------------------------------------------------
106 178
107 SECTION("argument checks") 179 // no argument is bad
108 { 180 S.requireFailure("lanes.gen()");
109 // no parameter is bad 181
110 S.requireFailure("lanes.gen()"); 182 // minimal generator needs a function
111 183 S.requireSuccess("lanes.gen(function() end)");
112 // minimal generator needs a function 184
113 S.requireSuccess("lanes.gen(function() end)"); 185 // acceptable arguments for the generator are strings, tables, nil, followed by the function body
114 186 S.requireSuccess("lanes.gen(nil, function() end)");
115 // acceptable parameters for the generator are strings, tables, nil, followed by the function body 187 S.requireSuccess("lanes.gen('', function() end)");
116 S.requireSuccess("lanes.gen(nil, function() end)"); 188 S.requireSuccess("lanes.gen({}, function() end)");
117 S.requireSuccess("lanes.gen('', function() end)"); 189 S.requireSuccess("lanes.gen('', {}, function() end)");
118 S.requireSuccess("lanes.gen({}, function() end)"); 190 S.requireSuccess("lanes.gen({}, '', function() end)");
119 S.requireSuccess("lanes.gen('', {}, function() end)"); 191 S.requireSuccess("lanes.gen('', '', function() end)");
120 S.requireSuccess("lanes.gen({}, '', function() end)"); 192 S.requireSuccess("lanes.gen({}, {}, function() end)");
121 S.requireSuccess("lanes.gen('', '', function() end)"); 193
122 S.requireSuccess("lanes.gen({}, {}, function() end)"); 194 // anything different should fail: booleans, numbers, any userdata
123 195 S.requireFailure("lanes.gen(false, function() end)");
124 // anything different should fail: booleans, numbers, any userdata 196 S.requireFailure("lanes.gen(true, function() end)");
125 S.requireFailure("lanes.gen(false, function() end)"); 197 S.requireFailure("lanes.gen(42, function() end)");
126 S.requireFailure("lanes.gen(true, function() end)"); 198 S.requireFailure("lanes.gen(io.stdin, function() end)");
127 S.requireFailure("lanes.gen(42, function() end)"); 199 S.requireFailure("lanes.gen(lanes.linda(), function() end)");
128 S.requireFailure("lanes.gen(io.stdin, function() end)"); 200 S.requireFailure("lanes.gen(lanes.linda():deep(), function() end)");
129 S.requireFailure("lanes.gen(lanes.linda(), function() end)"); 201
130 S.requireFailure("lanes.gen(lanes.linda():deep(), function() end)"); 202 // even if argument types are correct, the function must come last
131 203 S.requireFailure("lanes.gen(function() end, '')");
132 // even if parameter types are correct, the function must come last 204
133 S.requireFailure("lanes.gen(function() end, '')"); 205 // the strings should only list "known base libraries", in any order, or "*"
134 206 // if the particular Lua flavor we build for doesn't support them, they raise an error unless postfixed by '?'
135 // the strings should only list "known base libraries", in any order, or "*" 207 S.requireSuccess("lanes.gen('base', function() end)");
136 // if the particular Lua flavor we build for doesn't support them, they raise an error unless postfixed by '?' 208
137 S.requireSuccess("lanes.gen('base', function() end)"); 209 // bit, ffi, jit are LuaJIT-specific
138
139 // bit, ffi, jit are LuaJIT-specific
140#if LUAJIT_FLAVOR() == 0 210#if LUAJIT_FLAVOR() == 0
141 S.requireFailure("lanes.gen('bit,ffi,jit', function() end)"); 211 S.requireFailure("lanes.gen('bit,ffi,jit', function() end)");
142 S.requireSuccess("lanes.gen('bit?,ffi?,jit?', function() end)"); 212 S.requireSuccess("lanes.gen('bit?,ffi?,jit?', function() end)");
143#endif // LUAJIT_FLAVOR() 213#endif // LUAJIT_FLAVOR()
144 214
145 // bit32 library existed only in Lua 5.2, there is still a loader that will raise an error in Lua 5.3 215 // bit32 library existed only in Lua 5.2, there is still a loader that will raise an error in Lua 5.3
146#if LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 216#if LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503
147 S.requireSuccess("lanes.gen('bit32', function() end)"); 217 S.requireSuccess("lanes.gen('bit32', function() end)");
148#else // LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 218#else // LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503
149 S.requireFailure("lanes.gen('bit32', function() end)"); 219 S.requireFailure("lanes.gen('bit32', function() end)");
150 S.requireSuccess("lanes.gen('bit32?', function() end)"); 220 S.requireSuccess("lanes.gen('bit32?', function() end)");
151#endif // LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 221#endif // LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503
152 222
153 // coroutine library appeared with Lua 5.2 223 // coroutine library appeared with Lua 5.2
154#if LUA_VERSION_NUM == 501 224#if LUA_VERSION_NUM == 501
155 S.requireFailure("lanes.gen('coroutine', function() end)"); 225 S.requireFailure("lanes.gen('coroutine', function() end)");
156 S.requireSuccess("lanes.gen('coroutine?', function() end)"); 226 S.requireSuccess("lanes.gen('coroutine?', function() end)");
157#endif // LUA_VERSION_NUM == 501 227#endif // LUA_VERSION_NUM == 501
158 228
159 S.requireSuccess("lanes.gen('debug', function() end)"); 229 S.requireSuccess("lanes.gen('debug', function() end)");
160 S.requireSuccess("lanes.gen('io', function() end)"); 230 S.requireSuccess("lanes.gen('io', function() end)");
161 S.requireSuccess("lanes.gen('math', function() end)"); 231 S.requireSuccess("lanes.gen('math', function() end)");
162 S.requireSuccess("lanes.gen('os', function() end)"); 232 S.requireSuccess("lanes.gen('os', function() end)");
163 S.requireSuccess("lanes.gen('package', function() end)"); 233 S.requireSuccess("lanes.gen('package', function() end)");
164 S.requireSuccess("lanes.gen('string', function() end)"); 234 S.requireSuccess("lanes.gen('string', function() end)");
165 S.requireSuccess("lanes.gen('table', function() end)"); 235 S.requireSuccess("lanes.gen('table', function() end)");
166 236
167 // utf8 library appeared with Lua 5.3 237 // utf8 library appeared with Lua 5.3
168#if LUA_VERSION_NUM < 503 238#if LUA_VERSION_NUM < 503
169 S.requireFailure("lanes.gen('utf8', function() end)"); 239 S.requireFailure("lanes.gen('utf8', function() end)");
170 S.requireSuccess("lanes.gen('utf8?', function() end)"); 240 S.requireSuccess("lanes.gen('utf8?', function() end)");
171#endif // LUA_VERSION_NUM < 503 241#endif // LUA_VERSION_NUM < 503
172 242
173 S.requireSuccess("lanes.gen('lanes_core', function() end)"); 243 S.requireSuccess("lanes.gen('lanes_core', function() end)");
174 // "*" repeated or combined with anything else is forbidden 244 // "*" repeated or combined with anything else is forbidden
175 S.requireFailure("lanes.gen('*', '*', function() end)"); 245 S.requireFailure("lanes.gen('*', '*', function() end)");
176 S.requireFailure("lanes.gen('base', '*', function() end)"); 246 S.requireFailure("lanes.gen('base', '*', function() end)");
177 // unknown names are forbidden 247 // unknown names are forbidden
178 S.requireFailure("lanes.gen('Base', function() end)"); 248 S.requireFailure("lanes.gen('Base', function() end)");
179 // repeating the same library more than once is forbidden 249 // repeating the same library more than once is forbidden
180 S.requireFailure("lanes.gen('base,base', function() end)"); 250 S.requireFailure("lanes.gen('base,base', function() end)");
181 } 251}
252
253// #################################################################################################
254// #################################################################################################
255
256TEST_CASE("lanes.gen.priority")
257{
258 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
259 S.requireSuccess("lanes = require 'lanes'.configure()");
260
261 S.requireSuccess("lanes.gen({priority=1}, function() end)");
262 // AFAICT, 1 is accepted by all pthread flavors and win32 API
263 S.requireSuccess("lanes.gen({native_priority=1}, function() end)");
264 // shouldn't be able to provide 2 priority settings
265 S.requireFailure("lanes.gen({priority=1, native_priority=1}, function() end)");
266}
267
268// #################################################################################################
269// #################################################################################################
270
271TEST_CASE("lanes.gen.thread_naming")
272{
273 LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
274 S.requireSuccess("lanes = require 'lanes'.configure()");
182 275
183 // --------------------------------------------------------------------------------------------- 276 // ---------------------------------------------------------------------------------------------
184 277