aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-05-07 17:56:10 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-05-13 18:15:46 +0200
commit13f7f505375f7c1afd3a7e479a64cc147501b01d (patch)
tree3bccba196595305ffd5f2b30f838dd39fbc5d51d
parentd093c5555ec439affcfbecdceabfb122aa8c2f73 (diff)
downloadlanes-13f7f505375f7c1afd3a7e479a64cc147501b01d.tar.gz
lanes-13f7f505375f7c1afd3a7e479a64cc147501b01d.tar.bz2
lanes-13f7f505375f7c1afd3a7e479a64cc147501b01d.zip
Linda API changes
* timeout clarifications (negative values are no longer accepted, use nil instead) * linda(send, linda.null, key, ...) removed, if you want to send a nil, just do it as usual
Diffstat (limited to '')
-rw-r--r--docs/index.html51
-rw-r--r--src/cancel.cpp35
-rw-r--r--src/cancel.h2
-rw-r--r--src/lanes.cpp46
-rw-r--r--src/lanes.lua17
-rw-r--r--src/lanes_private.h2
-rw-r--r--src/linda.cpp25
-rw-r--r--src/lindafactory.cpp1
-rw-r--r--src/universe.cpp3
-rw-r--r--tests/basic.lua296
10 files changed, 250 insertions, 228 deletions
diff --git a/docs/index.html b/docs/index.html
index e811074..3c9cbcf 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -953,26 +953,29 @@
953</pre></td></tr></table> 953</pre></td></tr></table>
954 954
955<p> 955<p>
956 Waits until the lane finishes, or <tt>timeout</tt> seconds have passed. Returns <tt>nil, "timeout"</tt> on timeout, <tt>nil,err,stack_tbl</tt> if the lane hit an error, <tt>nil, "killed"</tt> if forcefully killed, or the return values of the lane. 956 <tt>timeout</tt> is an optional number &gt= 0 (the default if unspecified).
957 <br/>
958 Waits until the lane finishes, or <tt>timeout</tt> seconds have passed.
959 <br/>
960 Returns <tt>nil, "timeout"</tt> on timeout, <tt>nil,err,stack_tbl</tt> if the lane hit an error, <tt>nil, "killed"</tt> if forcefully killed, or the return values of the lane.
961 <br/>
957 Unlike in reading the results in table fashion, errors are not propagated. 962 Unlike in reading the results in table fashion, errors are not propagated.
958</p> 963</p>
959 964
960<p> 965<p>
961<tt>stack_tbl</tt> is a table describing where the error was thrown. 966<tt>stack_tbl</tt> is a table describing where the error was thrown.
962 <br/> 967 <br/>
963 In <tt>"extended"</tt> mode, <tt>stack_tbl</tt> is an array of tables containing info gathered with <tt>lua_getinfo()</tt> (<tt>"source"</tt>,<tt>"currentline"</tt>,<tt>"name"</tt>,<tt>"namewhat"</tt>,<tt>"what"</tt>). 968 In <tt>"extended"</tt> mode, <tt>stack_tbl</tt> is an array of tables containing info gathered with <tt>lua_getinfo()</tt> (<tt>"source"</tt>,<tt>"currentline"</tt>,<tt>"name"</tt>,<tt>"namewhat"</tt>,<tt>"what"</tt>).
964 <br/> 969 <br/>
965 In <tt>"basic mode"</tt>, <tt>stack_tbl</tt> is an array of "&lt;filename&gt;:&lt;line&gt;" strings. Use <tt>table.concat()</tt> to format it to your liking (or just ignore it). 970 In <tt>"basic"</tt> mode, <tt>stack_tbl</tt> is an array of <tt>"&lt;filename&gt;:&lt;line&gt;"</tt> strings. Use <tt>table.concat()</tt> to format it to your liking (or just ignore it).
966</p> 971</p>
967 972
968<p> 973<p>
969 If you use <tt>:join</tt>, make sure your lane main function returns a non-nil value so you can tell timeout and error cases apart from succesful return (using the <tt>.status</tt> property may be risky, since it might change between a timed out join and the moment you read it). 974 If you use <tt>:join()</tt>, make sure your lane main function returns a non-nil value so you can tell timeout and error cases apart from succesful return (using the <tt>.status</tt> property may be risky, since it might change between a timed out join and the moment you read it).
970</p> 975</p>
971 976
972<p>
973
974<table border=1 bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre> 977<table border=1 bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre>
975 require "lanes".configure() 978 local lanes = require "lanes".configure()
976 979
977 f = lanes.gen(function() error "!!!" end) 980 f = lanes.gen(function() error "!!!" end)
978 a = f(1) 981 a = f(1)
@@ -990,7 +993,7 @@
990</p> 993</p>
991 994
992<table border=1 bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre> 995<table border=1 bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre>
993 require "lanes".configure() 996 local lanes = require "lanes".configure()
994 997
995 local sync_linda = lanes.linda() 998 local sync_linda = lanes.linda()
996 f = lanes.gen(function() dostuff() sync_linda:send("done", true) end) 999 f = lanes.gen(function() dostuff() sync_linda:send("done", true) end)
@@ -1012,7 +1015,10 @@
1012</pre></td></tr></table> 1015</pre></td></tr></table>
1013 1016
1014<p> 1017<p>
1015 <tt>cancel()</tt> sends a cancellation request to the lane.<br/> 1018 <tt>timeout</tt> is an optional number &gt= 0. Defaults to 0 if left unspecified or <tt>nil</tt>.
1019 <br/>
1020 <tt>cancel()</tt> sends a cancellation request to the lane.
1021 <br/>
1016 First argument is a <tt>mode</tt> can be one of <tt>"hard"</tt>, <tt>"soft"</tt>, <tt>"call"</tt>, <tt>"ret"</tt>, <tt>"line"</tt>, <tt>"count"</tt>. 1022 First argument is a <tt>mode</tt> can be one of <tt>"hard"</tt>, <tt>"soft"</tt>, <tt>"call"</tt>, <tt>"ret"</tt>, <tt>"line"</tt>, <tt>"count"</tt>.
1017 If <tt>mode</tt> is not specified, it defaults to <tt>"hard"</tt>. 1023 If <tt>mode</tt> is not specified, it defaults to <tt>"hard"</tt>.
1018 If <tt>wake_lane</tt> is <tt>true</tt>, the lane is also signalled so that execution returns from any pending linda operation. Linda operations detecting the cancellation request return <tt>lanes.cancel_error</tt>. 1024 If <tt>wake_lane</tt> is <tt>true</tt>, the lane is also signalled so that execution returns from any pending linda operation. Linda operations detecting the cancellation request return <tt>lanes.cancel_error</tt>.
@@ -1056,17 +1062,17 @@
1056</pre></td></tr></table> 1062</pre></td></tr></table>
1057 1063
1058<p> 1064<p>
1059 The <tt>error</tt> call is used for throwing exceptions in Lua. What Lua does not offer, however, is scoped <a href="http://en.wikipedia.org/wiki/Finalizer">finalizers</a> 1065 The regular Lua <tt>error</tt> function is usable in lanes for throwing exceptions. What Lua does not offer, however, is scoped <a href="http://en.wikipedia.org/wiki/Finalizer">finalizers</a>
1060 that would get called when a certain block of instructions gets exited, whether through peaceful return or abrupt <tt>error</tt>. 1066 that would get called when a certain block of instructions gets exited, whether through peaceful return or abrupt <tt>error</tt>.
1061</p> 1067</p>
1062 1068
1063<p> 1069<p>
1064 Since 2.0.3, Lanes registers a function <tt>set_finalizer</tt> in the lane's Lua state for doing this. 1070 Lanes registers a function <tt>set_finalizer</tt> in the lane's Lua state for doing this.
1065 Any functions given to it will be called in the lane Lua state, just prior to closing it. It is possible to set more than one finalizer. They are not called in any particular order. 1071 Any functions given to it will be called in the lane Lua state, just prior to closing it. It is possible to set more than one finalizer. They are not called in any particular order.
1066</p> 1072</p>
1067 1073
1068<p> 1074<p>
1069 An error in a finalizer itself overrides the state of the regular chunk (in practise, it would be highly preferable <i>not</i> to have errors in finalizers). If one finalizer errors, the others may not get called. 1075 An error in a finalizer itself overrides the state of the regular chunk (in practice, it would be highly preferable <i>not</i> to have errors in finalizers). If one finalizer errors, the others may not get called.
1070 If a finalizer error occurs after an error in the lane body, then this new error replaces the previous one (including the full stack trace). 1076 If a finalizer error occurs after an error in the lane body, then this new error replaces the previous one (including the full stack trace).
1071</p> 1077</p>
1072 1078
@@ -1103,18 +1109,18 @@
1103</p> 1109</p>
1104 1110
1105<table border="1" bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre> 1111<table border="1" bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre>
1106 require "lanes".configure() 1112 local lanes = require "lanes".configure()
1107 1113
1108 local linda = lanes.linda() 1114 local linda = lanes.linda("my linda")
1109 1115
1110 local function loop(max) 1116 local function loop(max)
1111 for i = 1, max do 1117 for i = 1, max do
1112 print("sending: " .. i) 1118 print("sending: " .. i)
1113 linda:send("x", i) -- linda as upvalue 1119 linda:send("x", i) -- linda as upvalue of loop()
1114 end 1120 end
1115 end 1121 end
1116 1122
1117 a = lanes.gen("", loop)(10000) 1123 lane_h = lanes.gen("", loop)(10000)
1118 1124
1119 while true do 1125 while true do
1120 local key, val = linda:receive(3.0, "x") -- timeout in seconds 1126 local key, val = linda:receive(3.0, "x") -- timeout in seconds
@@ -1124,6 +1130,8 @@
1124 end 1130 end
1125 print(tostring(linda) .. " received: " .. val) 1131 print(tostring(linda) .. " received: " .. val)
1126 end 1132 end
1133
1134 lane_h:join()
1127</pre></td></tr></table> 1135</pre></td></tr></table>
1128 1136
1129<p> 1137<p>
@@ -1147,7 +1155,7 @@
1147<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1155<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1148 h = lanes.linda([opt_name, [opt_group]]) 1156 h = lanes.linda([opt_name, [opt_group]])
1149 1157
1150 [true|lanes.cancel_error] = h:send([timeout_secs,] [h.null,] key, ...) 1158 [true|lanes.cancel_error] = h:send([timeout_secs,] key, ...)
1151 1159
1152 [key, val]|[lanes.cancel_error] = h:receive([timeout_secs,] key [, ...]) 1160 [key, val]|[lanes.cancel_error] = h:receive([timeout_secs,] key [, ...])
1153 1161
@@ -1157,10 +1165,11 @@
1157</pre></td></tr></table> 1165</pre></td></tr></table>
1158 1166
1159<p> 1167<p>
1160 The <tt>send()</tt> and <tt>receive()</tt> methods use Linda keys as FIFO stacks (first in, first out). Timeouts are given in seconds (millisecond accuracy). If using numbers as the first Linda key, one must explicitly give <tt>nil</tt> as the timeout parameter to avoid ambiguities. 1168 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's equivalent to an infinite duration).
1161</p> 1169</p>
1162 1170
1163<p> 1171<p>
1172 The <tt>send()</tt> and <tt>receive()</tt> methods use Linda keys as FIFO stacks (first in, first out).<br/>
1164 By default, stack 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. Any negative value removes the limit. 1173 By default, stack 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. Any negative value removes the limit.
1165 <br/> 1174 <br/>
1166 A limit of 0 is allowed to block everything. 1175 A limit of 0 is allowed to block everything.
@@ -1181,7 +1190,7 @@
1181 <br/> 1190 <br/>
1182 <tt>send()</tt> returns <tt>lanes.cancel_error</tt> if interrupted by a soft cancel request. 1191 <tt>send()</tt> returns <tt>lanes.cancel_error</tt> if interrupted by a soft cancel request.
1183 <br/> 1192 <br/>
1184 If no data is provided after the key, <tt>send()</tt> raises an error. If provided with <tt>linda.null</tt> or <tt>lanes.null</tt> before the actual key and there is no data to send, <tt>send()</tt> sends a single <tt>nil</tt>. 1193 If no data is provided after the key, <tt>send()</tt> raises an error.
1185 <br/> 1194 <br/>
1186 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>. 1195 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>.
1187</p> 1196</p>
@@ -1395,7 +1404,7 @@ events to a common Linda, but... :).</font>
1395</p> 1404</p>
1396 1405
1397<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> 1406<table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre>
1398 void = lanes.sleep(['indefinitely'|seconds|false]) 1407 void = lanes.sleep(['indefinitely'|seconds|nil])
1399</pre></td></tr></table> 1408</pre></td></tr></table>
1400 1409
1401<p> 1410<p>
@@ -1775,7 +1784,7 @@ static MyDeepFactory g_MyDeepFactory;
1775 <ul> 1784 <ul>
1776 <li>Data passing (parameters, upvalues, Linda 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> 1785 <li>Data passing (parameters, upvalues, Linda 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>
1777 <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> 1786 <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>
1778 <li>Waiting Lindas are woken up (and execute some hidden Lua code) each time <u>any</u> key in the Lindas they are waiting for are changed. This may give essential slow-down (not measured, just a gut feeling) if a lot of Linda keys are used. Using separate Linda objects for logically separate issues will help (which is good practise anyhow).</li> 1787 <li>Waiting Lindas are woken up (and execute some hidden Lua code) each time <u>any</u> key in the Lindas they are waiting for are changed. This may give essential slow-down (not measured, just a gut feeling) if a lot of Linda keys are used. Using separate Linda objects for logically separate issues will help (which is good practice anyhow).</li>
1779 <li>Linda 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 Linda. Barely nothing.</li> 1788 <li>Linda 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 Linda. Barely nothing.</li>
1780 <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> 1789 <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>
1781 <li>If you are using a lot of Linda objects, it may be useful to try having more of these keeper states. By default, only one is used (see <a href="#initialization"><tt>lanes.configure()</tt></a>).</li> 1790 <li>If you are using a lot of Linda objects, it may be useful to try having more of these keeper states. 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 dd848a7..fe1623b 100644
--- a/src/cancel.cpp
+++ b/src/cancel.cpp
@@ -107,7 +107,7 @@ LUAG_FUNC(cancel_test)
107 107
108// ################################################################################################# 108// #################################################################################################
109 109
110[[nodiscard]] static CancelResult thread_cancel_soft(Lane* lane_, lua_Duration duration_, bool wakeLane_) 110[[nodiscard]] static CancelResult thread_cancel_soft(Lane* lane_, std::chrono::time_point<std::chrono::steady_clock> until_, bool wakeLane_)
111{ 111{
112 lane_->cancelRequest = CancelRequest::Soft; // it's now signaled to stop 112 lane_->cancelRequest = CancelRequest::Soft; // it's now signaled to stop
113 // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own 113 // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own
@@ -118,12 +118,12 @@ LUAG_FUNC(cancel_test)
118 } 118 }
119 } 119 }
120 120
121 return lane_->waitForCompletion(duration_) ? CancelResult::Cancelled : CancelResult::Timeout; 121 return lane_->waitForCompletion(until_) ? CancelResult::Cancelled : CancelResult::Timeout;
122} 122}
123 123
124// ################################################################################################# 124// #################################################################################################
125 125
126[[nodiscard]] static CancelResult thread_cancel_hard(Lane* lane_, lua_Duration duration_, bool wakeLane_) 126[[nodiscard]] static CancelResult thread_cancel_hard(Lane* lane_, std::chrono::time_point<std::chrono::steady_clock> until_, bool wakeLane_)
127{ 127{
128 lane_->cancelRequest = CancelRequest::Hard; // it's now signaled to stop 128 lane_->cancelRequest = CancelRequest::Hard; // it's now signaled to stop
129 // lane_->thread.get_stop_source().request_stop(); 129 // lane_->thread.get_stop_source().request_stop();
@@ -134,13 +134,13 @@ LUAG_FUNC(cancel_test)
134 } 134 }
135 } 135 }
136 136
137 CancelResult result{ lane_->waitForCompletion(duration_) ? CancelResult::Cancelled : CancelResult::Timeout }; 137 CancelResult result{ lane_->waitForCompletion(until_) ? CancelResult::Cancelled : CancelResult::Timeout };
138 return result; 138 return result;
139} 139}
140 140
141// ################################################################################################# 141// #################################################################################################
142 142
143CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hookCount_, lua_Duration duration_, bool wakeLane_) 143CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hookCount_, std::chrono::time_point<std::chrono::steady_clock> until_, bool wakeLane_)
144{ 144{
145 // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here 145 // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here
146 // We can read 'lane_->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) 146 // We can read 'lane_->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN)
@@ -152,12 +152,12 @@ CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hookCount_, lua_Durati
152 // signal the linda the wake up the thread so that it can react to the cancel query 152 // signal the linda the wake up the thread so that it can react to the cancel query
153 // let us hope we never land here with a pointer on a linda that has been destroyed... 153 // let us hope we never land here with a pointer on a linda that has been destroyed...
154 if (op_ == CancelOp::Soft) { 154 if (op_ == CancelOp::Soft) {
155 return thread_cancel_soft(lane_, duration_, wakeLane_); 155 return thread_cancel_soft(lane_, until_, wakeLane_);
156 } else if (static_cast<int>(op_) > static_cast<int>(CancelOp::Soft)) { 156 } else if (static_cast<int>(op_) > static_cast<int>(CancelOp::Soft)) {
157 lua_sethook(lane_->L, cancel_hook, static_cast<int>(op_), hookCount_); 157 lua_sethook(lane_->L, cancel_hook, static_cast<int>(op_), hookCount_);
158 } 158 }
159 159
160 return thread_cancel_hard(lane_, duration_, wakeLane_); 160 return thread_cancel_hard(lane_, until_, wakeLane_);
161} 161}
162 162
163// ################################################################################################# 163// #################################################################################################
@@ -200,7 +200,7 @@ CancelOp which_cancel_op(char const* opString_)
200 200
201// ################################################################################################# 201// #################################################################################################
202 202
203// bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, wake_lindas]) 203// bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, wake_lane])
204LUAG_FUNC(thread_cancel) 204LUAG_FUNC(thread_cancel)
205{ 205{
206 Lane* const lane{ ToLane(L_, 1) }; 206 Lane* const lane{ ToLane(L_, 1) };
@@ -215,14 +215,19 @@ LUAG_FUNC(thread_cancel)
215 } 215 }
216 } 216 }
217 217
218 lua_Duration wait_timeout{ 0.0 }; 218 std::chrono::time_point<std::chrono::steady_clock> until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
219 if (lua_type(L_, 2) == LUA_TNUMBER) { 219 if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion
220 wait_timeout = lua_Duration{ lua_tonumber(L_, 2) }; 220 lua_Duration const duration{ lua_tonumber(L_, 2) };
221 lua_remove(L_, 2); // argument is processed, remove it 221 if (duration.count() >= 0.0) {
222 if (wait_timeout.count() < 0.0) { 222 until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration);
223 raise_luaL_error(L_, "cancel timeout cannot be < 0"); 223 } else {
224 raise_luaL_argerror(L_, 2, "duration cannot be < 0");
224 } 225 }
226 lua_remove(L_, 2); // argument is processed, remove it
227 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
228 lua_remove(L_, 2); // argument is processed, remove it
225 } 229 }
230
226 // we wake by default in "hard" mode (remember that hook is hard too), but this can be turned off if desired 231 // we wake by default in "hard" mode (remember that hook is hard too), but this can be turned off if desired
227 bool wake_lane{ op != CancelOp::Soft }; 232 bool wake_lane{ op != CancelOp::Soft };
228 if (lua_gettop(L_) >= 2) { 233 if (lua_gettop(L_) >= 2) {
@@ -233,7 +238,7 @@ LUAG_FUNC(thread_cancel)
233 lua_remove(L_, 2); // argument is processed, remove it 238 lua_remove(L_, 2); // argument is processed, remove it
234 } 239 }
235 STACK_CHECK_START_REL(L_, 0); 240 STACK_CHECK_START_REL(L_, 0);
236 switch (thread_cancel(lane, op, hook_count, wait_timeout, wake_lane)) { 241 switch (thread_cancel(lane, op, hook_count, until, wake_lane)) {
237 default: // should never happen unless we added a case and forgot to handle it 242 default: // should never happen unless we added a case and forgot to handle it
238 LUA_ASSERT(L_, false); 243 LUA_ASSERT(L_, false);
239 break; 244 break;
diff --git a/src/cancel.h b/src/cancel.h
index 3df5252..1918df3 100644
--- a/src/cancel.h
+++ b/src/cancel.h
@@ -49,7 +49,7 @@ enum class CancelOp
49static constexpr UniqueKey kCancelError{ 0x0630345FEF912746ull, "lanes.cancel_error" }; // 'raise_cancel_error' sentinel 49static constexpr UniqueKey kCancelError{ 0x0630345FEF912746ull, "lanes.cancel_error" }; // 'raise_cancel_error' sentinel
50 50
51[[nodiscard]] CancelOp which_cancel_op(char const* opString_); 51[[nodiscard]] CancelOp which_cancel_op(char const* opString_);
52[[nodiscard]] CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hookCount_, lua_Duration secs_, bool wakeLane_); 52[[nodiscard]] CancelResult thread_cancel(Lane* lane_, CancelOp op_, int hookCount_, std::chrono::time_point<std::chrono::steady_clock> until_, bool wakeLane_);
53 53
54[[noreturn]] static inline void raise_cancel_error(lua_State* L_) 54[[noreturn]] static inline void raise_cancel_error(lua_State* L_)
55{ 55{
diff --git a/src/lanes.cpp b/src/lanes.cpp
index 90f0f9f..d211b6a 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -169,17 +169,12 @@ Lane::Lane(Universe* U_, lua_State* L_)
169 169
170// ################################################################################################# 170// #################################################################################################
171 171
172bool Lane::waitForCompletion(lua_Duration duration_) 172bool Lane::waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_)
173{ 173{
174 std::chrono::time_point<std::chrono::steady_clock> until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
175 if (duration_.count() >= 0.0) {
176 until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration_);
177 }
178
179 std::unique_lock lock{ doneMutex }; 174 std::unique_lock lock{ doneMutex };
180 // std::stop_token token{ thread.get_stop_token() }; 175 // std::stop_token token{ thread.get_stop_token() };
181 // return doneCondVar.wait_until(lock, token, secs_, [this](){ return status >= Lane::Done; }); 176 // return doneCondVar.wait_until(lock, token, secs_, [this](){ return status >= Lane::Done; });
182 return doneCondVar.wait_until(lock, until, [this]() { return status >= Lane::Done; }); 177 return doneCondVar.wait_until(lock, until_, [this]() { return status >= Lane::Done; });
183} 178}
184 179
185// ################################################################################################# 180// #################################################################################################
@@ -1209,22 +1204,33 @@ void Lane::pushThreadStatus(lua_State* L_)
1209LUAG_FUNC(thread_join) 1204LUAG_FUNC(thread_join)
1210{ 1205{
1211 Lane* const lane{ ToLane(L_, 1) }; 1206 Lane* const lane{ ToLane(L_, 1) };
1212 lua_Duration const duration{ luaL_optnumber(L_, 2, -1.0) };
1213 lua_State* const L2{ lane->L }; 1207 lua_State* const L2{ lane->L };
1214 1208
1215 bool const done{ !lane->thread.joinable() || lane->waitForCompletion(duration) }; 1209 std::chrono::time_point<std::chrono::steady_clock> until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
1210 if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion
1211 lua_Duration const duration{ lua_tonumber(L_, 2) };
1212 if (duration.count() >= 0.0) {
1213 until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration);
1214 } else {
1215 raise_luaL_argerror(L_, 2, "duration cannot be < 0");
1216 }
1217
1218 } else if (!lua_isnoneornil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
1219 raise_luaL_argerror(L_, 2, "incorrect duration type");
1220 }
1221
1222 bool const done{ !lane->thread.joinable() || lane->waitForCompletion(until) };
1223 lua_settop(L_, 1); // L_: lane
1216 if (!done || !L2) { 1224 if (!done || !L2) {
1217 STACK_GROW(L_, 2); 1225 lua_pushnil(L_); // L_: lane nil
1218 lua_pushnil(L_); // L_: lane timeout? nil 1226 lua_pushliteral(L_, "timeout"); // L_: lane nil "timeout"
1219 lua_pushliteral(L_, "timeout"); // L_: lane timeout? nil "timeout"
1220 return 2; 1227 return 2;
1221 } 1228 }
1222 1229
1223 STACK_CHECK_START_REL(L_, 0); 1230 STACK_CHECK_START_REL(L_, 0); // L_: lane
1224 // Thread is Done/Error/Cancelled; all ours now 1231 // Thread is Done/Error/Cancelled; all ours now
1225 1232
1226 int ret{ 0 }; 1233 int ret{ 0 };
1227 Universe* const U{ lane->U };
1228 // debugName is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed 1234 // debugName is a pointer to string possibly interned in the lane's state, that no longer exists when the state is closed
1229 // so store it in the userdata uservalue at a key that can't possibly collide 1235 // so store it in the userdata uservalue at a key that can't possibly collide
1230 lane->securizeDebugName(L_); 1236 lane->securizeDebugName(L_);
@@ -1234,8 +1240,8 @@ LUAG_FUNC(thread_join)
1234 int const n{ lua_gettop(L2) }; // whole L2 stack 1240 int const n{ lua_gettop(L2) }; // whole L2 stack
1235 if ( 1241 if (
1236 (n > 0) && 1242 (n > 0) &&
1237 (InterCopyContext{ U, DestState{ L_ }, SourceState{ L2 }, {}, {}, {}, {}, {} }.inter_move(n) != InterCopyResult::Success) 1243 (InterCopyContext{ lane->U, DestState{ L_ }, SourceState{ L2 }, {}, {}, {}, {}, {} }.inter_move(n) != InterCopyResult::Success)
1238 ) { // L_: lane timeout? results L2: 1244 ) { // L_: lane results L2:
1239 raise_luaL_error(L_, "tried to copy unsupported types"); 1245 raise_luaL_error(L_, "tried to copy unsupported types");
1240 } 1246 }
1241 ret = n; 1247 ret = n;
@@ -1244,12 +1250,12 @@ LUAG_FUNC(thread_join)
1244 1250
1245 case Lane::Error: 1251 case Lane::Error:
1246 { 1252 {
1247 int const n{ lua_gettop(L2) }; // L_: lane timeout? L2: "err" [trace] 1253 int const n{ lua_gettop(L2) }; // L_: lane L2: "err" [trace]
1248 STACK_GROW(L_, 3); 1254 STACK_GROW(L_, 3);
1249 lua_pushnil(L_); // L_: lane timeout? nil 1255 lua_pushnil(L_); // L_: lane nil
1250 // even when ERROR_FULL_STACK, if the error is not LUA_ERRRUN, the handler wasn't called, and we only have 1 error message on the stack ... 1256 // even when ERROR_FULL_STACK, if the error is not LUA_ERRRUN, the handler wasn't called, and we only have 1 error message on the stack ...
1251 InterCopyContext c{ U, DestState{ L_ }, SourceState{ L2 }, {}, {}, {}, {}, {} }; 1257 InterCopyContext c{ lane->U, DestState{ L_ }, SourceState{ L2 }, {}, {}, {}, {}, {} };
1252 if (c.inter_move(n) != InterCopyResult::Success) { // L_: lane timeout? nil "err" [trace] L2: 1258 if (c.inter_move(n) != InterCopyResult::Success) { // L_: lane nil "err" [trace] L2:
1253 raise_luaL_error(L_, "tried to copy unsupported types: %s", lua_tostring(L_, -n)); 1259 raise_luaL_error(L_, "tried to copy unsupported types: %s", lua_tostring(L_, -n));
1254 } 1260 }
1255 ret = 1 + n; 1261 ret = 1 + n;
diff --git a/src/lanes.lua b/src/lanes.lua
index caa8818..0ab6661 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -51,16 +51,17 @@ local lanes = setmetatable({}, lanesMeta)
51-- and 'table' visible. 51-- and 'table' visible.
52-- 52--
53local assert = assert(assert) 53local assert = assert(assert)
54local error = assert(error)
54local io = assert(io) 55local io = assert(io)
56local pairs = assert(pairs)
55local string_gmatch = assert(string.gmatch) 57local string_gmatch = assert(string.gmatch)
56local string_format = assert(string.format) 58local string_format = assert(string.format)
57local select = assert(select) 59local select = assert(select)
58local setmetatable = assert(setmetatable) 60local setmetatable = assert(setmetatable)
59local table_insert = assert(table.insert) 61local table_insert = assert(table.insert)
60local type = assert(type) 62local tonumber = assert(tonumber)
61local pairs = assert(pairs)
62local tostring = assert(tostring) 63local tostring = assert(tostring)
63local error = assert(error) 64local type = assert(type)
64 65
65-- ################################################################################################# 66-- #################################################################################################
66 67
@@ -625,10 +626,12 @@ end
625-- 626--
626-- PUBLIC LANES API 627-- PUBLIC LANES API
627local sleep = function(seconds_) 628local sleep = function(seconds_)
628 seconds_ = seconds_ or 0.0 -- this causes false and nil to be a valid input, equivalent to 0.0, but that's ok 629 local type = type(seconds_)
629 if seconds_ == 'indefinitely' then 630 if type == "string" then
630 seconds_ = nil 631 seconds_ = (seconds_ ~= 'indefinitely') and tonumber(seconds_) or nil
631 elseif type(seconds_) ~= "number" then 632 elseif type == "nil" then
633 seconds_ = 0
634 elseif type ~= "number" then
632 error("invalid duration " .. string_format("%q", tostring(seconds_))) 635 error("invalid duration " .. string_format("%q", tostring(seconds_)))
633 end 636 end
634 -- receive data on a channel no-one ever sends anything, thus blocking for the specified duration 637 -- receive data on a channel no-one ever sends anything, thus blocking for the specified duration
diff --git a/src/lanes_private.h b/src/lanes_private.h
index 196a346..a756c42 100644
--- a/src/lanes_private.h
+++ b/src/lanes_private.h
@@ -94,7 +94,7 @@ class Lane
94 Lane(Universe* U_, lua_State* L_); 94 Lane(Universe* U_, lua_State* L_);
95 ~Lane(); 95 ~Lane();
96 96
97 [[nodiscard]] bool waitForCompletion(lua_Duration duration_); 97 [[nodiscard]] bool waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_);
98 void startThread(int priority_); 98 void startThread(int priority_);
99 void pushThreadStatus(lua_State* L_); 99 void pushThreadStatus(lua_State* L_);
100 void changeDebugName(int nameIdx_); 100 void changeDebugName(int nameIdx_);
diff --git a/src/linda.cpp b/src/linda.cpp
index bbfbd69..40ef6c7 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -180,7 +180,7 @@ int Linda::ProtectedCall(lua_State* L_, lua_CFunction f_)
180// ################################################################################################# 180// #################################################################################################
181 181
182/* 182/*
183 * bool= linda_send( linda_ud, [timeout_secs=-1,] [linda.null,] key_num|str|bool|lightuserdata, ... ) 183 * bool= linda:linda_send([timeout_secs=nil,] key_num|str|bool|lightuserdata, ...)
184 * 184 *
185 * Send one or more values to a Linda. If there is a limit, all values must fit. 185 * Send one or more values to a Linda. If there is a limit, all values must fit.
186 * 186 *
@@ -192,25 +192,21 @@ LUAG_FUNC(linda_send)
192{ 192{
193 auto send = [](lua_State* L_) { 193 auto send = [](lua_State* L_) {
194 Linda* const linda{ ToLinda<false>(L_, 1) }; 194 Linda* const linda{ ToLinda<false>(L_, 1) };
195 std::chrono::time_point<std::chrono::steady_clock> until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
196 int key_i{ 2 }; // index of first key, if timeout not there 195 int key_i{ 2 }; // index of first key, if timeout not there
197 196
197 std::chrono::time_point<std::chrono::steady_clock> until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
198 if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion 198 if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion
199 lua_Duration const duration{ lua_tonumber(L_, 2) }; 199 lua_Duration const duration{ lua_tonumber(L_, 2) };
200 if (duration.count() >= 0.0) { 200 if (duration.count() >= 0.0) {
201 until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration); 201 until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration);
202 } else {
203 raise_luaL_argerror(L_, 2, "duration cannot be < 0");
202 } 204 }
203 ++key_i; 205 ++key_i;
204 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key 206 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
205 ++key_i; 207 ++key_i;
206 } 208 }
207 209
208 bool const as_nil_sentinel{ kNilSentinel.equals(L_, key_i) }; // if not nullptr, send() will silently send a single nil if nothing is provided
209 if (as_nil_sentinel) {
210 // the real key to send data to is after the kNilSentinel marker
211 ++key_i;
212 }
213
214 // make sure the key is of a valid type 210 // make sure the key is of a valid type
215 check_key_types(L_, key_i, key_i); 211 check_key_types(L_, key_i, key_i);
216 212
@@ -218,12 +214,7 @@ LUAG_FUNC(linda_send)
218 214
219 // make sure there is something to send 215 // make sure there is something to send
220 if (lua_gettop(L_) == key_i) { 216 if (lua_gettop(L_) == key_i) {
221 if (as_nil_sentinel) { 217 raise_luaL_error(L_, "no data to send");
222 // send a single nil if nothing is provided
223 kNilSentinel.pushKey(L_);
224 } else {
225 raise_luaL_error(L_, "no data to send");
226 }
227 } 218 }
228 219
229 // convert nils to some special non-nil sentinel in sent values 220 // convert nils to some special non-nil sentinel in sent values
@@ -322,7 +313,7 @@ LUAG_FUNC(linda_send)
322 313
323/* 314/*
324 * 2 modes of operation 315 * 2 modes of operation
325 * [val, key]= linda_receive( linda_ud, [timeout_secs_num=-1], key_num|str|bool|lightuserdata [, ...] ) 316 * [val, key]= linda_receive( linda_ud, [timeout_secs_num=nil], key_num|str|bool|lightuserdata [, ...] )
326 * Consumes a single value from the Linda, in any key. 317 * Consumes a single value from the Linda, in any key.
327 * Returns: received value (which is consumed from the slot), and the key which had it 318 * Returns: received value (which is consumed from the slot), and the key which had it
328 319
@@ -335,13 +326,15 @@ LUAG_FUNC(linda_receive)
335{ 326{
336 auto receive = [](lua_State* L_) { 327 auto receive = [](lua_State* L_) {
337 Linda* const linda{ ToLinda<false>(L_, 1) }; 328 Linda* const linda{ ToLinda<false>(L_, 1) };
338 std::chrono::time_point<std::chrono::steady_clock> until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
339 int key_i{ 2 }; // index of first key, if timeout not there 329 int key_i{ 2 }; // index of first key, if timeout not there
340 330
331 std::chrono::time_point<std::chrono::steady_clock> until{ std::chrono::time_point<std::chrono::steady_clock>::max() };
341 if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion 332 if (lua_type(L_, 2) == LUA_TNUMBER) { // we don't want to use lua_isnumber() because of autocoercion
342 lua_Duration const duration{ lua_tonumber(L_, 2) }; 333 lua_Duration const duration{ lua_tonumber(L_, 2) };
343 if (duration.count() >= 0.0) { 334 if (duration.count() >= 0.0) {
344 until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration); 335 until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration);
336 } else {
337 raise_luaL_argerror(L_, 2, "duration cannot be < 0");
345 } 338 }
346 ++key_i; 339 ++key_i;
347 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key 340 } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key
diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp
index 0ec5a0a..917d949 100644
--- a/src/lindafactory.cpp
+++ b/src/lindafactory.cpp
@@ -32,6 +32,7 @@ THE SOFTWARE.
32 32
33#include "lindafactory.h" 33#include "lindafactory.h"
34 34
35#include "lanes_private.h"
35#include "linda.h" 36#include "linda.h"
36 37
37// must be a #define instead of a constexpr to work with lua_pushliteral (until I templatize it) 38// must be a #define instead of a constexpr to work with lua_pushliteral (until I templatize it)
diff --git a/src/universe.cpp b/src/universe.cpp
index 6adc314..becffdd 100644
--- a/src/universe.cpp
+++ b/src/universe.cpp
@@ -89,13 +89,12 @@ void Universe::terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTim
89 { 89 {
90 std::lock_guard<std::mutex> guard{ selfdestructMutex }; 90 std::lock_guard<std::mutex> guard{ selfdestructMutex };
91 Lane* lane{ selfdestructFirst }; 91 Lane* lane{ selfdestructFirst };
92 lua_Duration timeout{ 1us };
93 while (lane != SELFDESTRUCT_END) { 92 while (lane != SELFDESTRUCT_END) {
94 // attempt the requested cancel with a small timeout. 93 // attempt the requested cancel with a small timeout.
95 // if waiting on a linda, they will raise a cancel_error. 94 // if waiting on a linda, they will raise a cancel_error.
96 // if a cancellation hook is desired, it will be installed to try to raise an error 95 // if a cancellation hook is desired, it will be installed to try to raise an error
97 if (lane->thread.joinable()) { 96 if (lane->thread.joinable()) {
98 std::ignore = thread_cancel(lane, op_, 1, timeout, true); 97 std::ignore = thread_cancel(lane, op_, 1, std::chrono::steady_clock::now() + 1us, true);
99 } 98 }
100 lane = lane->selfdestruct_next; 99 lane = lane->selfdestruct_next;
101 } 100 }
diff --git a/tests/basic.lua b/tests/basic.lua
index 1cf37e6..85a9889 100644
--- a/tests/basic.lua
+++ b/tests/basic.lua
@@ -8,16 +8,16 @@
8-- 8--
9 9
10local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{ with_timers = false, internal_allocator = "libc"} 10local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{ with_timers = false, internal_allocator = "libc"}
11print( "require_lanes_result:", require_lanes_result_1, require_lanes_result_2) 11print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
12local lanes = require_lanes_result_1 12local lanes = require_lanes_result_1
13 13
14local require_assert_result_1, require_assert_result_2 = require "assert" -- assert.fails() 14local require_assert_result_1, require_assert_result_2 = require "assert" -- assert.fails()
15print( "require_assert_result:", require_assert_result_1, require_assert_result_2) 15print("require_assert_result:", require_assert_result_1, require_assert_result_2)
16 16
17local lanes_gen= assert( lanes.gen ) 17local lanes_gen= assert(lanes.gen)
18local lanes_linda= assert( lanes.linda ) 18local lanes_linda= assert(lanes.linda)
19 19
20local tostring= assert( tostring ) 20local tostring= assert(tostring)
21 21
22local function PRINT(...) 22local function PRINT(...)
23 local str="" 23 local str=""
@@ -29,8 +29,8 @@ local function PRINT(...)
29 end 29 end
30end 30end
31 31
32local gc_cb = function( name_, status_) 32local gc_cb = function(name_, status_)
33 PRINT( " ---> lane '" .. name_ .. "' collected with status " .. status_) 33 PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
34end 34end
35--gc_cb = nil 35--gc_cb = nil
36 36
@@ -41,9 +41,9 @@ local tables_match
41 41
42-- true if 'a' is a subtable of 'b' 42-- true if 'a' is a subtable of 'b'
43-- 43--
44local function subtable( a, b ) 44local function subtable(a, b)
45 -- 45 --
46 assert( type(a)=="table" and type(b)=="table" ) 46 assert(type(a)=="table" and type(b)=="table")
47 47
48 for k,v in pairs(b) do 48 for k,v in pairs(b) do
49 if type(v)~=type(a[k]) then 49 if type(v)~=type(a[k]) then
@@ -59,18 +59,18 @@ end
59 59
60-- true when contents of 'a' and 'b' are identical 60-- true when contents of 'a' and 'b' are identical
61-- 61--
62tables_match= function( a, b ) 62tables_match= function(a, b)
63 return subtable( a, b ) and subtable( b, a ) 63 return subtable(a, b) and subtable(b, a)
64end 64end
65 65
66-- ################################################################################################## 66-- ##################################################################################################
67-- ################################################################################################## 67-- ##################################################################################################
68-- ################################################################################################## 68-- ##################################################################################################
69 69
70PRINT( "\n\n", "---=== Tasking (basic) ===---", "\n\n") 70PRINT("\n\n", "---=== Tasking (basic) ===---", "\n\n")
71 71
72local function task( a, b, c ) 72local function task(a, b, c)
73 set_debug_threadname( "task("..a..","..b..","..c..")") 73 set_debug_threadname("task("..a..","..b..","..c..")")
74 --error "111" -- testing error messages 74 --error "111" -- testing error messages
75 assert(hey) 75 assert(hey)
76 local v=0 76 local v=0
@@ -80,20 +80,20 @@ local function task( a, b, c )
80 return v, hey 80 return v, hey
81end 81end
82 82
83local task_launch= lanes_gen( "", { globals={hey=true}, gc_cb = gc_cb}, task ) 83local task_launch= lanes_gen("", { globals={hey=true}, gc_cb = gc_cb}, task)
84 -- base stdlibs, normal priority 84 -- base stdlibs, normal priority
85 85
86-- 'task_launch' is a factory of multithreaded tasks, we can launch several: 86-- 'task_launch' is a factory of multithreaded tasks, we can launch several:
87 87
88local lane1= task_launch( 100,200,3 ) 88local lane1= task_launch(100,200,3)
89local lane2= task_launch( 200,300,4 ) 89local lane2= task_launch(200,300,4)
90 90
91-- At this stage, states may be "pending", "running" or "done" 91-- At this stage, states may be "pending", "running" or "done"
92 92
93local st1,st2= lane1.status, lane2.status 93local st1,st2= lane1.status, lane2.status
94PRINT(st1,st2) 94PRINT(st1,st2)
95assert( st1=="pending" or st1=="running" or st1=="done" ) 95assert(st1=="pending" or st1=="running" or st1=="done")
96assert( st2=="pending" or st2=="running" or st2=="done" ) 96assert(st2=="pending" or st2=="running" or st2=="done")
97 97
98-- Accessing results ([1..N]) pends until they are available 98-- Accessing results ([1..N]) pends until they are available
99-- 99--
@@ -101,14 +101,14 @@ PRINT("waiting...")
101local v1, v1_hey= lane1[1], lane1[2] 101local v1, v1_hey= lane1[1], lane1[2]
102local v2, v2_hey= lane2[1], lane2[2] 102local v2, v2_hey= lane2[1], lane2[2]
103 103
104PRINT( v1, v1_hey ) 104PRINT(v1, v1_hey)
105assert( v1_hey == true ) 105assert(v1_hey == true)
106 106
107PRINT( v2, v2_hey ) 107PRINT(v2, v2_hey)
108assert( v2_hey == true ) 108assert(v2_hey == true)
109 109
110assert( lane1.status == "done" ) 110assert(lane1.status == "done")
111assert( lane1.status == "done" ) 111assert(lane1.status == "done")
112lane1, lane2 = nil 112lane1, lane2 = nil
113collectgarbage() 113collectgarbage()
114 114
@@ -116,9 +116,9 @@ collectgarbage()
116-- ################################################################################################## 116-- ##################################################################################################
117-- ################################################################################################## 117-- ##################################################################################################
118 118
119PRINT( "\n\n", "---=== Tasking (cancelling) ===---", "\n\n") 119PRINT("\n\n", "---=== Tasking (cancelling) ===---", "\n\n")
120 120
121local task_launch2= lanes_gen( "", { globals={hey=true}, gc_cb = gc_cb}, task ) 121local task_launch2= lanes_gen("", { globals={hey=true}, gc_cb = gc_cb}, task)
122 122
123local N=999999999 123local N=999999999
124local lane9= task_launch2(1,N,1) -- huuuuuuge... 124local lane9= task_launch2(1,N,1) -- huuuuuuge...
@@ -129,7 +129,7 @@ local st
129local t0= os.time() 129local t0= os.time()
130while os.time()-t0 < 5 do 130while os.time()-t0 < 5 do
131 st= lane9.status 131 st= lane9.status
132 io.stderr:write( (i==1) and st.." " or '.' ) 132 io.stderr:write((i==1) and st.." " or '.')
133 if st~="pending" then break end 133 if st~="pending" then break end
134end 134end
135PRINT(" "..st) 135PRINT(" "..st)
@@ -138,36 +138,36 @@ if st=="error" then
138 local _= lane9[0] -- propagate the error here 138 local _= lane9[0] -- propagate the error here
139end 139end
140if st=="done" then 140if st=="done" then
141 error( "Looping to "..N.." was not long enough (cannot test cancellation)" ) 141 error("Looping to "..N.." was not long enough (cannot test cancellation)")
142end 142end
143assert( st=="running" ) 143assert(st=="running")
144 144
145lane9:cancel( "count", 100) -- 0 timeout, 100 instructions count hook 145lane9:cancel("count", 100) -- 0 timeout, 100 instructions count hook
146 146
147local t0= os.time() 147local t0= os.time()
148while os.time()-t0 < 5 do 148while os.time()-t0 < 5 do
149 st= lane9.status 149 st= lane9.status
150 io.stderr:write( (i==1) and st.." " or '.' ) 150 io.stderr:write((i==1) and st.." " or '.')
151 if st~="running" then break end 151 if st~="running" then break end
152end 152end
153PRINT(" "..st) 153PRINT(" "..st)
154assert( st == "cancelled" ) 154assert(st == "cancelled")
155 155
156-- cancellation of lanes waiting on a linda 156-- cancellation of lanes waiting on a linda
157local limited = lanes.linda("limited") 157local limited = lanes.linda("limited")
158limited:limit( "key", 1) 158limited:limit("key", 1)
159-- [[################################################ 159-- [[################################################
160limited:send( "key", "hello") -- saturate linda 160limited:send("key", "hello") -- saturate linda
161for k, v in pairs( limited:dump()) do 161for k, v in pairs(limited:dump()) do
162 PRINT("limited[" .. tostring( k) .. "] = " .. tostring( v)) 162 PRINT("limited[" .. tostring(k) .. "] = " .. tostring(v))
163end 163end
164local wait_send = function() 164local wait_send = function()
165 local a,b 165 local a,b
166 set_finalizer( function() print( "wait_send", a, b) end) 166 set_finalizer(function() print("wait_send", a, b) end)
167 a,b = limited:send( "key", "bybye") -- infinite timeout, returns only when lane is cancelled 167 a,b = limited:send("key", "bybye") -- infinite timeout, returns only when lane is cancelled
168end 168end
169 169
170local wait_send_lane = lanes.gen( "*", wait_send)() 170local wait_send_lane = lanes.gen("*", wait_send)()
171repeat until wait_send_lane.status == "waiting" 171repeat until wait_send_lane.status == "waiting"
172print "wait_send_lane is waiting" 172print "wait_send_lane is waiting"
173wait_send_lane:cancel() -- hard cancel, 0 timeout 173wait_send_lane:cancel() -- hard cancel, 0 timeout
@@ -176,11 +176,11 @@ print "wait_send_lane is cancelled"
176--################################################]] 176--################################################]]
177local wait_receive = function() 177local wait_receive = function()
178 local k, v 178 local k, v
179 set_finalizer( function() print( "wait_receive", k, v) end) 179 set_finalizer(function() print("wait_receive", k, v) end)
180 k, v = limited:receive( "dummy") -- infinite timeout, returns only when lane is cancelled 180 k, v = limited:receive("dummy") -- infinite timeout, returns only when lane is cancelled
181end 181end
182 182
183local wait_receive_lane = lanes.gen( "*", wait_receive)() 183local wait_receive_lane = lanes.gen("*", wait_receive)()
184repeat until wait_receive_lane.status == "waiting" 184repeat until wait_receive_lane.status == "waiting"
185print "wait_receive_lane is waiting" 185print "wait_receive_lane is waiting"
186wait_receive_lane:cancel() -- hard cancel, 0 timeout 186wait_receive_lane:cancel() -- hard cancel, 0 timeout
@@ -189,11 +189,11 @@ print "wait_receive_lane is cancelled"
189--################################################]] 189--################################################]]
190local wait_receive_batched = function() 190local wait_receive_batched = function()
191 local k, v1, v2 191 local k, v1, v2
192 set_finalizer( function() print( "wait_receive_batched", k, v1, v2) end) 192 set_finalizer(function() print("wait_receive_batched", k, v1, v2) end)
193 k, v1, v2 = limited:receive( limited.batched, "dummy", 2) -- infinite timeout, returns only when lane is cancelled 193 k, v1, v2 = limited:receive(limited.batched, "dummy", 2) -- infinite timeout, returns only when lane is cancelled
194end 194end
195 195
196local wait_receive_batched_lane = lanes.gen( "*", wait_receive_batched)() 196local wait_receive_batched_lane = lanes.gen("*", wait_receive_batched)()
197repeat until wait_receive_batched_lane.status == "waiting" 197repeat until wait_receive_batched_lane.status == "waiting"
198print "wait_receive_batched_lane is waiting" 198print "wait_receive_batched_lane is waiting"
199wait_receive_batched_lane:cancel() -- hard cancel, 0 timeout 199wait_receive_batched_lane:cancel() -- hard cancel, 0 timeout
@@ -205,120 +205,126 @@ print "wait_receive_batched_lane is cancelled"
205-- ################################################################################################## 205-- ##################################################################################################
206-- ################################################################################################## 206-- ##################################################################################################
207 207
208PRINT( "\n\n", "---=== Communications ===---", "\n\n") 208PRINT("\n\n", "---=== Communications ===---", "\n\n")
209 209
210local function WR(...) io.stderr:write(...) end 210local function WR(...) io.stderr:write(...) end
211 211
212local chunk= function( linda ) 212local chunk= function(linda)
213 set_debug_threadname "chunk" 213 local function receive() return linda:receive("->") end
214 local function receive() return linda:receive( "->" ) end 214 local function send(...) linda:send("<-", ...) end
215 local function send(...) linda:send( "<-", ... ) end
216 215
217 WR( "Lane starts!\n" ) 216 WR("Lane starts!\n")
218 217
219 local k,v 218 local k,v
220 k,v=receive(); WR( v.." received\n" ); assert( v==1 ) 219 k,v=receive(); WR(v.." received\n"); assert(v==1)
221 k,v=receive(); WR( v.." received\n" ); assert( v==2 ) 220 k,v=receive(); WR(v.." received\n"); assert(v==2)
222 k,v=receive(); WR( v.." received\n" ); assert( v==3 ) 221 k,v=receive(); WR(v.." received\n"); assert(v==3)
222 k,v=receive(); WR(tostring(v).." received\n"); assert(v==nil)
223 223
224 send( 1,2,3 ); WR( "1,2,3 sent\n" ) 224 send(1,2,3); WR("1,2,3 sent\n")
225 send 'a'; WR( "'a' sent\n" ) 225 send 'a'; WR("'a' sent\n")
226 send { 'a', 'b', 'c', d=10 }; WR( "{'a','b','c',d=10} sent\n" ) 226 send(nil); WR("nil sent\n")
227 send { 'a', 'b', 'c', d=10 }; WR("{'a','b','c',d=10} sent\n")
227 228
228 k,v=receive(); WR( v.." received\n" ); assert( v==4 ) 229 k,v=receive(); WR(v.." received\n"); assert(v==4)
229 230
230 local subT1 = { "subT1"} 231 local subT1 = { "subT1"}
231 local subT2 = { "subT2"} 232 local subT2 = { "subT2"}
232 send { subT1, subT2, subT1, subT2}; WR( "{ subT1, subT2, subT1, subT2} sent\n" ) 233 send { subT1, subT2, subT1, subT2}; WR("{ subT1, subT2, subT1, subT2} sent\n")
233 234
234 WR( "Lane ends!\n" ) 235 WR("Lane ends!\n")
235end 236end
236 237
237local linda= lanes_linda("communications") 238local linda = lanes_linda("communications")
238assert( type(linda) == "userdata" ) 239assert(type(linda) == "userdata" and tostring(linda) == "Linda: communications")
239 -- 240 --
240 -- ["->"] master -> slave 241 -- ["->"] master -> slave
241 -- ["<-"] slave <- master 242 -- ["<-"] slave <- master
242 243
243local function PEEK() return linda:get("<-") end 244local function PEEK() return linda:get("<-") end
244local function SEND(...) linda:send( "->", ... ) end 245local function SEND(...) linda:send("->", ...) end
245local function RECEIVE() local k,v = linda:receive( 1, "<-" ) return v end 246local function RECEIVE() local k,v = linda:receive(1, "<-") return v end
246 247
247local t= lanes_gen("io", {gc_cb = gc_cb}, chunk)(linda) -- prepare & launch 248local comms_lane = lanes_gen("io", {gc_cb = gc_cb, name = "auto"}, chunk)(linda) -- prepare & launch
248 249
249SEND(1); WR( "1 sent\n" ) 250SEND(1); WR("1 sent\n")
250SEND(2); WR( "2 sent\n" ) 251SEND(2); WR("2 sent\n")
252SEND(3); WR("3 sent\n")
251for i=1,100 do 253for i=1,100 do
252 WR "." 254 WR "."
253 assert( PEEK() == nil ) -- nothing coming in, yet 255 lanes.sleep(0.0001)
256 assert(PEEK() == nil) -- nothing coming in, yet
254end 257end
255SEND(3); WR( "3 sent\n" ) 258SEND(nil); WR("\nnil sent\n")
256 259
257local a,b,c= RECEIVE(), RECEIVE(), RECEIVE() 260local a,b,c = RECEIVE(), RECEIVE(), RECEIVE()
258 261
259print( "lane status: " .. t.status) 262print("lane status: " .. comms_lane.status)
260if t.status == "error" then 263if comms_lane.status == "error" then
261 print( t:join()) 264 print(comms_lane:join())
262else 265else
263 WR( a..", "..b..", "..c.." received\n" ) 266 WR(a..", "..b..", "..c.." received\n")
264end 267end
265 268
266assert( a==1 and b==2 and c==3 ) 269assert(a==1 and b==2 and c==3)
267 270
268local a= RECEIVE(); WR( a.." received\n" ) 271local a = RECEIVE(); WR(a.." received\n")
269assert( a=='a' ) 272assert(a=='a')
270 273
271local a= RECEIVE(); WR( type(a).." received\n" ) 274local null = RECEIVE(); WR(tostring(null).." received\n")
272assert( tables_match( a, {'a','b','c',d=10} ) ) 275assert(null==nil)
273 276
274assert( PEEK() == nil ) 277local out_t = RECEIVE(); WR(type(out_t).." received\n")
278assert(tables_match(out_t, {'a','b','c',d=10}))
279
280assert(PEEK() == nil)
275SEND(4) 281SEND(4)
276 282
277local complex_table = RECEIVE(); WR( type(complex_table).." received\n" ) 283local complex_table = RECEIVE(); WR(type(complex_table).." received\n")
278assert( complex_table[1] == complex_table[3] and complex_table[2] == complex_table[4]) 284assert(complex_table[1] == complex_table[3] and complex_table[2] == complex_table[4])
279WR( table.concat( {complex_table[1][1],complex_table[2][1],complex_table[3][1],complex_table[4][1]},", ")) 285WR(table.concat({complex_table[1][1],complex_table[2][1],complex_table[3][1],complex_table[4][1]},", "))
280 286
281WR("collectgarbage") 287WR("collectgarbage")
282t = nil 288comms_lane = nil
283collectgarbage() 289collectgarbage()
284-- wait 290-- wait
285WR("waiting 1s") 291WR("waiting 1s")
286linda:receive( 1, "wait") 292lanes.sleep(1)
287 293
288-- ################################################################################################## 294-- ##################################################################################################
289-- ################################################################################################## 295-- ##################################################################################################
290-- ################################################################################################## 296-- ##################################################################################################
291 297
292PRINT( "\n\n", "---=== Stdlib naming ===---", "\n\n") 298PRINT("\n\n", "---=== Stdlib naming ===---", "\n\n")
293 299
294local function dump_g( _x) 300local function dump_g(_x)
295 set_debug_threadname "dump_g" 301 set_debug_threadname "dump_g"
296 assert(print) 302 assert(print)
297 print( "### dumping _G for '" .. _x .. "'") 303 print("### dumping _G for '" .. _x .. "'")
298 for k, v in pairs( _G) do 304 for k, v in pairs(_G) do
299 print( "\t" .. k .. ": " .. type( v)) 305 print("\t" .. k .. ": " .. type(v))
300 end 306 end
301 return true 307 return true
302end 308end
303 309
304local function io_os_f( _x) 310local function io_os_f(_x)
305 set_debug_threadname "io_os_f" 311 set_debug_threadname "io_os_f"
306 assert(print) 312 assert(print)
307 print( "### checking io and os libs existence for '" .. _x .. "'") 313 print("### checking io and os libs existence for '" .. _x .. "'")
308 assert(io) 314 assert(io)
309 assert(os) 315 assert(os)
310 return true 316 return true
311end 317end
312 318
313local function coro_f( _x) 319local function coro_f(_x)
314 set_debug_threadname "coro_f" 320 set_debug_threadname "coro_f"
315 assert(print) 321 assert(print)
316 print( "### checking coroutine lib existence for '" .. _x .. "'") 322 print("### checking coroutine lib existence for '" .. _x .. "'")
317 assert(coroutine) 323 assert(coroutine)
318 return true 324 return true
319end 325end
320 326
321assert.fails( function() lanes_gen( "xxx", {gc_cb = gc_cb}, io_os_f ) end ) 327assert.fails(function() lanes_gen("xxx", {gc_cb = gc_cb}, io_os_f) end)
322 328
323local stdlib_naming_tests = 329local stdlib_naming_tests =
324{ 330{
@@ -333,9 +339,9 @@ local stdlib_naming_tests =
333 { "io,os,base", io_os_f}, 339 { "io,os,base", io_os_f},
334} 340}
335 341
336for _, t in ipairs( stdlib_naming_tests) do 342for _, t in ipairs(stdlib_naming_tests) do
337 local f= lanes_gen( t[1], {gc_cb = gc_cb}, t[2]) -- any delimiter will do 343 local f= lanes_gen(t[1], {gc_cb = gc_cb}, t[2]) -- any delimiter will do
338 assert( f(t[1])[1] ) 344 assert(f(t[1])[1])
339end 345end
340 346
341WR("collectgarbage") 347WR("collectgarbage")
@@ -345,17 +351,17 @@ collectgarbage()
345-- ################################################################################################## 351-- ##################################################################################################
346-- ################################################################################################## 352-- ##################################################################################################
347 353
348PRINT( "\n\n", "---=== Comms criss cross ===---", "\n\n") 354PRINT("\n\n", "---=== Comms criss cross ===---", "\n\n")
349 355
350-- We make two identical lanes, which are using the same Linda channel. 356-- We make two identical lanes, which are using the same Linda channel.
351-- 357--
352local tc= lanes_gen( "io", {gc_cb = gc_cb}, 358local tc= lanes_gen("io", {gc_cb = gc_cb},
353 function( linda, ch_in, ch_out ) 359 function(linda, ch_in, ch_out)
354 set_debug_threadname( "criss cross " .. ch_in .. " -> " .. ch_out) 360 set_debug_threadname("criss cross " .. ch_in .. " -> " .. ch_out)
355 local function STAGE(str) 361 local function STAGE(str)
356 io.stderr:write( ch_in..": "..str.."\n" ) 362 io.stderr:write(ch_in..": "..str.."\n")
357 linda:send( nil, ch_out, str ) 363 linda:send(nil, ch_out, str)
358 local k,v= linda:receive( nil, ch_in ) 364 local k,v= linda:receive(nil, ch_in)
359 assert(v==str) 365 assert(v==str)
360 end 366 end
361 STAGE("Hello") 367 STAGE("Hello")
@@ -378,103 +384,103 @@ collectgarbage()
378-- ################################################################################################## 384-- ##################################################################################################
379-- ################################################################################################## 385-- ##################################################################################################
380 386
381PRINT( "\n\n", "---=== Receive & send of code ===---", "\n\n") 387PRINT("\n\n", "---=== Receive & send of code ===---", "\n\n")
382 388
383local upvalue="123" 389local upvalue="123"
384 390
385local function chunk2( linda ) 391local function chunk2(linda)
386 assert( upvalue=="123" ) -- even when running as separate thread 392 assert(upvalue=="123") -- even when running as separate thread
387 -- function name & line number should be there even as separate thread 393 -- function name & line number should be there even as separate thread
388 -- 394 --
389 local info= debug.getinfo(1) -- 1 = us 395 local info= debug.getinfo(1) -- 1 = us
390 -- 396 --
391 for k,v in pairs(info) do PRINT(k,v) end 397 for k,v in pairs(info) do PRINT(k,v) end
392 398
393 assert( info.nups == (_VERSION == "Lua 5.1" and 2 or 3) ) -- one upvalue + PRINT + _ENV (Lua 5.2 only) 399 assert(info.nups == (_VERSION == "Lua 5.1" and 2 or 3)) -- one upvalue + PRINT + _ENV (Lua 5.2 only)
394 assert( info.what == "Lua" ) 400 assert(info.what == "Lua")
395 --assert( info.name == "chunk2" ) -- name does not seem to come through 401 --assert(info.name == "chunk2") -- name does not seem to come through
396 assert( string.match( info.source, "^@.*basic.lua$" ) ) 402 assert(string.match(info.source, "^@.*basic.lua$"))
397 assert( string.match( info.short_src, "^.*basic.lua$" ) ) 403 assert(string.match(info.short_src, "^.*basic.lua$"))
398 -- These vary so let's not be picky (they're there..) 404 -- These vary so let's not be picky (they're there..)
399 -- 405 --
400 assert( info.linedefined > 200 ) -- start of 'chunk2' 406 assert(info.linedefined > 200) -- start of 'chunk2'
401 assert( info.currentline > info.linedefined ) -- line of 'debug.getinfo' 407 assert(info.currentline > info.linedefined) -- line of 'debug.getinfo'
402 assert( info.lastlinedefined > info.currentline ) -- end of 'chunk2' 408 assert(info.lastlinedefined > info.currentline) -- end of 'chunk2'
403 local k,func= linda:receive( "down" ) 409 local k,func= linda:receive("down")
404 assert( type(func)=="function" ) 410 assert(type(func)=="function")
405 assert( k=="down" ) 411 assert(k=="down")
406 412
407 func(linda) 413 func(linda)
408 414
409 local k,str= linda:receive( "down" ) 415 local k,str= linda:receive("down")
410 assert( str=="ok" ) 416 assert(str=="ok")
411 417
412 linda:send( "up", function() return ":)" end, "ok2" ) 418 linda:send("up", function() return ":)" end, "ok2")
413end 419end
414 420
415local linda= lanes.linda("linda") 421local linda= lanes.linda("linda")
416local t2= lanes_gen( "debug,string,io", {gc_cb = gc_cb}, chunk2 )(linda) -- prepare & launch 422local t2= lanes_gen("debug,string,io", {gc_cb = gc_cb}, chunk2)(linda) -- prepare & launch
417linda:send( "down", function(linda) linda:send( "up", "ready!" ) end, 423linda:send("down", function(linda) linda:send("up", "ready!") end,
418 "ok" ) 424 "ok")
419-- wait to see if the tiny function gets executed 425-- wait to see if the tiny function gets executed
420-- 426--
421local k,s= linda:receive( 1, "up" ) 427local k,s= linda:receive(1, "up")
422if t2.status == "error" then 428if t2.status == "error" then
423 print( "t2 error: " , t2:join()) 429 print("t2 error: " , t2:join())
424end 430end
425PRINT(s) 431PRINT(s)
426assert( s=="ready!" ) 432assert(s=="ready!")
427 433
428-- returns of the 'chunk2' itself 434-- returns of the 'chunk2' itself
429-- 435--
430local k,f= linda:receive( "up" ) 436local k,f= linda:receive("up")
431assert( type(f)=="function" ) 437assert(type(f)=="function")
432 438
433local s2= f() 439local s2= f()
434assert( s2==":)" ) 440assert(s2==":)")
435 441
436local k,ok2= linda:receive( "up" ) 442local k,ok2= linda:receive("up")
437assert( ok2 == "ok2" ) 443assert(ok2 == "ok2")
438 444
439-- ################################################################################################## 445-- ##################################################################################################
440-- ################################################################################################## 446-- ##################################################################################################
441-- ################################################################################################## 447-- ##################################################################################################
442 448
443PRINT( "\n\n", "---=== :join test ===---", "\n\n") 449PRINT("\n\n", "---=== :join test ===---", "\n\n")
444 450
445-- NOTE: 'unpack()' cannot be used on the lane handle; it will always return nil 451-- NOTE: 'unpack()' cannot be used on the lane handle; it will always return nil
446-- (unless [1..n] has been read earlier, in which case it would seemingly 452-- (unless [1..n] has been read earlier, in which case it would seemingly
447-- work). 453-- work).
448 454
449local S= lanes_gen( "table", {gc_cb = gc_cb}, 455local S= lanes_gen("table", {gc_cb = gc_cb},
450 function(arg) 456 function(arg)
451 set_debug_threadname "join test lane" 457 set_debug_threadname "join test lane"
452 set_finalizer( function() end) 458 set_finalizer(function() end)
453 aux= {} 459 aux= {}
454 for i, v in ipairs(arg) do 460 for i, v in ipairs(arg) do
455 table.insert (aux, 1, v) 461 table.insert (aux, 1, v)
456 end 462 end
457 -- unpack was renamed table.unpack in Lua 5.2: cater for both! 463 -- unpack was renamed table.unpack in Lua 5.2: cater for both!
458 return (unpack or table.unpack)(aux) 464 return (unpack or table.unpack)(aux)
459end ) 465end)
460 466
461h= S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values 467h= S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values
462-- wait a bit so that the lane hasa chance to set its debug name 468-- wait a bit so that the lane has a chance to set its debug name
463linda:receive(0.5, "gloupti") 469lanes.sleep(0.5)
464print( "joining with '" .. h:get_debug_threadname() .. "'") 470print("joining with '" .. h:get_debug_threadname() .. "'")
465local a,b,c,d= h:join() 471local a,b,c,d= h:join()
466if h.status == "error" then 472if h.status == "error" then
467 print( h:get_debug_threadname(), "error: " , a, b, c, d) 473 print(h:get_debug_threadname(), "error: " , a, b, c, d)
468else 474else
469 print( h:get_debug_threadname(), a,b,c,d) 475 print(h:get_debug_threadname(), a,b,c,d)
470 assert(a==14) 476 assert(a==14)
471 assert(b==13) 477 assert(b==13)
472 assert(c==12) 478 assert(c==12)
473 assert(d==nil) 479 assert(d==nil)
474end 480end
475 481
476local nameof_type, nameof_name = lanes.nameof( print) 482local nameof_type, nameof_name = lanes.nameof(print)
477PRINT( "name of " .. nameof_type .. " print = '" .. nameof_name .. "'") 483PRINT("name of " .. nameof_type .. " print = '" .. nameof_name .. "'")
478 484
479-- 485--
480io.stderr:write "Done! :)\n" 486io.stderr:write "Done! :)\n"