diff options
-rw-r--r-- | CHANGES | 1 | ||||
-rw-r--r-- | docs/index.html | 132 | ||||
-rw-r--r-- | src/cancel.cpp | 2 | ||||
-rw-r--r-- | src/compat.hpp | 10 | ||||
-rw-r--r-- | src/keeper.cpp | 145 | ||||
-rw-r--r-- | src/keeper.hpp | 14 | ||||
-rw-r--r-- | src/lanes.lua | 1 | ||||
-rw-r--r-- | src/linda.cpp | 104 | ||||
-rw-r--r-- | tests/cancel.lua | 11 | ||||
-rw-r--r-- | tests/fifo.lua | 2 |
10 files changed, 327 insertions, 95 deletions
@@ -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: <opt_name>"</tt></li> | 1250 | <li><tt>tostring(linda)</tt> returns a string of the form <tt>"Linda: <opt_name>"</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, <limit>) | 1276 | bool,string|(nil,[lanes.cancel_error|"timeout"]) = h:limit(slot, <limit>) |
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 [, "<mode>"]) |
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 (>= 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 (>= 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 | [<key>] = | 1412 | [<slot>] = |
1392 | { | 1413 | { |
1393 | first = <n> | 1414 | first = <n> |
1394 | count = <n> | 1415 | count = <n> |
1395 | limit = <n>|'unlimited' | 1416 | limit = <n>|'unlimited' |
1417 | restrict = "none"|"set/get"|"send/receive" | ||
1396 | fifo = { <array of values> } | 1418 | fifo = { <array of values> } |
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 | ||
275 | template<typename ENUM> | ||
276 | requires std::is_enum_v<ENUM> | ||
277 | [[nodiscard]] | ||
278 | ENUM 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 | |||
275 | inline void luaG_registerlibfuncs(lua_State* L_, luaL_Reg const* funcs_) | 285 | inline 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]] | ||
121 | LindaRestrict 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} |
119 | KeyUD* KeyUD::Create(KeeperState const K_) | 130 | KeyUD* 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 |
412 | int keepercall_get(lua_State* const L_) | 423 | int 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 | ||
577 | int 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 |
543 | int keepercall_send(lua_State* const L_) | 641 | int 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 |
579 | int keepercall_set(lua_State* const L_) | 681 | int 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 | ||
16 | enum class LindaRestrict | ||
17 | { | ||
18 | None, | ||
19 | SetGet, | ||
20 | SendReceive | ||
21 | }; | ||
22 | |||
23 | // ################################################################################################# | ||
24 | |||
16 | struct Keeper | 25 | struct 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 |
80 | static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" }; | 89 | static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" }; |
81 | 90 | ||
91 | // xxh64 of string "kRestrictedChannel" generated at https://www.pelock.com/products/hash-calculator | ||
92 | static constexpr UniqueKey kRestrictedChannel{ 0x4C8B879ECDE110F7ull }; | ||
93 | |||
82 | using keeper_api_t = lua_CFunction; | 94 | using 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]] |
97 | int keepercall_receive_batched(lua_State* L_); | 109 | int keepercall_receive_batched(lua_State* L_); |
98 | [[nodiscard]] | 110 | [[nodiscard]] |
111 | int keepercall_restrict(lua_State* L_); | ||
112 | [[nodiscard]] | ||
99 | int keepercall_send(lua_State* L_); | 113 | int keepercall_send(lua_State* L_); |
100 | [[nodiscard]] | 114 | [[nodiscard]] |
101 | int keepercall_set(lua_State* L_); | 115 | int 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 | */ |
583 | LUAG_FUNC(linda_receive) | 586 | LUAG_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 | */ | ||
744 | LUAG_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", | |||
60 | end | 60 | end |
61 | print "Dumping linda stats.. [4]" -- count everything | 61 | print "Dumping linda stats.. [4]" -- count everything |
62 | for key,contents in pairs(fifo_linda:dump()) do | 62 | for 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 |