diff options
author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-30 17:57:21 +0200 |
---|---|---|
committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-30 17:57:21 +0200 |
commit | 731556711e453a501f1d1d06a6013b8fbd53414e (patch) | |
tree | 4c5c28cd83de320fcf4c9b4c749f2e6e8d5bef48 | |
parent | a156aaeb07fada043b308409dcffcae1726eec0b (diff) | |
download | lanes-731556711e453a501f1d1d06a6013b8fbd53414e.tar.gz lanes-731556711e453a501f1d1d06a6013b8fbd53414e.tar.bz2 lanes-731556711e453a501f1d1d06a6013b8fbd53414e.zip |
Keeper management modernisation and improvements
* use a std::variant to manage the distinction between one or more keeper states. Use std::unique_ptr<Keeper[]> to manage the multiple keeper case.
* setting "nb_keepers" renamed "nb_user_keepers", to indicate these are in addition to internal keeper #0 used for timers.
* stricter lanes.linda() argument checking. group is imposed if more than one keeper is used.
* more tests
-rw-r--r-- | docs/index.html | 29 | ||||
-rw-r--r-- | src/keeper.cpp | 236 | ||||
-rw-r--r-- | src/keeper.h | 53 | ||||
-rw-r--r-- | src/lane.cpp | 2 | ||||
-rw-r--r-- | src/lanes.cpp | 43 | ||||
-rw-r--r-- | src/lanes.lua | 10 | ||||
-rw-r--r-- | src/lanesconf.h | 2 | ||||
-rw-r--r-- | src/linda.cpp | 25 | ||||
-rw-r--r-- | src/linda.h | 3 | ||||
-rw-r--r-- | src/universe.cpp | 210 | ||||
-rw-r--r-- | src/universe.h | 7 | ||||
-rw-r--r-- | tests/fibonacci.lua | 32 | ||||
-rw-r--r-- | tests/irayo_recursive.lua | 2 | ||||
-rw-r--r-- | tests/keeper.lua | 14 | ||||
-rw-r--r-- | tests/nameof.lua | 2 | ||||
-rw-r--r-- | tests/nb_keepers.lua | 20 | ||||
-rw-r--r-- | tests/parallel_os_calls.lua | 2 |
17 files changed, 423 insertions, 269 deletions
diff --git a/docs/index.html b/docs/index.html index 19cc442..0d97c29 100644 --- a/docs/index.html +++ b/docs/index.html | |||
@@ -280,12 +280,14 @@ | |||
280 | <th style="width:70%">definition</th> | 280 | <th style="width:70%">definition</th> |
281 | </tr> | 281 | </tr> |
282 | <tr valign=top> | 282 | <tr valign=top> |
283 | <td id="nb_keepers"> | 283 | <td id="nb_user_keepers"> |
284 | <code>.nb_keepers</code> | 284 | <code>.nb_user_keepers</code> |
285 | </td> | 285 | </td> |
286 | <td>integer >= 1</td> | 286 | <td>integer in [0,100]</td> |
287 | <td> | 287 | <td> |
288 | Controls the number of keeper states used internally by lindas to transfer data between lanes. (see <a href="#lindas">below</a>). Default is <tt>1</tt>. | 288 | Controls the number of "user" keeper states used internally by lindas to transfer data between lanes. (see <a href="#lindas">below</a>). Default is <tt>0</tt>.<br/> |
289 | Lanes always creates at least one keeper state for the internal timer linda. If <tt>nb_user_keepers</tt> is <tt>0</tt>, the other lindas you create will share this keeper by necessity.<br/> | ||
290 | If there is more than one keeper (in total), linda creation must specify the group it belongs to. | ||
289 | </td> | 291 | </td> |
290 | </tr> | 292 | </tr> |
291 | 293 | ||
@@ -712,8 +714,8 @@ | |||
712 | </td> | 714 | </td> |
713 | <td>table</td> | 715 | <td>table</td> |
714 | <td> | 716 | <td> |
715 | Lists modules that have to be required in order to be able to transfer functions they exposed. Starting with Lanes 3.0-beta, non-Lua functions are no longer copied by recreating a C closure from a C pointer, but are <a href="#function_notes">searched in lookup tables</a>. | 717 | Lists modules that have to be required in order to be able to transfer functions they exposed. Non-Lua functions are <a href="#function_notes">searched in lookup tables</a>. |
716 | These tables are built from the modules listed here. <tt>required</tt> must be a list of strings, each one being the name of a module to be required. Each module is required with <tt>require()</tt> before the lanes function is invoked. | 718 | These tables are built from the modules listed here. <tt>required</tt> must be an array of strings, each one being the name of a module to be required. Each module is required with <tt>require()</tt> before the lanes function is invoked. |
717 | So, from the required module's point of view, requiring it manually from inside the lane body or having it required this way doesn't change anything. From the lane body's point of view, the only difference is that a module not creating a global won't be accessible. | 719 | So, from the required module's point of view, requiring it manually from inside the lane body or having it required this way doesn't change anything. From the lane body's point of view, the only difference is that a module not creating a global won't be accessible. |
718 | Therefore, a lane body will also have to require a module manually, but this won't do anything more (see Lua's <tt>require</tt> documentation). <br/> | 720 | Therefore, a lane body will also have to require a module manually, but this won't do anything more (see Lua's <tt>require</tt> documentation). <br/> |
719 | ATTEMPTING TO TRANSFER A FUNCTION REGISTERED BY A MODULE NOT LISTED HERE WILL RAISE AN ERROR. | 721 | ATTEMPTING TO TRANSFER A FUNCTION REGISTERED BY A MODULE NOT LISTED HERE WILL RAISE AN ERROR. |
@@ -773,10 +775,10 @@ | |||
773 | </table> | 775 | </table> |
774 | 776 | ||
775 | <p> | 777 | <p> |
776 | Each lane gets a global function <tt>set_debug_threadname()</tt> that it can use anytime to do as the name says. Supported debuggers are Microsoft Visual Studio (for the C side) and <a href="https://github.com/unknownworlds/decoda">Decoda</a> (for the Lua side).<br/> | 778 | Each lane gets a global function <tt>set_debug_threadname()</tt> that it can use anytime to do as the name says. Supported debuggers are Microsoft Visual Studio (for the C side) and <a href="https://github.com/unknownworlds/decoda">Decoda</a> (for the Lua side).<br /> |
777 | Decoda support is limited to setting a special global variable <tt>decoda_name</tt>. This is disabled by default. Change <tt>HAVE_DECODA_NAME()</tt> in <tt>lanesconf.h</tt> if necessary.<br/> | 779 | Change <tt>HAVE_DECODA_SUPPORT()</tt> in <tt>lanesconf.h</tt> to enable the Decoda support, that sets a special global variable <tt>decoda_name</tt> in the lane's state.<br/> |
778 | The name is stored inside the Lua state registry so that it is available for error reporting. Changing <tt>decoda_name</tt> doesn't affect this hidden name or the OS thread name reported by MSVC.<br/> | 780 | The name is stored inside the Lua state registry so that it is available for error reporting. Changing <tt>decoda_name</tt> doesn't affect this hidden name or the OS thread name reported by MSVC.<br/> |
779 | When Lanes is initialized by the first <a href="#initialization"><tt>lanes.configure()</tt></a> call, <tt>"main"</tt> is stored in the registry in the same fashion (but <tt>decoda_name</tt> and the OS thread name are left unchanged).<br/> | 781 | When Lanes is initialized by the first <a href="#initialization"><tt>lanes.configure()</tt></a> call, <tt>"main"</tt> is stored in the registry in the same fashion (but <tt>decoda_name</tt> and the OS thread name are left unchanged).<br /> |
780 | The lane also has a method <tt>lane:get_debug_threadname()</tt> that gives access to that name from the caller side (returns <tt>"<unnamed>"</tt> if unset, <tt>"<closed>"</tt> if the internal Lua state is closed). | 782 | The lane also has a method <tt>lane:get_debug_threadname()</tt> that gives access to that name from the caller side (returns <tt>"<unnamed>"</tt> if unset, <tt>"<closed>"</tt> if the internal Lua state is closed). |
781 | </p> | 783 | </p> |
782 | 784 | ||
@@ -1186,7 +1188,8 @@ | |||
1186 | <li><tt>receive</tt> has a batched mode to consume more than one value from a single key, as in <tt>linda:receive(1.0, linda.batched, "key", 3, 6).</tt></li> | 1188 | <li><tt>receive</tt> has a batched mode to consume more than one value from a single key, as in <tt>linda:receive(1.0, linda.batched, "key", 3, 6).</tt></li> |
1187 | <li>individual keys' queue length can be limited, balancing speed differences in a producer/consumer scenario (making <tt>:send</tt> wait).</li> | 1189 | <li>individual keys' queue length can be limited, balancing speed differences in a producer/consumer scenario (making <tt>:send</tt> wait).</li> |
1188 | <li><tt>tostring(linda)</tt> returns a string of the form <tt>"Linda: <opt_name>"</tt></li> | 1190 | <li><tt>tostring(linda)</tt> returns a string of the form <tt>"Linda: <opt_name>"</tt></li> |
1189 | <li>several lindas may share the same keeper state. State assignation can be controlled with the linda's group (an integer). All lindas belonging to the same group will share the same keeper state. One keeper state may be shared by several groups.</li> | 1191 | <li>Several lindas may share the same keeper state. In case there is more than one user keeper state, assignation must be controlled with the linda's group (an integer in <tt>[0,nb_user_keepers]</tt>). |
1192 | Lanes has an internal linda used for timers and <tt>lanes.wait</tt>; this linda uses group 0.</li> | ||
1190 | </ul> | 1193 | </ul> |
1191 | </p> | 1194 | </p> |
1192 | 1195 | ||
@@ -1299,7 +1302,8 @@ | |||
1299 | </pre></td></tr></table> | 1302 | </pre></td></tr></table> |
1300 | 1303 | ||
1301 | <p> | 1304 | <p> |
1302 | Returns a table describing the full contents of a linda, or <tt>nil</tt> if the linda wasn't used yet. | 1305 | Returns a table describing the full contents of a linda, or <tt>nil</tt> if the linda wasn't used yet.<br/> |
1306 | If Decoda support is enabled with <tt>HAVE_DECODA_SUPPORT()</tt>, linda metatable contain a <tt>__towatch</tt> special function that generates a similar table used for debug display. | ||
1303 | </p> | 1307 | </p> |
1304 | 1308 | ||
1305 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> | 1309 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> |
@@ -1359,8 +1363,7 @@ On the other side, you need to use a common Linda for waiting for multiple keys. | |||
1359 | </p> | 1363 | </p> |
1360 | 1364 | ||
1361 | <p> | 1365 | <p> |
1362 | <font size="-1">Actually, you can. Make separate lanes to wait each, and then multiplex those | 1366 | <font size="-1">Actually, you can. Make separate lanes to wait each, and then multiplex those events to a common Linda, but... :).</font> |
1363 | events to a common Linda, but... :).</font> | ||
1364 | </p> | 1367 | </p> |
1365 | 1368 | ||
1366 | 1369 | ||
diff --git a/src/keeper.cpp b/src/keeper.cpp index ff5fe26..ea924e8 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp | |||
@@ -40,6 +40,7 @@ | |||
40 | #include "keeper.h" | 40 | #include "keeper.h" |
41 | 41 | ||
42 | #include "intercopycontext.h" | 42 | #include "intercopycontext.h" |
43 | #include "lane.h" | ||
43 | #include "linda.h" | 44 | #include "linda.h" |
44 | #include "state.h" | 45 | #include "state.h" |
45 | 46 | ||
@@ -548,22 +549,20 @@ int keepercall_count(lua_State* L_) | |||
548 | 549 | ||
549 | Keeper* Linda::acquireKeeper() const | 550 | Keeper* Linda::acquireKeeper() const |
550 | { | 551 | { |
551 | int const _nbKeepers{ U->keepers->nb_keepers }; | 552 | // can be nullptr if this happens during main state shutdown (lanes is being GC'ed -> no keepers) |
552 | // can be 0 if this happens during main state shutdown (lanes is being GC'ed -> no keepers) | 553 | Keeper* const _K{ whichKeeper() }; |
553 | if (_nbKeepers) { | 554 | if (_K) { |
554 | Keeper* const _K{ &U->keepers->keeper_array[keeperIndex] }; | ||
555 | _K->mutex.lock(); | 555 | _K->mutex.lock(); |
556 | return _K; | ||
557 | } | 556 | } |
558 | return nullptr; | 557 | return _K; |
559 | } | 558 | } |
560 | 559 | ||
561 | // ################################################################################################# | 560 | // ################################################################################################# |
562 | 561 | ||
563 | void Linda::releaseKeeper(Keeper* K_) const | 562 | void Linda::releaseKeeper(Keeper* const K_) const |
564 | { | 563 | { |
565 | if (K_) { // can be nullptr if we tried to acquire during shutdown | 564 | if (K_) { // can be nullptr if we tried to acquire during shutdown |
566 | assert(K_ == &U->keepers->keeper_array[keeperIndex]); | 565 | assert(K_ == whichKeeper()); |
567 | K_->mutex.unlock(); | 566 | K_->mutex.unlock(); |
568 | } | 567 | } |
569 | } | 568 | } |
@@ -595,16 +594,16 @@ KeeperCallResult keeper_call(KeeperState K_, keeper_api_t func_, lua_State* L_, | |||
595 | (InterCopyContext{ linda_->U, DestState{ K_ }, SourceState{ L_ }, {}, {}, {}, LookupMode::ToKeeper, {} }.inter_copy(_args) == InterCopyResult::Success) | 594 | (InterCopyContext{ linda_->U, DestState{ K_ }, SourceState{ L_ }, {}, {}, {}, LookupMode::ToKeeper, {} }.inter_copy(_args) == InterCopyResult::Success) |
596 | ) { // L: ... args... K_: func_ linda args... | 595 | ) { // L: ... args... K_: func_ linda args... |
597 | lua_call(K_, 1 + _args, LUA_MULTRET); // L: ... args... K_: result... | 596 | lua_call(K_, 1 + _args, LUA_MULTRET); // L: ... args... K_: result... |
598 | int const retvals{ lua_gettop(K_) - _top_K }; | 597 | int const _retvals{ lua_gettop(K_) - _top_K }; |
599 | // note that this can raise a lua error while the keeper state (and its mutex) is acquired | 598 | // note that this can raise a lua error while the keeper state (and its mutex) is acquired |
600 | // this may interrupt a lane, causing the destruction of the underlying OS thread | 599 | // this may interrupt a lane, causing the destruction of the underlying OS thread |
601 | // after this, another lane making use of this keeper can get an error code from the mutex-locking function | 600 | // after this, another lane making use of this keeper can get an error code from the mutex-locking function |
602 | // when attempting to grab the mutex again (WINVER <= 0x400 does this, but locks just fine, I don't know about pthread) | 601 | // when attempting to grab the mutex again (WINVER <= 0x400 does this, but locks just fine, I don't know about pthread) |
603 | if ( | 602 | if ( |
604 | (retvals == 0) || | 603 | (_retvals == 0) || |
605 | (InterCopyContext{ linda_->U, DestState{ L_ }, SourceState{ K_ }, {}, {}, {}, LookupMode::FromKeeper, {} }.inter_move(retvals) == InterCopyResult::Success) | 604 | (InterCopyContext{ linda_->U, DestState{ L_ }, SourceState{ K_ }, {}, {}, {}, LookupMode::FromKeeper, {} }.inter_move(_retvals) == InterCopyResult::Success) |
606 | ) { // L: ... args... result... K_: result... | 605 | ) { // L: ... args... result... K_: result... |
607 | _result.emplace(retvals); | 606 | _result.emplace(_retvals); |
608 | } | 607 | } |
609 | } | 608 | } |
610 | // whatever happens, restore the stack to where it was at the origin | 609 | // whatever happens, restore the stack to where it was at the origin |
@@ -613,7 +612,7 @@ KeeperCallResult keeper_call(KeeperState K_, keeper_api_t func_, lua_State* L_, | |||
613 | // don't do this for this particular function, as it is only called during Linda destruction, and we don't want to raise an error, ever | 612 | // don't do this for this particular function, as it is only called during Linda destruction, and we don't want to raise an error, ever |
614 | if (func_ != KEEPER_API(clear)) [[unlikely]] { | 613 | if (func_ != KEEPER_API(clear)) [[unlikely]] { |
615 | // since keeper state GC is stopped, let's run a step once in a while if required | 614 | // since keeper state GC is stopped, let's run a step once in a while if required |
616 | int const _gc_threshold{ linda_->U->keepers->gc_threshold }; | 615 | int const _gc_threshold{ linda_->U->keepers.gc_threshold }; |
617 | if (_gc_threshold == 0) [[unlikely]] { | 616 | if (_gc_threshold == 0) [[unlikely]] { |
618 | lua_gc(K_, LUA_GCSTEP, 0); | 617 | lua_gc(K_, LUA_GCSTEP, 0); |
619 | } else if (_gc_threshold > 0) [[likely]] { | 618 | } else if (_gc_threshold > 0) [[likely]] { |
@@ -632,8 +631,215 @@ KeeperCallResult keeper_call(KeeperState K_, keeper_api_t func_, lua_State* L_, | |||
632 | } | 631 | } |
633 | 632 | ||
634 | // ################################################################################################# | 633 | // ################################################################################################# |
634 | // ################################################################################################# | ||
635 | // Keeper | ||
636 | // ################################################################################################# | ||
637 | // ################################################################################################# | ||
635 | 638 | ||
636 | void Keepers::CreateFifosTable(lua_State* L_) | 639 | void* Keeper::operator new[](size_t size_, Universe* U_) noexcept |
637 | { | 640 | { |
638 | kFifosRegKey.setValue(L_, [](lua_State* L_) { lua_newtable(L_); }); | 641 | // size_ is the memory for the element count followed by the elements themselves |
642 | return U_->internalAllocator.alloc(size_); | ||
643 | } | ||
644 | |||
645 | // ################################################################################################# | ||
646 | |||
647 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception | ||
648 | void Keeper::operator delete[](void* p_, Universe* U_) | ||
649 | { | ||
650 | U_->internalAllocator.free(p_, *static_cast<size_t*>(p_) * sizeof(Keeper) + sizeof(size_t)); | ||
651 | } | ||
652 | |||
653 | // ################################################################################################# | ||
654 | // ################################################################################################# | ||
655 | // Keepers | ||
656 | // ################################################################################################# | ||
657 | // ################################################################################################# | ||
658 | |||
659 | void Keepers::DeleteKV::operator()(Keeper* k_) const | ||
660 | { | ||
661 | for (Keeper& _k : std::views::counted(k_, count)) { | ||
662 | _k.~Keeper(); | ||
663 | } | ||
664 | // operator[] returns the result of the allocation shifted by a size_t (the hidden element count) | ||
665 | U->internalAllocator.free(reinterpret_cast<size_t*>(k_) - 1, count * sizeof(Keeper)); | ||
666 | } | ||
667 | |||
668 | // ################################################################################################# | ||
669 | /* | ||
670 | * Initialize keeper states | ||
671 | * | ||
672 | * If there is a problem, returns nullptr and pushes the error message on the stack | ||
673 | * else returns the keepers bookkeeping structure. | ||
674 | * | ||
675 | * Note: Any problems would be design flaws; the created Lua state is left | ||
676 | * unclosed, because it does not really matter. In production code, this | ||
677 | * function never fails. | ||
678 | * settings table is expected at position 1 on the stack | ||
679 | */ | ||
680 | |||
681 | void Keepers::initialize(Universe& U_, lua_State* L_, int const nbKeepers_, int const gc_threshold_) | ||
682 | { | ||
683 | gc_threshold = gc_threshold_; | ||
684 | |||
685 | auto _initOneKeeper = [U = &U_, L = L_, gc_threshold = gc_threshold](Keeper& keeper_, int const i_) { | ||
686 | STACK_CHECK_START_REL(L, 0); | ||
687 | // note that we will leak K if we raise an error later | ||
688 | KeeperState const _K{ state::CreateState(U, L) }; // L: settings K: | ||
689 | if (_K == nullptr) { | ||
690 | raise_luaL_error(L, "out of memory while creating keeper states"); | ||
691 | } | ||
692 | |||
693 | keeper_.L = _K; | ||
694 | |||
695 | // Give a name to the state | ||
696 | lua_pushfstring(_K, "Keeper #%d", i_ + 1); // L: settings K: "Keeper #n" | ||
697 | if constexpr (HAVE_DECODA_SUPPORT()) { | ||
698 | lua_pushvalue(_K, -1); // K: "Keeper #n" Keeper #n" | ||
699 | lua_setglobal(_K, "decoda_name"); // L: settings K: "Keeper #n" | ||
700 | } | ||
701 | kLaneNameRegKey.setValue(_K, [](lua_State* L_) { lua_insert(L_, -2); }); // K: | ||
702 | |||
703 | STACK_CHECK_START_ABS(_K, 0); | ||
704 | |||
705 | // copy the universe pointer in the keeper itself | ||
706 | Universe::Store(_K, U); | ||
707 | STACK_CHECK(_K, 0); | ||
708 | |||
709 | // make sure 'package' is initialized in keeper states, so that we have require() | ||
710 | // this because this is needed when transferring deep userdata object | ||
711 | luaL_requiref(_K, LUA_LOADLIBNAME, luaopen_package, 1); // L: settings K: package | ||
712 | lua_pop(_K, 1); // L: settings K: | ||
713 | STACK_CHECK(_K, 0); | ||
714 | tools::SerializeRequire(_K); | ||
715 | STACK_CHECK(_K, 0); | ||
716 | |||
717 | // copy package.path and package.cpath from the source state | ||
718 | if (luaG_getmodule(L, LUA_LOADLIBNAME) != LuaType::NIL) { // L: settings package K: | ||
719 | // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately | ||
720 | InterCopyContext _c{ U, DestState{ _K }, SourceState{ L }, {}, SourceIndex{ lua_absindex(L, -1) }, {}, LookupMode::ToKeeper, {} }; | ||
721 | if (_c.inter_copy_package() != InterCopyResult::Success) { // L: settings ... error_msg K: | ||
722 | // if something went wrong, the error message is at the top of the stack | ||
723 | lua_remove(L, -2); // L: settings error_msg | ||
724 | raise_lua_error(L); | ||
725 | } | ||
726 | } | ||
727 | lua_pop(L, 1); // L: settings K: | ||
728 | STACK_CHECK(L, 0); | ||
729 | STACK_CHECK(_K, 0); | ||
730 | |||
731 | // attempt to call on_state_create(), if we have one and it is a C function | ||
732 | // (only support a C function because we can't transfer executable Lua code in keepers) | ||
733 | // will raise an error in L_ in case of problem | ||
734 | state::CallOnStateCreate(U, _K, L, LookupMode::ToKeeper); | ||
735 | |||
736 | // create the fifos table in the keeper state | ||
737 | kFifosRegKey.setValue(_K, [](lua_State* L_) { lua_newtable(L_); }); | ||
738 | STACK_CHECK(_K, 0); | ||
739 | |||
740 | // configure GC last | ||
741 | if (gc_threshold >= 0) { | ||
742 | lua_gc(_K, LUA_GCSTOP, 0); | ||
743 | } | ||
744 | }; | ||
745 | |||
746 | switch (nbKeepers_) { | ||
747 | case 0: | ||
748 | break; | ||
749 | |||
750 | case 1: | ||
751 | keeper_array.emplace<Keeper>(); | ||
752 | _initOneKeeper(std::get<Keeper>(keeper_array), 0); | ||
753 | break; | ||
754 | |||
755 | default: | ||
756 | KV& _kv = keeper_array.emplace<KV>( | ||
757 | std::unique_ptr<Keeper[], DeleteKV>{ new(&U_) Keeper[nbKeepers_], DeleteKV{ &U_, nbKeepers_ } }, | ||
758 | nbKeepers_ | ||
759 | ); | ||
760 | for (int const _i : std::ranges::iota_view{ 0, nbKeepers_ }) { | ||
761 | _initOneKeeper(_kv.keepers[_i], _i); | ||
762 | } | ||
763 | } | ||
764 | } | ||
765 | |||
766 | // ################################################################################################# | ||
767 | |||
768 | void Keepers::close() | ||
769 | { | ||
770 | if (isClosing.test_and_set(std::memory_order_release)) { | ||
771 | assert(false); // should never close more than once in practice | ||
772 | return; | ||
773 | } | ||
774 | |||
775 | if (std::holds_alternative<std::monostate>(keeper_array)) { | ||
776 | return; | ||
777 | } | ||
778 | |||
779 | auto _closeOneKeeper = [](Keeper& keeper_) | ||
780 | { | ||
781 | lua_State* const _K{ std::exchange(keeper_.L, KeeperState{ nullptr }) }; | ||
782 | if (_K) { | ||
783 | lua_close(_K); | ||
784 | } | ||
785 | return _K ? true : false; | ||
786 | }; | ||
787 | |||
788 | if (std::holds_alternative<Keeper>(keeper_array)) { | ||
789 | _closeOneKeeper(std::get<Keeper>(keeper_array)); | ||
790 | } else { | ||
791 | KV& _kv = std::get<KV>(keeper_array); | ||
792 | |||
793 | // NOTE: imagine some keeper state N+1 currently holds a linda that uses another keeper N, and a _gc that will make use of it | ||
794 | // when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists | ||
795 | // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success | ||
796 | // which is early-outed with a keepers->nbKeepers null-check | ||
797 | size_t const _nbKeepers{ std::exchange(_kv.nbKeepers, 0) }; | ||
798 | for (size_t const _i : std::ranges::iota_view{ size_t{ 0 }, _nbKeepers }) { | ||
799 | if (!_closeOneKeeper(_kv.keepers[_i])) { | ||
800 | // detected partial init: destroy only the mutexes that got initialized properly | ||
801 | break; | ||
802 | } | ||
803 | } | ||
804 | } | ||
805 | |||
806 | keeper_array.emplace<std::monostate>(); | ||
807 | } | ||
808 | |||
809 | // ################################################################################################# | ||
810 | |||
811 | [[nodiscard]] Keeper* Keepers::getKeeper(int idx_) | ||
812 | { | ||
813 | if (isClosing.test(std::memory_order_acquire)) { | ||
814 | return nullptr; | ||
815 | } | ||
816 | |||
817 | if (std::holds_alternative<std::monostate>(keeper_array)) { | ||
818 | return nullptr; | ||
819 | } | ||
820 | |||
821 | if (std::holds_alternative<Keeper>(keeper_array)) { | ||
822 | return &std::get<Keeper>(keeper_array); | ||
823 | } | ||
824 | |||
825 | return &std::get<KV>(keeper_array).keepers.get()[idx_]; | ||
826 | } | ||
827 | |||
828 | // ################################################################################################# | ||
829 | |||
830 | [[nodiscard]] int Keepers::getNbKeepers() const | ||
831 | { | ||
832 | if (isClosing.test(std::memory_order_acquire)) { | ||
833 | return 0; | ||
834 | } | ||
835 | |||
836 | if (std::holds_alternative<std::monostate>(keeper_array)) { | ||
837 | return 0; | ||
838 | } | ||
839 | |||
840 | if (std::holds_alternative<Keeper>(keeper_array)) { | ||
841 | return 1; | ||
842 | } | ||
843 | |||
844 | return static_cast<int>(std::get<KV>(keeper_array).nbKeepers); | ||
639 | } | 845 | } |
diff --git a/src/keeper.h b/src/keeper.h index a902caa..682c710 100644 --- a/src/keeper.h +++ b/src/keeper.h | |||
@@ -11,8 +11,10 @@ extern "C" | |||
11 | 11 | ||
12 | #include "uniquekey.h" | 12 | #include "uniquekey.h" |
13 | 13 | ||
14 | #include <optional> | 14 | #include <atomic> |
15 | #include <mutex> | 15 | #include <mutex> |
16 | #include <optional> | ||
17 | #include <variant> | ||
16 | 18 | ||
17 | // forwards | 19 | // forwards |
18 | class Linda; | 20 | class Linda; |
@@ -21,22 +23,63 @@ class Universe; | |||
21 | 23 | ||
22 | using KeeperState = Unique<lua_State*>; | 24 | using KeeperState = Unique<lua_State*>; |
23 | 25 | ||
26 | // ################################################################################################# | ||
27 | |||
24 | struct Keeper | 28 | struct Keeper |
25 | { | 29 | { |
26 | std::mutex mutex; | 30 | std::mutex mutex; |
27 | KeeperState L{ nullptr }; | 31 | KeeperState L{ nullptr }; |
28 | // int count; | 32 | |
33 | [[nodiscard]] static void* operator new[](size_t size_, Universe* U_) noexcept; | ||
34 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception | ||
35 | static void operator delete[](void* p_, Universe* U_); | ||
36 | |||
37 | |||
38 | ~Keeper() = default; | ||
39 | Keeper() = default; | ||
40 | // non-copyable, non-movable | ||
41 | Keeper(Keeper const&) = delete; | ||
42 | Keeper(Keeper const&&) = delete; | ||
43 | Keeper& operator=(Keeper const&) = delete; | ||
44 | Keeper& operator=(Keeper const&&) = delete; | ||
29 | }; | 45 | }; |
30 | 46 | ||
47 | // ################################################################################################# | ||
48 | |||
31 | struct Keepers | 49 | struct Keepers |
32 | { | 50 | { |
51 | private: | ||
52 | struct DeleteKV | ||
53 | { | ||
54 | Universe* U{}; | ||
55 | int count{}; | ||
56 | void operator()(Keeper* k_) const; | ||
57 | }; | ||
58 | // can't use std::vector<Keeper> because Keeper contains a mutex, so we need a raw memory buffer | ||
59 | struct KV | ||
60 | { | ||
61 | std::unique_ptr<Keeper[], DeleteKV> keepers{}; | ||
62 | size_t nbKeepers{}; | ||
63 | }; | ||
64 | std::variant<std::monostate, Keeper, KV> keeper_array; | ||
65 | std::atomic_flag isClosing; | ||
66 | |||
67 | public: | ||
33 | int gc_threshold{ 0 }; | 68 | int gc_threshold{ 0 }; |
34 | int nb_keepers{ 0 }; | ||
35 | Keeper keeper_array[1]; | ||
36 | 69 | ||
37 | static void CreateFifosTable(lua_State* L_); | 70 | public: |
71 | // can only be instanced as a data member | ||
72 | static void* operator new(size_t size_) = delete; | ||
73 | |||
74 | Keepers() = default; | ||
75 | void close(); | ||
76 | [[nodiscard]] Keeper* getKeeper(int idx_); | ||
77 | [[nodiscard]] int getNbKeepers() const; | ||
78 | void initialize(Universe& U_, lua_State* L_, int nbKeepers_, int gc_threshold_); | ||
38 | }; | 79 | }; |
39 | 80 | ||
81 | // ################################################################################################# | ||
82 | |||
40 | // xxh64 of string "kNilSentinel" generated at https://www.pelock.com/products/hash-calculator | 83 | // xxh64 of string "kNilSentinel" generated at https://www.pelock.com/products/hash-calculator |
41 | static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" }; | 84 | static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" }; |
42 | 85 | ||
diff --git a/src/lane.cpp b/src/lane.cpp index 22147f1..029efdb 100644 --- a/src/lane.cpp +++ b/src/lane.cpp | |||
@@ -811,7 +811,7 @@ void Lane::changeDebugName(int const nameIdx_) | |||
811 | kLaneNameRegKey.setValue(L, [idx = _nameIdx](lua_State* L_) { lua_pushvalue(L_, idx); }); // L: ... "name" ... | 811 | kLaneNameRegKey.setValue(L, [idx = _nameIdx](lua_State* L_) { lua_pushvalue(L_, idx); }); // L: ... "name" ... |
812 | // keep a direct pointer on the string | 812 | // keep a direct pointer on the string |
813 | debugName = lua_tostringview(L, _nameIdx); | 813 | debugName = lua_tostringview(L, _nameIdx); |
814 | if constexpr (HAVE_DECODA_NAME()) { | 814 | if constexpr (HAVE_DECODA_SUPPORT()) { |
815 | // to see VM name in Decoda debugger Virtual Machine window | 815 | // to see VM name in Decoda debugger Virtual Machine window |
816 | lua_pushvalue(L, _nameIdx); // L: ... "name" ... "name" | 816 | lua_pushvalue(L, _nameIdx); // L: ... "name" ... "name" |
817 | lua_setglobal(L, "decoda_name"); // L: ... "name" ... | 817 | lua_setglobal(L, "decoda_name"); // L: ... "name" ... |
diff --git a/src/lanes.cpp b/src/lanes.cpp index b74e9ec..4b4f9a8 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp | |||
@@ -637,50 +637,11 @@ LUAG_FUNC(configure) | |||
637 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); | 637 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); |
638 | 638 | ||
639 | if (_U == nullptr) { | 639 | if (_U == nullptr) { |
640 | _U = Universe::Create(L_); // L_: settings universe | ||
641 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope2{ _U }); | ||
642 | lua_createtable(L_, 0, 1); // L_: settings universe {mt} | ||
643 | std::ignore = luaG_getfield(L_, 1, "shutdown_timeout"); // L_: settings universe {mt} shutdown_timeout | ||
644 | std::ignore = luaG_getfield(L_, 1, "shutdown_mode"); // L_: settings universe {mt} shutdown_timeout shutdown_mode | ||
645 | lua_pushcclosure(L_, LG_universe_gc, 2); // L_: settings universe {mt} LG_universe_gc | ||
646 | lua_setfield(L_, -2, "__gc"); // L_: settings universe {mt} | ||
647 | lua_setmetatable(L_, -2); // L_: settings universe | ||
648 | lua_pop(L_, 1); // L_: settings | ||
649 | std::ignore = luaG_getfield(L_, 1, "verbose_errors"); // L_: settings verbose_errors | ||
650 | _U->verboseErrors = lua_toboolean(L_, -1) ? true : false; | ||
651 | lua_pop(L_, 1); // L_: settings | ||
652 | std::ignore = luaG_getfield(L_, 1, "demote_full_userdata"); // L_: settings demote_full_userdata | ||
653 | _U->demoteFullUserdata = lua_toboolean(L_, -1) ? true : false; | ||
654 | lua_pop(L_, 1); // L_: settings | ||
655 | |||
656 | // tracking | ||
657 | std::ignore = luaG_getfield(L_, 1, "track_lanes"); // L_: settings track_lanes | ||
658 | if (lua_toboolean(L_, -1)) { | ||
659 | _U->tracker.activate(); | ||
660 | } | ||
661 | lua_pop(L_, 1); // L_: settings | ||
662 | |||
663 | // Linked chains handling | ||
664 | _U->selfdestructFirst = SELFDESTRUCT_END; | ||
665 | _U->initializeAllocatorFunction(L_); | ||
666 | state::InitializeOnStateCreate(_U, L_); | ||
667 | _U->initializeKeepers(L_); | ||
668 | STACK_CHECK(L_, 1); | ||
669 | |||
670 | // Initialize 'timerLinda'; a common Linda object shared by all states | ||
671 | lua_pushcfunction(L_, LG_linda); // L_: settings lanes.linda | ||
672 | lua_pushliteral(L_, "lanes-timer"); // L_: settings lanes.linda "lanes-timer" | ||
673 | lua_call(L_, 1, 1); // L_: settings linda | ||
674 | STACK_CHECK(L_, 2); | ||
675 | |||
676 | // Proxy userdata contents is only a 'DeepPrelude*' pointer | ||
677 | _U->timerLinda = *lua_tofulluserdata<DeepPrelude*>(L_, -1); | ||
678 | // increment refcount so that this linda remains alive as long as the universe exists. | ||
679 | _U->timerLinda->refcount.fetch_add(1, std::memory_order_relaxed); | ||
680 | lua_pop(L_, 1); // L_: settings | ||
681 | // store a hidden reference in the registry to make sure the string is kept around even if a lane decides to manually change the "decoda_name" global... | 640 | // store a hidden reference in the registry to make sure the string is kept around even if a lane decides to manually change the "decoda_name" global... |
682 | kLaneNameRegKey.setValue(L_, [](lua_State* L_) { std::ignore = lua_pushstringview(L_, "main"); }); | 641 | kLaneNameRegKey.setValue(L_, [](lua_State* L_) { std::ignore = lua_pushstringview(L_, "main"); }); |
683 | 642 | ||
643 | // create the universe | ||
644 | _U = Universe::Create(L_); // L_: settings universe | ||
684 | } | 645 | } |
685 | STACK_CHECK(L_, 1); | 646 | STACK_CHECK(L_, 1); |
686 | 647 | ||
diff --git a/src/lanes.lua b/src/lanes.lua index e7763b1..1c36b46 100644 --- a/src/lanes.lua +++ b/src/lanes.lua | |||
@@ -90,7 +90,7 @@ local isLuaJIT = (package and package.loaded.jit and jit.version) and true or fa | |||
90 | 90 | ||
91 | local default_params = | 91 | local default_params = |
92 | { | 92 | { |
93 | nb_keepers = 1, | 93 | nb_user_keepers = 0, |
94 | keepers_gc_threshold = -1, | 94 | keepers_gc_threshold = -1, |
95 | on_state_create = nil, | 95 | on_state_create = nil, |
96 | shutdown_timeout = 0.25, | 96 | shutdown_timeout = 0.25, |
@@ -114,9 +114,9 @@ end | |||
114 | 114 | ||
115 | local param_checkers = | 115 | local param_checkers = |
116 | { | 116 | { |
117 | nb_keepers = function(val_) | 117 | nb_user_keepers = function(val_) |
118 | -- nb_keepers should be a number in [1,100] (so that nobody tries to run OOM by specifying a huge amount) | 118 | -- nb_user_keepers should be a number in [0,100] (so that nobody tries to run OOM by specifying a huge amount) |
119 | return type(val_) == "number" and val_ > 0 and val_ <= 100 | 119 | return type(val_) == "number" and val_ >= 0 and val_ <= 100 |
120 | end, | 120 | end, |
121 | keepers_gc_threshold = function(val_) | 121 | keepers_gc_threshold = function(val_) |
122 | -- keepers_gc_threshold should be a number | 122 | -- keepers_gc_threshold should be a number |
@@ -176,7 +176,7 @@ local params_checker = function(settings_) | |||
176 | param = default_params[key] | 176 | param = default_params[key] |
177 | end | 177 | end |
178 | if not checker(param) then | 178 | if not checker(param) then |
179 | error("Bad " .. key .. ": " .. tostring(param), 2) | 179 | error("Bad parameter " .. key .. ": " .. tostring(param), 2) |
180 | end | 180 | end |
181 | settings[key] = param | 181 | settings[key] = param |
182 | end | 182 | end |
diff --git a/src/lanesconf.h b/src/lanesconf.h index 2df7a71..938d743 100644 --- a/src/lanesconf.h +++ b/src/lanesconf.h | |||
@@ -42,4 +42,4 @@ | |||
42 | #endif // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) | 42 | #endif // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) |
43 | 43 | ||
44 | #define USE_DEBUG_SPEW() 0 | 44 | #define USE_DEBUG_SPEW() 0 |
45 | #define HAVE_DECODA_NAME() 0 | 45 | #define HAVE_DECODA_SUPPORT() 0 |
diff --git a/src/linda.cpp b/src/linda.cpp index 76cd347..f8d6cdc 100644 --- a/src/linda.cpp +++ b/src/linda.cpp | |||
@@ -109,15 +109,10 @@ template <bool OPT> | |||
109 | // #################################### Linda implementation ####################################### | 109 | // #################################### Linda implementation ####################################### |
110 | // ################################################################################################# | 110 | // ################################################################################################# |
111 | 111 | ||
112 | // Any hashing will do that maps pointers to [0..Universe::nb_keepers[ consistently. | ||
113 | // Pointers are often aligned by 8 or so - ignore the low order bits | ||
114 | // have to cast to unsigned long to avoid compilation warnings about loss of data when converting pointer-to-integer | ||
115 | static constexpr uintptr_t kPointerMagicShift{ 3 }; | ||
116 | |||
117 | Linda::Linda(Universe* U_, LindaGroup group_, std::string_view const& name_) | 112 | Linda::Linda(Universe* U_, LindaGroup group_, std::string_view const& name_) |
118 | : DeepPrelude{ LindaFactory::Instance } | 113 | : DeepPrelude{ LindaFactory::Instance } |
119 | , U{ U_ } | 114 | , U{ U_ } |
120 | , keeperIndex{ (group_ ? group_ : static_cast<int>(std::bit_cast<uintptr_t>(this) >> kPointerMagicShift)) % U_->keepers->nb_keepers } | 115 | , keeperIndex{ group_ % U_->keepers.getNbKeepers() } |
121 | { | 116 | { |
122 | setName(name_); | 117 | setName(name_); |
123 | } | 118 | } |
@@ -733,6 +728,7 @@ LUAG_FUNC(linda_tostring) | |||
733 | 728 | ||
734 | // ################################################################################################# | 729 | // ################################################################################################# |
735 | 730 | ||
731 | #if HAVE_DECODA_SUPPORT() | ||
736 | /* | 732 | /* |
737 | * table/string = linda:__towatch() | 733 | * table/string = linda:__towatch() |
738 | * return a table listing all pending data inside the linda, or the stringified linda if empty | 734 | * return a table listing all pending data inside the linda, or the stringified linda if empty |
@@ -748,6 +744,8 @@ LUAG_FUNC(linda_towatch) | |||
748 | return _pushed; | 744 | return _pushed; |
749 | } | 745 | } |
750 | 746 | ||
747 | #endif // HAVE_DECODA_SUPPORT() | ||
748 | |||
751 | // ################################################################################################# | 749 | // ################################################################################################# |
752 | 750 | ||
753 | namespace { | 751 | namespace { |
@@ -755,7 +753,9 @@ namespace { | |||
755 | static luaL_Reg const sLindaMT[] = { | 753 | static luaL_Reg const sLindaMT[] = { |
756 | { "__concat", LG_linda_concat }, | 754 | { "__concat", LG_linda_concat }, |
757 | { "__tostring", LG_linda_tostring }, | 755 | { "__tostring", LG_linda_tostring }, |
756 | #if HAVE_DECODA_SUPPORT() | ||
758 | { "__towatch", LG_linda_towatch }, // Decoda __towatch support | 757 | { "__towatch", LG_linda_towatch }, // Decoda __towatch support |
758 | #endif // HAVE_DECODA_SUPPORT() | ||
759 | { "cancel", LG_linda_cancel }, | 759 | { "cancel", LG_linda_cancel }, |
760 | { "count", LG_linda_count }, | 760 | { "count", LG_linda_count }, |
761 | { "deep", LG_linda_deep }, | 761 | { "deep", LG_linda_deep }, |
@@ -784,13 +784,24 @@ namespace { | |||
784 | LUAG_FUNC(linda) | 784 | LUAG_FUNC(linda) |
785 | { | 785 | { |
786 | int const _top{ lua_gettop(L_) }; | 786 | int const _top{ lua_gettop(L_) }; |
787 | int _groupIdx{}; | ||
787 | luaL_argcheck(L_, _top <= 2, _top, "too many arguments"); | 788 | luaL_argcheck(L_, _top <= 2, _top, "too many arguments"); |
788 | if (_top == 1) { | 789 | if (_top == 1) { |
789 | LuaType const _t{ lua_type_as_enum(L_, 1) }; | 790 | LuaType const _t{ lua_type_as_enum(L_, 1) }; |
790 | luaL_argcheck(L_, _t == LuaType::STRING || _t == LuaType::NUMBER, 1, "wrong parameter (should be a string or a number)"); | 791 | int const _nameIdx{ (_t == LuaType::STRING) ? 1 : 0 }; |
792 | _groupIdx = (_t == LuaType::NUMBER) ? 1 : 0; | ||
793 | luaL_argcheck(L_, _nameIdx || _groupIdx, 1, "wrong parameter (should be a string or a number)"); | ||
791 | } else if (_top == 2) { | 794 | } else if (_top == 2) { |
792 | luaL_checktype(L_, 1, LUA_TSTRING); | 795 | luaL_checktype(L_, 1, LUA_TSTRING); |
793 | luaL_checktype(L_, 2, LUA_TNUMBER); | 796 | luaL_checktype(L_, 2, LUA_TNUMBER); |
797 | _groupIdx = 2; | ||
798 | } | ||
799 | int const _nbKeepers{ Universe::Get(L_)->keepers.getNbKeepers() }; | ||
800 | if (!_groupIdx) { | ||
801 | luaL_argcheck(L_, _nbKeepers < 2, 0, "there are multiple keepers, you must specify a group"); | ||
802 | } else { | ||
803 | int const _group{ static_cast<int>(lua_tointeger(L_, _groupIdx)) }; | ||
804 | luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, _groupIdx, "group out of range"); | ||
794 | } | 805 | } |
795 | return LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, 0); | 806 | return LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, 0); |
796 | } | 807 | } |
diff --git a/src/linda.h b/src/linda.h index 3545544..5753258 100644 --- a/src/linda.h +++ b/src/linda.h | |||
@@ -38,7 +38,6 @@ class Linda | |||
38 | CancelRequest cancelRequest{ CancelRequest::None }; | 38 | CancelRequest cancelRequest{ CancelRequest::None }; |
39 | 39 | ||
40 | public: | 40 | public: |
41 | // a fifo full userdata has one uservalue, the table that holds the actual fifo contents | ||
42 | [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); } | 41 | [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); } |
43 | // always embedded somewhere else or "in-place constructed" as a full userdata | 42 | // always embedded somewhere else or "in-place constructed" as a full userdata |
44 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception | 43 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception |
@@ -64,5 +63,5 @@ class Linda | |||
64 | [[nodiscard]] std::string_view getName() const; | 63 | [[nodiscard]] std::string_view getName() const; |
65 | void releaseKeeper(Keeper* keeper_) const; | 64 | void releaseKeeper(Keeper* keeper_) const; |
66 | [[nodiscard]] static int ProtectedCall(lua_State* L_, lua_CFunction f_); | 65 | [[nodiscard]] static int ProtectedCall(lua_State* L_, lua_CFunction f_); |
67 | [[nodiscard]] Keeper* whichKeeper() const { return U->keepers->nb_keepers ? &U->keepers->keeper_array[keeperIndex] : nullptr; } | 66 | [[nodiscard]] Keeper* whichKeeper() const { return U->keepers.getKeeper(keeperIndex); } |
68 | }; | 67 | }; |
diff --git a/src/universe.cpp b/src/universe.cpp index 55c3e69..2e7fd55 100644 --- a/src/universe.cpp +++ b/src/universe.cpp | |||
@@ -38,6 +38,8 @@ THE SOFTWARE. | |||
38 | 38 | ||
39 | #include <ranges> | 39 | #include <ranges> |
40 | 40 | ||
41 | extern LUAG_FUNC(linda); | ||
42 | |||
41 | // ################################################################################################# | 43 | // ################################################################################################# |
42 | 44 | ||
43 | // xxh64 of string "kUniverseFullRegKey" generated at https://www.pelock.com/products/hash-calculator | 45 | // xxh64 of string "kUniverseFullRegKey" generated at https://www.pelock.com/products/hash-calculator |
@@ -77,12 +79,68 @@ Universe::Universe() | |||
77 | [[nodiscard]] Universe* Universe::Create(lua_State* const L_) | 79 | [[nodiscard]] Universe* Universe::Create(lua_State* const L_) |
78 | { | 80 | { |
79 | LUA_ASSERT(L_, Universe::Get(L_) == nullptr); | 81 | LUA_ASSERT(L_, Universe::Get(L_) == nullptr); |
80 | STACK_CHECK_START_REL(L_, 0); | 82 | LUA_ASSERT(L_, lua_gettop(L_) == 1 && lua_istable(L_, 1)); |
81 | Universe* const _U{ new (L_) Universe{} }; // L_: universe | 83 | STACK_CHECK_START_REL(L_, 0); // L_: settings |
84 | std::ignore = luaG_getfield(L_, 1, "nb_user_keepers"); // L_: settings nb_user_keepers | ||
85 | int const _nbUserKeepers{ static_cast<int>(lua_tointeger(L_, -1)) + 1}; | ||
86 | lua_pop(L_, 1); // L_: settings | ||
87 | if (_nbUserKeepers < 1) { | ||
88 | raise_luaL_error(L_, "Bad number of additional keepers (%d)", _nbUserKeepers); | ||
89 | } | ||
90 | STACK_CHECK(L_, 0); | ||
91 | std::ignore = luaG_getfield(L_, 1, "keepers_gc_threshold"); // L_: settings keepers_gc_threshold | ||
92 | int const _keepers_gc_threshold{ static_cast<int>(lua_tointeger(L_, -1)) }; | ||
93 | lua_pop(L_, 1); // L_: settings | ||
94 | STACK_CHECK(L_, 0); | ||
95 | |||
96 | Universe* const _U{ new (L_) Universe{} }; // L_: settings universe | ||
82 | STACK_CHECK(L_, 1); | 97 | STACK_CHECK(L_, 1); |
83 | kUniverseFullRegKey.setValue(L_, [](lua_State* L_) { lua_pushvalue(L_, -2); }); | 98 | kUniverseFullRegKey.setValue(L_, [](lua_State* L_) { lua_pushvalue(L_, -2); }); |
84 | kUniverseLightRegKey.setValue(L_, [U = _U](lua_State* L_) { lua_pushlightuserdata(L_, U); }); | 99 | kUniverseLightRegKey.setValue(L_, [U = _U](lua_State* L_) { lua_pushlightuserdata(L_, U); }); |
100 | STACK_CHECK(L_, 1); // L_: settings | ||
101 | |||
102 | DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); | ||
103 | lua_createtable(L_, 0, 1); // L_: settings universe {mt} | ||
104 | std::ignore = luaG_getfield(L_, 1, "shutdown_timeout"); // L_: settings universe {mt} shutdown_timeout | ||
105 | std::ignore = luaG_getfield(L_, 1, "shutdown_mode"); // L_: settings universe {mt} shutdown_timeout shutdown_mode | ||
106 | lua_pushcclosure(L_, LG_universe_gc, 2); // L_: settings universe {mt} LG_universe_gc | ||
107 | lua_setfield(L_, -2, "__gc"); // L_: settings universe {mt} | ||
108 | lua_setmetatable(L_, -2); // L_: settings universe | ||
109 | lua_pop(L_, 1); // L_: settings | ||
110 | std::ignore = luaG_getfield(L_, 1, "verbose_errors"); // L_: settings verbose_errors | ||
111 | _U->verboseErrors = lua_toboolean(L_, -1) ? true : false; | ||
112 | lua_pop(L_, 1); // L_: settings | ||
113 | std::ignore = luaG_getfield(L_, 1, "demote_full_userdata"); // L_: settings demote_full_userdata | ||
114 | _U->demoteFullUserdata = lua_toboolean(L_, -1) ? true : false; | ||
115 | lua_pop(L_, 1); // L_: settings | ||
116 | |||
117 | // tracking | ||
118 | std::ignore = luaG_getfield(L_, 1, "track_lanes"); // L_: settings track_lanes | ||
119 | if (lua_toboolean(L_, -1)) { | ||
120 | _U->tracker.activate(); | ||
121 | } | ||
122 | lua_pop(L_, 1); // L_: settings | ||
123 | |||
124 | // Linked chains handling | ||
125 | _U->selfdestructFirst = SELFDESTRUCT_END; | ||
126 | _U->initializeAllocatorFunction(L_); | ||
127 | state::InitializeOnStateCreate(_U, L_); | ||
128 | _U->keepers.initialize(*_U, L_, _nbUserKeepers, _keepers_gc_threshold); | ||
129 | STACK_CHECK(L_, 0); | ||
130 | |||
131 | // Initialize 'timerLinda'; a common Linda object shared by all states | ||
132 | lua_pushcfunction(L_, LG_linda); // L_: settings lanes.linda | ||
133 | std::ignore = lua_pushstringview(L_, "lanes-timer"); // L_: settings lanes.linda "lanes-timer" | ||
134 | lua_pushinteger(L_, 0); // L_: settings lanes.linda "lanes-timer" 0 | ||
135 | lua_call(L_, 2, 1); // L_: settings linda | ||
85 | STACK_CHECK(L_, 1); | 136 | STACK_CHECK(L_, 1); |
137 | |||
138 | // Proxy userdata contents is only a 'DeepPrelude*' pointer | ||
139 | _U->timerLinda = *lua_tofulluserdata<DeepPrelude*>(L_, -1); | ||
140 | // increment refcount so that this linda remains alive as long as the universe exists. | ||
141 | _U->timerLinda->refcount.fetch_add(1, std::memory_order_relaxed); | ||
142 | lua_pop(L_, 1); // L_: settings | ||
143 | STACK_CHECK(L_, 0); | ||
86 | return _U; | 144 | return _U; |
87 | } | 145 | } |
88 | 146 | ||
@@ -113,45 +171,6 @@ Universe::Universe() | |||
113 | 171 | ||
114 | // ################################################################################################# | 172 | // ################################################################################################# |
115 | 173 | ||
116 | /* | ||
117 | * Pool of keeper states | ||
118 | * | ||
119 | * Access to keeper states is locked (only one OS thread at a time) so the | ||
120 | * bigger the pool, the less chances of unnecessary waits. Lindas map to the | ||
121 | * keepers randomly, by a hash. | ||
122 | */ | ||
123 | |||
124 | // called as __gc for the keepers array userdata | ||
125 | void Universe::closeKeepers() | ||
126 | { | ||
127 | if (keepers != nullptr) { | ||
128 | int _nbKeepers{ keepers->nb_keepers }; | ||
129 | // NOTE: imagine some keeper state N+1 currently holds a linda that uses another keeper N, and a _gc that will make use of it | ||
130 | // when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists | ||
131 | // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success | ||
132 | // which is early-outed with a keepers->nbKeepers null-check | ||
133 | keepers->nb_keepers = 0; | ||
134 | for (int const _i : std::ranges::iota_view{ 0, _nbKeepers }) { | ||
135 | lua_State* const _K{ keepers->keeper_array[_i].L }; | ||
136 | keepers->keeper_array[_i].L = KeeperState{ nullptr }; | ||
137 | if (_K != nullptr) { | ||
138 | lua_close(_K); | ||
139 | } else { | ||
140 | // detected partial init: destroy only the mutexes that got initialized properly | ||
141 | _nbKeepers = _i; | ||
142 | } | ||
143 | } | ||
144 | for (int const _i : std::ranges::iota_view{ 0, _nbKeepers }) { | ||
145 | keepers->keeper_array[_i].~Keeper(); | ||
146 | } | ||
147 | // free the keeper bookkeeping structure | ||
148 | internalAllocator.free(keepers, sizeof(Keepers) + (_nbKeepers - 1) * sizeof(Keeper)); | ||
149 | keepers = nullptr; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | // ################################################################################################# | ||
154 | |||
155 | // called once at the creation of the universe (therefore L_ is the master Lua state everything originates from) | 174 | // called once at the creation of the universe (therefore L_ is the master Lua state everything originates from) |
156 | // Do I need to disable this when compiling for LuaJIT to prevent issues? | 175 | // Do I need to disable this when compiling for LuaJIT to prevent issues? |
157 | void Universe::initializeAllocatorFunction(lua_State* const L_) | 176 | void Universe::initializeAllocatorFunction(lua_State* const L_) |
@@ -224,113 +243,6 @@ int Universe::InitializeFinalizer(lua_State* const L_) | |||
224 | 243 | ||
225 | // ################################################################################################# | 244 | // ################################################################################################# |
226 | 245 | ||
227 | /* | ||
228 | * Initialize keeper states | ||
229 | * | ||
230 | * If there is a problem, returns nullptr and pushes the error message on the stack | ||
231 | * else returns the keepers bookkeeping structure. | ||
232 | * | ||
233 | * Note: Any problems would be design flaws; the created Lua state is left | ||
234 | * unclosed, because it does not really matter. In production code, this | ||
235 | * function never fails. | ||
236 | * settings table is expected at position 1 on the stack | ||
237 | */ | ||
238 | void Universe::initializeKeepers(lua_State* const L_) | ||
239 | { | ||
240 | LUA_ASSERT(L_, lua_gettop(L_) == 1 && lua_istable(L_, 1)); | ||
241 | STACK_CHECK_START_REL(L_, 0); // L_: settings | ||
242 | std::ignore = luaG_getfield(L_, 1, "nb_keepers"); // L_: settings nb_keepers | ||
243 | int const _nb_keepers{ static_cast<int>(lua_tointeger(L_, -1)) }; | ||
244 | lua_pop(L_, 1); // L_: settings | ||
245 | if (_nb_keepers < 1) { | ||
246 | raise_luaL_error(L_, "Bad number of keepers (%d)", _nb_keepers); | ||
247 | } | ||
248 | STACK_CHECK(L_, 0); | ||
249 | |||
250 | std::ignore = luaG_getfield(L_, 1, "keepers_gc_threshold"); // L_: settings keepers_gc_threshold | ||
251 | int const keepers_gc_threshold{ static_cast<int>(lua_tointeger(L_, -1)) }; | ||
252 | lua_pop(L_, 1); // L_: settings | ||
253 | STACK_CHECK(L_, 0); | ||
254 | |||
255 | // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states | ||
256 | { | ||
257 | size_t const _bytes{ sizeof(Keepers) + (_nb_keepers - 1) * sizeof(Keeper) }; | ||
258 | keepers = static_cast<Keepers*>(internalAllocator.alloc(_bytes)); | ||
259 | if (keepers == nullptr) { | ||
260 | raise_luaL_error(L_, "out of memory while creating keepers"); | ||
261 | } | ||
262 | keepers->Keepers::Keepers(); | ||
263 | keepers->gc_threshold = keepers_gc_threshold; | ||
264 | keepers->nb_keepers = _nb_keepers; | ||
265 | |||
266 | // we have to manually call the Keeper constructor on the additional array slots | ||
267 | for (int const _i : std::ranges::iota_view{ 1, _nb_keepers }) { | ||
268 | new (&keepers->keeper_array[_i]) Keeper{}; // placement new | ||
269 | } | ||
270 | } | ||
271 | |||
272 | for (int const _i : std::ranges::iota_view{ 0, _nb_keepers }) { | ||
273 | // note that we will leak K if we raise an error later | ||
274 | KeeperState const _K{ state::CreateState(this, L_) }; // L_: settings K: | ||
275 | if (_K == nullptr) { | ||
276 | raise_luaL_error(L_, "out of memory while creating keeper states"); | ||
277 | } | ||
278 | |||
279 | keepers->keeper_array[_i].L = _K; | ||
280 | |||
281 | if (keepers->gc_threshold >= 0) { | ||
282 | lua_gc(_K, LUA_GCSTOP, 0); | ||
283 | } | ||
284 | |||
285 | STACK_CHECK_START_ABS(_K, 0); | ||
286 | |||
287 | // copy the universe pointer in the keeper itself | ||
288 | Universe::Store(_K, this); | ||
289 | STACK_CHECK(_K, 0); | ||
290 | |||
291 | // make sure 'package' is initialized in keeper states, so that we have require() | ||
292 | // this because this is needed when transferring deep userdata object | ||
293 | luaL_requiref(_K, LUA_LOADLIBNAME, luaopen_package, 1); // L_: settings K: package | ||
294 | lua_pop(_K, 1); // L_: settings K: | ||
295 | STACK_CHECK(_K, 0); | ||
296 | tools::SerializeRequire(_K); | ||
297 | STACK_CHECK(_K, 0); | ||
298 | |||
299 | // copy package.path and package.cpath from the source state | ||
300 | if (luaG_getmodule(L_, LUA_LOADLIBNAME) != LuaType::NIL) { // L_: settings package K: | ||
301 | // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately | ||
302 | InterCopyContext _c{ this, DestState{ _K }, SourceState{ L_ }, {}, SourceIndex{ lua_absindex(L_, -1) }, {}, LookupMode::ToKeeper, {} }; | ||
303 | if (_c.inter_copy_package() != InterCopyResult::Success) { // L_: settings ... error_msg K: | ||
304 | // if something went wrong, the error message is at the top of the stack | ||
305 | lua_remove(L_, -2); // L_: settings error_msg | ||
306 | raise_lua_error(L_); | ||
307 | } | ||
308 | } | ||
309 | lua_pop(L_, 1); // L_: settings K: | ||
310 | STACK_CHECK(L_, 0); | ||
311 | STACK_CHECK(_K, 0); | ||
312 | |||
313 | // attempt to call on_state_create(), if we have one and it is a C function | ||
314 | // (only support a C function because we can't transfer executable Lua code in keepers) | ||
315 | // will raise an error in L_ in case of problem | ||
316 | state::CallOnStateCreate(this, _K, L_, LookupMode::ToKeeper); | ||
317 | |||
318 | // to see VM name in Decoda debugger | ||
319 | lua_pushfstring(_K, "Keeper #%d", _i + 1); // L_: settings K: "Keeper #n" | ||
320 | if constexpr (HAVE_DECODA_NAME()) { | ||
321 | lua_pushvalue(_K, -1); // K: "Keeper #n" Keeper #n" | ||
322 | lua_setglobal(_K, "decoda_name"); // L_: settings K: "Keeper #n" | ||
323 | } | ||
324 | kLaneNameRegKey.setValue(_K, [](lua_State* L_) { lua_insert(L_, -2); }); // K: | ||
325 | // create the fifos table in the keeper state | ||
326 | Keepers::CreateFifosTable(_K); | ||
327 | STACK_CHECK(_K, 0); | ||
328 | } | ||
329 | STACK_CHECK(L_, 0); | ||
330 | } | ||
331 | |||
332 | // ################################################################################################# | ||
333 | |||
334 | void Universe::terminateFreeRunningLanes(lua_State* const L_, lua_Duration const shutdownTimeout_, CancelOp const op_) | 246 | void Universe::terminateFreeRunningLanes(lua_State* const L_, lua_Duration const shutdownTimeout_, CancelOp const op_) |
335 | { | 247 | { |
336 | if (selfdestructFirst != SELFDESTRUCT_END) { | 248 | if (selfdestructFirst != SELFDESTRUCT_END) { |
@@ -425,7 +337,7 @@ LUAG_FUNC(universe_gc) | |||
425 | _U->timerLinda = nullptr; | 337 | _U->timerLinda = nullptr; |
426 | } | 338 | } |
427 | 339 | ||
428 | _U->closeKeepers(); | 340 | _U->keepers.close(); |
429 | 341 | ||
430 | // remove the protected allocator, if any | 342 | // remove the protected allocator, if any |
431 | _U->protectedAllocator.removeFrom(L_); | 343 | _U->protectedAllocator.removeFrom(L_); |
diff --git a/src/universe.h b/src/universe.h index 7624fed..7b9f309 100644 --- a/src/universe.h +++ b/src/universe.h | |||
@@ -9,10 +9,12 @@ extern "C" | |||
9 | } | 9 | } |
10 | #endif // __cplusplus | 10 | #endif // __cplusplus |
11 | 11 | ||
12 | #include "keeper.h" | ||
12 | #include "lanesconf.h" | 13 | #include "lanesconf.h" |
13 | #include "tracker.h" | 14 | #include "tracker.h" |
14 | #include "uniquekey.h" | 15 | #include "uniquekey.h" |
15 | 16 | ||
17 | #include <atomic> | ||
16 | #include <mutex> | 18 | #include <mutex> |
17 | 19 | ||
18 | // ################################################################################################# | 20 | // ################################################################################################# |
@@ -20,7 +22,6 @@ extern "C" | |||
20 | // forwards | 22 | // forwards |
21 | enum class CancelOp; | 23 | enum class CancelOp; |
22 | struct DeepPrelude; | 24 | struct DeepPrelude; |
23 | struct Keepers; | ||
24 | class Lane; | 25 | class Lane; |
25 | 26 | ||
26 | // ################################################################################################# | 27 | // ################################################################################################# |
@@ -144,7 +145,7 @@ class Universe | |||
144 | 145 | ||
145 | AllocatorDefinition internalAllocator; | 146 | AllocatorDefinition internalAllocator; |
146 | 147 | ||
147 | Keepers* keepers{ nullptr }; | 148 | Keepers keepers; |
148 | 149 | ||
149 | // Initialized by 'init_once_LOCKED()': the deep userdata Linda object | 150 | // Initialized by 'init_once_LOCKED()': the deep userdata Linda object |
150 | // used for timers (each lane will get a proxy to this) | 151 | // used for timers (each lane will get a proxy to this) |
@@ -183,12 +184,10 @@ class Universe | |||
183 | Universe& operator=(Universe const&) = delete; | 184 | Universe& operator=(Universe const&) = delete; |
184 | Universe& operator=(Universe&&) = delete; | 185 | Universe& operator=(Universe&&) = delete; |
185 | 186 | ||
186 | void closeKeepers(); | ||
187 | [[nodiscard]] static Universe* Create(lua_State* L_); | 187 | [[nodiscard]] static Universe* Create(lua_State* L_); |
188 | [[nodiscard]] static inline Universe* Get(lua_State* L_); | 188 | [[nodiscard]] static inline Universe* Get(lua_State* L_); |
189 | void initializeAllocatorFunction(lua_State* L_); | 189 | void initializeAllocatorFunction(lua_State* L_); |
190 | static int InitializeFinalizer(lua_State* L_); | 190 | static int InitializeFinalizer(lua_State* L_); |
191 | void initializeKeepers(lua_State* L_); | ||
192 | static inline void Store(lua_State* L_, Universe* U_); | 191 | static inline void Store(lua_State* L_, Universe* U_); |
193 | void terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_); | 192 | void terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_); |
194 | }; | 193 | }; |
diff --git a/tests/fibonacci.lua b/tests/fibonacci.lua index 6dba1dd..0ff2f37 100644 --- a/tests/fibonacci.lua +++ b/tests/fibonacci.lua | |||
@@ -12,7 +12,7 @@ | |||
12 | 12 | ||
13 | -- Need to say it's 'local' so it can be an upvalue | 13 | -- Need to say it's 'local' so it can be an upvalue |
14 | -- | 14 | -- |
15 | local lanes = require "lanes".configure{ nb_keepers =1, with_timers = false} | 15 | local lanes = require "lanes".configure() |
16 | 16 | ||
17 | local function WR(str) | 17 | local function WR(str) |
18 | io.stderr:write( str.."\n" ) | 18 | io.stderr:write( str.."\n" ) |
@@ -27,7 +27,7 @@ local KNOWN= { [0]=0, 1,1,2,3,5,8,13,21,34,55,89,144 } | |||
27 | set_debug_threadname = function ( ...) | 27 | set_debug_threadname = function ( ...) |
28 | end | 28 | end |
29 | 29 | ||
30 | -- | 30 | -- |
31 | -- uint= fib( n_uint ) | 31 | -- uint= fib( n_uint ) |
32 | -- | 32 | -- |
33 | local function fib( n ) | 33 | local function fib( n ) |
@@ -72,20 +72,20 @@ end | |||
72 | -- | 72 | -- |
73 | local right= | 73 | local right= |
74 | { | 74 | { |
75 | 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, | 75 | 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, |
76 | 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, | 76 | 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, |
77 | 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, | 77 | 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, |
78 | 701408733, 1134903170, 1836311903, 2971215073, 4807526976, 7778742049, 12586269025, 20365011074, | 78 | 701408733, 1134903170, 1836311903, 2971215073, 4807526976, 7778742049, 12586269025, 20365011074, |
79 | 32951280099, 53316291173, 86267571272, 139583862445, 225851433717, 365435296162, 591286729879, | 79 | 32951280099, 53316291173, 86267571272, 139583862445, 225851433717, 365435296162, 591286729879, |
80 | 956722026041, 1548008755920, 2504730781961, 4052739537881, 6557470319842, 10610209857723, | 80 | 956722026041, 1548008755920, 2504730781961, 4052739537881, 6557470319842, 10610209857723, |
81 | 17167680177565, 27777890035288, 44945570212853, 72723460248141, 117669030460994, 190392490709135, | 81 | 17167680177565, 27777890035288, 44945570212853, 72723460248141, 117669030460994, 190392490709135, |
82 | 308061521170129, 498454011879264, 806515533049393, 1304969544928657, 2111485077978050, | 82 | 308061521170129, 498454011879264, 806515533049393, 1304969544928657, 2111485077978050, |
83 | 3416454622906707, 5527939700884757, 8944394323791464, 14472334024676220, 23416728348467684, | 83 | 3416454622906707, 5527939700884757, 8944394323791464, 14472334024676220, 23416728348467684, |
84 | 37889062373143900, 61305790721611580, 99194853094755490, 160500643816367070, 259695496911122560, | 84 | 37889062373143900, 61305790721611580, 99194853094755490, 160500643816367070, 259695496911122560, |
85 | 420196140727489660, 679891637638612200, 1100087778366101900, 1779979416004714000, | 85 | 420196140727489660, 679891637638612200, 1100087778366101900, 1779979416004714000, |
86 | 2880067194370816000, 4660046610375530000, 7540113804746346000, 12200160415121877000, | 86 | 2880067194370816000, 4660046610375530000, 7540113804746346000, 12200160415121877000, |
87 | 19740274219868226000, 31940434634990105000, 51680708854858334000, 83621143489848440000, | 87 | 19740274219868226000, 31940434634990105000, 51680708854858334000, 83621143489848440000, |
88 | 135301852344706780000, 218922995834555200000 | 88 | 135301852344706780000, 218922995834555200000 |
89 | } | 89 | } |
90 | assert( #right==99 ) | 90 | assert( #right==99 ) |
91 | 91 | ||
diff --git a/tests/irayo_recursive.lua b/tests/irayo_recursive.lua index 2f8b8a8..fe722a3 100644 --- a/tests/irayo_recursive.lua +++ b/tests/irayo_recursive.lua | |||
@@ -1,4 +1,4 @@ | |||
1 | local lanes = require "lanes".configure{ nb_keepers = 1, with_timers = false} | 1 | local lanes = require "lanes".configure() |
2 | -- | 2 | -- |
3 | -- Bugs filed by irayo Jul-2008 | 3 | -- Bugs filed by irayo Jul-2008 |
4 | -- | 4 | -- |
diff --git a/tests/keeper.lua b/tests/keeper.lua index e758b54..d08dd98 100644 --- a/tests/keeper.lua +++ b/tests/keeper.lua | |||
@@ -3,20 +3,20 @@ | |||
3 | -- | 3 | -- |
4 | -- Test program for Lua Lanes | 4 | -- Test program for Lua Lanes |
5 | -- | 5 | -- |
6 | -- TODO: there is a random crash when nb_keepers > 1. Will have to investigate | 6 | -- TODO: there is a random crash when nb_user_keepers > 1. Will have to investigate if it rears its ugly head again |
7 | local lanes = require "lanes".configure{ with_timers = false, nb_keepers = 1, keepers_gc_threshold = 500} | 7 | local lanes = require "lanes".configure{ nb_user_keepers = 3, keepers_gc_threshold = 500 } |
8 | 8 | ||
9 | do | 9 | do |
10 | print "Linda names test:" | 10 | print "Linda names test:" |
11 | local unnamedLinda = lanes.linda() | 11 | local unnamedLinda = lanes.linda(1) |
12 | local unnamedLinda2 = lanes.linda("") | 12 | local unnamedLinda2 = lanes.linda("", 2) |
13 | local veeeerrrryyyylooongNamedLinda= lanes.linda( "veeeerrrryyyylooongNamedLinda", 1) | 13 | local veeeerrrryyyylooongNamedLinda= lanes.linda( "veeeerrrryyyylooongNamedLinda", 3) |
14 | print(unnamedLinda, unnamedLinda2, veeeerrrryyyylooongNamedLinda) | 14 | print(unnamedLinda, unnamedLinda2, veeeerrrryyyylooongNamedLinda) |
15 | print "GC deadlock test start" | 15 | print "GC deadlock test start" |
16 | -- store a linda in another linda (-> in a keeper) | 16 | -- store a linda in another linda (-> in a keeper) |
17 | unnamedLinda:set("here", lanes.linda("temporary linda")) | 17 | unnamedLinda:set("here", lanes.linda("temporary linda", 0)) |
18 | -- repeatedly add and remove stuff in the linda so that a GC happens during the keeper operation | 18 | -- repeatedly add and remove stuff in the linda so that a GC happens during the keeper operation |
19 | for i = 1, 1000 do | 19 | for i = 1, 100 do |
20 | for j = 1, 1000 do -- send 1000 tables | 20 | for j = 1, 1000 do -- send 1000 tables |
21 | -- print("send #" .. j) | 21 | -- print("send #" .. j) |
22 | unnamedLinda:send("here", {"a", "table", "with", "some", "stuff"}) | 22 | unnamedLinda:send("here", {"a", "table", "with", "some", "stuff"}) |
diff --git a/tests/nameof.lua b/tests/nameof.lua index 5c4f1b1..58df3e2 100644 --- a/tests/nameof.lua +++ b/tests/nameof.lua | |||
@@ -1,4 +1,4 @@ | |||
1 | local lanes = require "lanes".configure{on_state_create = function() end} | 1 | local lanes = require "lanes".configure{nb_user_keepers = 100, on_state_create = function() end} |
2 | 2 | ||
3 | print("Name of table: ", lanes.nameof({})) | 3 | print("Name of table: ", lanes.nameof({})) |
4 | print("Name of string.sub: ", lanes.nameof(string.sub)) | 4 | print("Name of string.sub: ", lanes.nameof(string.sub)) |
diff --git a/tests/nb_keepers.lua b/tests/nb_keepers.lua new file mode 100644 index 0000000..575138c --- /dev/null +++ b/tests/nb_keepers.lua | |||
@@ -0,0 +1,20 @@ | |||
1 | -- 2 keepers in addition to the one reserved for the timer linda | ||
2 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{nb_user_keepers = 2} | ||
3 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) | ||
4 | local lanes = require_lanes_result_1 | ||
5 | |||
6 | local require_assert_result_1, require_assert_result_2 = require "assert" -- assert.fails() | ||
7 | print("require_assert_result:", require_assert_result_1, require_assert_result_2) | ||
8 | |||
9 | local createLinda = function(...) | ||
10 | return lanes.linda(...) | ||
11 | end | ||
12 | |||
13 | -- should succeed | ||
14 | assert.failsnot(function() createLinda("one", 1) end) | ||
15 | assert.failsnot(function() createLinda("two", 2) end) | ||
16 | -- should fail | ||
17 | assert.fails(function() createLinda("none") end) | ||
18 | assert.fails(function() createLinda("zero", 0) end) | ||
19 | assert.fails(function() createLinda("three", 3) end) | ||
20 | print "TEST OK" | ||
diff --git a/tests/parallel_os_calls.lua b/tests/parallel_os_calls.lua index 7119272..596053c 100644 --- a/tests/parallel_os_calls.lua +++ b/tests/parallel_os_calls.lua | |||
@@ -1,4 +1,4 @@ | |||
1 | local lanes = require "lanes".configure{with_timers = false, nb_keepers = 1} | 1 | local lanes = require "lanes".configure() |
2 | print( os.date()) | 2 | print( os.date()) |
3 | local linda = lanes.linda() | 3 | local linda = lanes.linda() |
4 | local l1 = lanes.gen("os,base", function() print "start sleeping" linda:receive(10, "null") print("finished_sleeping " .. os.date()) end)() | 4 | local l1 = lanes.gen("os,base", function() print "start sleeping" linda:receive(10, "null") print("finished_sleeping " .. os.date()) end)() |