aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-05-30 17:57:21 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-05-30 17:57:21 +0200
commit731556711e453a501f1d1d06a6013b8fbd53414e (patch)
tree4c5c28cd83de320fcf4c9b4c749f2e6e8d5bef48
parenta156aaeb07fada043b308409dcffcae1726eec0b (diff)
downloadlanes-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.html29
-rw-r--r--src/keeper.cpp236
-rw-r--r--src/keeper.h53
-rw-r--r--src/lane.cpp2
-rw-r--r--src/lanes.cpp43
-rw-r--r--src/lanes.lua10
-rw-r--r--src/lanesconf.h2
-rw-r--r--src/linda.cpp25
-rw-r--r--src/linda.h3
-rw-r--r--src/universe.cpp210
-rw-r--r--src/universe.h7
-rw-r--r--tests/fibonacci.lua32
-rw-r--r--tests/irayo_recursive.lua2
-rw-r--r--tests/keeper.lua14
-rw-r--r--tests/nameof.lua2
-rw-r--r--tests/nb_keepers.lua20
-rw-r--r--tests/parallel_os_calls.lua2
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>"&lt;unnamed&gt;"</tt> if unset, <tt>"&lt;closed&gt;"</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>"&lt;unnamed&gt;"</tt> if unset, <tt>"&lt;closed&gt;"</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: &lt;opt_name&gt;"</tt></li> 1190 <li><tt>tostring(linda)</tt> returns a string of the form <tt>"Linda: &lt;opt_name&gt;"</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>
1363events 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
549Keeper* Linda::acquireKeeper() const 550Keeper* 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
563void Linda::releaseKeeper(Keeper* K_) const 562void 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
636void Keepers::CreateFifosTable(lua_State* L_) 639void* 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
648void 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
659void 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
681void 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
768void 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
18class Linda; 20class Linda;
@@ -21,22 +23,63 @@ class Universe;
21 23
22using KeeperState = Unique<lua_State*>; 24using KeeperState = Unique<lua_State*>;
23 25
26// #################################################################################################
27
24struct Keeper 28struct 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
31struct Keepers 49struct 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
41static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" }; 84static 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
91local default_params = 91local 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
115local param_checkers = 115local 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
115static constexpr uintptr_t kPointerMagicShift{ 3 };
116
117Linda::Linda(Universe* U_, LindaGroup group_, std::string_view const& name_) 112Linda::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
753namespace { 751namespace {
@@ -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 {
784LUAG_FUNC(linda) 784LUAG_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
41extern 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
125void 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?
157void Universe::initializeAllocatorFunction(lua_State* const L_) 176void 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 */
238void 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
334void Universe::terminateFreeRunningLanes(lua_State* const L_, lua_Duration const shutdownTimeout_, CancelOp const op_) 246void 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
21enum class CancelOp; 23enum class CancelOp;
22struct DeepPrelude; 24struct DeepPrelude;
23struct Keepers;
24class Lane; 25class 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--
15local lanes = require "lanes".configure{ nb_keepers =1, with_timers = false} 15local lanes = require "lanes".configure()
16 16
17local function WR(str) 17local 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 }
27set_debug_threadname = function ( ...) 27set_debug_threadname = function ( ...)
28end 28end
29 29
30 -- 30 --
31-- uint= fib( n_uint ) 31-- uint= fib( n_uint )
32-- 32--
33local function fib( n ) 33local function fib( n )
@@ -72,20 +72,20 @@ end
72-- 72--
73local right= 73local 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}
90assert( #right==99 ) 90assert( #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 @@
1local lanes = require "lanes".configure{ nb_keepers = 1, with_timers = false} 1local 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
7local lanes = require "lanes".configure{ with_timers = false, nb_keepers = 1, keepers_gc_threshold = 500} 7local lanes = require "lanes".configure{ nb_user_keepers = 3, keepers_gc_threshold = 500 }
8 8
9do 9do
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 @@
1local lanes = require "lanes".configure{on_state_create = function() end} 1local lanes = require "lanes".configure{nb_user_keepers = 100, on_state_create = function() end}
2 2
3print("Name of table: ", lanes.nameof({})) 3print("Name of table: ", lanes.nameof({}))
4print("Name of string.sub: ", lanes.nameof(string.sub)) 4print("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
2local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{nb_user_keepers = 2}
3print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
4local lanes = require_lanes_result_1
5
6local require_assert_result_1, require_assert_result_2 = require "assert" -- assert.fails()
7print("require_assert_result:", require_assert_result_1, require_assert_result_2)
8
9local createLinda = function(...)
10 return lanes.linda(...)
11end
12
13-- should succeed
14assert.failsnot(function() createLinda("one", 1) end)
15assert.failsnot(function() createLinda("two", 2) end)
16-- should fail
17assert.fails(function() createLinda("none") end)
18assert.fails(function() createLinda("zero", 0) end)
19assert.fails(function() createLinda("three", 3) end)
20print "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 @@
1local lanes = require "lanes".configure{with_timers = false, nb_keepers = 1} 1local lanes = require "lanes".configure()
2print( os.date()) 2print( os.date())
3local linda = lanes.linda() 3local linda = lanes.linda()
4local l1 = lanes.gen("os,base", function() print "start sleeping" linda:receive(10, "null") print("finished_sleeping " .. os.date()) end)() 4local l1 = lanes.gen("os,base", function() print "start sleeping" linda:receive(10, "null") print("finished_sleeping " .. os.date()) end)()