aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES1
-rw-r--r--docs/index.html132
-rw-r--r--src/cancel.cpp2
-rw-r--r--src/compat.hpp10
-rw-r--r--src/keeper.cpp145
-rw-r--r--src/keeper.hpp14
-rw-r--r--src/lanes.lua1
-rw-r--r--src/linda.cpp104
-rw-r--r--tests/cancel.lua11
-rw-r--r--tests/fifo.lua2
10 files changed, 327 insertions, 95 deletions
diff --git a/CHANGES b/CHANGES
index db44b2c..86da9e8 100644
--- a/CHANGES
+++ b/CHANGES
@@ -45,6 +45,7 @@ CHANGE 2: BGe 27-Nov-24
45 - linda:deep() result no longer contains the raw C pointer of the Linda object. 45 - linda:deep() result no longer contains the raw C pointer of the Linda object.
46 - linda :receive(), :send(), :get(), :set(), :limit() return nil, error in case of problem. Returned values in case of success change too. 46 - linda :receive(), :send(), :get(), :set(), :limit() return nil, error in case of problem. Returned values in case of success change too.
47 - linda:limit() can be used to read the value if no new limit is provided. 47 - linda:limit() can be used to read the value if no new limit is provided.
48 - linda:restrict() can restrain the use of send/receive or set/get on any key.
48 - 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.
49 - 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.
50 - linda:wake() can wake up threads waiting for a Linda without doing any I/O on it. 51 - linda:wake() can wake up threads waiting for a Linda without doing any I/O on it.
diff --git a/docs/index.html b/docs/index.html
index 7432c53..6757981 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1218,7 +1218,7 @@
1218 lane_h = lanes.gen("", loop)(10000) 1218 lane_h = lanes.gen("", loop)(10000)
1219 1219
1220 while true do 1220 while true do
1221 local key, val = linda:receive(3.0, "x") -- timeout in seconds 1221 local slot, val = linda:receive(3.0, "x") -- timeout in seconds
1222 if val == nil then 1222 if val == nil then
1223 print("timed out") 1223 print("timed out")
1224 break 1224 break
@@ -1233,7 +1233,7 @@
1233 Characteristics of the Lanes implementation of lindas are: 1233 Characteristics of the Lanes implementation of lindas are:
1234 1234
1235 <ul> 1235 <ul>
1236 <li>Keys can be of boolean, number, string, light userdata, and deep userdata type. Tables, functions and non-deep userdata can't be keys because their identity isn't preserved when transfered from one Lua state to another.</li> 1236 <li>Slots can be of boolean, number, string, light userdata, and deep userdata type. Tables, functions and non-deep userdata can't be slots because their identity isn't preserved when transfered from one Lua state to another.</li>
1237 <li>Values can be any type supported by inter-state copying (same <a href="#limitations">limits</a> as for function arguments and upvalues).</li> 1237 <li>Values can be any type supported by inter-state copying (same <a href="#limitations">limits</a> as for function arguments and upvalues).</li>
1238 <li> 1238 <li>
1239 Registered functions and userdata transiting into a Keeper state are converted to a special dummy closure that holds its actual identity. On transit out, the identity is used to find the real function or userdata in the destination. 1239 Registered functions and userdata transiting into a Keeper state are converted to a special dummy closure that holds its actual identity. On transit out, the identity is used to find the real function or userdata in the destination.
@@ -1242,10 +1242,11 @@
1242 <li>Consuming method is <tt>:receive</tt> (not in).</li> 1242 <li>Consuming method is <tt>:receive</tt> (not in).</li>
1243 <li>Non-consuming method is <tt>:get</tt> (not rd).</li> 1243 <li>Non-consuming method is <tt>:get</tt> (not rd).</li>
1244 <li>Two producer-side methods: <tt>:send</tt> and <tt>:set</tt> (not out).</li> 1244 <li>Two producer-side methods: <tt>:send</tt> and <tt>:set</tt> (not out).</li>
1245 <li><tt>send</tt> allows for sending multiple values -atomically- to a given key.</li> 1245 <li><tt>send</tt> allows for sending multiple values -atomically- to a given slot.</li>
1246 <li><tt>receive</tt> can wait for multiple keys at once.</li> 1246 <li><tt>receive</tt> can wait for multiple slots at once.</li>
1247 <li><tt>receive</tt> has a batched mode to consume more than one value from a single key, as in <tt>linda:receive(1.0, linda.batched, "key", 3, 6).</tt></li> 1247 <li><tt>receive</tt> has a batched mode to consume more than one value from a single slot, as in <tt>linda:receive(1.0, linda.batched, "slot", 3, 6).</tt></li>
1248 <li>Individual keys' queue length can be limited, balancing speed differences in a producer/consumer scenario (making <tt>:send</tt> wait).</li> 1248 <li><tt>restrict</tt> can restrain a particular slot to function either with <tt>send/receive</tt> or <tt>set/get</tt>.</li>
1249 <li>Individual slots' queue length can be limited, balancing speed differences in a producer/consumer scenario (making <tt>:send</tt> wait).</li>
1249 <li><tt>tostring(linda)</tt> returns a string of the form <tt>"Linda: &lt;opt_name&gt;"</tt></li> 1250 <li><tt>tostring(linda)</tt> returns a string of the form <tt>"Linda: &lt;opt_name&gt;"</tt></li>
1250 <li> 1251 <li>
1251 Several linda objects may share the same <a href="#keepers">Keeper state</a>. In case there is more than one user <a href="#keepers">Keeper state</a>, assignation must be controlled with the linda's group (an integer in <tt>[0,nb_user_keepers]</tt>). 1252 Several linda objects may share the same <a href="#keepers">Keeper state</a>. In case there is more than one user <a href="#keepers">Keeper state</a>, assignation must be controlled with the linda's group (an integer in <tt>[0,nb_user_keepers]</tt>).
@@ -1272,65 +1273,84 @@
1272</p> 1273</p>
1273 1274
1274<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1275<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1275 bool,string|(nil,[lanes.cancel_error|"timeout"]) = h:limit(key, &lt;limit&gt;) 1276 bool,string|(nil,[lanes.cancel_error|"timeout"]) = h:limit(slot, &lt;limit&gt;)
1276 (number|string),string = h:limit(key) 1277 (number|string),string = h:limit(slot)
1277</pre></td></tr></table> 1278</pre></td></tr></table>
1278 1279
1279<p> 1280<p>
1280 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 /> 1281 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 />
1281 A limit of 0 is allowed to block everything. <tt>"unlimited"</tt> removes the limit.<br /> 1282 A limit of 0 is allowed to block everything. <tt>"unlimited"</tt> removes the limit.<br />
1282 If the key was full but the limit change added some room, <tt>limit()</tt> first return value is <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>. 1283 If the slot was full but the limit change added some room, <tt>limit()</tt> first return value is <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>.
1283 If no limit is provided, <tt>limit()</tt> first return value is the current limit for the specified key.<br /> 1284 If no limit is provided, <tt>limit()</tt> first return value is the current limit for the specified slot.<br />
1284 The second returned value is a string representing the fill status relatively to the key's current limit (one of <tt>"over"</tt>, <tt>"under"</tt>, <tt>"exact"</tt>). 1285 The second returned value is a string representing the fill status relatively to the slot's current limit (one of <tt>"over"</tt>, <tt>"under"</tt>, <tt>"exact"</tt>).
1285 Whether reading or writing, if the linda is cancelled, <tt>limit()</tt> returns <tt>nil, lanes.cancel_error</tt>. 1286 Whether reading or writing, if the linda is cancelled, <tt>limit()</tt> returns <tt>nil, lanes.cancel_error</tt>.
1286</p> 1287</p>
1287 1288
1288<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1289<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1289 true|lanes.cancel_error = h:send([timeout_secs,] key, ...) 1290 string|(nil,[lanes.cancel_error|"timeout"]) = h:restrict(slot [, "&lt;mode&gt;"])
1291 string = h:restrict(slot)
1290</pre></td></tr></table> 1292</pre></td></tr></table>
1291 1293
1292<p> 1294<p>
1293 Timeouts are given in seconds (&gt= 0, millisecond accuracy) or <tt>nil</tt>. Timeout can be omitted only if the first key is not a number (then it is equivalent to an infinite duration).<br /> 1295 It is possible to restrict a particular slot in a Linda to either <tt>send/receive</tt> or <tt>set/get</tt> operations.<br />
1294 Each key acts as a FIFO queue. There is no limit to the number of keys a linda may contain. Different lindas can have identical keys, which are totally unrelated. 1296 Possible modes are <tt>"none"</tt>, <tt>"set/get"</tt> or <tt>"send/receive"</tt>.<br />
1297 If a new mode is specified, <tt>restrict()</tt> updates the mode and returns the previous one.<br />
1298 If no mode is specified, <tt>restrict()</tt> does nothing and returns the current mode.<br />
1299 If the linda is cancelled, <tt>restrict()</tt> returns <tt>nil, lanes.cancel_error</tt>.<br />
1300 If an unknown mode is specified, <tt>restrict()</tt> raises an error.
1301</p>
1302
1303<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1304 true|lanes.cancel_error = h:send([timeout_secs,] slot, ...)
1305</pre></td></tr></table>
1306
1307<p>
1308 Timeouts are given in seconds (&gt= 0, millisecond accuracy) or <tt>nil</tt>. Timeout can be omitted only if the first slot is not a number (then it is equivalent to an infinite duration).<br />
1309 Each slot acts as a FIFO queue. There is no limit to the number of slots a linda may contain. Different lindas can have identical slots, which are totally unrelated.
1295</p> 1310</p>
1296 1311
1297<p> 1312<p>
1298 Multiple values can be sent to a given key at once, atomically (the send will fail unless all the values fit within the queue limit). This can be useful for multiple producer scenarios, if the protocols used are giving data in streams of multiple units. 1313 Multiple values can be sent to a given slot at once, atomically (the send will fail unless all the values fit within the queue limit). This can be useful for multiple producer scenarios, if the protocols used are giving data in streams of multiple units.
1299 Atomicity avoids the producers from garbling each others messages, which could happen if the units were sent individually. 1314 Atomicity avoids the producers from garbling each others messages, which could happen if the units were sent individually.
1300</p> 1315</p>
1301 1316
1302<p> 1317<p>
1303 If no data is provided after the key, <tt>send()</tt> raises an error.<br /> 1318 If <tt>linda.null</tt> or <tt>lanes.null</tt> is sent as data in a linda, it will be read as a <tt>nil</tt>.<br />
1304 Also, if <tt>linda.null</tt> or <tt>lanes.null</tt> is sent as data in a linda, it will be read as a <tt>nil</tt>.<br /> 1319</p>
1320
1321<p>
1322 <tt>send()</tt> raises an error if no data is provided after the slot.<br />
1323 <tt>send()</tt> raises an error if called when a restriction forbids its use on the provided slot.<br />
1324 <tt>send()</tt> raises <tt>lanes.cancel_error</tt> if interrupted by a hard cancel request.<br />
1305 <tt>send()</tt> return values can be: 1325 <tt>send()</tt> return values can be:
1306 <ul> 1326 <ul>
1307 <li><tt>true</tt> on success.</li> 1327 <li><tt>true</tt> on success.</li>
1308 <li><tt>nil, "timeout"</tt> if the queue limit was met, and the queue did not empty enough during the given duration.</li> 1328 <li><tt>nil, "timeout"</tt> if the queue limit was met, and the queue did not empty enough during the given duration.</li>
1309 <li><tt>nil, lanes.cancel_error</tt> if interrupted by a soft cancel request.</li> 1329 <li><tt>nil, lanes.cancel_error</tt> if interrupted by a soft cancel request.</li>
1310 <li>Raises <tt>lanes.cancel_error</tt> if interrupted by a hard cancel request.</li>
1311 </ul> 1330 </ul>
1312</p> 1331</p>
1313 1332
1314<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1333<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1315 key, val = h:receive([timeout_secs,] key [, key...]) 1334 slot, val = h:receive([timeout_secs,] slot [, slot...])
1316 1335
1317 key, val [, val...] = h:receive([timeout,] h.batched, key, n_uint_min[, n_uint_max]) 1336 slot, val [, val...] = h:receive([timeout,] h.batched, slot, n_uint_min[, n_uint_max])
1318</pre></td></tr></table> 1337</pre></td></tr></table>
1319 1338
1320<p> 1339<p>
1340 <tt>receive()</tt> raises an error if called when a restriction forbids its use on any provided slot.<br />
1341 In batched mode, <tt>receive()</tt> will raise an error if <tt>min_count < 1</tt> or <tt>max_count < min_count</tt>.
1342</p>
1343
1344<p>
1321 Unbatched <tt>receive()</tt> return values can be: 1345 Unbatched <tt>receive()</tt> return values can be:
1322 <ul> 1346 <ul>
1323 <li><tt>nil, lanes.cancel_error</tt> if interrupted by a hard cancel request.</li> 1347 <li><tt>nil, lanes.cancel_error</tt> if interrupted by a hard cancel request.</li>
1324 <li><tt>nil, "timeout"</tt> if nothing was available.</li> 1348 <li><tt>nil, "timeout"</tt> if nothing was available.</li>
1325 <li>A key and the value extracted from it. Note that <tt>nil</tt> can be sent and received; the <tt>key</tt> value will tell it apart from a timeout.</li> 1349 <li>A slot and the value extracted from it. Note that <tt>nil</tt> can be sent and received; the first returned value <tt>slot</tt> will tell it apart from a timeout.</li>
1326 </ul> 1350 </ul>
1327</p> 1351</p>
1328 1352
1329<p> 1353<p>
1330 In batched mode, <tt>linda:receive()</tt> will raise an error if <tt>min_count < 1</tt> or <tt>max_count < min_count</tt>.
1331</p>
1332
1333<p>
1334 Note that any number of lanes can be reading or writing a linda. There can be many producers, and many consumers. It is up to you. 1354 Note that any number of lanes can be reading or writing a linda. There can be many producers, and many consumers. It is up to you.
1335</p> 1355</p>
1336 1356
@@ -1339,19 +1359,20 @@
1339</p> 1359</p>
1340 1360
1341<p> 1361<p>
1342 When receiving from multiple slots, the keys are checked in order, which can be used for making priority queues. 1362 When receiving from multiple slots, the slots are checked in order, which can be used for making priority queues.
1343</p> 1363</p>
1344 1364
1345<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1365<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1346 (bool,string)|(nil,lanes.cancel_error) = linda_h:set(key [, val [, ...]]) 1366 (bool,string)|(nil,lanes.cancel_error) = linda_h:set(slot [, val [, ...]])
1347 1367
1348 (number,[val [, ...]])|(nil,lanes.cancel_error) = linda_h:get(key [, count = 1]) 1368 (number,[val [, ...]])|(nil,lanes.cancel_error) = linda_h:get(slot [, count = 1])
1349</pre></td></tr></table> 1369</pre></td></tr></table>
1350 1370
1351<p> 1371<p>
1352 <tt>get()</tt>/<tt>set()</tt> and <tt>send()</tt>/<tt>receive()</tt> can be used together; reading a slot essentially peeks the next outcoming value of a queue.<br /> 1372 <tt>set()</tt> and <tt>get()</tt> raise an error if used when a restriction forbids their use on the provided slot.<br />
1353 <tt>get()</tt>/<tt>set()</tt> are for accessing a key without queuing or consuming. They can be used for making shared tables of storage among the lanes.<br /> 1373 Unless a particular slot is constrained with <tt>restrict()</tt>, <tt>get()</tt>/<tt>set()</tt> and <tt>send()</tt>/<tt>receive()</tt> can be used together; reading a slot essentially peeks the next outcoming value of a queue.<br />
1354 Writing to a key never blocks because it ignores the limit. It overwrites existing values and clears any possible queued entries.<br /> 1374 <tt>get()</tt>/<tt>set()</tt> are for accessing a slot without queuing or consuming. They can be used for making shared tables of storage among the lanes.<br />
1375 <tt>set()</tt> never blocks because it ignores the limit. It overwrites existing values and clears any possible queued entries.<br />
1355</p> 1376</p>
1356<p> 1377<p>
1357 <tt>get()</tt> can read several values at once, and does not block. Return values ares: 1378 <tt>get()</tt> can read several values at once, and does not block. Return values ares:
@@ -1361,12 +1382,12 @@
1361 </ul> 1382 </ul>
1362</p> 1383</p>
1363<p> 1384<p>
1364 <tt>set()</tt> can write several values at the specified key. Writing <tt>nil</tt> values is possible, and clearing the contents at the specified key is done by not providing any value.<br /> 1385 <tt>set()</tt> can write several values at the specified slot. Writing <tt>nil</tt> values is possible, and clearing the contents at the specified slot is done by not providing any value.<br />
1365 If <tt>set()</tt> actually stores data, the linda is signalled for write, so that <tt>receive()</tt>-blocked Lanes are awakened.<br /> 1386 If <tt>set()</tt> actually stores data, the linda is signalled for write, so that <tt>receive()</tt>-blocked Lanes are awakened.<br />
1366 Clearing the contents of a non-existent key does not create it!<br /> 1387 Clearing the contents of a non-existent slot does not create it!<br />
1367 If the key was full but the new data count of the key after <tt>set()</tt> is below its limit, <tt>set()</tt> first return value is <tt>true</tt> and the linda is also signaled for read, so that <tt>send()</tt>-blocked Lanes are awakened.<br /> 1388 If the slot was full but the new data count of the slot after <tt>set()</tt> is below its limit, <tt>set()</tt> first return value is <tt>true</tt> and the linda is also signaled for read, so that <tt>send()</tt>-blocked Lanes are awakened.<br />
1368 If the key was not already full, nothing additional happens, and <tt>set()</tt> first return value is <tt>false</tt>.<br /> 1389 If the slot was not already full, nothing additional happens, and <tt>set()</tt> first return value is <tt>false</tt>.<br />
1369 The second return value is a string representing the fill status relatively to the key's current limit (one of <tt>"over"</tt>, <tt>"under"</tt>, <tt>"exact"</tt>). 1390 The second return value is a string representing the fill status relatively to the slot's current limit (one of <tt>"over"</tt>, <tt>"under"</tt>, <tt>"exact"</tt>).
1370</p> 1391</p>
1371 1392
1372<p> 1393<p>
@@ -1374,25 +1395,26 @@
1374</p> 1395</p>
1375 1396
1376<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1397<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1377 [val] = linda_h:count([key[,...]]) 1398 [val] = linda_h:count([slot[,...]])
1378</pre></td></tr></table> 1399</pre></td></tr></table>
1379 1400
1380<p> 1401<p>
1381 Returns some information about the contents of the linda.<br /> 1402 Returns some information about the contents of the linda.<br />
1382 If no key is specified, and the linda is empty, returns nothing.<br /> 1403 If no slot is specified, and the linda is empty, returns nothing.<br />
1383 If no key is specified, and the linda is not empty, returns a table of key/count pairs that counts the number of items in each of the exiting keys of the linda. This count can be 0 if the key has been used but is empty.<br /> 1404 If no slot is specified, and the linda is not empty, returns a table of slot/count pairs that counts the number of items in each of the exiting slots of the linda. This count can be 0 if the slot has been used but is empty.<br />
1384 If a single key is specified, returns the number of pending items, or nothing if the key is unknown.<br /> 1405 If a single slot is specified, returns the number of pending items, or nothing if the slot is unknown.<br />
1385 If more than one key is specified, return a table of key/count pairs for the known keys. 1406 If more than one slot is specified, return a table of slot/count pairs for the known slots.
1386</p> 1407</p>
1387 1408
1388<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1409<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1389 linda_h:dump() -> 1410 linda_h:dump() ->
1390 { 1411 {
1391 [&lt;key&gt;] = 1412 [&lt;slot&gt;] =
1392 { 1413 {
1393 first = &lt;n&gt; 1414 first = &lt;n&gt;
1394 count = &lt;n&gt; 1415 count = &lt;n&gt;
1395 limit = &lt;n&gt;|'unlimited' 1416 limit = &lt;n&gt;|'unlimited'
1417 restrict = "none"|"set/get"|"send/receive"
1396 fifo = { &lt;array of values&gt; } 1418 fifo = { &lt;array of values&gt; }
1397 } 1419 }
1398 ... 1420 ...
@@ -1458,7 +1480,7 @@
1458 </li> 1480 </li>
1459 1481
1460 <li> 1482 <li>
1461 Namespace control. linda keys have a "flat" namespace, so collisions are possible if you try to use the same linda for too many separate uses. 1483 Namespace control. linda slots have a "flat" namespace, so collisions are possible if you try to use the same linda for too many separate uses.
1462 </li> 1484 </li>
1463 1485
1464 <li> 1486 <li>
@@ -1467,7 +1489,7 @@
1467 </li> 1489 </li>
1468 </ul> 1490 </ul>
1469 1491
1470 On the other side, you need to use a common linda for waiting for multiple keys. You cannot wait for keys from two separate linda objects at the same time. 1492 On the other side, you need to use a common linda for waiting for multiple slots. You cannot wait for slots from two separate linda objects at the same time.
1471</p> 1493</p>
1472 1494
1473<p> 1495<p>
@@ -1480,7 +1502,7 @@
1480<h2 id="timers">Timers</h2> 1502<h2 id="timers">Timers</h2>
1481 1503
1482<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1504<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1483 void = lanes.timer(linda_h, key, date_tbl|first_secs [,period_secs]) 1505 void = lanes.timer(linda_h, slot, date_tbl|first_secs [,period_secs])
1484</pre></td></tr></table> 1506</pre></td></tr></table>
1485 1507
1486<p> 1508<p>
@@ -1492,7 +1514,7 @@
1492</p> 1514</p>
1493 1515
1494<p> 1516<p>
1495 Once a timer expires, the <tt>key</tt> is set with the current time (in seconds, same offset as <tt>os.time()</tt> but with millisecond accuracy). The key can be waited upon using the regular <a href="#lindas">linda</a> <tt>:receive()</tt> method. 1517 Once a timer expires, the <tt>slot</tt> is set with the current time (in seconds, same offset as <tt>os.time()</tt> but with millisecond accuracy). The slot can be waited upon using the regular <a href="#lindas">linda</a> <tt>:receive()</tt> method.
1496</p> 1518</p>
1497 1519
1498<p> 1520<p>
@@ -1517,13 +1539,13 @@
1517 lanes.timer(linda, "min", t, 60) -- reoccur every minute (sharp) 1539 lanes.timer(linda, "min", t, 60) -- reoccur every minute (sharp)
1518 1540
1519 while true do 1541 while true do
1520 local key, v = linda:receive("sec", "min") 1542 local slot, v = linda:receive("sec", "min")
1521 print("Timer "..key..": "..v) 1543 print("Timer "..slot..": "..v)
1522 end 1544 end
1523</pre></td></tr></table> 1545</pre></td></tr></table>
1524 1546
1525<p> 1547<p>
1526 NOTE: Timer keys are set, not queued, so missing a beat is possible especially if the timer cycle is extremely small. The key value can be used to know the actual time passed. 1548 NOTE: Timer slots are set, not queued, so missing a beat is possible especially if the timer cycle is extremely small. The slot value can be used to know the actual time passed.
1527</p> 1549</p>
1528 1550
1529<table> 1551<table>
@@ -1533,7 +1555,7 @@
1533 <font size="-1"> 1555 <font size="-1">
1534 Having the API as <tt>lanes.timer()</tt> is intentional. Another alternative would be <tt>linda_h:timer()</tt> but timers are not traditionally seen to be part of lindas. Also, it would mean any lane getting a <a href="#lindas">linda</a> handle would be able to modify timers on it. 1556 Having the API as <tt>lanes.timer()</tt> is intentional. Another alternative would be <tt>linda_h:timer()</tt> but timers are not traditionally seen to be part of lindas. Also, it would mean any lane getting a <a href="#lindas">linda</a> handle would be able to modify timers on it.
1535 A third choice could be abstracting the timers out of <a href="#lindas">linda</a> realm altogether (<tt>timer_h= lanes.timer(date|first_secs, period_secs )</tt>) but that would mean separate waiting functions for timers, and lindas. 1557 A third choice could be abstracting the timers out of <a href="#lindas">linda</a> realm altogether (<tt>timer_h= lanes.timer(date|first_secs, period_secs )</tt>) but that would mean separate waiting functions for timers, and lindas.
1536 Even if a <a href="#lindas">linda</a> object and key was returned, that key couldn't be waited upon simultaneously with one's general <a href="#lindas">linda</a> events. 1558 Even if a <a href="#lindas">linda</a> object and slot was returned, that slot couldn't be waited upon simultaneously with one's general <a href="#lindas">linda</a> events.
1537 The current system gives maximum capabilities with minimum API, and any smoothenings can easily be crafted in Lua at the application level. 1559 The current system gives maximum capabilities with minimum API, and any smoothenings can easily be crafted in Lua at the application level.
1538 </font> 1560 </font>
1539 </td> 1561 </td>
@@ -1578,7 +1600,7 @@
1578</p> 1600</p>
1579 1601
1580<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1602<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1581 lock_func|lanes.cancel_error = lanes.genlock(linda_h, key [,N_uint=1]) 1603 lock_func|lanes.cancel_error = lanes.genlock(linda_h, slot [,N_uint=1])
1582 1604
1583 bool|lanes.cancel_error = lock_func(M_uint [, "try"] ) -- acquire 1605 bool|lanes.cancel_error = lock_func(M_uint [, "try"] ) -- acquire
1584 .. 1606 ..
@@ -1604,13 +1626,13 @@
1604<p> 1626<p>
1605 1627
1606<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1628<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1607 atomic_func|lanes.cancel_error = lanes.genatomic(linda_h, key [,initial_num=0.0]) 1629 atomic_func|lanes.cancel_error = lanes.genatomic(linda_h, slot [,initial_num=0.0])
1608 1630
1609 new_num|lanes.cancel_error = atomic_func([diff_num=+1.0]) 1631 new_num|lanes.cancel_error = atomic_func([diff_num=+1.0])
1610</pre></td></tr></table> 1632</pre></td></tr></table>
1611 1633
1612<p> 1634<p>
1613 Each time called, the generated function will change <tt>linda[key]</tt> atomically, without other lanes being able to interfere. The new value is returned. You can use either <tt>diff 0.0</tt> or <tt>get</tt> to just read the current value. 1635 Each time called, the generated function will change <tt>linda[slot]</tt> atomically, without other lanes being able to interfere. The new value is returned. You can use either <tt>diff 0.0</tt> or <tt>get</tt> to just read the current value.
1614</p> 1636</p>
1615 1637
1616<p> 1638<p>
@@ -1632,7 +1654,7 @@
1632 <li>Booleans, numbers, strings, light userdata, Lua functions and tables of such can always be passed.</li> 1654 <li>Booleans, numbers, strings, light userdata, Lua functions and tables of such can always be passed.</li>
1633 <li> 1655 <li>
1634 Cyclic tables and/or duplicate references are allowed and reproduced appropriately, but only <u>within the same transmission</u>. 1656 Cyclic tables and/or duplicate references are allowed and reproduced appropriately, but only <u>within the same transmission</u>.
1635 Using the same source table in multiple <a href="#lindas">linda</a> messages keeps no ties between the tables (this is the same reason why tables can't be used as keys). 1657 Using the same source table in multiple <a href="#lindas">linda</a> messages keeps no ties between the tables (this is the same reason why tables can't be used as slots).
1636 </li> 1658 </li>
1637 <li> 1659 <li>
1638 For tables and full userdata: before anything else, the metatable is searched for a <tt>__lanesconvert</tt> field. If found, the source object is converted as follows depending on <tt>__lanesconvert</tt>'s value: 1660 For tables and full userdata: before anything else, the metatable is searched for a <tt>__lanesconvert</tt> field. If found, the source object is converted as follows depending on <tt>__lanesconvert</tt>'s value:
@@ -1914,7 +1936,7 @@ static MyDeepFactory g_MyDeepFactory;
1914 <ul> 1936 <ul>
1915 <li>Data passing (arguments, upvalues, <a href="#lindas">linda</a> messages) is generally fast, doing two binary state-to-state copies (from source state to hidden state, hidden state to target state). Remember that not only the function you specify but also its upvalues, their upvalues, etc. etc. will get copied.</li> 1937 <li>Data passing (arguments, upvalues, <a href="#lindas">linda</a> messages) is generally fast, doing two binary state-to-state copies (from source state to hidden state, hidden state to target state). Remember that not only the function you specify but also its upvalues, their upvalues, etc. etc. will get copied.</li>
1916 <li>Lane startup is fast (1000's of lanes a second), depending on the number of standard libraries initialized. Initializing all standard libraries is about 3-4 times slower than having no standard libraries at all. If you throw in a lot of lanes per second, make sure you give them minimal necessary set of libraries.</li> 1938 <li>Lane startup is fast (1000's of lanes a second), depending on the number of standard libraries initialized. Initializing all standard libraries is about 3-4 times slower than having no standard libraries at all. If you throw in a lot of lanes per second, make sure you give them minimal necessary set of libraries.</li>
1917 <li>Waiting lindas are woken up (and execute some hidden Lua code) each time <u>any</u> key in the <a href="#lindas">lindas</a> they are waiting for are changed. This may give essential slow-down (not measured, just a gut feeling) if a lot of <a href="#lindas">linda</a> keys are used. Using separate <a href="#lindas">lindas</a> for logically separate issues will help (which is good practice anyhow).</li> 1939 <li>Waiting lindas are woken up (and execute some hidden Lua code) each time <u>any</u> slot in the <a href="#lindas">lindas</a> they are waiting for are changed. This may give essential slow-down (not measured, just a gut feeling) if a lot of <a href="#lindas">linda</a> slots are used. Using separate <a href="#lindas">lindas</a> for logically separate issues will help (which is good practice anyhow).</li>
1918 <li><a href="#lindas">linda</a> objects are light. The memory footprint is two OS-level signalling objects (<tt>HANDLE</tt> or <tt>pthread_cond_t</tt>) for each, plus one C pointer for the proxies per each Lua state using the <a href="#lindas">linda</a>. Barely nothing.</li> 1940 <li><a href="#lindas">linda</a> objects are light. The memory footprint is two OS-level signalling objects (<tt>HANDLE</tt> or <tt>pthread_cond_t</tt>) for each, plus one C pointer for the proxies per each Lua state using the <a href="#lindas">linda</a>. Barely nothing.</li>
1919 <li>Timers are light. You can probably expect timers up to 0.01 second resolution to be useful, but that is very system specific. All timers are merged into one main timer state (see <tt>timer.lua</tt>); no OS side timers are utilized.</li> 1941 <li>Timers are light. You can probably expect timers up to 0.01 second resolution to be useful, but that is very system specific. All timers are merged into one main timer state (see <tt>timer.lua</tt>); no OS side timers are utilized.</li>
1920 <li>If you are using a lot of <a href="#lindas">linda</a> objects, it may be useful to try having more of these <a href="#keepers">Keeper states</a>. By default, only one is used (see <a href="#initialization"><tt>lanes.configure()</tt></a>).</li> 1942 <li>If you are using a lot of <a href="#lindas">linda</a> objects, it may be useful to try having more of these <a href="#keepers">Keeper states</a>. By default, only one is used (see <a href="#initialization"><tt>lanes.configure()</tt></a>).</li>
diff --git a/src/cancel.cpp b/src/cancel.cpp
index 8fa68d5..31656c4 100644
--- a/src/cancel.cpp
+++ b/src/cancel.cpp
@@ -164,7 +164,7 @@ LUAG_FUNC(lane_cancel)
164 raise_luaL_argerror(L_, StackIndex{ 2 }, "duration cannot be < 0"); 164 raise_luaL_argerror(L_, StackIndex{ 2 }, "duration cannot be < 0");
165 } 165 }
166 lua_remove(L_, 2); // argument is processed, remove it 166 lua_remove(L_, 2); // argument is processed, remove it
167 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key 167 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil
168 lua_remove(L_, 2); // argument is processed, remove it 168 lua_remove(L_, 2); // argument is processed, remove it
169 } 169 }
170 170
diff --git a/src/compat.hpp b/src/compat.hpp
index f66f703..81f1665 100644
--- a/src/compat.hpp
+++ b/src/compat.hpp
@@ -272,6 +272,16 @@ LuaType luaG_getmodule(lua_State* L_, std::string_view const& name_);
272 272
273// ################################################################################################# 273// #################################################################################################
274 274
275template<typename ENUM>
276requires std::is_enum_v<ENUM>
277[[nodiscard]]
278ENUM luaG_optenum(lua_State* const L_, StackIndex const idx_, ENUM const def_)
279{
280 return static_cast<ENUM>(luaL_optinteger(L_, idx_, static_cast<std::underlying_type_t<ENUM>>(def_)));
281}
282
283// #################################################################################################
284
275inline void luaG_registerlibfuncs(lua_State* L_, luaL_Reg const* funcs_) 285inline void luaG_registerlibfuncs(lua_State* L_, luaL_Reg const* funcs_)
276{ 286{
277 // fake externs to make clang happy... 287 // fake externs to make clang happy...
diff --git a/src/keeper.cpp b/src/keeper.cpp
index 8a99a36..e7b02e6 100644
--- a/src/keeper.cpp
+++ b/src/keeper.cpp
@@ -73,6 +73,7 @@ class KeyUD
73 int first{ 1 }; 73 int first{ 1 };
74 int count{ 0 }; 74 int count{ 0 };
75 LindaLimit limit{ -1 }; 75 LindaLimit limit{ -1 };
76 LindaRestrict restrict { LindaRestrict::None };
76 77
77 // a fifo full userdata has one uservalue, the table that holds the actual fifo contents 78 // a fifo full userdata has one uservalue, the table that holds the actual fifo contents
78 [[nodiscard]] 79 [[nodiscard]]
@@ -84,6 +85,8 @@ class KeyUD
84 [[nodiscard]] 85 [[nodiscard]]
85 bool changeLimit(LindaLimit limit_); 86 bool changeLimit(LindaLimit limit_);
86 [[nodiscard]] 87 [[nodiscard]]
88 LindaRestrict changeRestrict(LindaRestrict restrict_);
89 [[nodiscard]]
87 static KeyUD* Create(KeeperState K_); 90 static KeyUD* Create(KeeperState K_);
88 [[nodiscard]] 91 [[nodiscard]]
89 static KeyUD* GetPtr(KeeperState K_, StackIndex idx_); 92 static KeyUD* GetPtr(KeeperState K_, StackIndex idx_);
@@ -114,6 +117,14 @@ bool KeyUD::changeLimit(LindaLimit const limit_)
114 117
115// ################################################################################################# 118// #################################################################################################
116 119
120[[nodiscard]]
121LindaRestrict KeyUD::changeRestrict(LindaRestrict const restrict_)
122{
123 return std::exchange(restrict, restrict_);
124}
125
126// #################################################################################################
127
117// in: nothing 128// in: nothing
118// out: { first = 1, count = 0, limit = -1} 129// out: { first = 1, count = 0, limit = -1}
119KeyUD* KeyUD::Create(KeeperState const K_) 130KeyUD* KeyUD::Create(KeeperState const K_)
@@ -280,7 +291,7 @@ bool KeyUD::reset(KeeperState const K_)
280 LUA_ASSERT(K_, KeyUD::GetPtr(K_, kIdxTop) == this); 291 LUA_ASSERT(K_, KeyUD::GetPtr(K_, kIdxTop) == this);
281 STACK_CHECK_START_REL(K_, 0); 292 STACK_CHECK_START_REL(K_, 0);
282 bool const _wasFull{ (limit > 0) && (count >= limit) }; 293 bool const _wasFull{ (limit > 0) && (count >= limit) };
283 // empty the KeyUD: replace uservalue with a virgin table, reset counters, but leave limit unchanged! 294 // empty the KeyUD: replace uservalue with a virgin table, reset counters, but leave limit and restrict unchanged!
284 // if we have an actual limit, use it to preconfigure the table 295 // if we have an actual limit, use it to preconfigure the table
285 lua_createtable(K_, (limit <= 0) ? 0 : limit.value(), 0); // K_: KeysDB key val... KeyUD {} 296 lua_createtable(K_, (limit <= 0) ? 0 : limit.value(), 0); // K_: KeysDB key val... KeyUD {}
286 lua_setiuservalue(K_, StackIndex{ -2 }, kContentsTableIndex); // K_: KeysDB key val... KeyUD 297 lua_setiuservalue(K_, StackIndex{ -2 }, kContentsTableIndex); // K_: KeysDB key val... KeyUD
@@ -408,7 +419,7 @@ int keepercall_destruct(lua_State* const L_)
408// ################################################################################################# 419// #################################################################################################
409 420
410// in: linda_ud key [count] 421// in: linda_ud key [count]
411// out: bool + at most <count> values 422// out: N <N values>|kRestrictedChannel
412int keepercall_get(lua_State* const L_) 423int keepercall_get(lua_State* const L_)
413{ 424{
414 KeeperState const _K{ L_ }; 425 KeeperState const _K{ L_ };
@@ -423,7 +434,13 @@ int keepercall_get(lua_State* const L_)
423 lua_remove(_K, 1); // _K: KeyUD 434 lua_remove(_K, 1); // _K: KeyUD
424 KeyUD const* const _key{ KeyUD::GetPtr(_K, kIdxTop) }; 435 KeyUD const* const _key{ KeyUD::GetPtr(_K, kIdxTop) };
425 if (_key != nullptr) { 436 if (_key != nullptr) {
426 _key->peek(_K, _count); // _K: N val... 437 if (_key->restrict == LindaRestrict::SendReceive) { // can we use set/get?
438 lua_settop(_K, 0); // _K:
439 kRestrictedChannel.pushKey(_K); // _K: kRestrictedChannel
440 return 1;
441 } else {
442 _key->peek(_K, _count); // _K: N val...
443 }
427 } else { 444 } else {
428 // no fifo was ever registered for this key, or it is empty 445 // no fifo was ever registered for this key, or it is empty
429 lua_pop(_K, 1); // _K: 446 lua_pop(_K, 1); // _K:
@@ -492,6 +509,17 @@ int keepercall_receive(lua_State* const L_)
492 lua_rawget(_K, 1); // _K: KeysDB keys... KeyUD 509 lua_rawget(_K, 1); // _K: KeysDB keys... KeyUD
493 KeyUD* const _key{ KeyUD::GetPtr(_K, kIdxTop) }; 510 KeyUD* const _key{ KeyUD::GetPtr(_K, kIdxTop) };
494 if (_key != nullptr) { // it's fine to attempt a read on a key that wasn't yet written to 511 if (_key != nullptr) { // it's fine to attempt a read on a key that wasn't yet written to
512 if (_key->restrict == LindaRestrict::SetGet) { // can we use send/receive?
513 kRestrictedChannel.pushKey(_K); // _K: KeysDB keys... key[i] kRestrictedChannel
514 lua_replace(_K, 1); // _K: kRestrictedChannel keys... key[i]
515 lua_settop(_K, _keyIdx); // _K: kRestrictedChannel keys... key[i]
516 if (_keyIdx != 2) {
517 lua_replace(_K, 2); // _K: kRestrictedChannel key[i] keys...
518 lua_settop(_K, 2); // _K: kRestrictedChannel key[i]
519 }
520 lua_insert(_K, 1); // _K: key kRestrictedChannel
521 return 2;
522 }
495 int const _popped{ _key->pop(_K, 1, 1) }; // _K: KeysDB keys... val 523 int const _popped{ _key->pop(_K, 1, 1) }; // _K: KeysDB keys... val
496 if (_popped > 0) { 524 if (_popped > 0) {
497 lua_replace(_K, 1); // _K: val keys... 525 lua_replace(_K, 1); // _K: val keys...
@@ -527,19 +555,89 @@ int keepercall_receive_batched(lua_State* const L_)
527 lua_rawget(_K, 2); // _K: key KeysDB KeyUD 555 lua_rawget(_K, 2); // _K: key KeysDB KeyUD
528 lua_remove(_K, 2); // _K: key KeyUD 556 lua_remove(_K, 2); // _K: key KeyUD
529 KeyUD* const _key{ KeyUD::GetPtr(_K, kIdxTop) }; 557 KeyUD* const _key{ KeyUD::GetPtr(_K, kIdxTop) };
530 if (_key == nullptr || _key->pop(_K, _min_count, _max_count) == 0) { // _K: [key val...]|crap 558 if (!_key) {
531 // Lua will adjust the stack for us when we return 559 return 0; // Lua will adjust the stack for us when we return
532 return 0; 560 }
561 if (_key->restrict == LindaRestrict::SetGet) { // can we use send/receive?
562 lua_settop(_K, 1); // _K: key
563 kRestrictedChannel.pushKey(_K); // _K: key kRestrictedChannel
564 return 2;
565 }
566 if (_key->pop(_K, _min_count, _max_count) == 0) { // _K: [key val...]|crap
567 return 0; // Lua will adjust the stack for us when we return
568 }
569 // return whatever remains on the stack at that point: the key and the values we pulled from the fifo
570 return lua_gettop(_K);
571}
572
573// #################################################################################################
574
575// in: linda key [mode]
576// out: mode
577int keepercall_restrict(lua_State* const L_)
578{
579 KeeperState const _K{ L_ };
580 STACK_CHECK_START_ABS(_K, lua_gettop(_K));
581 // no restriction to set, means we read and return the current restriction instead
582 bool const _reading{ lua_gettop(_K) == 2 };
583 auto _decodeRestrict = [_K, _reading]() {
584 if (_reading) {
585 return LindaRestrict::None;
586 }
587 std::string_view const _val{ luaG_tostring(_K, StackIndex{ 3 }) };
588 if (_val == "set/get") {
589 return LindaRestrict::SetGet;
590 }
591 if (_val == "send/receive") {
592 return LindaRestrict::SendReceive;
593 }
594 return LindaRestrict::None;
595 };
596 auto _encodeRestrict = [](LindaRestrict const val_) {
597 switch (val_) {
598 default:
599 case LindaRestrict::None:
600 return std::string_view{ "none" };
601 case LindaRestrict::SetGet:
602 return std::string_view{ "set/get" };
603 case LindaRestrict::SendReceive:
604 return std::string_view{ "send/receive" };
605 }
606 };
607 LindaRestrict const _rstrct{ _decodeRestrict() }; // if we read nil because the argument is absent
608 lua_settop(_K, 2); // _K: linda key
609 PushKeysDB(_K, StackIndex{ 1 }); // _K: linda key KeysDB
610 lua_replace(_K, 1); // _K: KeysDB key
611 lua_pushvalue(_K, -1); // _K: KeysDB key key
612 lua_rawget(_K, -3); // _K: KeysDB key KeyUD|nil
613 KeyUD* _key{ KeyUD::GetPtr(_K, kIdxTop) };
614 if (_reading) {
615 // remove any clutter on the stack
616 lua_settop(_K, 0); // _K:
617 auto const _prevRstrct{ _key ? _key->restrict : LindaRestrict::None };
618 // return a single value: the restrict mode of the key
619 luaG_pushstring(_K, _encodeRestrict(_prevRstrct)); // _K: _previous
533 } else { 620 } else {
534 // return whatever remains on the stack at that point: the key and the values we pulled from the fifo 621 if (_key == nullptr) { // _K: KeysDB key nil
535 return lua_gettop(_K); 622 lua_pop(_K, 1); // _K: KeysDB key
623 _key = KeyUD::Create(_K); // _K: KeysDB key KeyUD
624 lua_rawset(_K, -3); // _K: KeysDB
625 }
626 // remove any clutter on the stack
627 lua_settop(_K, 0); // _K:
628 // return true if we decide that blocked threads waiting to write on that key should be awakened
629 // this is the case if we detect the key was full but it is no longer the case
630 LindaRestrict const _previous{ _key->changeRestrict(_rstrct) };
631 luaG_pushstring(_K, _encodeRestrict(_previous)); // _K: _previous
536 } 632 }
633 STACK_CHECK(_K, 1);
634 return 1;
537} 635}
538 636
539// ################################################################################################# 637// #################################################################################################
540 638
541// in: linda, key, ... 639// in: linda, key, ...
542// out: true|false 640// out: true|false|kRestrictedChannel
543int keepercall_send(lua_State* const L_) 641int keepercall_send(lua_State* const L_)
544{ 642{
545 KeeperState const _K{ L_ }; 643 KeeperState const _K{ L_ };
@@ -561,7 +659,11 @@ int keepercall_send(lua_State* const L_)
561 lua_pop(_K, 1); // _K: linda KeyUD val... 659 lua_pop(_K, 1); // _K: linda KeyUD val...
562 STACK_CHECK(_K, 0); 660 STACK_CHECK(_K, 0);
563 KeyUD* const _key{ KeyUD::GetPtr(_K, StackIndex{ 2 }) }; 661 KeyUD* const _key{ KeyUD::GetPtr(_K, StackIndex{ 2 }) };
564 if (_key && _key->push(_K, _n, true)) { // not enough room? 662 if (_key->restrict == LindaRestrict::SetGet) { // can we use send/receive?
663 lua_settop(_K, 0); // _K:
664 kRestrictedChannel.pushKey(_K); // _K: kRestrictedChannel
665 }
666 else if (_key->push(_K, _n, true)) { // not enough room?
565 lua_settop(_K, 0); // _K: 667 lua_settop(_K, 0); // _K:
566 lua_pushboolean(_K, 1); // _K: true 668 lua_pushboolean(_K, 1); // _K: true
567 } else { 669 } else {
@@ -575,7 +677,7 @@ int keepercall_send(lua_State* const L_)
575// ################################################################################################# 677// #################################################################################################
576 678
577// in: linda key [val...] 679// in: linda key [val...]
578// out: true if the linda was full but it's no longer the case, else false 680// out: true if the linda was full but it's no longer the case, else false, or kRestrictedChannel if the key is restricted
579int keepercall_set(lua_State* const L_) 681int keepercall_set(lua_State* const L_)
580{ 682{
581 KeeperState const _K{ L_ }; 683 KeeperState const _K{ L_ };
@@ -588,11 +690,16 @@ int keepercall_set(lua_State* const L_)
588 lua_pushvalue(_K, 2); // _K: KeysDB key val... key 690 lua_pushvalue(_K, 2); // _K: KeysDB key val... key
589 lua_rawget(_K, 1); // _K: KeysDB key val KeyUD|nil 691 lua_rawget(_K, 1); // _K: KeysDB key val KeyUD|nil
590 KeyUD* _key{ KeyUD::GetPtr(_K, kIdxTop) }; 692 KeyUD* _key{ KeyUD::GetPtr(_K, kIdxTop) };
693 if (_key && _key->restrict == LindaRestrict::SendReceive) { // can we use send/receive?
694 lua_settop(_K, 0); // _K:
695 kRestrictedChannel.pushKey(_K); // _K: kRestrictedChannel
696 return 1;
697 }
591 698
592 if (lua_gettop(_K) == 3) { // no value to set // _K: KeysDB key KeyUD|nil 699 if (lua_gettop(_K) == 3) { // no value to set // _K: KeysDB key KeyUD|nil
593 // empty the KeyUD for the specified key: replace uservalue with a virgin table, reset counters, but leave limit unchanged! 700 // empty the KeyUD for the specified key: replace uservalue with a virgin table, reset counters, but leave limit unchanged!
594 if (_key != nullptr) { // might be nullptr if we set a nonexistent key to nil // _K: KeysDB key KeyUD 701 if (_key != nullptr) { // might be nullptr if we set a nonexistent key to nil // _K: KeysDB key KeyUD
595 if (_key->limit < 0) { // KeyUD limit value is the default (unlimited): we can totally remove it 702 if (_key->limit < 0 && _key->restrict == LindaRestrict::None) { // KeyUD limit value and restrict mode are the default (unlimited/none): we can totally remove it
596 lua_pop(_K, 1); // _K: KeysDB key 703 lua_pop(_K, 1); // _K: KeysDB key
597 lua_pushnil(_K); // _K: KeysDB key nil 704 lua_pushnil(_K); // _K: KeysDB key nil
598 lua_rawset(_K, -3); // _K: KeysDB 705 lua_rawset(_K, -3); // _K: KeysDB
@@ -777,6 +884,20 @@ int Keeper::PushLindaStorage(Linda& linda_, DestState const L_)
777 } 884 }
778 STACK_CHECK(L_, 5); 885 STACK_CHECK(L_, 5);
779 lua_setfield(L_, -3, "limit"); // _K: KeysDB key L_: out key keyout fifo 886 lua_setfield(L_, -3, "limit"); // _K: KeysDB key L_: out key keyout fifo
887 // keyout.restrict
888 switch (_key->restrict) {
889 case LindaRestrict::None:
890 luaG_pushstring(L_, "none"); // _K: KeysDB key L_: out key keyout fifo restrict
891 break;
892 case LindaRestrict::SetGet:
893 luaG_pushstring(L_, "set/get"); // _K: KeysDB key L_: out key keyout fifo restrict
894 break;
895 case LindaRestrict::SendReceive:
896 luaG_pushstring(L_, "send/receive"); // _K: KeysDB key L_: out key keyout fifo restrict
897 break;
898 }
899 STACK_CHECK(L_, 5);
900 lua_setfield(L_, -3, "restrict"); // _K: KeysDB key L_: out key keyout fifo
780 // keyout.fifo 901 // keyout.fifo
781 lua_setfield(L_, -2, "fifo"); // _K: KeysDB key L_: out key keyout 902 lua_setfield(L_, -2, "fifo"); // _K: KeysDB key L_: out key keyout
782 // out[key] = keyout 903 // out[key] = keyout
diff --git a/src/keeper.hpp b/src/keeper.hpp
index b77f1a9..e2ad445 100644
--- a/src/keeper.hpp
+++ b/src/keeper.hpp
@@ -13,6 +13,15 @@ DECLARE_UNIQUE_TYPE(KeeperIndex, int);
13 13
14// ################################################################################################# 14// #################################################################################################
15 15
16enum class LindaRestrict
17{
18 None,
19 SetGet,
20 SendReceive
21};
22
23// #################################################################################################
24
16struct Keeper 25struct Keeper
17{ 26{
18 std::mutex mutex; 27 std::mutex mutex;
@@ -79,6 +88,9 @@ DECLARE_UNIQUE_TYPE(KeeperCallResult, std::optional<int>);
79// xxh64 of string "kNilSentinel" generated at https://www.pelock.com/products/hash-calculator 88// xxh64 of string "kNilSentinel" generated at https://www.pelock.com/products/hash-calculator
80static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" }; 89static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" };
81 90
91// xxh64 of string "kRestrictedChannel" generated at https://www.pelock.com/products/hash-calculator
92static constexpr UniqueKey kRestrictedChannel{ 0x4C8B879ECDE110F7ull };
93
82using keeper_api_t = lua_CFunction; 94using keeper_api_t = lua_CFunction;
83#define KEEPER_API(_op) keepercall_##_op 95#define KEEPER_API(_op) keepercall_##_op
84 96
@@ -96,6 +108,8 @@ int keepercall_receive(lua_State* L_);
96[[nodiscard]] 108[[nodiscard]]
97int keepercall_receive_batched(lua_State* L_); 109int keepercall_receive_batched(lua_State* L_);
98[[nodiscard]] 110[[nodiscard]]
111int keepercall_restrict(lua_State* L_);
112[[nodiscard]]
99int keepercall_send(lua_State* L_); 113int keepercall_send(lua_State* L_);
100[[nodiscard]] 114[[nodiscard]]
101int keepercall_set(lua_State* L_); 115int keepercall_set(lua_State* L_);
diff --git a/src/lanes.lua b/src/lanes.lua
index dfa959d..8d8f25d 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -448,6 +448,7 @@ local configure_timers = function()
448 448
449 -- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally has 'table' always declared) 449 -- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally has 'table' always declared)
450 local first_time_key = "first time" 450 local first_time_key = "first time"
451 timerLinda:restrict(first_time_key, "set/get")
451 local _, _first_time_val = timerLinda:get(first_time_key) 452 local _, _first_time_val = timerLinda:get(first_time_key)
452 local first_time = (_first_time_val == nil) 453 local first_time = (_first_time_val == nil)
453 timerLinda:set(first_time_key, true) 454 timerLinda:set(first_time_key, true)
diff --git a/src/linda.cpp b/src/linda.cpp
index 43d2a91..6ebbc1f 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -57,7 +57,7 @@ namespace {
57 57
58 case LuaType::USERDATA: 58 case LuaType::USERDATA:
59 if (!DeepFactory::IsDeepUserdata(L_, _i)) { 59 if (!DeepFactory::IsDeepUserdata(L_, _i)) {
60 raise_luaL_error(L_, "argument #%d: can't use non-deep userdata as a key", _i); 60 raise_luaL_error(L_, "argument #%d: can't use non-deep userdata as a slot", _i);
61 } 61 }
62 break; 62 break;
63 63
@@ -66,7 +66,7 @@ namespace {
66 static constexpr std::array<std::reference_wrapper<UniqueKey const>, 3> kKeysToCheck{ kLindaBatched, kCancelError, kNilSentinel }; 66 static constexpr std::array<std::reference_wrapper<UniqueKey const>, 3> kKeysToCheck{ kLindaBatched, kCancelError, kNilSentinel };
67 for (UniqueKey const& _key : kKeysToCheck) { 67 for (UniqueKey const& _key : kKeysToCheck) {
68 if (_key.equals(L_, _i)) { 68 if (_key.equals(L_, _i)) {
69 raise_luaL_error(L_, "argument #%d: can't use %s as a key", _i, _key.debugName.data()); 69 raise_luaL_error(L_, "argument #%d: can't use %s as a slot", _i, _key.debugName.data());
70 break; 70 break;
71 } 71 }
72 } 72 }
@@ -74,7 +74,7 @@ namespace {
74 break; 74 break;
75 75
76 default: 76 default:
77 raise_luaL_error(L_, "argument #%d: invalid key type (not a boolean, string, number or light userdata)", _i); 77 raise_luaL_error(L_, "argument #%d: invalid slot type (not a boolean, string, number or light userdata)", _i);
78 } 78 }
79 } 79 }
80 STACK_CHECK(L_, 0); 80 STACK_CHECK(L_, 0);
@@ -416,7 +416,7 @@ static LUAG_FUNC(linda_index)
416// ################################################################################################# 416// #################################################################################################
417 417
418/* 418/*
419 * [val] = linda_count( linda_ud, [key [, ...]]) 419 * [val] = linda_count( linda_ud, [slot [, ...]])
420 * 420 *
421 * Get a count of the pending elements in the specified keys 421 * Get a count of the pending elements in the specified keys
422 */ 422 */
@@ -430,7 +430,7 @@ LUAG_FUNC(linda_count)
430 430
431 Keeper* const _keeper{ _linda->whichKeeper() }; 431 Keeper* const _keeper{ _linda->whichKeeper() };
432 KeeperCallResult const _pushed{ keeper_call(_keeper->K, KEEPER_API(count), L_, _linda, StackIndex{ 2 }) }; 432 KeeperCallResult const _pushed{ keeper_call(_keeper->K, KEEPER_API(count), L_, _linda, StackIndex{ 2 }) };
433 return OptionalValue(_pushed, L_, "tried to count an invalid key"); 433 return OptionalValue(_pushed, L_, "Tried to count an invalid slot");
434 } 434 }
435 }; 435 };
436 return Linda::ProtectedCall(L_, _count); 436 return Linda::ProtectedCall(L_, _count);
@@ -487,13 +487,16 @@ LUAG_FUNC(linda_get)
487 lua_Integer const _count{ luaL_optinteger(L_, 3, 1) }; 487 lua_Integer const _count{ luaL_optinteger(L_, 3, 1) };
488 luaL_argcheck(L_, _count >= 1, 3, "count should be >= 1"); 488 luaL_argcheck(L_, _count >= 1, 3, "count should be >= 1");
489 luaL_argcheck(L_, lua_gettop(L_) <= 3, 4, "too many arguments"); 489 luaL_argcheck(L_, lua_gettop(L_) <= 3, 4, "too many arguments");
490 // make sure the key is of a valid type (throws an error if not the case) 490 // make sure the slot is of a valid type (throws an error if not the case)
491 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 }); 491 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 });
492 492
493 KeeperCallResult _pushed; 493 KeeperCallResult _pushed;
494 if (_linda->cancelStatus == Linda::Active) { 494 if (_linda->cancelStatus == Linda::Active) {
495 Keeper* const _keeper{ _linda->whichKeeper() }; 495 Keeper* const _keeper{ _linda->whichKeeper() };
496 _pushed = keeper_call(_keeper->K, KEEPER_API(get), L_, _linda, StackIndex{ 2 }); 496 _pushed = keeper_call(_keeper->K, KEEPER_API(get), L_, _linda, StackIndex{ 2 });
497 if (_pushed.has_value() && kRestrictedChannel.equals(L_, kIdxTop)) {
498 raise_luaL_error(L_, "Key is restricted");
499 }
497 } else { // linda is cancelled 500 } else { // linda is cancelled
498 // do nothing and return nil,lanes.cancel_error 501 // do nothing and return nil,lanes.cancel_error
499 lua_pushnil(L_); 502 lua_pushnil(L_);
@@ -511,7 +514,7 @@ LUAG_FUNC(linda_get)
511 514
512/* 515/*
513 * [bool]|nil,cancel_error = linda:limit(key_num|str|bool|lightuserdata, [int]) 516 * [bool]|nil,cancel_error = linda:limit(key_num|str|bool|lightuserdata, [int])
514 * "unlimited"|number = linda:limit(key) 517 * "unlimited"|number = linda:limit(slot)
515 * 518 *
516 * Read or set limit to 1 Linda keys. 519 * Read or set limit to 1 Linda keys.
517 * Optionally wake threads waiting to write on the linda, in case the limit enables them to do so 520 * Optionally wake threads waiting to write on the linda, in case the limit enables them to do so
@@ -522,7 +525,7 @@ LUAG_FUNC(linda_limit)
522 static constexpr lua_CFunction _limit{ 525 static constexpr lua_CFunction _limit{
523 +[](lua_State* const L_) { 526 +[](lua_State* const L_) {
524 Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) }; 527 Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) };
525 // make sure we got 2 or 3 arguments: the linda, a key and optionally a limit 528 // make sure we got 2 or 3 arguments: the linda, a slot and optionally a limit
526 int const _nargs{ lua_gettop(L_) }; 529 int const _nargs{ lua_gettop(L_) };
527 luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments"); 530 luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments");
528 // make sure we got a numeric limit, or "unlimited", (or nothing) 531 // make sure we got a numeric limit, or "unlimited", (or nothing)
@@ -531,7 +534,7 @@ LUAG_FUNC(linda_limit)
531 if (_val < 0) { 534 if (_val < 0) {
532 raise_luaL_argerror(L_, StackIndex{ 3 }, "limit must be >= 0"); 535 raise_luaL_argerror(L_, StackIndex{ 3 }, "limit must be >= 0");
533 } 536 }
534 // make sure the key is of a valid type 537 // make sure the slot is of a valid type
535 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 }); 538 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 });
536 539
537 KeeperCallResult _pushed; 540 KeeperCallResult _pushed;
@@ -539,8 +542,8 @@ LUAG_FUNC(linda_limit)
539 if (_unlimited) { 542 if (_unlimited) {
540 LUA_ASSERT(L_, lua_gettop(L_) == 3 && luaG_tostring(L_, StackIndex{ 3 }) == "unlimited"); 543 LUA_ASSERT(L_, lua_gettop(L_) == 3 && luaG_tostring(L_, StackIndex{ 3 }) == "unlimited");
541 // inside the Keeper, unlimited is signified with a -1 limit (can't use nil because of nil kNilSentinel conversions!) 544 // inside the Keeper, unlimited is signified with a -1 limit (can't use nil because of nil kNilSentinel conversions!)
542 lua_pop(L_, 1); // L_: linda key 545 lua_pop(L_, 1); // L_: linda slot
543 lua_pushinteger(L_, -1); // L_: linda key nil 546 lua_pushinteger(L_, -1); // L_: linda slot nil
544 } 547 }
545 Keeper* const _keeper{ _linda->whichKeeper() }; 548 Keeper* const _keeper{ _linda->whichKeeper() };
546 _pushed = keeper_call(_keeper->K, KEEPER_API(limit), L_, _linda, StackIndex{ 2 }); 549 _pushed = keeper_call(_keeper->K, KEEPER_API(limit), L_, _linda, StackIndex{ 2 });
@@ -572,12 +575,12 @@ LUAG_FUNC(linda_limit)
572 575
573/* 576/*
574 * 2 modes of operation 577 * 2 modes of operation
575 * [val, key]= linda:receive([timeout_secs_num=nil], key_num|str|bool|lightuserdata [, ...] ) 578 * [val, slot]= linda:receive([timeout_secs_num=nil], key_num|str|bool|lightuserdata [, ...] )
576 * Consumes a single value from the Linda, in any key. 579 * Consumes a single value from the Linda, in any slot.
577 * Returns: received value (which is consumed from the slot), and the key which had it 580 * Returns: received value (which is consumed from the slot), and the slot which had it
578 581
579 * [val1, ... valCOUNT]= linda_receive( linda_ud, [timeout_secs_num=-1], linda.batched, key_num|str|bool|lightuserdata, min_COUNT[, max_COUNT]) 582 * [val1, ... valCOUNT]= linda_receive( linda_ud, [timeout_secs_num=-1], linda.batched, key_num|str|bool|lightuserdata, min_COUNT[, max_COUNT])
580 * Consumes between min_COUNT and max_COUNT values from the linda, from a single key. 583 * Consumes between min_COUNT and max_COUNT values from the linda, from a single slot.
581 * returns the actual consumed values, or nil if there weren't enough values to consume 584 * returns the actual consumed values, or nil if there weren't enough values to consume
582 */ 585 */
583LUAG_FUNC(linda_receive) 586LUAG_FUNC(linda_receive)
@@ -585,7 +588,7 @@ LUAG_FUNC(linda_receive)
585 static constexpr lua_CFunction _receive{ 588 static constexpr lua_CFunction _receive{
586 +[](lua_State* const L_) { 589 +[](lua_State* const L_) {
587 Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) }; 590 Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) };
588 StackIndex _key_i{ 2 }; // index of first key, if timeout not there 591 StackIndex _key_i{ 2 }; // index of first slot, if timeout not there
589 592
590 std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; 593 std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
591 if (luaG_type(L_, StackIndex{ 2 }) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion 594 if (luaG_type(L_, StackIndex{ 2 }) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion
@@ -596,7 +599,7 @@ LUAG_FUNC(linda_receive)
596 raise_luaL_argerror(L_, StackIndex{ 2 }, "duration cannot be < 0"); 599 raise_luaL_argerror(L_, StackIndex{ 2 }, "duration cannot be < 0");
597 } 600 }
598 ++_key_i; 601 ++_key_i;
599 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key 602 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the slot
600 ++_key_i; 603 ++_key_i;
601 } 604 }
602 605
@@ -616,7 +619,7 @@ LUAG_FUNC(linda_receive)
616 raise_luaL_argerror(L_, StackIndex{ _key_i + 1 }, "bad min count"); 619 raise_luaL_argerror(L_, StackIndex{ _key_i + 1 }, "bad min count");
617 } 620 }
618 _expected_pushed_max = (int) luaL_optinteger(L_, _key_i + 2, _expected_pushed_min); 621 _expected_pushed_max = (int) luaL_optinteger(L_, _key_i + 2, _expected_pushed_min);
619 // don't forget to count the key in addition to the values 622 // don't forget to count the slot in addition to the values
620 ++_expected_pushed_min; 623 ++_expected_pushed_min;
621 ++_expected_pushed_max; 624 ++_expected_pushed_max;
622 if (_expected_pushed_min > _expected_pushed_max) { 625 if (_expected_pushed_min > _expected_pushed_max) {
@@ -627,7 +630,7 @@ LUAG_FUNC(linda_receive)
627 CheckKeyTypes(L_, _key_i, StackIndex{ lua_gettop(L_) }); 630 CheckKeyTypes(L_, _key_i, StackIndex{ lua_gettop(L_) });
628 // receive a single value, checking multiple slots 631 // receive a single value, checking multiple slots
629 _selected_keeper_receive = KEEPER_API(receive); 632 _selected_keeper_receive = KEEPER_API(receive);
630 // we expect a single (value, key) pair of returned values 633 // we expect a single (value, slot) pair of returned values
631 _expected_pushed_min = _expected_pushed_max = 2; 634 _expected_pushed_min = _expected_pushed_max = 2;
632 } 635 }
633 636
@@ -660,6 +663,9 @@ LUAG_FUNC(linda_receive)
660 } 663 }
661 if (_pushed.value() > 0) { 664 if (_pushed.value() > 0) {
662 LUA_ASSERT(L_, _pushed.value() >= _expected_pushed_min && _pushed.value() <= _expected_pushed_max); 665 LUA_ASSERT(L_, _pushed.value() >= _expected_pushed_min && _pushed.value() <= _expected_pushed_max);
666 if (kRestrictedChannel.equals(L_, StackIndex{ kIdxTop })) {
667 raise_luaL_error(L_, "Key is restricted");
668 }
663 _linda->readHappened.notify_all(); 669 _linda->readHappened.notify_all();
664 break; 670 break;
665 } 671 }
@@ -730,6 +736,49 @@ LUAG_FUNC(linda_receive)
730// ################################################################################################# 736// #################################################################################################
731 737
732/* 738/*
739 * "string" = linda:restrict(key_num|str|bool|lightuserdata, [string])
740 * "string" = linda:restrict(slot)
741 *
742 * Read or set restrict mode to 1 Linda slot.
743 */
744LUAG_FUNC(linda_restrict)
745{
746 static constexpr lua_CFunction _rstrct{
747 +[](lua_State* const L_) {
748 Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) };
749 // make sure we got 2 or 3 arguments: the linda, a slot and optionally a restrict mode
750 int const _nargs{ lua_gettop(L_) };
751 luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments");
752 // make sure we got a known restrict mode, (or nothing)
753 std::string_view const _mode{ luaG_tostring(L_, StackIndex{ 3 }) };
754 if (!_mode.empty() && (_mode != "none" && _mode != "set/get" && _mode != "send/receive")) {
755 raise_luaL_argerror(L_, StackIndex{ 3 }, "unknown restrict mode");
756 }
757 // make sure the slot is of a valid type
758 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 });
759
760 KeeperCallResult _pushed;
761 if (_linda->cancelStatus == Linda::Active) {
762 Keeper* const _keeper{ _linda->whichKeeper() };
763 _pushed = keeper_call(_keeper->K, KEEPER_API(restrict), L_, _linda, StackIndex{ 2 });
764 // we should get a single return value: the string describing the previous restrict mode
765 LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 1) && luaG_type(L_, kIdxTop) == LuaType::STRING);
766 } else { // linda is cancelled
767 // do nothing and return nil,lanes.cancel_error
768 lua_pushnil(L_);
769 kCancelError.pushKey(L_);
770 _pushed.emplace(2);
771 }
772 // propagate returned values
773 return _pushed.value();
774 }
775 };
776 return Linda::ProtectedCall(L_, _rstrct);
777}
778
779// #################################################################################################
780
781/*
733 * bool= linda:linda_send([timeout_secs=nil,] key_num|str|bool|lightuserdata, ...) 782 * bool= linda:linda_send([timeout_secs=nil,] key_num|str|bool|lightuserdata, ...)
734 * 783 *
735 * Send one or more values to a Linda. If there is a limit, all values must fit. 784 * Send one or more values to a Linda. If there is a limit, all values must fit.
@@ -743,7 +792,7 @@ LUAG_FUNC(linda_send)
743 static constexpr lua_CFunction _send{ 792 static constexpr lua_CFunction _send{
744 +[](lua_State* const L_) { 793 +[](lua_State* const L_) {
745 Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) }; 794 Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) };
746 StackIndex _key_i{ 2 }; // index of first key, if timeout not there 795 StackIndex _key_i{ 2 }; // index of first slot, if timeout not there
747 796
748 std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; 797 std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
749 if (luaG_type(L_, StackIndex{ 2 }) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion 798 if (luaG_type(L_, StackIndex{ 2 }) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion
@@ -754,11 +803,11 @@ LUAG_FUNC(linda_send)
754 raise_luaL_argerror(L_, StackIndex{ 2 }, "duration cannot be < 0"); 803 raise_luaL_argerror(L_, StackIndex{ 2 }, "duration cannot be < 0");
755 } 804 }
756 ++_key_i; 805 ++_key_i;
757 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key 806 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the slot
758 ++_key_i; 807 ++_key_i;
759 } 808 }
760 809
761 // make sure the key is of a valid type 810 // make sure the slot is of a valid type
762 CheckKeyTypes(L_, _key_i, _key_i); 811 CheckKeyTypes(L_, _key_i, _key_i);
763 812
764 STACK_GROW(L_, 1); 813 STACK_GROW(L_, 1);
@@ -799,6 +848,9 @@ LUAG_FUNC(linda_send)
799 } 848 }
800 LUA_ASSERT(L_, _pushed.value() == 1); 849 LUA_ASSERT(L_, _pushed.value() == 1);
801 850
851 if (kRestrictedChannel.equals(L_, StackIndex{ kIdxTop })) {
852 raise_luaL_error(L_, "Key is restricted");
853 }
802 _ret = lua_toboolean(L_, -1) ? true : false; 854 _ret = lua_toboolean(L_, -1) ? true : false;
803 lua_pop(L_, 1); 855 lua_pop(L_, 1);
804 856
@@ -884,7 +936,7 @@ LUAG_FUNC(linda_set)
884 +[](lua_State* const L_) { 936 +[](lua_State* const L_) {
885 Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) }; 937 Linda* const _linda{ ToLinda<false>(L_, StackIndex{ 1 }) };
886 bool const _has_data{ lua_gettop(L_) > 2 }; 938 bool const _has_data{ lua_gettop(L_) > 2 };
887 // make sure the key is of a valid type (throws an error if not the case) 939 // make sure the slot is of a valid type (throws an error if not the case)
888 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 }); 940 CheckKeyTypes(L_, StackIndex{ 2 }, StackIndex{ 2 });
889 941
890 KeeperCallResult _pushed; 942 KeeperCallResult _pushed;
@@ -892,6 +944,9 @@ LUAG_FUNC(linda_set)
892 Keeper* const _keeper{ _linda->whichKeeper() }; 944 Keeper* const _keeper{ _linda->whichKeeper() };
893 _pushed = keeper_call(_keeper->K, KEEPER_API(set), L_, _linda, StackIndex{ 2 }); 945 _pushed = keeper_call(_keeper->K, KEEPER_API(set), L_, _linda, StackIndex{ 2 });
894 if (_pushed.has_value()) { // no error? 946 if (_pushed.has_value()) { // no error?
947 if (kRestrictedChannel.equals(L_, kIdxTop)) {
948 raise_luaL_error(L_, "Key is restricted");
949 }
895 LUA_ASSERT(L_, _pushed.value() == 2 && luaG_type(L_, kIdxTop) == LuaType::STRING && luaG_type(L_, StackIndex{ -2 }) == LuaType::BOOLEAN); 950 LUA_ASSERT(L_, _pushed.value() == 2 && luaG_type(L_, kIdxTop) == LuaType::STRING && luaG_type(L_, StackIndex{ -2 }) == LuaType::BOOLEAN);
896 951
897 if (_has_data) { 952 if (_has_data) {
@@ -899,7 +954,7 @@ LUAG_FUNC(linda_set)
899 _linda->writeHappened.notify_all(); // To be done from within the 'K' locking area 954 _linda->writeHappened.notify_all(); // To be done from within the 'K' locking area
900 } 955 }
901 if (lua_toboolean(L_, -2)) { 956 if (lua_toboolean(L_, -2)) {
902 // the key was full, but it is no longer the case, tell writers they should wake 957 // the slot was full, but it is no longer the case, tell writers they should wake
903 _linda->readHappened.notify_all(); // To be done from within the 'K' locking area 958 _linda->readHappened.notify_all(); // To be done from within the 'K' locking area
904 } 959 }
905 } 960 }
@@ -992,6 +1047,7 @@ namespace {
992 { "get", LG_linda_get }, 1047 { "get", LG_linda_get },
993 { "limit", LG_linda_limit }, 1048 { "limit", LG_linda_limit },
994 { "receive", LG_linda_receive }, 1049 { "receive", LG_linda_receive },
1050 { "restrict", LG_linda_restrict },
995 { "send", LG_linda_send }, 1051 { "send", LG_linda_send },
996 { "set", LG_linda_set }, 1052 { "set", LG_linda_set },
997 { "wake", LG_linda_wake }, 1053 { "wake", LG_linda_wake },
diff --git a/tests/cancel.lua b/tests/cancel.lua
index 1984c85..e65d794 100644
--- a/tests/cancel.lua
+++ b/tests/cancel.lua
@@ -39,10 +39,17 @@ if not next(which_tests) or which_tests.genlock then
39 local lock = lanes.genlock( linda, "lock", 1) 39 local lock = lanes.genlock( linda, "lock", 1)
40 local atomic = lanes.genatomic( linda, "atomic") 40 local atomic = lanes.genatomic( linda, "atomic")
41 41
42 local check_returned_cancel_error = function(_status, _err)
43 assert(_status == nil and _err == lanes.cancel_error)
44 end
42 -- check that cancelled lindas give cancel_error as they should 45 -- check that cancelled lindas give cancel_error as they should
43 linda:cancel() 46 linda:cancel()
44 local _status, _err = linda:get( "empty") 47 check_returned_cancel_error(linda:set( "empty", 42))
45 assert(_status == nil and _err == lanes.cancel_error) 48 check_returned_cancel_error(linda:get( "empty"))
49 check_returned_cancel_error(linda:send( "empty", 42))
50 check_returned_cancel_error(linda:receive( "empty"))
51 check_returned_cancel_error(linda:limit( "empty", 5))
52 check_returned_cancel_error(linda:restrict( "empty", "set/get"))
46 assert( lanes.genlock( linda, "any", 1) == lanes.cancel_error) 53 assert( lanes.genlock( linda, "any", 1) == lanes.cancel_error)
47 assert( lanes.genatomic( linda, "any") == lanes.cancel_error) 54 assert( lanes.genatomic( linda, "any") == lanes.cancel_error)
48 55
diff --git a/tests/fifo.lua b/tests/fifo.lua
index e1bfeae..9efcbd9 100644
--- a/tests/fifo.lua
+++ b/tests/fifo.lua
@@ -60,7 +60,7 @@ for key,count in pairs(fifo_linda:count("unknown key", A.channel, "unknown key",
60end 60end
61print "Dumping linda stats.. [4]" -- count everything 61print "Dumping linda stats.. [4]" -- count everything
62for key,contents in pairs(fifo_linda:dump()) do 62for key,contents in pairs(fifo_linda:dump()) do
63 print("channel " .. key .. ": limit=".. contents.limit, " first=" .. contents.first, " count=" .. contents.count) 63 print("channel " .. key .. ": limit=".. contents.limit, " first=" .. contents.first, " count=" .. contents.count .. " restrict=" .. contents.restrict)
64 for k,v in pairs(contents.fifo) do 64 for k,v in pairs(contents.fifo) do
65 print("[".. k.."] = " .. v) 65 print("[".. k.."] = " .. v)
66 end 66 end