aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-10-25 16:45:28 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-10-25 16:45:28 +0200
commitc860f557a7ba72e6a39ea5db36e293de802adea5 (patch)
tree1d15a9b798b2f812c5315022d579e8f083b4385a
parentf2d578555bf51da7a0acd1d1d7e724860b89a149 (diff)
downloadlanes-c860f557a7ba72e6a39ea5db36e293de802adea5.tar.gz
lanes-c860f557a7ba72e6a39ea5db36e293de802adea5.tar.bz2
lanes-c860f557a7ba72e6a39ea5db36e293de802adea5.zip
New linda:wake() and linda.status
-rw-r--r--CHANGES4
-rw-r--r--docs/index.html14
-rw-r--r--src/linda.cpp109
-rw-r--r--src/linda.h11
-rw-r--r--src/lindafactory.cpp30
5 files changed, 143 insertions, 25 deletions
diff --git a/CHANGES b/CHANGES
index 3a6df10..b8dff6d 100644
--- a/CHANGES
+++ b/CHANGES
@@ -47,7 +47,9 @@ CHANGE 2: BGe 04-Jul-24
47 - linda :receive(), :send(), :get(), :set(), :limit() return nil, error in case of problem. Returned values in case of success change too. 47 - linda :receive(), :send(), :get(), :set(), :limit() return nil, error in case of problem. Returned values in case of success change too.
48 - linda:limit() can be used to read the value if no new limit is provided. 48 - linda:limit() can be used to read the value if no new limit is provided.
49 - New __close metamethod that calls any suitable handler that was provided at Linda creation. 49 - New __close metamethod that calls any suitable handler that was provided at Linda creation.
50 - linda:dump outputs <key>.limit as 'unlimited' instead of -1 for unlimited keys. 50 - linda:dump() outputs <key>.limit as 'unlimited' instead of -1 for unlimited keys.
51 - linda:wake() can wake up threads waiting for a Linda without doing any I/O on it.
52 - linda.status reads the cancel status of the Linda.
51 - Deep userdata are an acceptable key to send data into (for example, another linda). 53 - Deep userdata are an acceptable key to send data into (for example, another linda).
52 - Full userdata conversion: 54 - Full userdata conversion:
53 - __lanesconvert added. 55 - __lanesconvert added.
diff --git a/docs/index.html b/docs/index.html
index a8eadbf..b95bc0e 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -70,7 +70,7 @@
70 </p> 70 </p>
71 71
72 <p> 72 <p>
73 This document was revised on 24-Sep-24, and applies to version <tt>4.0.0</tt>. 73 This document was revised on 25-Oct-24, and applies to version <tt>4.0.0</tt>.
74 </p> 74 </p>
75 </font> 75 </font>
76 </center> 76 </center>
@@ -1405,16 +1405,26 @@
1405 1405
1406<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1406<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1407 void = linda_h:cancel("read"|"write"|"both"|"none") 1407 void = linda_h:cancel("read"|"write"|"both"|"none")
1408 "cancelled"|"active" = linda_h.status
1408</pre></td></tr></table> 1409</pre></td></tr></table>
1409 1410
1410<p> 1411<p>
1411 Signals the linda so that lanes waiting for read, write, or both, wake up. 1412 <tt>linda:cancel()</tt> signals the linda so that lanes waiting for read, write, or both, wake up.
1412 All linda operations (including <tt>get()</tt> and <tt>set()</tt>) will return <tt>lanes.cancel_error</tt> as when the calling lane is <a href="#cancelling">soft-cancelled</a> as long as the linda is marked as cancelled.<br /> 1413 All linda operations (including <tt>get()</tt> and <tt>set()</tt>) will return <tt>lanes.cancel_error</tt> as when the calling lane is <a href="#cancelling">soft-cancelled</a> as long as the linda is marked as cancelled.<br />
1413 <tt>"none"</tt> reset the linda's cancel status, but doesn't signal it.<br /> 1414 <tt>"none"</tt> reset the linda's cancel status, but doesn't signal it.<br />
1415 <tt>linda.status</tt> reads the current cancel status.
1414 If not void, the lane's cancel status overrides the linda's cancel status. 1416 If not void, the lane's cancel status overrides the linda's cancel status.
1415</p> 1417</p>
1416 1418
1417<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1419<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1420 void = linda_h:wake("read"|"write"|"both")
1421</pre></td></tr></table>
1422
1423<p>
1424 Signals the linda so that lanes waiting for read, write, or both, wake up. Does not change the current Linda's cancel status. Can be used to break early out of a blocking wait.
1425</p>
1426
1427<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1418 light userdata = h:deep() 1428 light userdata = h:deep()
1419</pre></td></tr></table> 1429</pre></td></tr></table>
1420 1430
diff --git a/src/linda.cpp b/src/linda.cpp
index 15e7a2c..67526a7 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -233,6 +233,13 @@ int Linda::ProtectedCall(lua_State* const L_, lua_CFunction const f_)
233 233
234// ################################################################################################# 234// #################################################################################################
235 235
236void Linda::pushCancelString(lua_State* L_) const
237{
238 luaG_pushstring(L_, cancelStatus == Status::Cancelled ? "cancelled" : "active");
239}
240
241// #################################################################################################
242
236void Linda::releaseKeeper(Keeper* const keeper_) const 243void Linda::releaseKeeper(Keeper* const keeper_) const
237{ 244{
238 if (keeper_) { // can be nullptr if we tried to acquire during shutdown 245 if (keeper_) { // can be nullptr if we tried to acquire during shutdown
@@ -284,18 +291,20 @@ LUAG_FUNC(linda_cancel)
284 // make sure we got 2 arguments: the linda and the cancellation mode 291 // make sure we got 2 arguments: the linda and the cancellation mode
285 luaL_argcheck(L_, lua_gettop(L_) <= 2, 2, "wrong number of arguments"); 292 luaL_argcheck(L_, lua_gettop(L_) <= 2, 2, "wrong number of arguments");
286 293
287 _linda->cancelRequest = CancelRequest::Soft; 294 if (_who == "both") { // tell everyone to wake up
288 if (_who == "both") { // tell everyone writers to wake up 295 _linda->cancelStatus = Linda::Status::Cancelled;
289 _linda->writeHappened.notify_all(); 296 _linda->writeHappened.notify_all();
290 _linda->readHappened.notify_all(); 297 _linda->readHappened.notify_all();
291 } else if (_who == "none") { // reset flag 298 } else if (_who == "none") { // reset flag
292 _linda->cancelRequest = CancelRequest::None; 299 _linda->cancelStatus = Linda::Status::Active;
293 } else if (_who == "read") { // tell blocked readers to wake up 300 } else if (_who == "read") { // tell blocked readers to wake up
301 _linda->cancelStatus = Linda::Status::Cancelled;
294 _linda->writeHappened.notify_all(); 302 _linda->writeHappened.notify_all();
295 } else if (_who == "write") { // tell blocked writers to wake up 303 } else if (_who == "write") { // tell blocked writers to wake up
304 _linda->cancelStatus = Linda::Status::Cancelled;
296 _linda->readHappened.notify_all(); 305 _linda->readHappened.notify_all();
297 } else { 306 } else {
298 raise_luaL_error(L_, "unknown wake hint '%s'", _who); 307 raise_luaL_error(L_, "unknown wake hint '%s'", _who.data());
299 } 308 }
300 return 0; 309 return 0;
301} 310}
@@ -357,6 +366,53 @@ LUAG_FUNC(linda_concat)
357 366
358// ################################################################################################# 367// #################################################################################################
359 368
369// If key is "status" return the linda cancel status
370static int linda_index_string(lua_State* L_)
371{
372 static constexpr StackIndex kIdxSelf{ 1 };
373 static constexpr StackIndex kIdxKey{ 2 };
374
375 Linda* const _linda{ ToLinda<false>(L_, kIdxSelf) };
376 LUA_ASSERT(L_, lua_gettop(L_) == 2); // L_: linda "key"
377
378 std::string_view const _keystr{ luaG_tostring(L_, kIdxKey) };
379 lua_settop(L_, 2); // keep only our original arguments on the stack
380
381 // look in metatable first
382 lua_getmetatable(L_, kIdxSelf); // L_: linda "key" mt
383 lua_replace(L_, -3); // L_: mt "key"
384 lua_rawget(L_, -2); // L_: mt value
385 if (luaG_type(L_, kIdxTop) != LuaType::NIL) { // found something?
386 return 1; // done
387 }
388
389 lua_pop(L_, 2); // L_:
390 if (_keystr == "status") {
391 _linda->pushCancelString(L_); // L_: "<status>"
392 return 1;
393 }
394 raise_luaL_error(L_, "unknown field '%s'", _keystr.data());
395}
396
397// #################################################################################################
398
399// linda:__index(key,usr) -> value
400static LUAG_FUNC(linda_index)
401{
402 static constexpr StackIndex kIdxKey{ 2 };
403 LUA_ASSERT(L_, lua_gettop(L_) == 2);
404
405 switch (luaG_type(L_, kIdxKey)) {
406 case LuaType::STRING:
407 return linda_index_string(L_); // stack modification is undefined, returned value is at the top
408
409 default: // unknown key
410 raise_luaL_error(L_, "Unsupported linda indexing key type %s", luaG_typename(L_, kIdxKey).data());
411 }
412}
413
414// #################################################################################################
415
360/* 416/*
361 * [val] = linda_count( linda_ud, [key [, ...]]) 417 * [val] = linda_count( linda_ud, [key [, ...]])
362 * 418 *
@@ -433,7 +489,7 @@ LUAG_FUNC(linda_get)
433 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 }); 489 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 });
434 490
435 KeeperCallResult _pushed; 491 KeeperCallResult _pushed;
436 if (_linda->cancelRequest == CancelRequest::None) { 492 if (_linda->cancelStatus == Linda::Active) {
437 Keeper* const _keeper{ _linda->whichKeeper() }; 493 Keeper* const _keeper{ _linda->whichKeeper() };
438 _pushed = keeper_call(_keeper->K, KEEPER_API(get), L_, _linda, StackIndex{ 2 }); 494 _pushed = keeper_call(_keeper->K, KEEPER_API(get), L_, _linda, StackIndex{ 2 });
439 } else { // linda is cancelled 495 } else { // linda is cancelled
@@ -477,7 +533,7 @@ LUAG_FUNC(linda_limit)
477 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 }); 533 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 });
478 534
479 KeeperCallResult _pushed; 535 KeeperCallResult _pushed;
480 if (_linda->cancelRequest == CancelRequest::None) { 536 if (_linda->cancelStatus == Linda::Active) {
481 if (_unlimited) { 537 if (_unlimited) {
482 LUA_ASSERT(L_, lua_gettop(L_) == 3 && luaG_tostring(L_, StackIndex{ 3 }) == "unlimited"); 538 LUA_ASSERT(L_, lua_gettop(L_) == 3 && luaG_tostring(L_, StackIndex{ 3 }) == "unlimited");
483 // inside the Keeper, unlimited is signified with a -1 limit (can't use nil because of nil kNilSentinel conversions!) 539 // inside the Keeper, unlimited is signified with a -1 limit (can't use nil because of nil kNilSentinel conversions!)
@@ -586,7 +642,9 @@ LUAG_FUNC(linda_receive)
586 if (_lane != nullptr) { 642 if (_lane != nullptr) {
587 _cancel = _lane->cancelRequest; 643 _cancel = _lane->cancelRequest;
588 } 644 }
589 _cancel = (_cancel != CancelRequest::None) ? _cancel : _linda->cancelRequest; 645 _cancel = (_cancel != CancelRequest::None)
646 ? _cancel
647 : ((_linda->cancelStatus == Linda::Cancelled) ? CancelRequest::Soft : CancelRequest::None);
590 // if user wants to cancel, or looped because of a timeout, the call returns without sending anything 648 // if user wants to cancel, or looped because of a timeout, the call returns without sending anything
591 if (!_try_again || _cancel != CancelRequest::None) { 649 if (!_try_again || _cancel != CancelRequest::None) {
592 _pushed.emplace(0); 650 _pushed.emplace(0);
@@ -723,7 +781,9 @@ LUAG_FUNC(linda_send)
723 if (_lane != nullptr) { 781 if (_lane != nullptr) {
724 _cancel = _lane->cancelRequest; 782 _cancel = _lane->cancelRequest;
725 } 783 }
726 _cancel = (_cancel != CancelRequest::None) ? _cancel : _linda->cancelRequest; 784 _cancel = (_cancel != CancelRequest::None)
785 ? _cancel
786 : ((_linda->cancelStatus == Linda::Cancelled) ? CancelRequest::Soft : CancelRequest::None);
727 // if user wants to cancel, or looped because of a timeout, the call returns without sending anything 787 // if user wants to cancel, or looped because of a timeout, the call returns without sending anything
728 if (!_try_again || _cancel != CancelRequest::None) { 788 if (!_try_again || _cancel != CancelRequest::None) {
729 _pushed.emplace(0); 789 _pushed.emplace(0);
@@ -826,7 +886,7 @@ LUAG_FUNC(linda_set)
826 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 }); 886 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 });
827 887
828 KeeperCallResult _pushed; 888 KeeperCallResult _pushed;
829 if (_linda->cancelRequest == CancelRequest::None) { 889 if (_linda->cancelStatus == Linda::Active) {
830 Keeper* const _keeper{ _linda->whichKeeper() }; 890 Keeper* const _keeper{ _linda->whichKeeper() };
831 _pushed = keeper_call(_keeper->K, KEEPER_API(set), L_, _linda, StackIndex{ 2 }); 891 _pushed = keeper_call(_keeper->K, KEEPER_API(set), L_, _linda, StackIndex{ 2 });
832 if (_pushed.has_value()) { // no error? 892 if (_pushed.has_value()) { // no error?
@@ -884,6 +944,33 @@ LUAG_FUNC(linda_towatch)
884 944
885// ################################################################################################# 945// #################################################################################################
886 946
947/*
948 * (void) = linda_wake( linda_ud, "read"|"write"|"both")
949 *
950 * Signal linda so that waiting threads wake up as if their own lane was cancelled
951 */
952LUAG_FUNC(linda_wake)
953{
954 Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) };
955 std::string_view const _who{ luaG_optstring(L_, StackIndex{ 2 }, "both") };
956 // make sure we got 2 arguments: the linda and the wake targets
957 luaL_argcheck(L_, lua_gettop(L_) <= 2, 2, "wrong number of arguments");
958
959 if (_who == "both") { // tell everyone to wake up
960 _linda->writeHappened.notify_all();
961 _linda->readHappened.notify_all();
962 } else if (_who == "read") { // simulate a read to wake writers
963 _linda->writeHappened.notify_all();
964 } else if (_who == "write") { // simulate a write to wake readers
965 _linda->readHappened.notify_all();
966 } else {
967 raise_luaL_error(L_, "unknown wake hint '%s'", _who.data());
968 }
969 return 0;
970}
971
972// #################################################################################################
973
887namespace { 974namespace {
888 namespace local { 975 namespace local {
889 static luaL_Reg const sLindaMT[] = { 976 static luaL_Reg const sLindaMT[] = {
@@ -891,6 +978,7 @@ namespace {
891 { "__close", LG_linda_close }, 978 { "__close", LG_linda_close },
892#endif // LUA_VERSION_NUM >= 504 979#endif // LUA_VERSION_NUM >= 504
893 { "__concat", LG_linda_concat }, 980 { "__concat", LG_linda_concat },
981 { "__index", LG_linda_index },
894 { "__tostring", LG_linda_tostring }, 982 { "__tostring", LG_linda_tostring },
895#if HAVE_DECODA_SUPPORT() 983#if HAVE_DECODA_SUPPORT()
896 { "__towatch", LG_linda_towatch }, // Decoda __towatch support 984 { "__towatch", LG_linda_towatch }, // Decoda __towatch support
@@ -904,6 +992,7 @@ namespace {
904 { "receive", LG_linda_receive }, 992 { "receive", LG_linda_receive },
905 { "send", LG_linda_send }, 993 { "send", LG_linda_send },
906 { "set", LG_linda_set }, 994 { "set", LG_linda_set },
995 { "wake", LG_linda_wake },
907 { nullptr, nullptr } 996 { nullptr, nullptr }
908 }; 997 };
909 } // namespace local 998 } // namespace local
@@ -985,7 +1074,7 @@ LUAG_FUNC(linda)
985 LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, _nuv); // L_: name group close_handler linda 1074 LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, _nuv); // L_: name group close_handler linda
986 if (_closeHandlerIdx != 0) { 1075 if (_closeHandlerIdx != 0) {
987 lua_replace(L_, 2); // L_: name linda close_handler 1076 lua_replace(L_, 2); // L_: name linda close_handler
988 lua_setiuservalue(L_, StackIndex{ 2 }, 1); // L_: name linda 1077 lua_setiuservalue(L_, StackIndex{ 2 }, UserValueIndex{ 1 }); // L_: name linda
989 } 1078 }
990 // depending on whether we have a handler or not, the stack is not in the same state at this point 1079 // depending on whether we have a handler or not, the stack is not in the same state at this point
991 // just make sure we have our Linda at the top 1080 // just make sure we have our Linda at the top
diff --git a/src/linda.h b/src/linda.h
index c05fb14..02b0514 100644
--- a/src/linda.h
+++ b/src/linda.h
@@ -40,7 +40,15 @@ class Linda
40 } 40 }
41 }; 41 };
42 42
43 enum class Status
44 {
45 Active,
46 Cancelled
47 };
48 using enum Status;
49
43 private: 50 private:
51
44 static constexpr size_t kEmbeddedNameLength = 24; 52 static constexpr size_t kEmbeddedNameLength = 24;
45 using EmbeddedName = std::array<char, kEmbeddedNameLength>; 53 using EmbeddedName = std::array<char, kEmbeddedNameLength>;
46 // depending on the name length, it is either embedded inside the Linda, or allocated separately 54 // depending on the name length, it is either embedded inside the Linda, or allocated separately
@@ -53,7 +61,7 @@ class Linda
53 std::condition_variable writeHappened{}; 61 std::condition_variable writeHappened{};
54 Universe* const U{ nullptr }; // the universe this linda belongs to 62 Universe* const U{ nullptr }; // the universe this linda belongs to
55 KeeperIndex const keeperIndex{ -1 }; // the keeper associated to this linda 63 KeeperIndex const keeperIndex{ -1 }; // the keeper associated to this linda
56 CancelRequest cancelRequest{ CancelRequest::None }; 64 Status cancelStatus{ Status::Active };
57 65
58 public: 66 public:
59 [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); } 67 [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); }
@@ -89,6 +97,7 @@ class Linda
89 }; 97 };
90 void releaseKeeper(Keeper* keeper_) const; 98 void releaseKeeper(Keeper* keeper_) const;
91 [[nodiscard]] static int ProtectedCall(lua_State* L_, lua_CFunction f_); 99 [[nodiscard]] static int ProtectedCall(lua_State* L_, lua_CFunction f_);
100 void pushCancelString(lua_State* L_) const;
92 [[nodiscard]] KeeperOperationInProgress startKeeperOperation(lua_State* const L_) { return KeeperOperationInProgress{ *this, L_ }; }; 101 [[nodiscard]] KeeperOperationInProgress startKeeperOperation(lua_State* const L_) { return KeeperOperationInProgress{ *this, L_ }; };
93 [[nodiscard]] Keeper* whichKeeper() const { return U->keepers.getKeeper(keeperIndex); } 102 [[nodiscard]] Keeper* whichKeeper() const { return U->keepers.getKeeper(keeperIndex); }
94}; 103};
diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp
index cb801dd..11e2cff 100644
--- a/src/lindafactory.cpp
+++ b/src/lindafactory.cpp
@@ -41,25 +41,33 @@ static constexpr std::string_view kLindaMetatableName{ "Linda" };
41 41
42void LindaFactory::createMetatable(lua_State* L_) const 42void LindaFactory::createMetatable(lua_State* L_) const
43{ 43{
44 static constexpr std::string_view kIndex{ "__index" };
45
44 STACK_CHECK_START_REL(L_, 0); 46 STACK_CHECK_START_REL(L_, 0);
45 lua_newtable(L_); 47 lua_newtable(L_); // L_: mt
46 // metatable is its own index
47 lua_pushvalue(L_, -1);
48 lua_setfield(L_, -2, "__index");
49 48
50 // protect metatable from external access 49 // protect metatable from external access
51 luaG_pushstring(L_, kLindaMetatableName); 50 luaG_pushstring(L_, kLindaMetatableName); // L_: mt "<name>"
52 lua_setfield(L_, -2, "__metatable"); 51 lua_setfield(L_, -2, "__metatable"); // L_: mt
53 52
54 // the linda functions 53 // the linda functions
55 luaG_registerlibfuncs(L_, mLindaMT); 54 luaG_registerlibfuncs(L_, mLindaMT);
56 55
57 // some constants 56 // some constants
58 kLindaBatched.pushKey(L_); 57 kLindaBatched.pushKey(L_); // L_: mt kLindaBatched
59 lua_setfield(L_, -2, "batched"); 58 lua_setfield(L_, -2, "batched"); // L_: mt
60 59
61 kNilSentinel.pushKey(L_); 60 kNilSentinel.pushKey(L_); // L_: mt kNilSentinel
62 lua_setfield(L_, -2, "null"); 61 lua_setfield(L_, -2, "null"); // L_: mt
62
63 // if the metatable contains __index, leave it as is
64 if (luaG_getfield(L_, kIdxTop, kIndex) != LuaType::NIL) { // L_: mt __index
65 lua_pop(L_, 1); // L_: mt __index
66 } else {
67 // metatable is its own index
68 lua_pushvalue(L_, kIdxTop); // L_: mt mt
69 luaG_setfield(L_, StackIndex{ -2 }, kIndex); // L_: mt
70 }
63 71
64 STACK_CHECK(L_, 1); 72 STACK_CHECK(L_, 1);
65} 73}