aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--CHANGES1
-rw-r--r--docs/index.html12
-rw-r--r--src/keeper.cpp53
-rw-r--r--src/keeper.h1
-rw-r--r--src/linda.cpp29
-rw-r--r--src/macros_and_utils.h2
-rw-r--r--tests/cancel.lua4
7 files changed, 66 insertions, 36 deletions
diff --git a/CHANGES b/CHANGES
index dc38f1b..59ad924 100644
--- a/CHANGES
+++ b/CHANGES
@@ -29,6 +29,7 @@ CHANGE 2: BGe 11-Jun-24
29 - specifying a group to lanes.linda() is mandatory when Lanes is configured with user Keepers. 29 - specifying a group to lanes.linda() is mandatory when Lanes is configured with user Keepers.
30 - linda:deep() result no longer contains the raw C pointer of the Linda object. 30 - linda:deep() result no longer contains the raw C pointer of the Linda object.
31 - linda :receive(), :send(), :get(), :set(), :limit() return nil, error in case of problem. Returned values in case of success change too. 31 - linda :receive(), :send(), :get(), :set(), :limit() return nil, error in case of problem. Returned values in case of success change too.
32 - linda:limit() can be used to read the value if no new limit is provided.
32 - Lindas have a __close metamethod that calls any suitable handler that was provided at Linda creation. 33 - Lindas have a __close metamethod that calls any suitable handler that was provided at Linda creation.
33 - linda:dump outputs <key>.limit as 'unlimited' instead of -1 for unlimited keys. 34 - linda:dump outputs <key>.limit as 'unlimited' instead of -1 for unlimited keys.
34 - deep userdata are an acceptable key to send data into (for example, another linda). 35 - deep userdata are an acceptable key to send data into (for example, another linda).
diff --git a/docs/index.html b/docs/index.html
index c6cf474..8e84fcb 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1222,14 +1222,16 @@
1222</p> 1222</p>
1223 1223
1224<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1224<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1225 bool|(nil,[lanes.cancel_error|"timeout"]) = h:limit(key, n_uint) 1225 bool|(nil,[lanes.cancel_error|"timeout"]) = h:limit(key, &lt;limit&gt;)
1226 &gt;limit&lt; = h:limit(key)
1226</pre></td></tr></table> 1227</pre></td></tr></table>
1227 1228
1228<p> 1229<p>
1229 By default, queue sizes are unlimited but limits can be enforced using the <tt>limit()</tt> method. This can be useful to balance execution speeds in a producer/consumer scenario. <tt>nil</tt> removes the limit.<br /> 1230 By default, queue sizes are unlimited but limits can be enforced using the <tt>limit()</tt> method. This can be useful to balance execution speeds in a producer/consumer scenario.<br />
1230 A limit of 0 is allowed to block everything.<br /> 1231 A limit of 0 is allowed to block everything. <tt>"unlimited"</tt> removes the limit.<br />
1231 If the key was full but the limit change added some room, <tt>limit()</tt> returns <tt>true</tt> and the Linda is signalled so that <tt>send()</tt>-blocked threads are awakened, else the return value is <tt>false</tt>.<br /> 1232 If the key was full but the limit change added some room, <tt>limit()</tt> returns <tt>true</tt> and the Linda is signalled so that <tt>send()</tt>-blocked threads are awakened, else the return value is <tt>false</tt>.
1232 Or <tt>nil, lanes.cancel_error</tt> in case of cancellation, of course. 1233 If no limit is provided, <tt>limit()</tt> returns a single value, the current limit for the specified key.<br />
1234 Whether reading or writing, if the Linda is cancelled, <tt>limit()</tt> returns <tt>nil, lanes.cancel_error</tt>.
1233</p> 1235</p>
1234 1236
1235<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1237<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
diff --git a/src/keeper.cpp b/src/keeper.cpp
index 3054f86..ae09b37 100644
--- a/src/keeper.cpp
+++ b/src/keeper.cpp
@@ -68,7 +68,7 @@ class KeyUD
68 public: 68 public:
69 int first{ 1 }; 69 int first{ 1 };
70 int count{ 0 }; 70 int count{ 0 };
71 int limit{ -1 }; 71 LindaLimit limit{ -1 };
72 72
73 // a fifo full userdata has one uservalue, the table that holds the actual fifo contents 73 // a fifo full userdata has one uservalue, the table that holds the actual fifo contents
74 [[nodiscard]] static void* operator new([[maybe_unused]] size_t size_, KeeperState L_) noexcept { return luaG_newuserdatauv<KeyUD>(L_, 1); } 74 [[nodiscard]] static void* operator new([[maybe_unused]] size_t size_, KeeperState L_) noexcept { return luaG_newuserdatauv<KeyUD>(L_, 1); }
@@ -76,19 +76,19 @@ class KeyUD
76 // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception 76 // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception
77 static void operator delete([[maybe_unused]] void* p_, [[maybe_unused]] KeeperState L_) { LUA_ASSERT(L_, !"should never be called"); } 77 static void operator delete([[maybe_unused]] void* p_, [[maybe_unused]] KeeperState L_) { LUA_ASSERT(L_, !"should never be called"); }
78 78
79 [[nodiscard]] bool changeLimit(int limit_); 79 [[nodiscard]] bool changeLimit(LindaLimit limit_);
80 [[nodiscard]] static KeyUD* Create(KeeperState K_); 80 [[nodiscard]] static KeyUD* Create(KeeperState K_);
81 [[nodiscard]] static KeyUD* GetPtr(KeeperState K_, int idx_); 81 [[nodiscard]] static KeyUD* GetPtr(KeeperState K_, int idx_);
82 void peek(KeeperState K_, int count_) const; // keepercall_get 82 void peek(KeeperState K_, int count_) const; // keepercall_get
83 [[nodiscard]] int pop(KeeperState K_, int minCount_, int maxCount_); // keepercall_receive[_batched] 83 [[nodiscard]] int pop(KeeperState K_, int minCount_, int maxCount_); // keepercall_receive[_batched]
84 void prepareAccess(KeeperState K_, int idx_) const; 84 void prepareAccess(KeeperState K_, int idx_) const;
85 [[nodiscard]] bool push(KeeperState K_, int count_); // keepercall_send 85 [[nodiscard]] bool push(KeeperState K_, int count_, bool enforceLimit_); // keepercall_send and keepercall_set
86 [[nodiscard]] bool reset(KeeperState K_); 86 [[nodiscard]] bool reset(KeeperState K_);
87}; 87};
88 88
89// ################################################################################################# 89// #################################################################################################
90 90
91bool KeyUD::changeLimit(int const limit_) 91bool KeyUD::changeLimit(LindaLimit const limit_)
92{ 92{
93 bool const _newSlackAvailable{ 93 bool const _newSlackAvailable{
94 ((limit >= 0) && (count >= limit)) // then: the key was full if limited and count exceeded the previous limit 94 ((limit >= 0) && (count >= limit)) // then: the key was full if limited and count exceeded the previous limit
@@ -209,11 +209,11 @@ void KeyUD::prepareAccess(KeeperState const K_, int const idx_) const
209 209
210// in: expect this val... on top of the stack 210// in: expect this val... on top of the stack
211// out: nothing, removes all pushed values from the stack 211// out: nothing, removes all pushed values from the stack
212bool KeyUD::push(KeeperState const K_, int const count_) 212bool KeyUD::push(KeeperState const K_, int const count_, bool const enforceLimit_)
213{ 213{
214 int const _fifoIdx{ luaG_absindex(K_, -1 - count_) }; 214 int const _fifoIdx{ luaG_absindex(K_, -1 - count_) };
215 LUA_ASSERT(K_, KeyUD::GetPtr(K_, _fifoIdx) == this); // K_: this val... 215 LUA_ASSERT(K_, KeyUD::GetPtr(K_, _fifoIdx) == this); // K_: this val...
216 if (limit >= 0 && count + count_ > limit) { // not enough room 216 if (enforceLimit_ && (limit >= 0) && (count + count_ > limit)) { // not enough room
217 return false; 217 return false;
218 } 218 }
219 219
@@ -239,7 +239,8 @@ bool KeyUD::reset(KeeperState const K_)
239 STACK_CHECK_START_REL(K_, 0); 239 STACK_CHECK_START_REL(K_, 0);
240 bool const _wasFull{ (limit > 0) && (count >= limit) }; 240 bool const _wasFull{ (limit > 0) && (count >= limit) };
241 // empty the KeyUD: replace uservalue with a virgin table, reset counters, but leave limit unchanged! 241 // empty the KeyUD: replace uservalue with a virgin table, reset counters, but leave limit unchanged!
242 lua_newtable(K_); // K_: KeysDB key val... KeyUD {} 242 // if we have an actual limit, use it to preconfigure the table
243 lua_createtable(K_, (limit <= 0) ? 0 : limit, 0); // K_: KeysDB key val... KeyUD {}
243 lua_setiuservalue(K_, -2, kContentsTableIndex); // K_: KeysDB key val... KeyUD 244 lua_setiuservalue(K_, -2, kContentsTableIndex); // K_: KeysDB key val... KeyUD
244 first = 1; 245 first = 1;
245 count = 0; 246 count = 0;
@@ -397,23 +398,34 @@ int keepercall_get(lua_State* const L_)
397int keepercall_limit(lua_State* const L_) 398int keepercall_limit(lua_State* const L_)
398{ 399{
399 KeeperState const _K{ L_ }; 400 KeeperState const _K{ L_ };
400 int const _limit{ static_cast<int>(luaL_optinteger(_K, 3, -1)) }; // -1 if we read nil because the argument is absent 401 // no limit to set, means we read and return the current limit instead
402 bool const _reading{ lua_gettop(_K) == 2 };
403 LindaLimit const _limit{ static_cast<LindaLimit::type>(luaL_optinteger(_K, 3, -1)) }; // -1 if we read nil because the argument is absent
401 lua_settop(_K, 2); // _K: linda key 404 lua_settop(_K, 2); // _K: linda key
402 PushKeysDB(_K, 1); // _K: linda key KeysDB 405 PushKeysDB(_K, 1); // _K: linda key KeysDB
403 lua_replace(_K, 1); // _K: KeysDB key 406 lua_replace(_K, 1); // _K: KeysDB key
404 lua_pushvalue(_K, -1); // _K: KeysDB key key 407 lua_pushvalue(_K, -1); // _K: KeysDB key key
405 lua_rawget(_K, -3); // _K: KeysDB key KeyUD|nil 408 lua_rawget(_K, -3); // _K: KeysDB key KeyUD|nil
406 KeyUD* _key{ KeyUD::GetPtr(_K, -1) }; 409 KeyUD* _key{ KeyUD::GetPtr(_K, -1) };
407 if (_key == nullptr) { // _K: KeysDB key nil 410 if (_reading) {
408 lua_pop(_K, 1); // _K: KeysDB key 411 if (_key && _key->limit >= 0) {
409 _key = KeyUD::Create(_K); // _K: KeysDB key KeyUD 412 lua_pushinteger(_K, _key->limit); // _K: KeysDB key KeyUD limit
410 lua_rawset(_K, -3); // _K: KeysDB 413 } else { // if the key doesn't exist, it is unlimited by default
414 luaG_pushstring(_K, "unlimited"); // _K: KeysDB key KeyUD "unlimited"
415 }
416 // return a single value: the limit of the key
417 } else {
418 if (_key == nullptr) { // _K: KeysDB key nil
419 lua_pop(_K, 1); // _K: KeysDB key
420 _key = KeyUD::Create(_K); // _K: KeysDB key KeyUD
421 lua_rawset(_K, -3); // _K: KeysDB
422 }
423 // remove any clutter on the stack
424 lua_settop(_K, 0); // _K:
425 // return true if we decide that blocked threads waiting to write on that key should be awakened
426 // this is the case if we detect the key was full but it is no longer the case
427 lua_pushboolean(_K, _key->changeLimit(_limit) ? 1 : 0); // _K: bool
411 } 428 }
412 // remove any clutter on the stack
413 lua_settop(_K, 0); // _K:
414 // return true if we decide that blocked threads waiting to write on that key should be awakened
415 // this is the case if we detect the key was full but it is no longer the case
416 lua_pushboolean(_K, _key->changeLimit(_limit) ? 1 : 0); // _K: bool
417 return 1; 429 return 1;
418} 430}
419 431
@@ -502,7 +514,7 @@ int keepercall_send(lua_State* const L_)
502 lua_pop(_K, 1); // _K: linda KeyUD val... 514 lua_pop(_K, 1); // _K: linda KeyUD val...
503 STACK_CHECK(_K, 0); 515 STACK_CHECK(_K, 0);
504 KeyUD* const _key{ KeyUD::GetPtr(_K, 2) }; 516 KeyUD* const _key{ KeyUD::GetPtr(_K, 2) };
505 if (_key && _key->push(_K, _n)) { // not enough room? 517 if (_key && _key->push(_K, _n, true)) { // not enough room?
506 lua_settop(_K, 0); // _K: 518 lua_settop(_K, 0); // _K:
507 lua_pushboolean(_K, 1); // _K: true 519 lua_pushboolean(_K, 1); // _K: true
508 } else { 520 } else {
@@ -527,8 +539,7 @@ int keepercall_set(lua_State* const L_)
527 PushKeysDB(_K, 1); // _K: linda key val... KeysDB 539 PushKeysDB(_K, 1); // _K: linda key val... KeysDB
528 lua_replace(_K, 1); // _K: KeysDB key val... 540 lua_replace(_K, 1); // _K: KeysDB key val...
529 541
530 // make sure we have a value on the stack 542 if (lua_gettop(_K) == 2) { // no value to set // _K: KeysDB key
531 if (lua_gettop(_K) == 2) { // _K: KeysDB key
532 lua_pushvalue(_K, -1); // _K: KeysDB key key 543 lua_pushvalue(_K, -1); // _K: KeysDB key key
533 lua_rawget(_K, 1); // _K: KeysDB key KeyUD|nil 544 lua_rawget(_K, 1); // _K: KeysDB key KeyUD|nil
534 // empty the KeyUD for the specified key: replace uservalue with a virgin table, reset counters, but leave limit unchanged! 545 // empty the KeyUD for the specified key: replace uservalue with a virgin table, reset counters, but leave limit unchanged!
@@ -563,7 +574,7 @@ int keepercall_set(lua_State* const L_)
563 } 574 }
564 // replace the key with the KeyUD in the stack 575 // replace the key with the KeyUD in the stack
565 lua_replace(_K, -2 - _count); // _K: KeysDB KeyUD val... 576 lua_replace(_K, -2 - _count); // _K: KeysDB KeyUD val...
566 [[maybe_unused]] bool const _pushed{ _key->push(_K, _count) }; // _K: KeysDB 577 [[maybe_unused]] bool const _pushed{ _key->push(_K, _count, false) }; // _K: KeysDB
567 } 578 }
568 // stack isn't the same here depending on what we did before, but that's not a problem 579 // stack isn't the same here depending on what we did before, but that's not a problem
569 lua_pushboolean(_K, _should_wake_writers ? 1 : 0); // _K: ... bool 580 lua_pushboolean(_K, _should_wake_writers ? 1 : 0); // _K: ... bool
diff --git a/src/keeper.h b/src/keeper.h
index 102d006..05e3547 100644
--- a/src/keeper.h
+++ b/src/keeper.h
@@ -8,6 +8,7 @@ enum class LookupMode;
8class Universe; 8class Universe;
9 9
10using KeeperState = Unique<lua_State*>; 10using KeeperState = Unique<lua_State*>;
11using LindaLimit = Unique<int>;
11 12
12// ################################################################################################# 13// #################################################################################################
13 14
diff --git a/src/linda.cpp b/src/linda.cpp
index 079ab9d..f4dd7e7 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -452,21 +452,23 @@ LUAG_FUNC(linda_get)
452 452
453/* 453/*
454 * [bool]|nil,cancel_error = linda:limit(key_num|str|bool|lightuserdata, [int]) 454 * [bool]|nil,cancel_error = linda:limit(key_num|str|bool|lightuserdata, [int])
455 * "unlimited"|number = linda:limit(key)
455 * 456 *
456 * Set limit to 1 Linda keys. 457 * Read or set limit to 1 Linda keys.
457 * Optionally wake threads waiting to write on the linda, in case the limit enables them to do so 458 * Optionally wake threads waiting to write on the linda, in case the limit enables them to do so
458 * Limit can be 0 to completely block everything, nil to reset 459 * Limit can be 0 to completely block everything, "unlimited" to reset
459 */ 460 */
460LUAG_FUNC(linda_limit) 461LUAG_FUNC(linda_limit)
461{ 462{
462 static constexpr lua_CFunction _limit{ 463 static constexpr lua_CFunction _limit{
463 +[](lua_State* const L_) { 464 +[](lua_State* const L_) {
464 Linda* const _linda{ ToLinda<false>(L_, 1) }; 465 Linda* const _linda{ ToLinda<false>(L_, 1) };
465 // make sure we got 3 arguments: the linda, a key and a limit 466 // make sure we got 2 or 3 arguments: the linda, a key and optionally a limit
466 int const _nargs{ lua_gettop(L_) }; 467 int const _nargs{ lua_gettop(L_) };
467 luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments"); 468 luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments");
468 // make sure we got a numeric limit 469 // make sure we got a numeric limit, or "unlimited", (or nothing)
469 lua_Integer const _val{ luaL_optinteger(L_, 3, 0) }; 470 bool const _unlimited{ luaG_tostring(L_, 3) == "unlimited" };
471 LindaLimit const _val{ _unlimited ? std::numeric_limits<LindaLimit::type>::max() : LindaLimit{ static_cast<LindaLimit::type>(luaL_optinteger(L_, 3, 0)) } };
470 if (_val < 0) { 472 if (_val < 0) {
471 raise_luaL_argerror(L_, 3, "limit must be >= 0"); 473 raise_luaL_argerror(L_, 3, "limit must be >= 0");
472 } 474 }
@@ -476,10 +478,21 @@ LUAG_FUNC(linda_limit)
476 KeeperCallResult _pushed; 478 KeeperCallResult _pushed;
477 if (_linda->cancelRequest == CancelRequest::None) { 479 if (_linda->cancelRequest == CancelRequest::None) {
478 Keeper* const _keeper{ _linda->whichKeeper() }; 480 Keeper* const _keeper{ _linda->whichKeeper() };
481 if (_unlimited) {
482 LUA_ASSERT(L_, lua_gettop(L_) == 3 && luaG_tostring(L_, 3) == "unlimited");
483 // inside the Keeper, unlimited is signified with a -1 limit (can't use nil because of nil kNilSentinel conversions!)
484 lua_pop(L_, 1); // L_: linda key
485 lua_pushinteger(L_, -1); // L_: linda key nil
486 }
479 _pushed = keeper_call(_keeper->K, KEEPER_API(limit), L_, _linda, 2); 487 _pushed = keeper_call(_keeper->K, KEEPER_API(limit), L_, _linda, 2);
480 LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 1) && luaG_type(L_, -1) == LuaType::BOOLEAN); // no error, boolean value saying if we should wake blocked writer threads 488 LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 1));
481 if (lua_toboolean(L_, -1)) { 489 if (_nargs == 3) { // 3 args: setting the limit
482 _linda->readHappened.notify_all(); // To be done from within the 'K' locking area 490 LUA_ASSERT(L_, luaG_type(L_, -1) == LuaType::BOOLEAN); // changing the limit: no error, boolean value saying if we should wake blocked writer threads
491 if (lua_toboolean(L_, -1)) {
492 _linda->readHappened.notify_all(); // To be done from within the 'K' locking area
493 }
494 } else { // 2 args: reading the limit
495 LUA_ASSERT(L_, luaG_type(L_, -1) == LuaType::NUMBER || luaG_tostring(L_, -1) == "unlimited"); // reading the limit: a number >=0 or "unlimited"
483 } 496 }
484 } else { // linda is cancelled 497 } else { // linda is cancelled
485 // do nothing and return nil,lanes.cancel_error 498 // do nothing and return nil,lanes.cancel_error
diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h
index 787cf03..1b1ced6 100644
--- a/src/macros_and_utils.h
+++ b/src/macros_and_utils.h
@@ -32,6 +32,7 @@ class Unique
32 T val; 32 T val;
33 33
34 public: 34 public:
35 using type = T;
35 Unique() = default; 36 Unique() = default;
36 operator T() const { return val; } 37 operator T() const { return val; }
37 explicit Unique(T b_) 38 explicit Unique(T b_)
@@ -45,6 +46,7 @@ class Unique<T, lambda, std::enable_if_t<!std::is_scalar_v<T>>>
45: public T 46: public T
46{ 47{
47 public: 48 public:
49 using type = T;
48 using T::T; 50 using T::T;
49 explicit Unique(T const& b_) 51 explicit Unique(T const& b_)
50 : T{ b_ } 52 : T{ b_ }
diff --git a/tests/cancel.lua b/tests/cancel.lua
index d6c293d..1724e6a 100644
--- a/tests/cancel.lua
+++ b/tests/cancel.lua
@@ -50,9 +50,9 @@ if not next(which_tests) or which_tests.genlock then
50 50
51 -- reset the linda so that the other tests work 51 -- reset the linda so that the other tests work
52 linda:cancel("none") 52 linda:cancel("none")
53 linda:limit("lock") 53 linda:limit("lock", "unlimited")
54 linda:set("lock") 54 linda:set("lock")
55 linda:limit("atomic") 55 linda:limit("atomic", "unlimited")
56 linda:set("atomic") 56 linda:set("atomic")
57 57
58 print "test OK" 58 print "test OK"