aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-05-24 08:59:08 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-05-24 09:09:56 +0200
commitc3e9e26d5eb455f95e62fa8801cca9d4e7792acc (patch)
tree5b8645817abe4eb1123647a4e9c5204d0e35d9b1
parent3be94c4282bbe77895e952afb12a81e55c2a4391 (diff)
downloadlanes-c3e9e26d5eb455f95e62fa8801cca9d4e7792acc.tar.gz
lanes-c3e9e26d5eb455f95e62fa8801cca9d4e7792acc.tar.bz2
lanes-c3e9e26d5eb455f95e62fa8801cca9d4e7792acc.zip
Process upvalues equal to _G in Lua51 as in other flavors
-rw-r--r--src/intercopycontext.cpp30
-rw-r--r--src/lane.cpp20
-rw-r--r--src/lane.h2
-rw-r--r--src/lanes.cpp13
-rw-r--r--src/state.cpp66
-rw-r--r--src/tracker.cpp4
-rw-r--r--src/tracker.h7
-rw-r--r--src/universe.h2
-rw-r--r--tests/basic.lua5
9 files changed, 59 insertions, 90 deletions
diff --git a/src/intercopycontext.cpp b/src/intercopycontext.cpp
index adbb502..6684f3f 100644
--- a/src/intercopycontext.cpp
+++ b/src/intercopycontext.cpp
@@ -209,19 +209,17 @@ void InterCopyContext::copy_func() const
209 { 209 {
210 char const* _fname{}; 210 char const* _fname{};
211#define LOG_FUNC_INFO 0 211#define LOG_FUNC_INFO 0
212#if LOG_FUNC_INFO 212 if constexpr (LOG_FUNC_INFO)
213 // "To get information about a function you push it onto the
214 // stack and start the what string with the character '>'."
215 //
216 { 213 {
217 lua_Debug _ar; 214 lua_Debug _ar;
218 lua_pushvalue(L1, L1_i); // L1: ... b f 215 lua_pushvalue(L1, L1_i); // L1: ... b f
216 // "To get information about a function you push it onto the stack and start the what string with the character '>'."
219 // fills 'fname' 'namewhat' and 'linedefined', pops function 217 // fills 'fname' 'namewhat' and 'linedefined', pops function
220 lua_getinfo(L1, ">nS", &_ar); // L1: ... b 218 lua_getinfo(L1, ">nS", &_ar); // L1: ... b
221 _fname = _ar.namewhat; 219 _fname = _ar.namewhat;
222 DEBUGSPEW_CODE(DebugSpew(U) << "FNAME: " << _ar.short_src << " @ " << _ar.linedefined << std::endl); 220 DEBUGSPEW_CODE(DebugSpew(U) << "FNAME: " << _ar.short_src << " @ " << _ar.linedefined << std::endl);
223 } 221 }
224#endif // LOG_FUNC_INFO 222
225 { 223 {
226 std::string_view const _bytecode{ lua_tostringview(L1, -1) }; // L1: ... b 224 std::string_view const _bytecode{ lua_tostringview(L1, -1) }; // L1: ... b
227 LUA_ASSERT(L1, !_bytecode.empty()); 225 LUA_ASSERT(L1, !_bytecode.empty());
@@ -255,26 +253,20 @@ void InterCopyContext::copy_func() const
255 * cache so we don't end up in eternal loop. 253 * cache so we don't end up in eternal loop.
256 * Lua5.2 and Lua5.3: one of the upvalues is _ENV, which we don't want to copy! 254 * Lua5.2 and Lua5.3: one of the upvalues is _ENV, which we don't want to copy!
257 * instead, the function shall have LUA_RIDX_GLOBALS taken in the destination state! 255 * instead, the function shall have LUA_RIDX_GLOBALS taken in the destination state!
256 * TODO: this can probably be factorized as InterCopyContext::copyUpvalues(...)
258 */ 257 */
259 int _n{ 0 }; 258 int _n{ 0 };
260 { 259 {
261 InterCopyContext _c{ U, L2, L1, L2_cache_i, {}, VT::NORMAL, mode, {} }; 260 InterCopyContext _c{ U, L2, L1, L2_cache_i, {}, VT::NORMAL, mode, {} };
262#if LUA_VERSION_NUM >= 502 261 // if we encounter an upvalue equal to the global table in the source, bind it to the destination's global table
263 // Starting with Lua 5.2, each Lua function gets its environment as one of its upvalues (named LUA_ENV, aka "_ENV" by default)
264 // Generally this is LUA_RIDX_GLOBALS, which we don't want to copy from the source to the destination state...
265 // -> if we encounter an upvalue equal to the global table in the source, bind it to the destination's global table
266 lua_pushglobaltable(L1); // L1: ... _G 262 lua_pushglobaltable(L1); // L1: ... _G
267#endif // LUA_VERSION_NUM
268 for (_n = 0; (_c.name = lua_getupvalue(L1, L1_i, 1 + _n)) != nullptr; ++_n) { // L1: ... _G up[n] 263 for (_n = 0; (_c.name = lua_getupvalue(L1, L1_i, 1 + _n)) != nullptr; ++_n) { // L1: ... _G up[n]
269 DEBUGSPEW_CODE(DebugSpew(U) << "UPNAME[" << _n << "]: " << _c.name << " -> "); 264 DEBUGSPEW_CODE(DebugSpew(U) << "UPNAME[" << _n << "]: " << _c.name << " -> ");
270#if LUA_VERSION_NUM >= 502
271 if (lua_rawequal(L1, -1, -2)) { // is the upvalue equal to the global table? 265 if (lua_rawequal(L1, -1, -2)) { // is the upvalue equal to the global table?
272 DEBUGSPEW_CODE(DebugSpew(nullptr) << "pushing destination global scope" << std::endl); 266 DEBUGSPEW_CODE(DebugSpew(U) << "pushing destination global scope" << std::endl);
273 lua_pushglobaltable(L2); // L2: ... {cache} ... function <upvalues> 267 lua_pushglobaltable(L2); // L2: ... {cache} ... function <upvalues>
274 } else 268 } else {
275#endif // LUA_VERSION_NUM 269 DEBUGSPEW_CODE(DebugSpew(U) << "copying value" << std::endl);
276 {
277 DEBUGSPEW_CODE(DebugSpew(nullptr) << "copying value" << std::endl);
278 _c.L1_i = SourceIndex{ lua_gettop(L1) }; 270 _c.L1_i = SourceIndex{ lua_gettop(L1) };
279 if (!_c.inter_copy_one()) { // L2: ... {cache} ... function <upvalues> 271 if (!_c.inter_copy_one()) { // L2: ... {cache} ... function <upvalues>
280 raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); 272 raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1));
@@ -282,12 +274,8 @@ void InterCopyContext::copy_func() const
282 } 274 }
283 lua_pop(L1, 1); // L1: ... _G 275 lua_pop(L1, 1); // L1: ... _G
284 } 276 }
285#if LUA_VERSION_NUM >= 502
286 lua_pop(L1, 1); // L1: ... 277 lua_pop(L1, 1); // L1: ...
287#endif // LUA_VERSION_NUM 278 } // L2: ... {cache} ... function + 'n' upvalues (>=0)
288 }
289 // L2: ... {cache} ... function + 'n' upvalues (>=0)
290
291 STACK_CHECK(L1, 0); 279 STACK_CHECK(L1, 0);
292 280
293 // Set upvalues (originally set to 'nil' by 'lua_load') 281 // Set upvalues (originally set to 'nil' by 'lua_load')
diff --git a/src/lane.cpp b/src/lane.cpp
index 4b6500a..7650d6b 100644
--- a/src/lane.cpp
+++ b/src/lane.cpp
@@ -254,7 +254,6 @@ static int thread_index_number(lua_State* L_)
254 if (!lua_isnil(L_, -1)) { // an error was stored // L_: lane n {uv} <error> 254 if (!lua_isnil(L_, -1)) { // an error was stored // L_: lane n {uv} <error>
255 lua_getmetatable(L_, 1); // L_: lane n {uv} <error> {mt} 255 lua_getmetatable(L_, 1); // L_: lane n {uv} <error> {mt}
256 lua_replace(L_, -3); // L_: lane n {mt} <error> 256 lua_replace(L_, -3); // L_: lane n {mt} <error>
257#if LUA_VERSION_NUM == 501
258 // Note: Lua 5.1 interpreter is not prepared to show 257 // Note: Lua 5.1 interpreter is not prepared to show
259 // non-string errors, so we use 'tostring()' here 258 // non-string errors, so we use 'tostring()' here
260 // to get meaningful output. --AKa 22-Jan-2009 259 // to get meaningful output. --AKa 22-Jan-2009
@@ -266,13 +265,14 @@ static int thread_index_number(lua_State* L_)
266 // Level 3 should show the line where 'h[x]' was read 265 // Level 3 should show the line where 'h[x]' was read
267 // but this only seems to work for string messages 266 // but this only seems to work for string messages
268 // (Lua 5.1.4). No idea, why. --AKa 22-Jan-2009 267 // (Lua 5.1.4). No idea, why. --AKa 22-Jan-2009
269 if (!lua_isstring(L_, -1)) { 268 if constexpr (LUA_VERSION_NUM == 501) {
270 kCachedTostring.pushKey(L_); // L_: lane n {mt} <error> kCachedTostring 269 if (!lua_isstring(L_, -1)) {
271 lua_rawget(L_, -3); // L_: lane n {mt} <error> tostring() 270 kCachedTostring.pushKey(L_); // L_: lane n {mt} <error> kCachedTostring
272 lua_insert(L_, -2); // L_: lane n {mt} tostring() <error> 271 lua_rawget(L_, -3); // L_: lane n {mt} <error> tostring()
273 lua_call(L_, 1, 1); // tostring(errstring) // L_: lane n {mt} "error" 272 lua_insert(L_, -2); // L_: lane n {mt} tostring() <error>
273 lua_call(L_, 1, 1); // tostring(errstring) // L_: lane n {mt} "error"
274 }
274 } 275 }
275#endif // LUA_VERSION_NUM == 501
276 kCachedError.pushKey(L_); // L_: lane n {mt} "error" kCachedError 276 kCachedError.pushKey(L_); // L_: lane n {mt} "error" kCachedError
277 lua_rawget(L_, -3); // L_: lane n {mt} "error" error() 277 lua_rawget(L_, -3); // L_: lane n {mt} "error" error()
278 lua_replace(L_, -3); // L_: lane n error() "error" 278 lua_replace(L_, -3); // L_: lane n error() "error"
@@ -784,20 +784,14 @@ Lane::Lane(Universe* U_, lua_State* L_, ErrorTraceLevel errorTraceLevel_)
784{ 784{
785 assert(errorTraceLevel == ErrorTraceLevel::Minimal || errorTraceLevel == ErrorTraceLevel::Basic || errorTraceLevel == ErrorTraceLevel::Extended); 785 assert(errorTraceLevel == ErrorTraceLevel::Minimal || errorTraceLevel == ErrorTraceLevel::Basic || errorTraceLevel == ErrorTraceLevel::Extended);
786 kExtendedStackTraceRegKey.setValue(L_, [yes = errorTraceLevel == ErrorTraceLevel::Extended ? 1 : 0](lua_State* L_) { lua_pushboolean(L_, yes); }); 786 kExtendedStackTraceRegKey.setValue(L_, [yes = errorTraceLevel == ErrorTraceLevel::Extended ? 1 : 0](lua_State* L_) { lua_pushboolean(L_, yes); });
787#if HAVE_LANE_TRACKING()
788 U->tracker.tracking_add(this); 787 U->tracker.tracking_add(this);
789#endif // HAVE_LANE_TRACKING()
790} 788}
791 789
792// ################################################################################################# 790// #################################################################################################
793 791
794Lane::~Lane() 792Lane::~Lane()
795{ 793{
796 // Clean up after a (finished) thread
797 //
798#if HAVE_LANE_TRACKING()
799 std::ignore = U->tracker.tracking_remove(this); 794 std::ignore = U->tracker.tracking_remove(this);
800#endif // HAVE_LANE_TRACKING()
801} 795}
802 796
803// ################################################################################################# 797// #################################################################################################
diff --git a/src/lane.h b/src/lane.h
index 3c1ab58..1fdd955 100644
--- a/src/lane.h
+++ b/src/lane.h
@@ -110,10 +110,8 @@ class Lane
110 // is still running 110 // is still running
111 // S: cleans up after itself if non-nullptr at lane exit 111 // S: cleans up after itself if non-nullptr at lane exit
112 112
113#if HAVE_LANE_TRACKING()
114 // For tracking only 113 // For tracking only
115 Lane* volatile tracking_next{ nullptr }; 114 Lane* volatile tracking_next{ nullptr };
116#endif // HAVE_LANE_TRACKING()
117 115
118 ErrorTraceLevel const errorTraceLevel{ Basic }; 116 ErrorTraceLevel const errorTraceLevel{ Basic };
119 117
diff --git a/src/lanes.cpp b/src/lanes.cpp
index 000668a..8033de7 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -505,17 +505,13 @@ LUAG_FUNC(lane_new)
505 505
506// ################################################################################################ 506// ################################################################################################
507 507
508#if HAVE_LANE_TRACKING()
509//---
510// threads() -> {}|nil 508// threads() -> {}|nil
511//
512// Return a list of all known lanes 509// Return a list of all known lanes
513LUAG_FUNC(threads) 510LUAG_FUNC(threads)
514{ 511{
515 LaneTracker const& _tracker = universe_get(L_)->tracker; 512 LaneTracker const& _tracker = universe_get(L_)->tracker;
516 return _tracker.pushThreadsTable(L_); 513 return _tracker.pushThreadsTable(L_);
517} 514}
518#endif // HAVE_LANE_TRACKING()
519 515
520// ################################################################################################# 516// #################################################################################################
521// ######################################## Timer support ########################################## 517// ######################################## Timer support ##########################################
@@ -652,13 +648,14 @@ LUAG_FUNC(configure)
652 std::ignore = luaG_getfield(L_, 1, "demote_full_userdata"); // L_: settings demote_full_userdata 648 std::ignore = luaG_getfield(L_, 1, "demote_full_userdata"); // L_: settings demote_full_userdata
653 _U->demoteFullUserdata = lua_toboolean(L_, -1) ? true : false; 649 _U->demoteFullUserdata = lua_toboolean(L_, -1) ? true : false;
654 lua_pop(L_, 1); // L_: settings 650 lua_pop(L_, 1); // L_: settings
655#if HAVE_LANE_TRACKING() 651
652 // tracking
656 std::ignore = luaG_getfield(L_, 1, "track_lanes"); // L_: settings track_lanes 653 std::ignore = luaG_getfield(L_, 1, "track_lanes"); // L_: settings track_lanes
657 if (lua_toboolean(L_, -1)) { 654 if (lua_toboolean(L_, -1)) {
658 _U->tracker.activate(); 655 _U->tracker.activate();
659 } 656 }
660 lua_pop(L_, 1); // L_: settings 657 lua_pop(L_, 1); // L_: settings
661#endif // HAVE_LANE_TRACKING() 658
662 // Linked chains handling 659 // Linked chains handling
663 _U->selfdestructFirst = SELFDESTRUCT_END; 660 _U->selfdestructFirst = SELFDESTRUCT_END;
664 _U->initializeAllocatorFunction(L_); 661 _U->initializeAllocatorFunction(L_);
@@ -690,13 +687,13 @@ LUAG_FUNC(configure)
690 lua_setfield(L_, -2, "configure"); // L_: settings M 687 lua_setfield(L_, -2, "configure"); // L_: settings M
691 // add functions to the module's table 688 // add functions to the module's table
692 luaG_registerlibfuncs(L_, global::sLanesFunctions); 689 luaG_registerlibfuncs(L_, global::sLanesFunctions);
693#if HAVE_LANE_TRACKING() 690
694 // register core.threads() only if settings say it should be available 691 // register core.threads() only if settings say it should be available
695 if (_U->tracker.isActive()) { 692 if (_U->tracker.isActive()) {
696 lua_pushcfunction(L_, LG_threads); // L_: settings M LG_threads() 693 lua_pushcfunction(L_, LG_threads); // L_: settings M LG_threads()
697 lua_setfield(L_, -2, "threads"); // L_: settings M 694 lua_setfield(L_, -2, "threads"); // L_: settings M
698 } 695 }
699#endif // HAVE_LANE_TRACKING() 696
700 STACK_CHECK(L_, 2); 697 STACK_CHECK(L_, 2);
701 698
702 { 699 {
diff --git a/src/state.cpp b/src/state.cpp
index 7ce8db0..73c94f0 100644
--- a/src/state.cpp
+++ b/src/state.cpp
@@ -238,24 +238,28 @@ void InitializeOnStateCreate(Universe* U_, lua_State* L_)
238 238
239lua_State* create_state([[maybe_unused]] Universe* U_, lua_State* from_) 239lua_State* create_state([[maybe_unused]] Universe* U_, lua_State* from_)
240{ 240{
241 lua_State* _L; 241 lua_State* const _L {
242#if LUAJIT_FLAVOR() == 64 242 std::invoke(
243 // for some reason, LuaJIT 64 bits does not support creating a state with lua_newstate... 243 [U = U_, from = from_]() {
244 _L = luaL_newstate(); 244 if constexpr (LUAJIT_FLAVOR() == 64) {
245#else // LUAJIT_FLAVOR() == 64 245 // for some reason, LuaJIT 64 bits does not support creating a state with lua_newstate...
246 if (U_->provideAllocator != nullptr) { // we have a function we can call to obtain an allocator 246 return luaL_newstate();
247 lua_pushcclosure(from_, U_->provideAllocator, 0); 247 } else {
248 lua_call(from_, 0, 1); 248 if (U->provideAllocator != nullptr) { // we have a function we can call to obtain an allocator
249 { 249 lua_pushcclosure(from, U->provideAllocator, 0);
250 AllocatorDefinition* const def{ lua_tofulluserdata<AllocatorDefinition>(from_, -1) }; 250 lua_call(from, 0, 1);
251 _L = lua_newstate(def->allocF, def->allocUD); 251 AllocatorDefinition* const _def{ lua_tofulluserdata<AllocatorDefinition>(from, -1) };
252 } 252 lua_State* const _L{ lua_newstate(_def->allocF, _def->allocUD) };
253 lua_pop(from_, 1); 253 lua_pop(from, 1);
254 } else { 254 return _L;
255 // reuse the allocator provided when the master state was created 255 } else {
256 _L = lua_newstate(U_->protectedAllocator.allocF, U_->protectedAllocator.allocUD); 256 // reuse the allocator provided when the master state was created
257 } 257 return lua_newstate(U->protectedAllocator.allocF, U->protectedAllocator.allocUD);
258#endif // LUAJIT_FLAVOR() == 64 258 }
259 }
260 }
261 )
262 };
259 263
260 if (_L == nullptr) { 264 if (_L == nullptr) {
261 raise_luaL_error(from_, "luaG_newstate() failed while creating state; out of memory"); 265 raise_luaL_error(from_, "luaG_newstate() failed while creating state; out of memory");
@@ -355,20 +359,20 @@ lua_State* luaG_newstate(Universe* U_, SourceState from_, char const* libs_)
355 open1lib(_L, kLanesCoreLibName); 359 open1lib(_L, kLanesCoreLibName);
356 libs_ = nullptr; // done with libs 360 libs_ = nullptr; // done with libs
357 } else { 361 } else {
358#if LUAJIT_FLAVOR() != 0 // building against LuaJIT headers, always open jit 362 if constexpr (LUAJIT_FLAVOR() != 0) { // building against LuaJIT headers, always open jit
359 DEBUGSPEW_CODE(DebugSpew(U_) << "opening 'jit' library" << std::endl); 363 DEBUGSPEW_CODE(DebugSpew(U_) << "opening 'jit' library" << std::endl);
360 open1lib(_L, LUA_JITLIBNAME); 364 open1lib(_L, LUA_JITLIBNAME);
361#endif // LUAJIT_FLAVOR() 365 }
362 DEBUGSPEW_CODE(DebugSpew(U_) << "opening 'base' library" << std::endl); 366 DEBUGSPEW_CODE(DebugSpew(U_) << "opening 'base' library" << std::endl);
363#if LUA_VERSION_NUM >= 502 367 if constexpr (LUA_VERSION_NUM >= 502) {
364 // open base library the same way as in luaL_openlibs() 368 // open base library the same way as in luaL_openlibs()
365 luaL_requiref(_L, LUA_GNAME, luaopen_base, 1); 369 luaL_requiref(_L, LUA_GNAME, luaopen_base, 1);
366 lua_pop(_L, 1); 370 lua_pop(_L, 1);
367#else // LUA_VERSION_NUM 371 } else {
368 lua_pushcfunction(_L, luaopen_base); 372 lua_pushcfunction(_L, luaopen_base);
369 lua_pushstring(_L, ""); 373 lua_pushstring(_L, "");
370 lua_call(_L, 1, 0); 374 lua_call(_L, 1, 0);
371#endif // LUA_VERSION_NUM 375 }
372 } 376 }
373 } 377 }
374 STACK_CHECK(_L, 0); 378 STACK_CHECK(_L, 0);
diff --git a/src/tracker.cpp b/src/tracker.cpp
index 69cd90c..d42eb35 100644
--- a/src/tracker.cpp
+++ b/src/tracker.cpp
@@ -28,8 +28,6 @@ THE SOFTWARE.
28 28
29// ################################################################################################# 29// #################################################################################################
30 30
31#if HAVE_LANE_TRACKING()
32
33/* 31/*
34 * Add the lane to tracking chain; the ones still running at the end of the 32 * Add the lane to tracking chain; the ones still running at the end of the
35 * whole process will be cancelled. 33 * whole process will be cancelled.
@@ -104,5 +102,3 @@ void LaneTracker::tracking_add(Lane* lane_)
104 } 102 }
105 return lua_gettop(L_) - _top; // L_: 0 or 1 103 return lua_gettop(L_) - _top; // L_: 0 or 1
106} 104}
107
108#endif // HAVE_LANE_TRACKING()
diff --git a/src/tracker.h b/src/tracker.h
index 087598c..14926ec 100644
--- a/src/tracker.h
+++ b/src/tracker.h
@@ -2,11 +2,6 @@
2 2
3#include <mutex> 3#include <mutex>
4 4
5// Do we want to activate full lane tracking feature?
6#define HAVE_LANE_TRACKING() 1
7
8#if HAVE_LANE_TRACKING()
9
10class Lane; 5class Lane;
11struct lua_State; 6struct lua_State;
12 7
@@ -31,5 +26,3 @@ class LaneTracker
31 return trackingFirst != nullptr; 26 return trackingFirst != nullptr;
32 } 27 }
33}; 28};
34
35#endif // HAVE_LANE_TRACKING()
diff --git a/src/universe.h b/src/universe.h
index 97b613e..f5b31a3 100644
--- a/src/universe.h
+++ b/src/universe.h
@@ -150,9 +150,7 @@ class Universe
150 // used for timers (each lane will get a proxy to this) 150 // used for timers (each lane will get a proxy to this)
151 DeepPrelude* timerLinda{ nullptr }; 151 DeepPrelude* timerLinda{ nullptr };
152 152
153#if HAVE_LANE_TRACKING()
154 LaneTracker tracker; 153 LaneTracker tracker;
155#endif // HAVE_LANE_TRACKING()
156 154
157 // Protects modifying the selfdestruct chain 155 // Protects modifying the selfdestruct chain
158 std::mutex selfdestructMutex; 156 std::mutex selfdestructMutex;
diff --git a/tests/basic.lua b/tests/basic.lua
index 5a905e7..cfe6fd5 100644
--- a/tests/basic.lua
+++ b/tests/basic.lua
@@ -142,7 +142,8 @@ if st=="done" then
142end 142end
143assert(st=="running") 143assert(st=="running")
144 144
145lane9:cancel("count", 100) -- 0 timeout, 100 instructions count hook 145-- when running under luajit, the function is JIT-ed, and the instruction count isn't hit, so we need a different hook
146lane9:cancel(jit and "line" or "count", 100) -- 0 timeout, hook triggers cancelslation when reaching the specified count
146 147
147local t0= os.time() 148local t0= os.time()
148while os.time()-t0 < 5 do 149while os.time()-t0 < 5 do
@@ -151,7 +152,7 @@ while os.time()-t0 < 5 do
151 if st~="running" then break end 152 if st~="running" then break end
152end 153end
153PRINT(" "..st) 154PRINT(" "..st)
154assert(st == "cancelled") 155assert(st == "cancelled", "st is '" .. st .. "' instead of 'cancelled'")
155 156
156-- cancellation of lanes waiting on a linda 157-- cancellation of lanes waiting on a linda
157local limited = lanes.linda("limited") 158local limited = lanes.linda("limited")