diff options
| author | Benoit Germain <benoit.germain@ubisoft.com> | 2026-03-05 15:58:11 +0100 |
|---|---|---|
| committer | Benoit Germain <benoit.germain@ubisoft.com> | 2026-03-05 15:58:11 +0100 |
| commit | a9f59c675749d870f6d902a11360ad8f3b6bbd58 (patch) | |
| tree | 0dc66bf435b587927ed021e3a1c8e09b5be426d3 /src | |
| parent | 10eb2454a21e9b1f1bd675eed799cb89a7518fa8 (diff) | |
| download | lanes-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.cpp | 26 | ||||
| -rw-r--r-- | src/lane.hpp | 1 | ||||
| -rw-r--r-- | src/lanes.cpp | 277 | ||||
| -rw-r--r-- | src/lanes.hpp | 2 | ||||
| -rw-r--r-- | src/lanes.lua | 10 |
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 | // ################################################################################################# |
| 1242 | void 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 | ||
| 1232 | void Lane::startThread(lua_State* const L_, int const priority_, NativePrioFlag native_) | 1258 | void 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 | // |
| 493 | LUAG_FUNC(lane_new) | 501 | LUAG_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 | ||
| 38 | local core = require "lanes_core" | 38 | local core = require "lanes_core" |
| 39 | local 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 |
| 450 | end -- make_generator | 453 | end -- 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) |
