aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2026-03-05 15:58:11 +0100
committerBenoit Germain <benoit.germain@ubisoft.com>2026-03-05 15:58:11 +0100
commita9f59c675749d870f6d902a11360ad8f3b6bbd58 (patch)
tree0dc66bf435b587927ed021e3a1c8e09b5be426d3 /src
parent10eb2454a21e9b1f1bd675eed799cb89a7518fa8 (diff)
downloadlanes-a9f59c675749d870f6d902a11360ad8f3b6bbd58.tar.gz
lanes-a9f59c675749d870f6d902a11360ad8f3b6bbd58.tar.bz2
lanes-a9f59c675749d870f6d902a11360ad8f3b6bbd58.zip
Internal improvements
(1) replace RAII/destructor-based error handling in lane_new with lua_pcall (2) validate native thread priorities (3) improve unit test coverage for the latter
Diffstat (limited to 'src')
-rw-r--r--src/lane.cpp26
-rw-r--r--src/lane.hpp1
-rw-r--r--src/lanes.cpp277
-rw-r--r--src/lanes.hpp2
-rw-r--r--src/lanes.lua10
5 files changed, 166 insertions, 150 deletions
diff --git a/src/lane.cpp b/src/lane.cpp
index b23ff78..952b337 100644
--- a/src/lane.cpp
+++ b/src/lane.cpp
@@ -775,6 +775,10 @@ static void lane_main(Lane* const lane_)
775 delete lane_; 775 delete lane_;
776 return; 776 return;
777 } 777 }
778 } else {
779 // some error occurred in lane_new and we don't have anything to do
780 lua_settop(_L, 0); // should already be the case, but I'm paranoid
781 kCancelError.pushKey(_L); // this is our return value, as if we were cancelled right from the start
778 } 782 }
779 783
780 // leave results (1..top) or error message + stack trace (1..2) on the stack - master will copy them 784 // leave results (1..top) or error message + stack trace (1..2) on the stack - master will copy them
@@ -872,6 +876,13 @@ Lane::Lane(Universe* const U_, lua_State* const L_, ErrorTraceLevel const errorT
872, errorTraceLevel{ errorTraceLevel_ } 876, errorTraceLevel{ errorTraceLevel_ }
873{ 877{
874 STACK_CHECK_START_REL(S, 0); 878 STACK_CHECK_START_REL(S, 0);
879
880 // Store a pointer to ourselves in the lane's registry, for 'cancel_test()' (we do cancel tests at pending send/receive).
881 kLanePointerRegKey.setValue(S,
882 [lane = this](lua_State* L_) { lua_pushlightuserdata(L_, lane); } // S:
883 );
884 STACK_CHECK(S, 0);
885
875 assert(errorTraceLevel == ErrorTraceLevel::Minimal || errorTraceLevel == ErrorTraceLevel::Basic || errorTraceLevel == ErrorTraceLevel::Extended); 886 assert(errorTraceLevel == ErrorTraceLevel::Minimal || errorTraceLevel == ErrorTraceLevel::Basic || errorTraceLevel == ErrorTraceLevel::Extended);
876 kExtendedStackTraceRegKey.setValue(S, [yes = errorTraceLevel == ErrorTraceLevel::Extended ? 1 : 0](lua_State* L_) { lua_pushboolean(L_, yes); }); 887 kExtendedStackTraceRegKey.setValue(S, [yes = errorTraceLevel == ErrorTraceLevel::Extended ? 1 : 0](lua_State* L_) { lua_pushboolean(L_, yes); });
877 U->tracker.tracking_add(this); 888 U->tracker.tracking_add(this);
@@ -1228,6 +1239,21 @@ void Lane::securizeDebugName(lua_State* const L_)
1228} 1239}
1229 1240
1230// ################################################################################################# 1241// #################################################################################################
1242void Lane::signalReady(bool const canRun_)
1243{
1244 if (!canRun_) {
1245 std::lock_guard _guard{ doneMutex };
1246 // this will cause lane_main to skip actual running (because we are not Pending anymore)
1247 status.store(Lane::Running, std::memory_order_release);
1248 }
1249#ifndef __PROSPERO__
1250 ready.count_down();
1251#else // __PROSPERO__
1252 ready.test_and_set();
1253#endif // __PROSPERO__
1254}
1255
1256// #################################################################################################
1231 1257
1232void Lane::startThread(lua_State* const L_, int const priority_, NativePrioFlag native_) 1258void Lane::startThread(lua_State* const L_, int const priority_, NativePrioFlag native_)
1233{ 1259{
diff --git a/src/lane.hpp b/src/lane.hpp
index bd328b1..e38a04e 100644
--- a/src/lane.hpp
+++ b/src/lane.hpp
@@ -208,6 +208,7 @@ class Lane final
208 [[nodiscard]] 208 [[nodiscard]]
209 bool selfdestructRemove(); 209 bool selfdestructRemove();
210 void securizeDebugName(lua_State* L_); 210 void securizeDebugName(lua_State* L_);
211 void signalReady(bool const canRun_);
211 void startThread(lua_State* L_, int priority_, NativePrioFlag native_); 212 void startThread(lua_State* L_, int priority_, NativePrioFlag native_);
212 void storeDebugName( std::string_view const& name_); 213 void storeDebugName( std::string_view const& name_);
213 [[nodiscard]] 214 [[nodiscard]]
diff --git a/src/lanes.cpp b/src/lanes.cpp
index 74a9d95..08402d6 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -266,7 +266,7 @@ namespace {
266 }; 266 };
267 267
268 // Read the priority-is-native flag and optional priority integer from the lane_new() argument stack. 268 // Read the priority-is-native flag and optional priority integer from the lane_new() argument stack.
269 // Validates the mapped priority range if native mode is not requested. 269 // Validates the priority against the acceptable range
270 [[nodiscard]] 270 [[nodiscard]]
271 static LanePriority ResolveLanePriority(lua_State* const L_, StackIndex const prinIdx_, StackIndex const prioIdx_) 271 static LanePriority ResolveLanePriority(lua_State* const L_, StackIndex const prinIdx_, StackIndex const prioIdx_)
272 { 272 {
@@ -275,9 +275,16 @@ namespace {
275 return { kThreadPrioDefault, _native }; 275 return { kThreadPrioDefault, _native };
276 } 276 }
277 int const _priority{ static_cast<int>(lua_tointeger(L_, prioIdx_)) }; 277 int const _priority{ static_cast<int>(lua_tointeger(L_, prioIdx_)) };
278 if (!_native && (_priority < kThreadPrioMin || _priority > kThreadPrioMax)) { 278
279 raise_luaL_error(L_, "Priority out of range: %d..+%d (%d)", kThreadPrioMin, kThreadPrioMax, _priority); 279 auto _checkPriorityRange = [L_, _priority](std::pair<int, int> prios_) {
280 } 280 auto const [_prio_min, _prio_max] = prios_;
281 if (_priority < _prio_min || _priority > _prio_max) {
282 raise_luaL_error(L_, "Priority out of range: %d..+%d (%d)", _prio_min, _prio_max, _priority);
283 }
284 };
285
286 _checkPriorityRange(_native ? THREAD_NATIVE_PRIOS() : std::pair{ kThreadPrioMin, kThreadPrioMax });
287
281 return { _priority, _native }; 288 return { _priority, _native };
282 } 289 }
283 290
@@ -420,6 +427,7 @@ namespace {
420 static void PrepareLaneUserData(lua_State* const L_, Lane* const lane_, StackIndex const gcCbIdx_, StackIndex const nameIdx_, StackIndex const funcIdx_) 427 static void PrepareLaneUserData(lua_State* const L_, Lane* const lane_, StackIndex const gcCbIdx_, StackIndex const nameIdx_, StackIndex const funcIdx_)
421 { 428 {
422 DEBUGSPEW_CODE(DebugSpew(lane_->U) << "lane_new: preparing lane userdata" << std::endl); 429 DEBUGSPEW_CODE(DebugSpew(lane_->U) << "lane_new: preparing lane userdata" << std::endl);
430 STACK_GROW(L_, 4);
423 STACK_CHECK_START_REL(L_, 0); 431 STACK_CHECK_START_REL(L_, 0);
424 // a Lane full userdata needs a single uservalue 432 // a Lane full userdata needs a single uservalue
425 Lane** const _ud{ luaW_newuserdatauv<Lane*>(L_, UserValueCount{ 1 }) }; // L_: ... lane 433 Lane** const _ud{ luaW_newuserdatauv<Lane*>(L_, UserValueCount{ 1 }) }; // L_: ... lane
@@ -492,155 +500,132 @@ namespace {
492// 500//
493LUAG_FUNC(lane_new) 501LUAG_FUNC(lane_new)
494{ 502{
495 static constexpr StackIndex kFuncIdx{ 1 }; 503 // this is to communicate the lane pointer back to us from inside the protected call
496 static constexpr StackIndex kLibsIdx{ 2 }; 504 Lane* _lane{};
497 static constexpr StackIndex kPrinIdx{ 3 };
498 static constexpr StackIndex kPrioIdx{ 4 };
499 static constexpr StackIndex kGlobIdx{ 5 };
500 static constexpr StackIndex kPackIdx{ 6 };
501 static constexpr StackIndex kRequIdx{ 7 };
502 static constexpr StackIndex kGcCbIdx{ 8 };
503 static constexpr StackIndex kNameIdx{ 9 };
504 static constexpr StackIndex kErTlIdx{ 10 };
505 static constexpr StackIndex kAsCoro{ 11 };
506 static constexpr StackIndex kFixedArgsIdx{ 11 };
507
508 int const _nargs{ lua_gettop(L_) - kFixedArgsIdx };
509 LUA_ASSERT(L_, _nargs >= 0);
510 505
511 Universe* const _U{ Universe::Get(L_) }; 506 auto _protectedLaneNew = [](lua_State* const L_) // stateless lambda convertible to a lua_CFunction
512 DEBUGSPEW_CODE(DebugSpew(_U) << "lane_new: setup" << std::endl);
513
514 std::optional<std::string_view> _libs_str{ lua_isnil(L_, kLibsIdx) ? std::nullopt : std::make_optional(luaW_tostring(L_, kLibsIdx)) };
515 lua_State* const _S{ state::NewLaneState(_U, SourceState{ L_ }, _libs_str) }; // L_: [fixed] ... L2:
516 STACK_CHECK_START_REL(_S, 0);
517
518 // 'lane' is allocated from heap, not Lua, since its life span may surpass the handle's (if free running thread)
519 Lane::ErrorTraceLevel const _errorTraceLevel{ static_cast<Lane::ErrorTraceLevel>(lua_tointeger(L_, kErTlIdx)) };
520 bool const _asCoroutine{ lua_toboolean(L_, kAsCoro) ? true : false };
521 Lane* const _lane{ new (_U) Lane{ _U, _S, _errorTraceLevel, _asCoroutine } };
522 STACK_CHECK(_S, _asCoroutine ? 1 : 0); // the Lane's thread is on the Lane's state stack
523 lua_State* const _L2{ _lane->L };
524 STACK_CHECK_START_REL(_L2, 0);
525 if (_lane == nullptr) {
526 raise_luaL_error(L_, "could not create lane: out of memory");
527 }
528
529 class OnExit final
530 { 507 {
531 private: 508 static constexpr StackIndex kFuncIdx{ 1 };
532 lua_State* const L; 509 static constexpr StackIndex kLibsIdx{ 2 };
533 Lane* lane{ nullptr }; 510 static constexpr StackIndex kPrinIdx{ 3 };
534 DEBUGSPEW_CODE(DebugSpewIndentScope scope); 511 static constexpr StackIndex kLaneIdx{ 3 }; // <-- this is where the Lane userdata is stored (after prio is read from the stack)
535 512 static constexpr StackIndex kPrioIdx{ 4 };
536 public: 513 static constexpr StackIndex kGlobIdx{ 5 };
537 OnExit(lua_State* L_, Lane* lane_) 514 static constexpr StackIndex kPackIdx{ 6 };
538 : L{ L_ } 515 static constexpr StackIndex kRequIdx{ 7 };
539 , lane{ lane_ } 516 static constexpr StackIndex kGcCbIdx{ 8 };
540 DEBUGSPEW_COMMA_PARAM(scope{ lane_->U }) 517 static constexpr StackIndex kNameIdx{ 9 };
541 { 518 static constexpr StackIndex kErTlIdx{ 10 };
519 static constexpr StackIndex kCoroIdx{ 11 };
520 static constexpr StackIndex kFixedArgsIdx{ 11 };
521
522 int const _nargs{ lua_gettop(L_) - kFixedArgsIdx };
523 LUA_ASSERT(L_, _nargs >= 0);
524
525 Universe* const _U{ Universe::Get(L_) };
526 DEBUGSPEW_CODE(DebugSpew(_U) << "lane_new: setup" << std::endl);
527
528 // Get priority early, because it can fail by raising an error
529 auto const [_priority, _native]{ local::ResolveLanePriority(L_, kPrinIdx, kPrioIdx) };
530
531 std::optional<std::string_view> _libs_str{ lua_isnil(L_, kLibsIdx) ? std::nullopt : std::make_optional(luaW_tostring(L_, kLibsIdx)) };
532 lua_State* const _S{ state::NewLaneState(_U, SourceState{ L_ }, _libs_str) }; // L_: [fixed] ... L2:
533 STACK_CHECK_START_REL(_S, 0);
534
535 Lane::ErrorTraceLevel const _errorTraceLevel{ static_cast<Lane::ErrorTraceLevel>(lua_tointeger(L_, kErTlIdx)) };
536 bool const _asCoroutine{ lua_toboolean(L_, kCoroIdx) ? true : false };
537
538 // 'lane' is allocated from heap, not Lua, since its life span may surpass the handle's (if free running thread)
539 Lane* const _lane{ new (_U) Lane{ _U, _S, _errorTraceLevel, _asCoroutine } };
540 if (_lane == nullptr) {
541 raise_luaL_error(L_, "could not create lane: out of memory");
542 } 542 }
543 543 // write back to the outer _lane variable so it can call signalReady()
544 ~OnExit() 544 *static_cast<Lane**>(lua_touserdata(L_, lua_upvalueindex(2))) = _lane;
545 { 545
546 if (lane) { 546 STACK_CHECK(_S, _asCoroutine ? 1 : 0); // the Lane's thread is on the Lane's state stack
547 STACK_CHECK_START_REL(L, 0); 547 lua_State* const _L2{ _lane->L };
548 // we still need a full userdata so that garbage collection can do its thing 548 STACK_CHECK_START_REL(_L2, 0);
549 local::PrepareLaneUserData(L, lane, kGcCbIdx, kNameIdx, kFuncIdx); 549
550 // remove it immediately from the stack so that the error that landed us here is at the top 550 DEBUGSPEW_CODE(DebugSpew(_U) << "lane_new: launching thread" << std::endl);
551 lua_pop(L, 1); 551
552 STACK_CHECK(L, 0); 552 // launch the thread early, it will sync with a std::latch to parallelize OS thread warmup and L2 preparation
553 // leave a single cancel_error on the stack for the caller 553 _lane->startThread(L_, _priority, _native);
554 lua_settop(lane->L, 0); 554
555 kCancelError.pushKey(lane->L); 555 STACK_CHECK_START_REL(L_, 0);
556 { 556
557 std::lock_guard _guard{ lane->doneMutex }; 557 // create the wrapping full userdata, so that it can be cleaned up properly in case of error
558 // this will cause lane_main to skip actual running (because we are not Pending anymore) 558 local::PrepareLaneUserData(L_, _lane, kGcCbIdx, kNameIdx, kFuncIdx); // L_: [fixed] args... LaneUD
559 lane->status.store(Lane::Running, std::memory_order_release); 559 STACK_CHECK(L_, 1);
560 } 560 // store the userdata in a reusable stack slot until we know everything went well
561 // unblock the thread so that it can terminate gracefully 561 lua_replace(L_, kLaneIdx); // L_: [fixed] args...
562#ifndef __PROSPERO__ 562
563 lane->ready.count_down(); 563 STACK_GROW(_L2, _nargs + 3);
564#else // __PROSPERO__ 564 STACK_GROW(L_, 3);
565 lane->ready.test_and_set(); 565
566#endif // __PROSPERO__ 566 // package
567 } 567 local::TransferPackage(_U, L_, _L2, kPackIdx);
568 STACK_CHECK(L_, 0);
569 STACK_CHECK(_L2, 0);
570
571 // modules to require in the target lane *before* the function is transfered!
572 local::RequireModulesInLane(_U, L_, _L2, kRequIdx);
573 STACK_CHECK(L_, 0);
574 STACK_CHECK(_L2, 0); // L_: [fixed] args... L2:
575
576 // Appending the specified globals to the global environment
577 // *after* stdlibs have been loaded and modules required, in case we transfer references to native functions they exposed...
578 local::TransferGlobals(_U, L_, _L2, kGlobIdx);
579 STACK_CHECK(L_, 0);
580 STACK_CHECK(_L2, 0);
581
582 // Lane main function, optionally preceded by an error handler (depending on error_trace_level setting)
583 int const _errorHandlerCount{ local::TransferLaneBody(_U, L_, _L2, kFuncIdx, _lane) }; // L_: [fixed] args... L2: eh? func
584 STACK_CHECK(L_, 0);
585 STACK_CHECK(_L2, _errorHandlerCount + 1);
586 LUA_ASSERT(L_, lua_isfunction(_L2, _errorHandlerCount + 1));
587
588 // *Move* arguments in the lane state
589 local::TransferArguments(_U, L_, _L2, _nargs); // L_: [fixed] L2: eh? func args...
590 STACK_CHECK(L_, -_nargs);
591 LUA_ASSERT(L_, lua_gettop(L_) == kFixedArgsIdx);
592 STACK_CHECK(_L2, _errorHandlerCount + 1 + _nargs);
593
594 // if in coroutine mode, the Lane's master state stack should contain the thread
595 if (_asCoroutine) {
596 LUA_ASSERT(L_, _S != _L2);
597 STACK_CHECK(_S, 1);
568 } 598 }
599 // and the thread's stack has whatever is needed to run
600 STACK_CHECK(_L2, _errorHandlerCount + 1 + _nargs);
569 601
570 public: 602 // all went well, return the LaneUD by cutting the stack at the position we stored it earlier
571 void success() 603 lua_settop(L_, kLaneIdx); // L_: ... lane L2: <living its own life>
572 { 604 return 1;
573 local::PrepareLaneUserData(L, lane, kGcCbIdx, kNameIdx, kFuncIdx); 605 };
574 // unblock the thread so that it can terminate gracefully
575#ifndef __PROSPERO__
576 lane->ready.count_down();
577#else // __PROSPERO__
578 lane->ready.test_and_set();
579#endif // __PROSPERO__
580 lane = nullptr;
581 }
582 } _onExit{ L_, _lane};
583 // launch the thread early, it will sync with a std::latch to parallelize OS thread warmup and L2 preparation
584 DEBUGSPEW_CODE(DebugSpew(_U) << "lane_new: launching thread" << std::endl);
585 // public Lanes API accepts a generic range -3/+3
586 // that will be remapped into the platform-specific scheduler priority scheme
587 // On some platforms, -3 is equivalent to -2 and +3 to +2
588 auto const [_priority, _native]{ local::ResolveLanePriority(L_, kPrinIdx, kPrioIdx) };
589
590 _lane->startThread(L_, _priority, _native);
591
592 STACK_GROW(_L2, _nargs + 3);
593 STACK_GROW(L_, 3);
594 STACK_CHECK_START_REL(L_, 0);
595
596 // package
597 local::TransferPackage(_U, L_, _L2, kPackIdx);
598 STACK_CHECK(L_, 0);
599 STACK_CHECK(_L2, 0);
600
601 // modules to require in the target lane *before* the function is transfered!
602 local::RequireModulesInLane(_U, L_, _L2, kRequIdx);
603 STACK_CHECK(L_, 0);
604 STACK_CHECK(_L2, 0); // L_: [fixed] args... L2:
605
606 // Appending the specified globals to the global environment
607 // *after* stdlibs have been loaded and modules required, in case we transfer references to native functions they exposed...
608 local::TransferGlobals(_U, L_, _L2, kGlobIdx);
609 STACK_CHECK(L_, 0);
610 STACK_CHECK(_L2, 0);
611 606
612 // Lane main function 607 // the protected closure has a 2 uservalues:
613 int const _errorHandlerCount{ local::TransferLaneBody(_U, L_, _L2, kFuncIdx, _lane) }; 608 // - uv#1 is our own uservalue (the Lane userdata's metatable)
614 STACK_CHECK(L_, 0); 609 // - uv#2: a pointer to our local variable _lane, to give us back the Lane pointer
615 STACK_CHECK(_L2, _errorHandlerCount + 1); 610 lua_pushvalue(L_, lua_upvalueindex(1)); // L_: nil [fixed] args... mt
616 LUA_ASSERT(L_, lua_isfunction(_L2, _errorHandlerCount + 1)); 611 lua_pushlightuserdata(L_, &_lane); // L_: nil [fixed] args... mt &_lane
617 612 lua_pushcclosure(L_, _protectedLaneNew, 2); // L_: nil [fixed] args... wrapped
618 // revive arguments 613 lua_replace(L_, 1); // L_: wrapped [fixed] args...
619 local::TransferArguments(_U, L_, _L2, _nargs); 614 LuaError _rc{ lua_pcall(L_, lua_gettop(L_) - 1, 1, 0) }; // L_: lane|error
620 STACK_CHECK(L_, -_nargs); 615 // _lane can be nullptr if we failed to allocate it!
621 LUA_ASSERT(L_, lua_gettop(L_) == kFixedArgsIdx); 616 if (_rc == LuaError::OK) {
622 STACK_CHECK(_L2, _errorHandlerCount + 1 + _nargs); 617 // unblock the thread so that it can start doing its work
623 618 if (_lane) {
624 // Store 'lane' in the lane's registry, for 'cancel_test()' (we do cancel tests at pending send/receive). 619 _lane->signalReady(true);
625 kLanePointerRegKey.setValue( 620 }
626 _L2, [lane = _lane](lua_State* L_) { lua_pushlightuserdata(L_, lane); } // L_: [fixed] L2: eh? func args... 621 return 1;
627 ); 622 } else {
628 STACK_CHECK(_L2, _errorHandlerCount + 1 + _nargs); 623 // unblock the thread so that it can terminate gracefully (but will abort early)
629 624 if (_lane) {
630 // if in coroutine mode, the Lane's master state stack should contain the thread 625 _lane->signalReady(false);
631 if (_asCoroutine) { 626 }
632 LUA_ASSERT(L_, _S != _L2); 627 raise_lua_error(L_);
633 STACK_CHECK(_S, 1);
634 } 628 }
635 // and the thread's stack has whatever is needed to run
636 STACK_CHECK(_L2, _errorHandlerCount + 1 + _nargs);
637
638 STACK_CHECK_RESET_REL(L_, 0);
639 // all went well, the lane's thread can start working
640 _onExit.success(); // L_: [fixed] lane L2: <living its own life>
641 // we should have the lane userdata on top of the stack
642 STACK_CHECK(L_, 1);
643 return 1;
644} 629}
645 630
646// ################################################################################################# 631// #################################################################################################
diff --git a/src/lanes.hpp b/src/lanes.hpp
index 550e6e6..a0f2b30 100644
--- a/src/lanes.hpp
+++ b/src/lanes.hpp
@@ -4,7 +4,7 @@
4 4
5#define LANES_VERSION_MAJOR 4 5#define LANES_VERSION_MAJOR 4
6#define LANES_VERSION_MINOR 0 6#define LANES_VERSION_MINOR 0
7#define LANES_VERSION_PATCH 0 7#define LANES_VERSION_PATCH 1
8 8
9#define LANES_MIN_VERSION_REQUIRED(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR > MAJOR) || (LANES_VERSION_MAJOR == MAJOR && (LANES_VERSION_MINOR > MINOR || (LANES_VERSION_MINOR == MINOR && LANES_VERSION_PATCH >= PATCH)))) 9#define LANES_MIN_VERSION_REQUIRED(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR > MAJOR) || (LANES_VERSION_MAJOR == MAJOR && (LANES_VERSION_MINOR > MINOR || (LANES_VERSION_MINOR == MINOR && LANES_VERSION_PATCH >= PATCH))))
10#define LANES_VERSION_LESS_THAN(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR < MAJOR) || (LANES_VERSION_MAJOR == MAJOR && (LANES_VERSION_MINOR < MINOR || (LANES_VERSION_MINOR == MINOR && LANES_VERSION_PATCH < PATCH)))) 10#define LANES_VERSION_LESS_THAN(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR < MAJOR) || (LANES_VERSION_MAJOR == MAJOR && (LANES_VERSION_MINOR < MINOR || (LANES_VERSION_MINOR == MINOR && LANES_VERSION_PATCH < PATCH))))
diff --git a/src/lanes.lua b/src/lanes.lua
index 98b1bf6..35f4cca 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -36,6 +36,8 @@ THE SOFTWARE.
36]]-- 36]]--
37 37
38local core = require "lanes_core" 38local core = require "lanes_core"
39local min_prio, max_prio -- initialized in configure()
40
39-- Lua 5.1: module() creates a global variable 41-- Lua 5.1: module() creates a global variable
40-- Lua 5.2: module() is gone 42-- Lua 5.2: module() is gone
41-- almost everything module() does is done by require() anyway 43-- almost everything module() does is done by require() anyway
@@ -445,7 +447,8 @@ local make_generator = function(is_coro_, ...)
445 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] 447 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]
446 return function(...) 448 return function(...)
447 -- must pass functions args last else they will be truncated to the first one 449 -- must pass functions args last else they will be truncated to the first one
448 return core_lane_new(func, libs, prio_is_native, priority, globals, package, required, gc_cb, name, error_trace_level, is_coro_, ...) 450 -- also, core_lane_new will pcall-invoke a wrapper internally. save time by reserving the stack slot here and now (the first nil)
451 return core_lane_new(nil, func, libs, prio_is_native, priority, globals, package, required, gc_cb, name, error_trace_level, is_coro_, ...)
449 end 452 end
450end -- make_generator 453end -- make_generator
451 454
@@ -689,8 +692,8 @@ local configure_timers = function()
689 end 692 end
690 end 693 end
691 end -- timer_body() 694 end -- timer_body()
692 local min_prio, max_prio = core.thread_priority_range() 695 -- max_prio was obtained during configure()
693 timer_lane = gen("lanes_core,table", { name = "LanesTimer", package = {}, priority = max_prio }, timer_body)() 696 timer_lane = gen("lanes_core,table", { name = "LanesTimer", package = {}, priority = assert(max_prio) }, timer_body)()
694 end -- first_time 697 end -- first_time
695 698
696 ----- 699 -----
@@ -887,6 +890,7 @@ local configure = function(settings_)
887 cancel_error = assert(core.cancel_error) 890 cancel_error = assert(core.cancel_error)
888 supported_libs = assert(core.supported_libs()) 891 supported_libs = assert(core.supported_libs())
889 timerLinda = assert(core.timerLinda) 892 timerLinda = assert(core.timerLinda)
893 min_prio, max_prio = core.thread_priority_range("mapped")
890 894
891 if settings.with_timers then 895 if settings.with_timers then
892 configure_timers(settings) 896 configure_timers(settings)