diff options
Diffstat (limited to 'src/linda.cpp')
-rw-r--r-- | src/linda.cpp | 190 |
1 files changed, 79 insertions, 111 deletions
diff --git a/src/linda.cpp b/src/linda.cpp index a19b126..0a1ca3f 100644 --- a/src/linda.cpp +++ b/src/linda.cpp | |||
@@ -30,21 +30,22 @@ THE SOFTWARE. | |||
30 | =============================================================================== | 30 | =============================================================================== |
31 | */ | 31 | */ |
32 | 32 | ||
33 | #include "linda.h" | ||
34 | |||
33 | #include "compat.h" | 35 | #include "compat.h" |
34 | #include "deep.h" | ||
35 | #include "keeper.h" | 36 | #include "keeper.h" |
36 | #include "lanes_private.h" | 37 | #include "lanes_private.h" |
37 | #include "threading.h" | 38 | #include "threading.h" |
38 | #include "tools.h" | 39 | #include "tools.h" |
39 | #include "universe.h" | 40 | #include "universe.h" |
40 | 41 | ||
41 | #include <array> | ||
42 | #include <functional> | 42 | #include <functional> |
43 | #include <variant> | ||
44 | 43 | ||
45 | // xxh64 of string "CANCEL_ERROR" generated at https://www.pelock.com/products/hash-calculator | 44 | // xxh64 of string "CANCEL_ERROR" generated at https://www.pelock.com/products/hash-calculator |
46 | static constexpr UniqueKey BATCH_SENTINEL{ 0x2DDFEE0968C62AA7ull, "linda.batched" }; | 45 | static constexpr UniqueKey BATCH_SENTINEL{ 0x2DDFEE0968C62AA7ull, "linda.batched" }; |
47 | 46 | ||
47 | // ################################################################################################# | ||
48 | |||
48 | class LindaFactory : public DeepFactory | 49 | class LindaFactory : public DeepFactory |
49 | { | 50 | { |
50 | private: | 51 | private: |
@@ -58,117 +59,84 @@ class LindaFactory : public DeepFactory | |||
58 | static LindaFactory g_LindaFactory; | 59 | static LindaFactory g_LindaFactory; |
59 | 60 | ||
60 | // ################################################################################################# | 61 | // ################################################################################################# |
62 | // ################################################################################################# | ||
61 | 63 | ||
62 | /* | 64 | // Any hashing will do that maps pointers to [0..Universe::nb_keepers[ consistently. |
63 | * Actual data is kept within a keeper state, which is hashed by the 'Linda' | 65 | // Pointers are often aligned by 8 or so - ignore the low order bits |
64 | * pointer (which is same to all userdatas pointing to it). | 66 | // have to cast to unsigned long to avoid compilation warnings about loss of data when converting pointer-to-integer |
65 | */ | 67 | static constexpr uintptr_t KEEPER_MAGIC_SHIFT{ 3 }; |
66 | class Linda : public DeepPrelude // Deep userdata MUST start with this header | 68 | |
69 | Linda::Linda(Universe* U_, LindaGroup group_, char const* name_, size_t len_) | ||
70 | : DeepPrelude{ g_LindaFactory } | ||
71 | , U{ U_ } | ||
72 | , m_keeper_index{ (group_ ? group_ : static_cast<int>(std::bit_cast<uintptr_t>(this) >> KEEPER_MAGIC_SHIFT)) % U_->keepers->nb_keepers } | ||
67 | { | 73 | { |
68 | private: | 74 | setName(name_, len_); |
75 | } | ||
69 | 76 | ||
70 | static constexpr size_t kEmbeddedNameLength = 24; | 77 | // ################################################################################################# |
71 | using EmbeddedName = std::array<char, kEmbeddedNameLength>; | ||
72 | struct AllocatedName | ||
73 | { | ||
74 | size_t len{ 0 }; | ||
75 | char* name{ nullptr }; | ||
76 | }; | ||
77 | // depending on the name length, it is either embedded inside the Linda, or allocated separately | ||
78 | std::variant<AllocatedName, EmbeddedName> m_name; | ||
79 | |||
80 | public: | ||
81 | |||
82 | std::condition_variable m_read_happened; | ||
83 | std::condition_variable m_write_happened; | ||
84 | Universe* const U; // the universe this linda belongs to | ||
85 | uintptr_t const group; // a group to control keeper allocation between lindas | ||
86 | CancelRequest simulate_cancel{ CancelRequest::None }; | ||
87 | |||
88 | public: | ||
89 | |||
90 | // a fifo full userdata has one uservalue, the table that holds the actual fifo contents | ||
91 | [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internal_allocator.alloc(size_); } | ||
92 | // always embedded somewhere else or "in-place constructed" as a full userdata | ||
93 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception | ||
94 | static void operator delete(void* p_, Universe* U_) { U_->internal_allocator.free(p_, sizeof(Linda)); } | ||
95 | // this one is for us, to make sure memory is freed by the correct allocator | ||
96 | static void operator delete(void* p_) { static_cast<Linda*>(p_)->U->internal_allocator.free(p_, sizeof(Linda)); } | ||
97 | |||
98 | Linda(Universe* U_, uintptr_t group_, char const* name_, size_t len_) | ||
99 | : DeepPrelude{ g_LindaFactory } | ||
100 | , U{ U_ } | ||
101 | , group{ group_ << KEEPER_MAGIC_SHIFT } | ||
102 | { | ||
103 | setName(name_, len_); | ||
104 | } | ||
105 | 78 | ||
106 | ~Linda() | 79 | Linda::~Linda() |
80 | { | ||
81 | if (std::holds_alternative<AllocatedName>(m_name)) | ||
107 | { | 82 | { |
108 | if (std::holds_alternative<AllocatedName>(m_name)) | 83 | AllocatedName& name = std::get<AllocatedName>(m_name); |
109 | { | 84 | U->internal_allocator.free(name.name, name.len); |
110 | AllocatedName& name = std::get<AllocatedName>(m_name); | ||
111 | U->internal_allocator.free(name.name, name.len); | ||
112 | } | ||
113 | } | 85 | } |
86 | } | ||
114 | 87 | ||
115 | static int ProtectedCall(lua_State* L, lua_CFunction f_); | 88 | // ################################################################################################# |
116 | |||
117 | private : | ||
118 | 89 | ||
119 | void setName(char const* name_, size_t len_) | 90 | void Linda::setName(char const* name_, size_t len_) |
91 | { | ||
92 | // keep default | ||
93 | if (!name_ || len_ == 0) | ||
120 | { | 94 | { |
121 | // keep default | 95 | return; |
122 | if (!name_ || len_ == 0) | ||
123 | { | ||
124 | return; | ||
125 | } | ||
126 | ++len_; // don't forget terminating 0 | ||
127 | if (len_ < kEmbeddedNameLength) | ||
128 | { | ||
129 | m_name.emplace<EmbeddedName>(); | ||
130 | char* const name{ std::get<EmbeddedName>(m_name).data() }; | ||
131 | memcpy(name, name_, len_); | ||
132 | } | ||
133 | else | ||
134 | { | ||
135 | AllocatedName& name = std::get<AllocatedName>(m_name); | ||
136 | name.name = static_cast<char*>(U->internal_allocator.alloc(len_)); | ||
137 | name.len = len_; | ||
138 | memcpy(name.name, name_, len_); | ||
139 | } | ||
140 | } | 96 | } |
97 | ++len_; // don't forget terminating 0 | ||
98 | if (len_ < kEmbeddedNameLength) | ||
99 | { | ||
100 | m_name.emplace<EmbeddedName>(); | ||
101 | char* const name{ std::get<EmbeddedName>(m_name).data() }; | ||
102 | memcpy(name, name_, len_); | ||
103 | } | ||
104 | else | ||
105 | { | ||
106 | AllocatedName& name = std::get<AllocatedName>(m_name); | ||
107 | name.name = static_cast<char*>(U->internal_allocator.alloc(len_)); | ||
108 | name.len = len_; | ||
109 | memcpy(name.name, name_, len_); | ||
110 | } | ||
111 | } | ||
141 | 112 | ||
142 | public: | 113 | // ################################################################################################# |
143 | |||
144 | uintptr_t hashSeed() const { return group ? group : std::bit_cast<uintptr_t>(this); } | ||
145 | 114 | ||
146 | char const* getName() const | 115 | char const* Linda::getName() const |
116 | { | ||
117 | if (std::holds_alternative<AllocatedName>(m_name)) | ||
147 | { | 118 | { |
148 | if (std::holds_alternative<AllocatedName>(m_name)) | 119 | AllocatedName const& name = std::get<AllocatedName>(m_name); |
149 | { | 120 | return name.name; |
150 | AllocatedName const& name = std::get<AllocatedName>(m_name); | ||
151 | return name.name; | ||
152 | } | ||
153 | if (std::holds_alternative<EmbeddedName>(m_name)) | ||
154 | { | ||
155 | char const* const name{ std::get<EmbeddedName>(m_name).data() }; | ||
156 | return name; | ||
157 | } | ||
158 | return nullptr; | ||
159 | } | 121 | } |
160 | }; | 122 | if (std::holds_alternative<EmbeddedName>(m_name)) |
123 | { | ||
124 | char const* const name{ std::get<EmbeddedName>(m_name).data() }; | ||
125 | return name; | ||
126 | } | ||
127 | return nullptr; | ||
128 | } | ||
161 | 129 | ||
162 | // ################################################################################################# | 130 | // ################################################################################################# |
163 | 131 | ||
164 | template <bool OPT> | 132 | template <bool OPT> |
165 | [[nodiscard]] static inline Linda* ToLinda(lua_State* L, int idx_) | 133 | [[nodiscard]] static inline Linda* ToLinda(lua_State* L_, int idx_) |
166 | { | 134 | { |
167 | Linda* const linda{ static_cast<Linda*>(g_LindaFactory.toDeep(L, idx_)) }; | 135 | Linda* const linda{ static_cast<Linda*>(g_LindaFactory.toDeep(L_, idx_)) }; |
168 | if constexpr (!OPT) | 136 | if constexpr (!OPT) |
169 | { | 137 | { |
170 | luaL_argcheck(L, linda != nullptr, idx_, "expecting a linda object"); | 138 | luaL_argcheck(L_, linda != nullptr, idx_, "expecting a linda object"); // doesn't return if linda is nullptr |
171 | LUA_ASSERT(L, linda->U == universe_get(L)); | 139 | LUA_ASSERT(L_, linda->U == universe_get(L_)); |
172 | } | 140 | } |
173 | return linda; | 141 | return linda; |
174 | } | 142 | } |
@@ -213,7 +181,7 @@ int Linda::ProtectedCall(lua_State* L, lua_CFunction f_) | |||
213 | Linda* const linda{ ToLinda<false>(L, 1) }; | 181 | Linda* const linda{ ToLinda<false>(L, 1) }; |
214 | 182 | ||
215 | // acquire the keeper | 183 | // acquire the keeper |
216 | Keeper* const K{ keeper_acquire(linda->U->keepers, linda->hashSeed()) }; | 184 | Keeper* const K{ linda->acquireKeeper() }; |
217 | lua_State* const KL{ K ? K->L : nullptr }; | 185 | lua_State* const KL{ K ? K->L : nullptr }; |
218 | if (KL == nullptr) | 186 | if (KL == nullptr) |
219 | return 0; | 187 | return 0; |
@@ -229,7 +197,7 @@ int Linda::ProtectedCall(lua_State* L, lua_CFunction f_) | |||
229 | lua_settop(KL, 0); | 197 | lua_settop(KL, 0); |
230 | 198 | ||
231 | // release the keeper | 199 | // release the keeper |
232 | keeper_release(K); | 200 | linda->releaseKeeper(K); |
233 | 201 | ||
234 | // if there was an error, forward it | 202 | // if there was an error, forward it |
235 | if (rc != LUA_OK) | 203 | if (rc != LUA_OK) |
@@ -306,7 +274,7 @@ LUAG_FUNC(linda_send) | |||
306 | KeeperCallResult pushed; | 274 | KeeperCallResult pushed; |
307 | { | 275 | { |
308 | Lane* const lane{ LANE_POINTER_REGKEY.readLightUserDataValue<Lane>(L) }; | 276 | Lane* const lane{ LANE_POINTER_REGKEY.readLightUserDataValue<Lane>(L) }; |
309 | Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; | 277 | Keeper* const K{ linda->whichKeeper() }; |
310 | KeeperState const KL{ K ? K->L : nullptr }; | 278 | KeeperState const KL{ K ? K->L : nullptr }; |
311 | if (KL == nullptr) | 279 | if (KL == nullptr) |
312 | return 0; | 280 | return 0; |
@@ -472,7 +440,7 @@ LUAG_FUNC(linda_receive) | |||
472 | } | 440 | } |
473 | 441 | ||
474 | Lane* const lane{ LANE_POINTER_REGKEY.readLightUserDataValue<Lane>(L) }; | 442 | Lane* const lane{ LANE_POINTER_REGKEY.readLightUserDataValue<Lane>(L) }; |
475 | Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; | 443 | Keeper* const K{ linda->whichKeeper() }; |
476 | KeeperState const KL{ K ? K->L : nullptr }; | 444 | KeeperState const KL{ K ? K->L : nullptr }; |
477 | if (KL == nullptr) | 445 | if (KL == nullptr) |
478 | return 0; | 446 | return 0; |
@@ -584,7 +552,7 @@ LUAG_FUNC(linda_set) | |||
584 | // make sure the key is of a valid type (throws an error if not the case) | 552 | // make sure the key is of a valid type (throws an error if not the case) |
585 | check_key_types(L, 2, 2); | 553 | check_key_types(L, 2, 2); |
586 | 554 | ||
587 | Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; | 555 | Keeper* const K{ linda->whichKeeper() }; |
588 | KeeperCallResult pushed; | 556 | KeeperCallResult pushed; |
589 | if (linda->simulate_cancel == CancelRequest::None) | 557 | if (linda->simulate_cancel == CancelRequest::None) |
590 | { | 558 | { |
@@ -639,7 +607,7 @@ LUAG_FUNC(linda_count) | |||
639 | // make sure the keys are of a valid type | 607 | // make sure the keys are of a valid type |
640 | check_key_types(L, 2, lua_gettop(L)); | 608 | check_key_types(L, 2, lua_gettop(L)); |
641 | 609 | ||
642 | Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; | 610 | Keeper* const K{ linda->whichKeeper() }; |
643 | KeeperCallResult const pushed{ keeper_call(linda->U, K->L, KEEPER_API(count), L, linda, 2) }; | 611 | KeeperCallResult const pushed{ keeper_call(linda->U, K->L, KEEPER_API(count), L, linda, 2) }; |
644 | return OptionalValue(pushed, L, "tried to count an invalid key"); | 612 | return OptionalValue(pushed, L, "tried to count an invalid key"); |
645 | }; | 613 | }; |
@@ -667,7 +635,7 @@ LUAG_FUNC(linda_get) | |||
667 | KeeperCallResult pushed; | 635 | KeeperCallResult pushed; |
668 | if (linda->simulate_cancel == CancelRequest::None) | 636 | if (linda->simulate_cancel == CancelRequest::None) |
669 | { | 637 | { |
670 | Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; | 638 | Keeper* const K{ linda->whichKeeper() }; |
671 | pushed = keeper_call(linda->U, K->L, KEEPER_API(get), L, linda, 2); | 639 | pushed = keeper_call(linda->U, K->L, KEEPER_API(get), L, linda, 2); |
672 | if (pushed.value_or(0) > 0) | 640 | if (pushed.value_or(0) > 0) |
673 | { | 641 | { |
@@ -709,7 +677,7 @@ LUAG_FUNC(linda_limit) | |||
709 | KeeperCallResult pushed; | 677 | KeeperCallResult pushed; |
710 | if (linda->simulate_cancel == CancelRequest::None) | 678 | if (linda->simulate_cancel == CancelRequest::None) |
711 | { | 679 | { |
712 | Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; | 680 | Keeper* const K{ linda->whichKeeper() }; |
713 | pushed = keeper_call(linda->U, K->L, KEEPER_API(limit), L, linda, 2); | 681 | pushed = keeper_call(linda->U, K->L, KEEPER_API(limit), L, linda, 2); |
714 | LUA_ASSERT(L, pushed.has_value() && (pushed.value() == 0 || pushed.value() == 1)); // no error, optional boolean value saying if we should wake blocked writer threads | 682 | LUA_ASSERT(L, pushed.has_value() && (pushed.value() == 0 || pushed.value() == 1)); // no error, optional boolean value saying if we should wake blocked writer threads |
715 | if (pushed.value() == 1) | 683 | if (pushed.value() == 1) |
@@ -863,7 +831,7 @@ LUAG_FUNC(linda_dump) | |||
863 | auto dump = [](lua_State* L) | 831 | auto dump = [](lua_State* L) |
864 | { | 832 | { |
865 | Linda* const linda{ ToLinda<false>(L, 1) }; | 833 | Linda* const linda{ ToLinda<false>(L, 1) }; |
866 | return keeper_push_linda_storage(linda->U, DestState{ L }, linda, linda->hashSeed()); | 834 | return keeper_push_linda_storage(*linda, DestState{ L }); |
867 | }; | 835 | }; |
868 | return Linda::ProtectedCall(L, dump); | 836 | return Linda::ProtectedCall(L, dump); |
869 | } | 837 | } |
@@ -871,13 +839,13 @@ LUAG_FUNC(linda_dump) | |||
871 | // ################################################################################################# | 839 | // ################################################################################################# |
872 | 840 | ||
873 | /* | 841 | /* |
874 | * table = linda:dump() | 842 | * table/string = linda:__towatch() |
875 | * return a table listing all pending data inside the linda | 843 | * return a table listing all pending data inside the linda, or the stringified linda if empty |
876 | */ | 844 | */ |
877 | LUAG_FUNC(linda_towatch) | 845 | LUAG_FUNC(linda_towatch) |
878 | { | 846 | { |
879 | Linda* const linda{ ToLinda<false>(L, 1) }; | 847 | Linda* const linda{ ToLinda<false>(L, 1) }; |
880 | int pushed{ keeper_push_linda_storage(linda->U, DestState{ L }, linda, linda->hashSeed()) }; | 848 | int pushed{ keeper_push_linda_storage(*linda, DestState{ L }) }; |
881 | if (pushed == 0) | 849 | if (pushed == 0) |
882 | { | 850 | { |
883 | // if the linda is empty, don't return nil | 851 | // if the linda is empty, don't return nil |
@@ -892,7 +860,7 @@ DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* L) const | |||
892 | { | 860 | { |
893 | size_t name_len = 0; | 861 | size_t name_len = 0; |
894 | char const* linda_name = nullptr; | 862 | char const* linda_name = nullptr; |
895 | unsigned long linda_group = 0; | 863 | LindaGroup linda_group{ 0 }; |
896 | // should have a string and/or a number of the stack as parameters (name and group) | 864 | // should have a string and/or a number of the stack as parameters (name and group) |
897 | switch (lua_gettop(L)) | 865 | switch (lua_gettop(L)) |
898 | { | 866 | { |
@@ -906,13 +874,13 @@ DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* L) const | |||
906 | } | 874 | } |
907 | else | 875 | else |
908 | { | 876 | { |
909 | linda_group = (unsigned long) lua_tointeger(L, -1); | 877 | linda_group = LindaGroup{ static_cast<int>(lua_tointeger(L, -1)) }; |
910 | } | 878 | } |
911 | break; | 879 | break; |
912 | 880 | ||
913 | case 2: // 2 parameters, a name and group, in that order | 881 | case 2: // 2 parameters, a name and group, in that order |
914 | linda_name = lua_tolstring(L, -2, &name_len); | 882 | linda_name = lua_tolstring(L, -2, &name_len); |
915 | linda_group = (unsigned long) lua_tointeger(L, -1); | 883 | linda_group = LindaGroup{ static_cast<int>(lua_tointeger(L, -1)) }; |
916 | break; | 884 | break; |
917 | } | 885 | } |
918 | 886 | ||
@@ -932,7 +900,7 @@ void LindaFactory::deleteDeepObjectInternal(lua_State* L, DeepPrelude* o_) const | |||
932 | { | 900 | { |
933 | Linda* const linda{ static_cast<Linda*>(o_) }; | 901 | Linda* const linda{ static_cast<Linda*>(o_) }; |
934 | LUA_ASSERT(L, linda); | 902 | LUA_ASSERT(L, linda); |
935 | Keeper* const myK{ which_keeper(linda->U->keepers, linda->hashSeed()) }; | 903 | Keeper* const myK{ linda->whichKeeper() }; |
936 | // if collected after the universe, keepers are already destroyed, and there is nothing to clear | 904 | // if collected after the universe, keepers are already destroyed, and there is nothing to clear |
937 | if (myK) | 905 | if (myK) |
938 | { | 906 | { |
@@ -940,13 +908,13 @@ void LindaFactory::deleteDeepObjectInternal(lua_State* L, DeepPrelude* o_) const | |||
940 | // because we are already inside a protected area, and trying to do so would deadlock! | 908 | // because we are already inside a protected area, and trying to do so would deadlock! |
941 | bool const need_acquire_release{ myK->L != L }; | 909 | bool const need_acquire_release{ myK->L != L }; |
942 | // Clean associated structures in the keeper state. | 910 | // Clean associated structures in the keeper state. |
943 | Keeper* const K{ need_acquire_release ? keeper_acquire(linda->U->keepers, linda->hashSeed()) : myK }; | 911 | Keeper* const K{ need_acquire_release ? linda->acquireKeeper() : myK }; |
944 | // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... | 912 | // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... |
945 | [[maybe_unused]] KeeperCallResult const result{ keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0) }; | 913 | [[maybe_unused]] KeeperCallResult const result{ keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0) }; |
946 | LUA_ASSERT(L, result.has_value() && result.value() == 0); | 914 | LUA_ASSERT(L, result.has_value() && result.value() == 0); |
947 | if (need_acquire_release) | 915 | if (need_acquire_release) |
948 | { | 916 | { |
949 | keeper_release(K); | 917 | linda->releaseKeeper(K); |
950 | } | 918 | } |
951 | } | 919 | } |
952 | 920 | ||