aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cancel.h2
-rw-r--r--src/keeper.cpp28
-rw-r--r--src/keeper.h6
-rw-r--r--src/linda.cpp147
-rw-r--r--src/macros_and_utils.h44
-rw-r--r--src/uniquekey.h5
-rw-r--r--tests/errhangtest.lua74
7 files changed, 200 insertions, 106 deletions
diff --git a/src/cancel.h b/src/cancel.h
index 060edb3..10a9804 100644
--- a/src/cancel.h
+++ b/src/cancel.h
@@ -47,7 +47,7 @@ enum class CancelOp
47}; 47};
48 48
49// crc64/we of string "CANCEL_ERROR" generated at http://www.nitrxgen.net/hashgen/ 49// crc64/we of string "CANCEL_ERROR" generated at http://www.nitrxgen.net/hashgen/
50static constexpr UniqueKey CANCEL_ERROR{ 0xe97d41626cc97577ull }; // 'raise_cancel_error' sentinel 50static constexpr UniqueKey CANCEL_ERROR{ 0xe97d41626cc97577ull, "lanes.cancel_error" }; // 'raise_cancel_error' sentinel
51 51
52[[nodiscard]] CancelOp which_cancel_op(char const* op_string_); 52[[nodiscard]] CancelOp which_cancel_op(char const* op_string_);
53[[nodiscard]] CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hook_count_, lua_Duration secs_, bool wake_lindas_); 53[[nodiscard]] CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hook_count_, lua_Duration secs_, bool wake_lindas_);
diff --git a/src/keeper.cpp b/src/keeper.cpp
index f56c50c..61321e1 100644
--- a/src/keeper.cpp
+++ b/src/keeper.cpp
@@ -418,7 +418,7 @@ int keepercall_limit(lua_State* L)
418// ################################################################################################## 418// ##################################################################################################
419 419
420// in: linda_ud key [[val] ...] 420// in: linda_ud key [[val] ...]
421//out: true or nil 421//out: true if the linda was full but it's no longer the case, else nothing
422int keepercall_set(lua_State* L) 422int keepercall_set(lua_State* L)
423{ 423{
424 bool should_wake_writers{ false }; 424 bool should_wake_writers{ false };
@@ -826,35 +826,35 @@ void keeper_toggle_nil_sentinels(lua_State* L, int val_i_, LookupMode const mode
826* 'linda': deep Linda pointer (used only as a unique table key, first parameter) 826* 'linda': deep Linda pointer (used only as a unique table key, first parameter)
827* 'starting_index': first of the rest of parameters (none if 0) 827* 'starting_index': first of the rest of parameters (none if 0)
828* 828*
829* Returns: number of return values (pushed to 'L') or -1 in case of error 829* Returns: number of return values (pushed to 'L'), unset in case of error
830*/ 830*/
831int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, void* linda, int starting_index) 831KeeperCallResult keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, void* linda, int starting_index)
832{ 832{
833 KeeperCallResult result;
833 int const args{ starting_index ? (lua_gettop(L) - starting_index + 1) : 0 }; 834 int const args{ starting_index ? (lua_gettop(L) - starting_index + 1) : 0 };
834 int const Ktos{ lua_gettop(K) }; 835 int const top_K{ lua_gettop(K) };
835 int retvals = -1;
836 836
837 STACK_GROW(K, 2); 837 STACK_GROW(K, 2);
838 838
839 PUSH_KEEPER_FUNC(K, func_); 839 PUSH_KEEPER_FUNC(K, func_); // func_
840 840
841 lua_pushlightuserdata(K, linda); 841 lua_pushlightuserdata(K, linda); // func_ linda
842 842
843 if ((args == 0) || luaG_inter_copy(U, Source{ L }, Dest{ K }, args, LookupMode::ToKeeper) == InterCopyResult::Success) // L->K 843 if ((args == 0) || luaG_inter_copy(U, Source{ L }, Dest{ K }, args, LookupMode::ToKeeper) == InterCopyResult::Success) // func_ linda args...
844 { 844 {
845 lua_call(K, 1 + args, LUA_MULTRET); 845 lua_call(K, 1 + args, LUA_MULTRET); // result...
846 retvals = lua_gettop(K) - Ktos; 846 int const retvals{ lua_gettop(K) - top_K };
847 // note that this can raise a luaL_error while the keeper state (and its mutex) is acquired 847 // note that this can raise a luaL_error while the keeper state (and its mutex) is acquired
848 // this may interrupt a lane, causing the destruction of the underlying OS thread 848 // this may interrupt a lane, causing the destruction of the underlying OS thread
849 // after this, another lane making use of this keeper can get an error code from the mutex-locking function 849 // after this, another lane making use of this keeper can get an error code from the mutex-locking function
850 // when attempting to grab the mutex again (WINVER <= 0x400 does this, but locks just fine, I don't know about pthread) 850 // when attempting to grab the mutex again (WINVER <= 0x400 does this, but locks just fine, I don't know about pthread)
851 if ((retvals > 0) && luaG_inter_move(U, Source{ K }, Dest{ L }, retvals, LookupMode::FromKeeper) != InterCopyResult::Success) // K->L 851 if ((retvals == 0) || (luaG_inter_move(U, Source{ K }, Dest{ L }, retvals, LookupMode::FromKeeper) == InterCopyResult::Success)) // K->L
852 { 852 {
853 retvals = -1; 853 result.emplace(retvals);
854 } 854 }
855 } 855 }
856 // whatever happens, restore the stack to where it was at the origin 856 // whatever happens, restore the stack to where it was at the origin
857 lua_settop(K, Ktos); 857 lua_settop(K, top_K);
858 858
859 // 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 859 // 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
860 if (func_ != KEEPER_API(clear)) [[unlikely]] 860 if (func_ != KEEPER_API(clear)) [[unlikely]]
@@ -880,5 +880,5 @@ int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, voi
880 } 880 }
881 } 881 }
882 882
883 return retvals; 883 return result;
884} 884}
diff --git a/src/keeper.h b/src/keeper.h
index 627c7ea..7ec8b15 100644
--- a/src/keeper.h
+++ b/src/keeper.h
@@ -11,6 +11,7 @@ extern "C" {
11#include "threading.h" 11#include "threading.h"
12#include "uniquekey.h" 12#include "uniquekey.h"
13 13
14#include <optional>
14#include <mutex> 15#include <mutex>
15 16
16// forwards 17// forwards
@@ -33,7 +34,7 @@ struct Keepers
33 34
34static constexpr uintptr_t KEEPER_MAGIC_SHIFT{ 3 }; 35static constexpr uintptr_t KEEPER_MAGIC_SHIFT{ 3 };
35// crc64/we of string "NIL_SENTINEL" generated at http://www.nitrxgen.net/hashgen/ 36// crc64/we of string "NIL_SENTINEL" generated at http://www.nitrxgen.net/hashgen/
36static constexpr UniqueKey NIL_SENTINEL{ 0x7eaafa003a1d11a1ull }; 37static constexpr UniqueKey NIL_SENTINEL{ 0x7eaafa003a1d11a1ull, "internal nil sentinel" };
37 38
38void init_keepers(Universe* U, lua_State* L); 39void init_keepers(Universe* U, lua_State* L);
39void close_keepers(Universe* U); 40void close_keepers(Universe* U);
@@ -57,4 +58,5 @@ using keeper_api_t = lua_CFunction;
57[[nodiscard]] int keepercall_set(lua_State* L); 58[[nodiscard]] int keepercall_set(lua_State* L);
58[[nodiscard]] int keepercall_count(lua_State* L); 59[[nodiscard]] int keepercall_count(lua_State* L);
59 60
60[[nodiscard]] int keeper_call(Universe* U, lua_State* K, keeper_api_t _func, lua_State* L, void* linda, int starting_index); 61using KeeperCallResult = Unique<std::optional<int>>;
62[[nodiscard]] KeeperCallResult keeper_call(Universe* U, lua_State* K, keeper_api_t _func, lua_State* L, void* linda, int starting_index);
diff --git a/src/linda.cpp b/src/linda.cpp
index e749f52..dbd6b21 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -39,8 +39,12 @@ THE SOFTWARE.
39#include "universe.h" 39#include "universe.h"
40 40
41#include <array> 41#include <array>
42#include <functional>
42#include <variant> 43#include <variant>
43 44
45// xxh64 of string "CANCEL_ERROR" generated at https://www.pelock.com/products/hash-calculator
46static constexpr UniqueKey BATCH_SENTINEL{ 0x2DDFEE0968C62AA7ull, "linda.batched" };
47
44/* 48/*
45* Actual data is kept within a keeper state, which is hashed by the 'Linda' 49* Actual data is kept within a keeper state, which is hashed by the 'Linda'
46* pointer (which is same to all userdatas pointing to it). 50* pointer (which is same to all userdatas pointing to it).
@@ -140,14 +144,14 @@ class Linda : public DeepPrelude // Deep userdata MUST start with this header
140[[nodiscard]] static void* linda_id(lua_State*, DeepOp); 144[[nodiscard]] static void* linda_id(lua_State*, DeepOp);
141 145
142template<bool OPT> 146template<bool OPT>
143[[nodiscard]] static inline Linda* lua_toLinda(lua_State* L, int idx_) 147[[nodiscard]] static inline Linda* ToLinda(lua_State* L, int idx_)
144{ 148{
145 Linda* const linda{ static_cast<Linda*>(luaG_todeep(L, linda_id, idx_)) }; 149 Linda* const linda{ static_cast<Linda*>(luaG_todeep(L, linda_id, idx_)) };
146 if (!OPT) 150 if constexpr (!OPT)
147 { 151 {
148 luaL_argcheck(L, linda != nullptr, idx_, "expecting a linda object"); 152 luaL_argcheck(L, linda != nullptr, idx_, "expecting a linda object");
153 ASSERT_L(linda->U == universe_get(L));
149 } 154 }
150 ASSERT_L(linda->U == universe_get(L));
151 return linda; 155 return linda;
152} 156}
153 157
@@ -157,10 +161,28 @@ static void check_key_types(lua_State* L, int start_, int end_)
157{ 161{
158 for (int i{ start_ }; i <= end_; ++i) 162 for (int i{ start_ }; i <= end_; ++i)
159 { 163 {
160 int const t{ lua_type(L, i) }; 164 LuaType const t{ lua_type_as_enum(L, i) };
161 if (t == LUA_TBOOLEAN || t == LUA_TNUMBER || t == LUA_TSTRING || t == LUA_TLIGHTUSERDATA) 165 switch (t)
162 { 166 {
167 case LuaType::BOOLEAN:
168 case LuaType::NUMBER:
169 case LuaType::STRING:
163 continue; 170 continue;
171
172 case LuaType::LIGHTUSERDATA:
173 {
174 // NIL_SENTINEL isn't publicly exposed, but it doesn't hurt to check
175 static constexpr std::array<std::reference_wrapper<UniqueKey const>, 3> to_check{ BATCH_SENTINEL, CANCEL_ERROR, NIL_SENTINEL };
176 for (UniqueKey const& key : to_check)
177 {
178 if (key.equals(L, i))
179 {
180 luaL_error(L, "argument #%d: can't use %s as a key", i, key.m_debugName); // doesn't return
181 break;
182 }
183 }
184 }
185 break;
164 } 186 }
165 luaL_error(L, "argument #%d: invalid key type (not a boolean, string, number or light userdata)", i); // doesn't return 187 luaL_error(L, "argument #%d: invalid key type (not a boolean, string, number or light userdata)", i); // doesn't return
166 } 188 }
@@ -170,7 +192,7 @@ static void check_key_types(lua_State* L, int start_, int end_)
170 192
171LUAG_FUNC(linda_protected_call) 193LUAG_FUNC(linda_protected_call)
172{ 194{
173 Linda* const linda{ lua_toLinda<false>(L, 1) }; 195 Linda* const linda{ ToLinda<false>(L, 1) };
174 196
175 // acquire the keeper 197 // acquire the keeper
176 Keeper* const K{ keeper_acquire(linda->U->keepers, linda->hashSeed()) }; 198 Keeper* const K{ keeper_acquire(linda->U->keepers, linda->hashSeed()) };
@@ -209,7 +231,7 @@ LUAG_FUNC(linda_protected_call)
209*/ 231*/
210LUAG_FUNC(linda_send) 232LUAG_FUNC(linda_send)
211{ 233{
212 Linda* const linda{ lua_toLinda<false>(L, 1) }; 234 Linda* const linda{ ToLinda<false>(L, 1) };
213 std::chrono::time_point<std::chrono::steady_clock> until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; 235 std::chrono::time_point<std::chrono::steady_clock> until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
214 int key_i{ 2 }; // index of first key, if timeout not there 236 int key_i{ 2 }; // index of first key, if timeout not there
215 237
@@ -257,7 +279,7 @@ LUAG_FUNC(linda_send)
257 keeper_toggle_nil_sentinels(L, key_i + 1, LookupMode::ToKeeper); 279 keeper_toggle_nil_sentinels(L, key_i + 1, LookupMode::ToKeeper);
258 bool ret{ false }; 280 bool ret{ false };
259 CancelRequest cancel{ CancelRequest::None }; 281 CancelRequest cancel{ CancelRequest::None };
260 int pushed{ 0 }; 282 KeeperCallResult pushed;
261 { 283 {
262 Lane* const lane{ LANE_POINTER_REGKEY.readLightUserDataValue<Lane>(L) }; 284 Lane* const lane{ LANE_POINTER_REGKEY.readLightUserDataValue<Lane>(L) };
263 Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; 285 Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) };
@@ -276,17 +298,17 @@ LUAG_FUNC(linda_send)
276 // if user wants to cancel, or looped because of a timeout, the call returns without sending anything 298 // if user wants to cancel, or looped because of a timeout, the call returns without sending anything
277 if (!try_again || cancel != CancelRequest::None) 299 if (!try_again || cancel != CancelRequest::None)
278 { 300 {
279 pushed = 0; 301 pushed.emplace(0);
280 break; 302 break;
281 } 303 }
282 304
283 STACK_CHECK(KL, 0); 305 STACK_CHECK(KL, 0);
284 pushed = keeper_call(linda->U, KL, KEEPER_API(send), L, linda, key_i); 306 pushed = keeper_call(linda->U, KL, KEEPER_API(send), L, linda, key_i);
285 if (pushed < 0) 307 if (!pushed.has_value())
286 { 308 {
287 break; 309 break;
288 } 310 }
289 ASSERT_L(pushed == 1); 311 ASSERT_L(pushed.value() == 1);
290 312
291 ret = lua_toboolean(L, -1) ? true : false; 313 ret = lua_toboolean(L, -1) ? true : false;
292 lua_pop(L, 1); 314 lua_pop(L, 1);
@@ -331,9 +353,9 @@ LUAG_FUNC(linda_send)
331 STACK_CHECK(KL, 0); 353 STACK_CHECK(KL, 0);
332 } 354 }
333 355
334 if (pushed < 0) 356 if (!pushed.has_value())
335 { 357 {
336 return luaL_error(L, "tried to copy unsupported types"); 358 luaL_error(L, "tried to copy unsupported types"); // doesn't return
337 } 359 }
338 360
339 switch (cancel) 361 switch (cancel)
@@ -366,11 +388,9 @@ LUAG_FUNC(linda_send)
366 * returns the actual consumed values, or nil if there weren't enough values to consume 388 * returns the actual consumed values, or nil if there weren't enough values to consume
367 * 389 *
368 */ 390 */
369// xxh64 of string "CANCEL_ERROR" generated at https://www.pelock.com/products/hash-calculator
370static constexpr UniqueKey BATCH_SENTINEL{ 0x2DDFEE0968C62AA7ull };
371LUAG_FUNC(linda_receive) 391LUAG_FUNC(linda_receive)
372{ 392{
373 Linda* const linda{ lua_toLinda<false>(L, 1) }; 393 Linda* const linda{ ToLinda<false>(L, 1) };
374 std::chrono::time_point<std::chrono::steady_clock> until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; 394 std::chrono::time_point<std::chrono::steady_clock> until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
375 int key_i{ 2 }; // index of first key, if timeout not there 395 int key_i{ 2 }; // index of first key, if timeout not there
376 396
@@ -430,7 +450,7 @@ LUAG_FUNC(linda_receive)
430 return 0; 450 return 0;
431 451
432 CancelRequest cancel{ CancelRequest::None }; 452 CancelRequest cancel{ CancelRequest::None };
433 int pushed{ 0 }; 453 KeeperCallResult pushed;
434 STACK_CHECK_START_REL(KL, 0); 454 STACK_CHECK_START_REL(KL, 0);
435 for (bool try_again{ true };;) 455 for (bool try_again{ true };;)
436 { 456 {
@@ -442,21 +462,21 @@ LUAG_FUNC(linda_receive)
442 // if user wants to cancel, or looped because of a timeout, the call returns without sending anything 462 // if user wants to cancel, or looped because of a timeout, the call returns without sending anything
443 if (!try_again || cancel != CancelRequest::None) 463 if (!try_again || cancel != CancelRequest::None)
444 { 464 {
445 pushed = 0; 465 pushed.emplace(0);
446 break; 466 break;
447 } 467 }
448 468
449 // all arguments of receive() but the first are passed to the keeper's receive function 469 // all arguments of receive() but the first are passed to the keeper's receive function
450 pushed = keeper_call(linda->U, KL, selected_keeper_receive, L, linda, key_i); 470 pushed = keeper_call(linda->U, KL, selected_keeper_receive, L, linda, key_i);
451 if (pushed < 0) 471 if (!pushed.has_value())
452 { 472 {
453 break; 473 break;
454 } 474 }
455 if (pushed > 0) 475 if (pushed.value() > 0)
456 { 476 {
457 ASSERT_L(pushed >= expected_pushed_min && pushed <= expected_pushed_max); 477 ASSERT_L(pushed.value() >= expected_pushed_min && pushed.value() <= expected_pushed_max);
458 // replace sentinels with real nils 478 // replace sentinels with real nils
459 keeper_toggle_nil_sentinels(L, lua_gettop(L) - pushed, LookupMode::FromKeeper); 479 keeper_toggle_nil_sentinels(L, lua_gettop(L) - pushed.value(), LookupMode::FromKeeper);
460 // To be done from within the 'K' locking area 480 // To be done from within the 'K' locking area
461 // 481 //
462 linda->m_read_happened.notify_all(); 482 linda->m_read_happened.notify_all();
@@ -494,7 +514,7 @@ LUAG_FUNC(linda_receive)
494 } 514 }
495 STACK_CHECK(KL, 0); 515 STACK_CHECK(KL, 0);
496 516
497 if (pushed < 0) 517 if (!pushed.has_value())
498 { 518 {
499 return luaL_error(L, "tried to copy unsupported types"); 519 return luaL_error(L, "tried to copy unsupported types");
500 } 520 }
@@ -511,7 +531,7 @@ LUAG_FUNC(linda_receive)
511 raise_cancel_error(L); // raises an error and doesn't return 531 raise_cancel_error(L); // raises an error and doesn't return
512 532
513 default: 533 default:
514 return pushed; 534 return pushed.value();
515 } 535 }
516} 536}
517 537
@@ -527,13 +547,13 @@ LUAG_FUNC(linda_receive)
527*/ 547*/
528LUAG_FUNC(linda_set) 548LUAG_FUNC(linda_set)
529{ 549{
530 Linda* const linda{ lua_toLinda<false>(L, 1) }; 550 Linda* const linda{ ToLinda<false>(L, 1) };
531 bool const has_value{ lua_gettop(L) > 2 }; 551 bool const has_value{ lua_gettop(L) > 2 };
532 // 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)
533 check_key_types(L, 2, 2); 553 check_key_types(L, 2, 2);
534 554
535 Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; 555 Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) };
536 int pushed{ 0 }; 556 KeeperCallResult pushed;
537 if (linda->simulate_cancel == CancelRequest::None) 557 if (linda->simulate_cancel == CancelRequest::None)
538 { 558 {
539 if (has_value) 559 if (has_value)
@@ -542,16 +562,16 @@ LUAG_FUNC(linda_set)
542 keeper_toggle_nil_sentinels(L, 3, LookupMode::ToKeeper); 562 keeper_toggle_nil_sentinels(L, 3, LookupMode::ToKeeper);
543 } 563 }
544 pushed = keeper_call(linda->U, K->L, KEEPER_API(set), L, linda, 2); 564 pushed = keeper_call(linda->U, K->L, KEEPER_API(set), L, linda, 2);
545 if (pushed >= 0) // no error? 565 if (pushed.has_value()) // no error?
546 { 566 {
547 ASSERT_L(pushed == 0 || pushed == 1); 567 ASSERT_L(pushed.value() == 0 || pushed.value() == 1);
548 568
549 if (has_value) 569 if (has_value)
550 { 570 {
551 // we put some data in the slot, tell readers that they should wake 571 // we put some data in the slot, tell readers that they should wake
552 linda->m_write_happened.notify_all(); // To be done from within the 'K' locking area 572 linda->m_write_happened.notify_all(); // To be done from within the 'K' locking area
553 } 573 }
554 if (pushed == 1) 574 if (pushed.value() == 1)
555 { 575 {
556 // the key was full, but it is no longer the case, tell writers they should wake 576 // the key was full, but it is no longer the case, tell writers they should wake
557 ASSERT_L(lua_type(L, -1) == LUA_TBOOLEAN && lua_toboolean(L, -1) == 1); 577 ASSERT_L(lua_type(L, -1) == LUA_TBOOLEAN && lua_toboolean(L, -1) == 1);
@@ -563,11 +583,11 @@ LUAG_FUNC(linda_set)
563 { 583 {
564 // do nothing and return lanes.cancel_error 584 // do nothing and return lanes.cancel_error
565 CANCEL_ERROR.pushKey(L); 585 CANCEL_ERROR.pushKey(L);
566 pushed = 1; 586 pushed.emplace(1);
567 } 587 }
568 588
569 // must trigger any error after keeper state has been released 589 // must trigger any error after keeper state has been released
570 return (pushed < 0) ? luaL_error(L, "tried to copy unsupported types") : pushed; 590 return OptionalValue(pushed, L, "tried to copy unsupported types");
571} 591}
572 592
573// ################################################################################################# 593// #################################################################################################
@@ -579,17 +599,13 @@ LUAG_FUNC(linda_set)
579 */ 599 */
580LUAG_FUNC(linda_count) 600LUAG_FUNC(linda_count)
581{ 601{
582 Linda* const linda{ lua_toLinda<false>(L, 1) }; 602 Linda* const linda{ ToLinda<false>(L, 1) };
583 // make sure the keys are of a valid type 603 // make sure the keys are of a valid type
584 check_key_types(L, 2, lua_gettop(L)); 604 check_key_types(L, 2, lua_gettop(L));
585 605
586 Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; 606 Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) };
587 int const pushed{ keeper_call(linda->U, K->L, KEEPER_API(count), L, linda, 2) }; 607 KeeperCallResult const pushed{ keeper_call(linda->U, K->L, KEEPER_API(count), L, linda, 2) };
588 if (pushed < 0) 608 return OptionalValue(pushed, L, "tried to count an invalid key");
589 {
590 return luaL_error(L, "tried to count an invalid key");
591 }
592 return pushed;
593} 609}
594 610
595// ################################################################################################# 611// #################################################################################################
@@ -601,36 +617,31 @@ LUAG_FUNC(linda_count)
601*/ 617*/
602LUAG_FUNC(linda_get) 618LUAG_FUNC(linda_get)
603{ 619{
604 Linda* const linda{ lua_toLinda<false>(L, 1) }; 620 Linda* const linda{ ToLinda<false>(L, 1) };
605 lua_Integer const count{ luaL_optinteger(L, 3, 1) }; 621 lua_Integer const count{ luaL_optinteger(L, 3, 1) };
606 luaL_argcheck(L, count >= 1, 3, "count should be >= 1"); 622 luaL_argcheck(L, count >= 1, 3, "count should be >= 1");
607 luaL_argcheck(L, lua_gettop(L) <= 3, 4, "too many arguments"); 623 luaL_argcheck(L, lua_gettop(L) <= 3, 4, "too many arguments");
608 // make sure the key is of a valid type (throws an error if not the case) 624 // make sure the key is of a valid type (throws an error if not the case)
609 check_key_types(L, 2, 2); 625 check_key_types(L, 2, 2);
610 626
611 int pushed{ 0 }; 627 KeeperCallResult pushed;
612 if (linda->simulate_cancel == CancelRequest::None) 628 if (linda->simulate_cancel == CancelRequest::None)
613 { 629 {
614 Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; 630 Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) };
615 pushed = keeper_call(linda->U, K->L, KEEPER_API(get), L, linda, 2); 631 pushed = keeper_call(linda->U, K->L, KEEPER_API(get), L, linda, 2);
616 if (pushed > 0) 632 if (pushed.value_or(0) > 0)
617 { 633 {
618 keeper_toggle_nil_sentinels(L, lua_gettop(L) - pushed, LookupMode::FromKeeper); 634 keeper_toggle_nil_sentinels(L, lua_gettop(L) - pushed.value(), LookupMode::FromKeeper);
619 } 635 }
620 } 636 }
621 else // linda is cancelled 637 else // linda is cancelled
622 { 638 {
623 // do nothing and return lanes.cancel_error 639 // do nothing and return lanes.cancel_error
624 CANCEL_ERROR.pushKey(L); 640 CANCEL_ERROR.pushKey(L);
625 pushed = 1; 641 pushed.emplace(1);
626 } 642 }
627 // an error can be raised if we attempt to read an unregistered function 643 // an error can be raised if we attempt to read an unregistered function
628 if (pushed < 0) 644 return OptionalValue(pushed, L, "tried to copy unsupported types");
629 {
630 return luaL_error(L, "tried to copy unsupported types");
631 }
632
633 return pushed;
634} 645}
635 646
636// ################################################################################################# 647// #################################################################################################
@@ -641,9 +652,9 @@ LUAG_FUNC(linda_get)
641* Set limit to 1 Linda keys. 652* Set limit to 1 Linda keys.
642* Optionally wake threads waiting to write on the linda, in case the limit enables them to do so 653* Optionally wake threads waiting to write on the linda, in case the limit enables them to do so
643*/ 654*/
644LUAG_FUNC( linda_limit) 655LUAG_FUNC(linda_limit)
645{ 656{
646 Linda* const linda{ lua_toLinda<false>(L, 1) }; 657 Linda* const linda{ ToLinda<false>(L, 1) };
647 // make sure we got 3 arguments: the linda, a key and a limit 658 // make sure we got 3 arguments: the linda, a key and a limit
648 luaL_argcheck( L, lua_gettop( L) == 3, 2, "wrong number of arguments"); 659 luaL_argcheck( L, lua_gettop( L) == 3, 2, "wrong number of arguments");
649 // make sure we got a numeric limit 660 // make sure we got a numeric limit
@@ -651,13 +662,13 @@ LUAG_FUNC( linda_limit)
651 // make sure the key is of a valid type 662 // make sure the key is of a valid type
652 check_key_types( L, 2, 2); 663 check_key_types( L, 2, 2);
653 664
654 int pushed{ 0 }; 665 KeeperCallResult pushed;
655 if (linda->simulate_cancel == CancelRequest::None) 666 if (linda->simulate_cancel == CancelRequest::None)
656 { 667 {
657 Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) }; 668 Keeper* const K{ which_keeper(linda->U->keepers, linda->hashSeed()) };
658 pushed = keeper_call(linda->U, K->L, KEEPER_API(limit), L, linda, 2); 669 pushed = keeper_call(linda->U, K->L, KEEPER_API(limit), L, linda, 2);
659 ASSERT_L( pushed == 0 || pushed == 1); // no error, optional boolean value saying if we should wake blocked writer threads 670 ASSERT_L( pushed.has_value() && (pushed.value() == 0 || pushed.value() == 1)); // no error, optional boolean value saying if we should wake blocked writer threads
660 if( pushed == 1) 671 if (pushed.value() == 1)
661 { 672 {
662 ASSERT_L( lua_type( L, -1) == LUA_TBOOLEAN && lua_toboolean( L, -1) == 1); 673 ASSERT_L( lua_type( L, -1) == LUA_TBOOLEAN && lua_toboolean( L, -1) == 1);
663 linda->m_read_happened.notify_all(); // To be done from within the 'K' locking area 674 linda->m_read_happened.notify_all(); // To be done from within the 'K' locking area
@@ -667,10 +678,10 @@ LUAG_FUNC( linda_limit)
667 { 678 {
668 // do nothing and return lanes.cancel_error 679 // do nothing and return lanes.cancel_error
669 CANCEL_ERROR.pushKey(L); 680 CANCEL_ERROR.pushKey(L);
670 pushed = 1; 681 pushed.emplace(1);
671 } 682 }
672 // propagate pushed boolean if any 683 // propagate pushed boolean if any
673 return pushed; 684 return pushed.value();
674} 685}
675 686
676// ################################################################################################# 687// #################################################################################################
@@ -682,7 +693,7 @@ LUAG_FUNC( linda_limit)
682*/ 693*/
683LUAG_FUNC(linda_cancel) 694LUAG_FUNC(linda_cancel)
684{ 695{
685 Linda* const linda{ lua_toLinda<false>(L, 1) }; 696 Linda* const linda{ ToLinda<false>(L, 1) };
686 char const* who = luaL_optstring(L, 2, "both"); 697 char const* who = luaL_optstring(L, 2, "both");
687 // make sure we got 3 arguments: the linda, a key and a limit 698 // make sure we got 3 arguments: the linda, a key and a limit
688 luaL_argcheck(L, lua_gettop(L) <= 2, 2, "wrong number of arguments"); 699 luaL_argcheck(L, lua_gettop(L) <= 2, 2, "wrong number of arguments");
@@ -726,7 +737,7 @@ LUAG_FUNC(linda_cancel)
726*/ 737*/
727LUAG_FUNC(linda_deep) 738LUAG_FUNC(linda_deep)
728{ 739{
729 Linda* const linda{ lua_toLinda<false>(L, 1) }; 740 Linda* const linda{ ToLinda<false>(L, 1) };
730 lua_pushlightuserdata(L, linda); // just the address 741 lua_pushlightuserdata(L, linda); // just the address
731 return 1; 742 return 1;
732} 743}
@@ -742,9 +753,9 @@ LUAG_FUNC(linda_deep)
742*/ 753*/
743 754
744template <bool OPT> 755template <bool OPT>
745[[nodiscard]] static int linda_tostring(lua_State* L, int idx_) 756[[nodiscard]] static int LindaToString(lua_State* L, int idx_)
746{ 757{
747 Linda* const linda{ lua_toLinda<OPT>(L, idx_) }; 758 Linda* const linda{ ToLinda<OPT>(L, idx_) };
748 if (linda != nullptr) 759 if (linda != nullptr)
749 { 760 {
750 char text[128]; 761 char text[128];
@@ -761,7 +772,7 @@ template <bool OPT>
761 772
762LUAG_FUNC(linda_tostring) 773LUAG_FUNC(linda_tostring)
763{ 774{
764 return linda_tostring<false>(L, 1); 775 return LindaToString<false>(L, 1);
765} 776}
766 777
767// ################################################################################################# 778// #################################################################################################
@@ -777,12 +788,12 @@ LUAG_FUNC(linda_concat)
777{ // linda1? linda2? 788{ // linda1? linda2?
778 bool atLeastOneLinda{ false }; 789 bool atLeastOneLinda{ false };
779 // Lua semantics enforce that one of the 2 arguments is a Linda, but not necessarily both. 790 // Lua semantics enforce that one of the 2 arguments is a Linda, but not necessarily both.
780 if (linda_tostring<true>(L, 1)) 791 if (LindaToString<true>(L, 1))
781 { 792 {
782 atLeastOneLinda = true; 793 atLeastOneLinda = true;
783 lua_replace(L, 1); 794 lua_replace(L, 1);
784 } 795 }
785 if (linda_tostring<true>(L, 2)) 796 if (LindaToString<true>(L, 2))
786 { 797 {
787 atLeastOneLinda = true; 798 atLeastOneLinda = true;
788 lua_replace(L, 2); 799 lua_replace(L, 2);
@@ -803,7 +814,7 @@ LUAG_FUNC(linda_concat)
803 */ 814 */
804LUAG_FUNC(linda_dump) 815LUAG_FUNC(linda_dump)
805{ 816{
806 Linda* const linda{ lua_toLinda<false>(L, 1) }; 817 Linda* const linda{ ToLinda<false>(L, 1) };
807 return keeper_push_linda_storage(linda->U, Dest{ L }, linda, linda->hashSeed()); 818 return keeper_push_linda_storage(linda->U, Dest{ L }, linda, linda->hashSeed());
808} 819}
809 820
@@ -815,12 +826,12 @@ LUAG_FUNC(linda_dump)
815 */ 826 */
816LUAG_FUNC(linda_towatch) 827LUAG_FUNC(linda_towatch)
817{ 828{
818 Linda* const linda{ lua_toLinda<false>(L, 1) }; 829 Linda* const linda{ ToLinda<false>(L, 1) };
819 int pushed{ keeper_push_linda_storage(linda->U, Dest{ L }, linda, linda->hashSeed()) }; 830 int pushed{ keeper_push_linda_storage(linda->U, Dest{ L }, linda, linda->hashSeed()) };
820 if (pushed == 0) 831 if (pushed == 0)
821 { 832 {
822 // if the linda is empty, don't return nil 833 // if the linda is empty, don't return nil
823 pushed = linda_tostring<false>(L, 1); 834 pushed = LindaToString<false>(L, 1);
824 } 835 }
825 return pushed; 836 return pushed;
826} 837}
@@ -907,7 +918,8 @@ LUAG_FUNC(linda_towatch)
907 // Clean associated structures in the keeper state. 918 // Clean associated structures in the keeper state.
908 Keeper* const K{ need_acquire_release ? keeper_acquire(linda->U->keepers, linda->hashSeed()) : myK }; 919 Keeper* const K{ need_acquire_release ? keeper_acquire(linda->U->keepers, linda->hashSeed()) : myK };
909 // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... 920 // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex...
910 std::ignore = keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0); 921 [[maybe_unused]] KeeperCallResult const result{ keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0) };
922 ASSERT_L(result.has_value() && result.value() == 0);
911 if (need_acquire_release) 923 if (need_acquire_release)
912 { 924 {
913 keeper_release(K); 925 keeper_release(K);
@@ -943,6 +955,7 @@ LUAG_FUNC(linda_towatch)
943 // protected calls, to ensure associated keeper is always released even in case of error 955 // protected calls, to ensure associated keeper is always released even in case of error
944 // all function are the protected call wrapper, where the actual operation is provided as upvalue 956 // all function are the protected call wrapper, where the actual operation is provided as upvalue
945 // note that this kind of thing can break function lookup as we use the function pointer here and there 957 // note that this kind of thing can break function lookup as we use the function pointer here and there
958 // TODO: change that and use different functions!
946 959
947 lua_pushcfunction(L, LG_linda_send); 960 lua_pushcfunction(L, LG_linda_send);
948 lua_pushcclosure(L, LG_linda_protected_call, 1); 961 lua_pushcclosure(L, LG_linda_protected_call, 1);
diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h
index e8d5ab5..77bcfe2 100644
--- a/src/macros_and_utils.h
+++ b/src/macros_and_utils.h
@@ -178,16 +178,48 @@ using lua_Duration = std::chrono::template duration<lua_Number>;
178// ################################################################################################# 178// #################################################################################################
179 179
180// A unique type generator 180// A unique type generator
181template <typename T, auto = []{}> 181template <typename T, auto = [] {}, typename specialization = void>
182struct Unique 182class Unique
183{ 183{
184 private:
185
184 T m_val; 186 T m_val;
185 constexpr Unique() = default; 187
186 constexpr operator T() const { return m_val; } 188 public:
187 constexpr explicit Unique(T b_) : m_val{ b_ } {} 189
190 Unique() = default;
191 operator T() const { return m_val; }
192 explicit Unique(T b_) : m_val{ b_ } {}
193};
194
195template <typename T, auto lambda>
196class Unique<T, lambda, std::enable_if_t<!std::is_scalar_v<T>>> : public T
197{
198 public:
199
200 using T::T;
201 explicit Unique(T const& b_) : T{ b_ } {}
188}; 202};
189 203
190// ################################################################################################# 204// #################################################################################################
191 205
192using Source = Unique<lua_State*>; 206using Source = Unique<lua_State*>;
193using Dest = Unique<lua_State*>; \ No newline at end of file 207using Dest = Unique<lua_State*>;
208
209// #################################################################################################
210
211// A helper to issue an error if the provided optional doesn't contain a value
212// we can't use std::optional::value_or(luaL_error(...)), because the 'or' value is always evaluated
213template <typename T>
214concept IsOptional = requires(T x){ x.value_or(T{}); };
215
216template<typename T, typename ...Ts>
217requires IsOptional<T>
218typename T::value_type const& OptionalValue(T const& x_, Ts... args_)
219{
220 if (!x_.has_value())
221 {
222 luaL_error(std::forward<Ts>(args_)...); // doesn't return
223 }
224 return x_.value();
225}
diff --git a/src/uniquekey.h b/src/uniquekey.h
index a89ecd3..78c0765 100644
--- a/src/uniquekey.h
+++ b/src/uniquekey.h
@@ -13,12 +13,15 @@ class UniqueKey
13 13
14 public: 14 public:
15 15
16 constexpr explicit UniqueKey(uint64_t val_) 16 char const* m_debugName{ nullptr };
17
18 constexpr explicit UniqueKey(uint64_t val_, char const* debugName_ = nullptr)
17#if LUAJIT_FLAVOR() == 64 // building against LuaJIT headers for 64 bits, light userdata is restricted to 47 significant bits, because LuaJIT uses the other bits for internal optimizations 19#if LUAJIT_FLAVOR() == 64 // building against LuaJIT headers for 64 bits, light userdata is restricted to 47 significant bits, because LuaJIT uses the other bits for internal optimizations
18 : m_storage{ static_cast<uintptr_t>(val_ & 0x7fffffffffffull) } 20 : m_storage{ static_cast<uintptr_t>(val_ & 0x7fffffffffffull) }
19#else // LUAJIT_FLAVOR() 21#else // LUAJIT_FLAVOR()
20 : m_storage{ static_cast<uintptr_t>(val_) } 22 : m_storage{ static_cast<uintptr_t>(val_) }
21#endif // LUAJIT_FLAVOR() 23#endif // LUAJIT_FLAVOR()
24 , m_debugName{ debugName_ }
22 { 25 {
23 } 26 }
24 constexpr UniqueKey(UniqueKey const& rhs_) = default; 27 constexpr UniqueKey(UniqueKey const& rhs_) = default;
diff --git a/tests/errhangtest.lua b/tests/errhangtest.lua
index d26dcef..811601e 100644
--- a/tests/errhangtest.lua
+++ b/tests/errhangtest.lua
@@ -1,22 +1,66 @@
1local lanes = require "lanes".configure{with_timers=false} 1local lanes = require "lanes".configure{with_timers=false}
2
3local linda = lanes.linda() 2local linda = lanes.linda()
4 3
5local coro = coroutine.create(function() end) 4-- we are not allowed to send coroutines through a lane
5-- however, this should raise an error, not hang the program...
6if true then
7 print "#### coro set"
8 local coro = coroutine.create(function() end)
9 print(pcall(linda.set, linda, 'test', coro))
10 assert(linda:get("test") == nil)
11 print "OK"
12end
13
14if true then
15 print "\n#### reserved sentinels"
16 print(pcall(linda.set, linda, lanes.cancel_error))
17 print(pcall(linda.set, linda, linda.batched))
18 assert(linda:get("test") == nil)
19 print "OK"
20end
6 21
7local fun = function() print "fun" end 22-- get/set a few values
8local t_in = { [fun] = fun, fun = fun } 23if true then
24 print "\n#### set 3 -> receive batched"
25 local fun = function() print "function test ok" end
26 print(pcall(linda.set, linda, 'test', true, nil, fun))
27 local k,b,n,f = linda:receive(linda.batched, 'test', 3) -- read back the contents
28 assert(linda:get("test") == nil)
29 print(k, b, n)
30 f()
31 print "OK"
32end
9 33
10-- send a string
11print( pcall(linda.send,linda, 'test', "oh boy"))
12-- send a table that contains a function 34-- send a table that contains a function
13print( pcall(linda.send,linda, 'test', t_in)) 35if true then
14-- we are not allowed to send coroutines through a lanes 36 print "\n#### send table with a function"
37 local fun = function() print "function test ok" end
38 local t_in = { [fun] = fun, fun = fun }
39 print(pcall(linda.send, linda, 'test', t_in))
40 local k,t_out = linda:receive('test') -- read the contents successfully sent
41 t_out.fun()
42 -- TODO: t_out should contain a single entry, as [fun] = fun should have been discarded because functions are not acceptable keys
43 print "OK"
44end
45
46-- send a string
47if true then
48 print "\n#### send string"
49 print(pcall(linda.send, linda, 'test', "string test ok"))
50 local k,str = linda:receive('test') -- read the contents successfully sent
51 print(str)
52 print "OK"
53end
54
55-- we are not allowed to send coroutines through a lane
15-- however, this should raise an error, not hang the program... 56-- however, this should raise an error, not hang the program...
16print( pcall(linda.send,linda, 'test', coro)) 57if true then
17k,str = linda:receive('test') -- read the contents successfully sent 58 print "\n#### coro send"
18print( str) -- "oh boy" 59 local coro = coroutine.create(function() end)
19k,t_out = linda:receive('test') -- read the contents successfully sent 60 print(pcall(linda.send, linda, 'test', coro))
20t_out.fun() -- "fun" 61 assert(linda:get("test") == nil)
21-- linda:send( 'test', coro) 62 print "OK"
22print "SUCCESS" \ No newline at end of file 63end
64
65-- done
66print "\nSUCCESS" \ No newline at end of file