aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-05-28 18:01:55 +0200
committerBenoit Germain <benoit.germain@ubisoft.com>2024-05-28 18:01:55 +0200
commit92944be3c3718095efa38e2a8db94844b1c7f739 (patch)
treee050cc4e493f6bc69be6b00b9b0562295ba5b123
parent8d7791f3eb1c5fc449490845254b59fdde30e9e0 (diff)
downloadlanes-92944be3c3718095efa38e2a8db94844b1c7f739.tar.gz
lanes-92944be3c3718095efa38e2a8db94844b1c7f739.tar.bz2
lanes-92944be3c3718095efa38e2a8db94844b1c7f739.zip
New Lanes finalizer API lanes.finally()
-rw-r--r--docs/index.html57
-rw-r--r--src/lanes.cpp1
-rw-r--r--src/lanes.lua1
-rw-r--r--src/state.cpp2
-rw-r--r--src/universe.cpp40
-rw-r--r--src/universe.h3
-rw-r--r--tests/basic.lua6
-rw-r--r--tests/finalizer.lua70
8 files changed, 126 insertions, 54 deletions
diff --git a/docs/index.html b/docs/index.html
index f5a074f..20dccfa 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -336,8 +336,8 @@
336 <tt>nil</tt>/<tt>"protected"</tt>/function 336 <tt>nil</tt>/<tt>"protected"</tt>/function
337 </td> 337 </td>
338 <td> 338 <td>
339 If <tt>nil</tt>, Lua states are created with <tt>lua_newstate()</tt> and reuse the allocator from the master state.<br /> 339 If <tt>nil</tt>, Lua states are created with <tt>lua_newstate()</tt> and reuse the allocator from the master state.<br/>
340 If <tt>"protected"</tt>, The default allocator obtained from <tt>lua_getallocf()</tt> in the master state is wrapped inside a critical section and used in all newly created states.<br /> 340 If <tt>"protected"</tt>, The default allocator obtained from <tt>lua_getallocf()</tt> in the master state is wrapped inside a critical section and used in all newly created states.<br/>
341 If a <tt>function</tt>, this function is called prior to creating the state. It should return a full userdata containing the following structure: 341 If a <tt>function</tt>, this function is called prior to creating the state. It should return a full userdata containing the following structure:
342 <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"> 342 <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%">
343 <tr> 343 <tr>
@@ -360,8 +360,8 @@
360 </td> 360 </td>
361 <td> 361 <td>
362 Controls which allocator is used for Lanes internal allocations (for keeper, linda and lane management). 362 Controls which allocator is used for Lanes internal allocations (for keeper, linda and lane management).
363 If <tt>"libc"</tt>, Lanes uses <tt>realloc</tt> and <tt>free</tt>.<br /> 363 If <tt>"libc"</tt>, Lanes uses <tt>realloc</tt> and <tt>free</tt>.<br/>
364 If <tt>"allocator"</tt>, Lanes uses whatever was obtained from the <tt>"allocator"</tt> setting.<br /> 364 If <tt>"allocator"</tt>, Lanes uses whatever was obtained from the <tt>"allocator"</tt> setting.<br/>
365 This option is mostly useful for embedders that want control all memory allocations, but have issues when Lanes tries to use the Lua State allocator for internal purposes (especially with LuaJIT). 365 This option is mostly useful for embedders that want control all memory allocations, but have issues when Lanes tries to use the Lua State allocator for internal purposes (especially with LuaJIT).
366 </td> 366 </td>
367 </tr> 367 </tr>
@@ -455,6 +455,27 @@
455 </tr> 455 </tr>
456</table> 456</table>
457 457
458<p>
459 It is also possible to install a function that will be called when Lanes is shutdown (that is, when the first state that required Lanes is closed).
460</p>
461
462<p>
463 <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%">
464 <tr>
465 <td>
466 <pre> lanes.finally(&lt;some function&gt;|nil)</pre>
467 </td>
468 </tr>
469 </table>
470</p>
471
472<p>
473 An error will be raised if you attempt to do this from inside a lane, or on bad arguments (non-function, or too many arguments).<br/>
474 Only the last registered finalizer is kept. It can be cleared by passing <tt>nil</tt> or nothing.<br/>
475 The installed function is called after all free-running lanes are terminated, but before lindas become unusable.<br/>
476 If an error occurs inside this finalizer, it is silently swallowed, since it happens only during state shutdown, and you can't do anything about it.
477</p>
478
458<hr/> 479<hr/>
459<h2 id="creation">Creation</h2> 480<h2 id="creation">Creation</h2>
460 481
@@ -682,8 +703,7 @@
682 </td> 703 </td>
683 <td>table</td> 704 <td>table</td>
684 <td> 705 <td>
685 Sets the globals table for the launched threads. This can be used for giving them constants. The key/value pairs of <tt>table</tt> are transfered in the lane globals after the libraries have been loaded and the modules required. 706 Sets the globals table for the launched threads. This can be used for giving them constants. The key/value pairs of <tt>table</tt> are transfered in the lane globals after the libraries have been loaded and the modules required.<br/>
686 <br />
687 The global values of different lanes are in no manner connected; modifying one will only affect the particular lane. 707 The global values of different lanes are in no manner connected; modifying one will only affect the particular lane.
688 </td> 708 </td>
689 </tr> 709 </tr>
@@ -696,8 +716,7 @@
696 Lists modules that have to be required in order to be able to transfer functions they exposed. Starting with Lanes 3.0-beta, non-Lua functions are no longer copied by recreating a C closure from a C pointer, but are <a href="#function_notes">searched in lookup tables</a>. 716 Lists modules that have to be required in order to be able to transfer functions they exposed. Starting with Lanes 3.0-beta, non-Lua functions are no longer copied by recreating a C closure from a C pointer, but are <a href="#function_notes">searched in lookup tables</a>.
697 These tables are built from the modules listed here. <tt>required</tt> must be a list of strings, each one being the name of a module to be required. Each module is required with <tt>require()</tt> before the lanes function is invoked. 717 These tables are built from the modules listed here. <tt>required</tt> must be a list of strings, each one being the name of a module to be required. Each module is required with <tt>require()</tt> before the lanes function is invoked.
698 So, from the required module's point of view, requiring it manually from inside the lane body or having it required this way doesn't change anything. From the lane body's point of view, the only difference is that a module not creating a global won't be accessible. 718 So, from the required module's point of view, requiring it manually from inside the lane body or having it required this way doesn't change anything. From the lane body's point of view, the only difference is that a module not creating a global won't be accessible.
699 Therefore, a lane body will also have to require a module manually, but this won't do anything more (see Lua's <tt>require</tt> documentation). 719 Therefore, a lane body will also have to require a module manually, but this won't do anything more (see Lua's <tt>require</tt> documentation). <br/>
700 <br />
701 ATTEMPTING TO TRANSFER A FUNCTION REGISTERED BY A MODULE NOT LISTED HERE WILL RAISE AN ERROR. 720 ATTEMPTING TO TRANSFER A FUNCTION REGISTERED BY A MODULE NOT LISTED HERE WILL RAISE AN ERROR.
702 </td> 721 </td>
703 </tr> 722 </tr>
@@ -707,8 +726,8 @@
707 </td> 726 </td>
708 <td>string</td> 727 <td>string</td>
709 <td> 728 <td>
710 Sets the error reporting mode. One of <tt>"minimal"</tt> (the default), <tt>"basic"</tt>, <tt>"extended"</tt>.<br /> 729 Sets the error reporting mode. One of <tt>"minimal"</tt> (the default), <tt>"basic"</tt>, <tt>"extended"</tt>.<br/>
711 <tt>"minimal"</tt> yields only the location of the error.<br /> 730 <tt>"minimal"</tt> yields only the location of the error.<br/>
712 The other 2 yield a full stack trace, with different amounts of data extracted from the debug infos. See <a href="#results">Results</a>. 731 The other 2 yield a full stack trace, with different amounts of data extracted from the debug infos. See <a href="#results">Results</a>.
713 </td> 732 </td>
714 </tr> 733 </tr>
@@ -737,10 +756,8 @@
737 <td>integer</td> 756 <td>integer</td>
738 <td> 757 <td>
739 The priority of lanes generated in the range -3..+3 (default is 0). 758 The priority of lanes generated in the range -3..+3 (default is 0).
740 These values are a mapping over the actual priority range of the underlying implementation. 759 These values are a mapping over the actual priority range of the underlying implementation.<br/>
741 <br /> 760 Implementation and dependability of priorities varies by platform. Especially Linux kernel 2.6 is not supporting priorities in user mode.<br/>
742 Implementation and dependability of priorities varies by platform. Especially Linux kernel 2.6 is not supporting priorities in user mode.
743 <br />
744 A lane can also change its own thread priority dynamically with <a href="#priority"><tt>lanes.set_thread_priority()</tt></a>. 761 A lane can also change its own thread priority dynamically with <a href="#priority"><tt>lanes.set_thread_priority()</tt></a>.
745 </td> 762 </td>
746 </tr> 763 </tr>
@@ -750,8 +767,7 @@
750 </td> 767 </td>
751 <td> table</td> 768 <td> table</td>
752 <td> 769 <td>
753 Specifying it when <code>libs_str</code> doesn't cause the <code>package</code> library to be loaded will generate an error. 770 Specifying it when <code>libs_str</code> doesn't cause the <code>package</code> library to be loaded will generate an error.<br/>
754 <br />
755 If not specified, the created lane will receive the current values of <tt>package</tt>. Only <tt>path</tt>, <tt>cpath</tt>, <tt>preload</tt> and <tt>loaders</tt> (Lua 5.1)/<tt>searchers</tt> (Lua 5.2) are transfered. 771 If not specified, the created lane will receive the current values of <tt>package</tt>. Only <tt>path</tt>, <tt>cpath</tt>, <tt>preload</tt> and <tt>loaders</tt> (Lua 5.1)/<tt>searchers</tt> (Lua 5.2) are transfered.
756 </td> 772 </td>
757 </tr> 773 </tr>
@@ -1754,12 +1770,9 @@ static MyDeepFactory g_MyDeepFactory;
1754</p> 1770</p>
1755 1771
1756<p> 1772<p>
1757 Deep userdata in transit inside keeper states (sent in a linda but not yet consumed) don't call <tt>deleteDeepObjectInternal</tt> and aren't considered by reference counting. The rationale is the following: 1773 Deep userdata in transit inside keeper states (sent in a linda but not yet consumed) don't call <tt>deleteDeepObjectInternal</tt> and aren't considered by reference counting. The rationale is the following:<br/>
1758 <br /> 1774 If some non-keeper state holds a deep userdata for some deep object, then even if the keeper collects its own deep userdata, it shouldn't be cleaned up since the refcount is not 0.<br/>
1759 If some non-keeper state holds a deep userdata for some deep object, then even if the keeper collects its own deep userdata, it shouldn't be cleaned up since the refcount is not 0. 1775 OTOH, if a keeper state holds the last deep userdata for some deep object, then no lane can do actual work with it. Deep userdata's <tt>factory()</tt> interface is never accessed from a keeper state.<br/>
1760 <br />
1761 OTOH, if a keeper state holds the last deep userdata for some deep object, then no lane can do actual work with it. Deep userdata's <tt>factory()</tt> interface is never accessed from a keeper state.
1762 <br />
1763 Therefore, Lanes can just call <tt>deleteDeepObjectInternal</tt> when the last non-keeper-held deep userdata is collected, as long as it doesn't do the same in a keeper state after that, since any remaining deep userdata in keeper states now hold stale pointers. 1776 Therefore, Lanes can just call <tt>deleteDeepObjectInternal</tt> when the last non-keeper-held deep userdata is collected, as long as it doesn't do the same in a keeper state after that, since any remaining deep userdata in keeper states now hold stale pointers.
1764</p> 1777</p>
1765 1778
diff --git a/src/lanes.cpp b/src/lanes.cpp
index 0ea0900..109fba4 100644
--- a/src/lanes.cpp
+++ b/src/lanes.cpp
@@ -598,6 +598,7 @@ namespace {
598 { "set_thread_affinity", LG_set_thread_affinity }, 598 { "set_thread_affinity", LG_set_thread_affinity },
599 { "nameof", luaG_nameof }, 599 { "nameof", luaG_nameof },
600 { "register", LG_register }, 600 { "register", LG_register },
601 { Universe::kFinally, Universe::InitializeFinalizer },
601 { "set_singlethreaded", LG_set_singlethreaded }, 602 { "set_singlethreaded", LG_set_singlethreaded },
602 { nullptr, nullptr } 603 { nullptr, nullptr }
603 }; 604 };
diff --git a/src/lanes.lua b/src/lanes.lua
index e75e840..6616667 100644
--- a/src/lanes.lua
+++ b/src/lanes.lua
@@ -799,6 +799,7 @@ local configure = function(settings_)
799 lanes.null = core.null 799 lanes.null = core.null
800 lanes.require = core.require 800 lanes.require = core.require
801 lanes.register = core.register 801 lanes.register = core.register
802 lanes.finally = core.finally
802 lanes.set_singlethreaded = core.set_singlethreaded 803 lanes.set_singlethreaded = core.set_singlethreaded
803 lanes.set_thread_affinity = core.set_thread_affinity 804 lanes.set_thread_affinity = core.set_thread_affinity
804 lanes.set_thread_priority = core.set_thread_priority 805 lanes.set_thread_priority = core.set_thread_priority
diff --git a/src/state.cpp b/src/state.cpp
index 5a1d2fb..d9f5499 100644
--- a/src/state.cpp
+++ b/src/state.cpp
@@ -209,7 +209,7 @@ static void copy_one_time_settings(Universe* U_, SourceState L1_, DestState L2_)
209 209
210// ################################################################################################# 210// #################################################################################################
211 211
212static constexpr char const* kOnStateCreate{ "on_state_create" }; 212static constexpr char const* kOnStateCreate{ "on_state_create" }; // update lanes.lua if the name changes!
213 213
214void InitializeOnStateCreate(Universe* U_, lua_State* L_) 214void InitializeOnStateCreate(Universe* U_, lua_State* L_)
215{ 215{
diff --git a/src/universe.cpp b/src/universe.cpp
index 5794048..3271e70 100644
--- a/src/universe.cpp
+++ b/src/universe.cpp
@@ -197,6 +197,28 @@ void Universe::initializeAllocatorFunction(lua_State* L_)
197 197
198// ################################################################################################# 198// #################################################################################################
199 199
200// should be called ONLY from the state that created the universe
201int Universe::InitializeFinalizer(lua_State* L_)
202{
203 luaL_argcheck(L_, lua_gettop(L_) <= 1, 1, "too many arguments"); // L_: f?
204 lua_settop(L_, 1); // L_: f|nil
205 luaL_argcheck(L_, lua_isnoneornil(L_, 1) || lua_isfunction(L_, 1), 1, "finalizer should be a function");
206
207 // make sure we are only called from the Master Lua State!
208 kUniverseFullRegKey.pushValue(L_); // L_: f U
209 if (lua_type_as_enum(L_, -1) != LuaType::USERDATA) {
210 raise_luaL_error(L_, "lanes.%s called from inside a lane", kFinally);
211 }
212 lua_pop(L_, 1); // L_: f
213 STACK_GROW(L_, 3);
214 // _R[kFinalizerRegKey] = f
215 kFinalizerRegKey.setValue(L_, [](lua_State* L_) { lua_insert(L_, -2); }); // L_:
216 // no need to adjust the stack, Lua does this for us
217 return 0;
218}
219
220// #################################################################################################
221
200/* 222/*
201 * Initialize keeper states 223 * Initialize keeper states
202 * 224 *
@@ -373,9 +395,22 @@ int universe_gc(lua_State* L_)
373{ 395{
374 lua_Duration const _shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) }; 396 lua_Duration const _shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) };
375 std::string_view const _op_string{ lua_tostringview(L_, lua_upvalueindex(2)) }; 397 std::string_view const _op_string{ lua_tostringview(L_, lua_upvalueindex(2)) };
376 Universe* const _U{ lua_tofulluserdata<Universe>(L_, 1) }; 398 STACK_CHECK_START_ABS(L_, 1);
399 Universe* const _U{ lua_tofulluserdata<Universe>(L_, 1) }; // L_: U
377 _U->terminateFreeRunningLanes(L_, _shutdown_timeout, which_cancel_op(_op_string)); 400 _U->terminateFreeRunningLanes(L_, _shutdown_timeout, which_cancel_op(_op_string));
378 401
402 // invoke the function installed by lanes.finally()
403 kFinalizerRegKey.pushValue(L_); // L_: U finalizer|nil
404 if (!lua_isnil(L_, -1)) {
405 lua_pcall(L_, 0, 0, 0); // L_: U
406 // discard any error that might have occured
407 lua_settop(L_, 1);
408 } else {
409 lua_pop(L_, 1); // L_: U
410 }
411 // in case of error, the message is pushed on the stack
412 STACK_CHECK(L_, 1);
413
379 // no need to mutex-protect this as all threads in the universe are gone at that point 414 // no need to mutex-protect this as all threads in the universe are gone at that point
380 if (_U->timerLinda != nullptr) { // test in case some early internal error prevented Lanes from creating the deep timer 415 if (_U->timerLinda != nullptr) { // test in case some early internal error prevented Lanes from creating the deep timer
381 [[maybe_unused]] int const _prev_ref_count{ _U->timerLinda->refcount.fetch_sub(1, std::memory_order_relaxed) }; 416 [[maybe_unused]] int const _prev_ref_count{ _U->timerLinda->refcount.fetch_sub(1, std::memory_order_relaxed) };
@@ -389,6 +424,9 @@ int universe_gc(lua_State* L_)
389 // remove the protected allocator, if any 424 // remove the protected allocator, if any
390 _U->protectedAllocator.removeFrom(L_); 425 _U->protectedAllocator.removeFrom(L_);
391 426
427 // no longer found in the registry
428 kUniverseFullRegKey.setValue(L_, [](lua_State* L_) { lua_pushnil(L_); });
429 kUniverseLightRegKey.setValue(L_, [](lua_State* L_) { lua_pushnil(L_); });
392 _U->Universe::~Universe(); 430 _U->Universe::~Universe();
393 431
394 return 0; 432 return 0;
diff --git a/src/universe.h b/src/universe.h
index f5b31a3..4be6a9a 100644
--- a/src/universe.h
+++ b/src/universe.h
@@ -120,6 +120,8 @@ static constexpr RegistryUniqueKey kUniverseLightRegKey{ 0x48BBE9CEAB0BA04Full }
120class Universe 120class Universe
121{ 121{
122 public: 122 public:
123 static constexpr char const* kFinally{ "finally" }; // update lanes.lua if the name changes!
124
123#ifdef PLATFORM_LINUX 125#ifdef PLATFORM_LINUX
124 // Linux needs to check, whether it's been run as root 126 // Linux needs to check, whether it's been run as root
125 bool const sudo{ geteuid() == 0 }; 127 bool const sudo{ geteuid() == 0 };
@@ -180,6 +182,7 @@ class Universe
180 182
181 void closeKeepers(); 183 void closeKeepers();
182 void initializeAllocatorFunction(lua_State* L_); 184 void initializeAllocatorFunction(lua_State* L_);
185 static int InitializeFinalizer(lua_State* L_);
183 void initializeKeepers(lua_State* L_); 186 void initializeKeepers(lua_State* L_);
184 void terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_); 187 void terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_);
185}; 188};
diff --git a/tests/basic.lua b/tests/basic.lua
index cfe6fd5..ab8a080 100644
--- a/tests/basic.lua
+++ b/tests/basic.lua
@@ -485,6 +485,6 @@ end
485 485
486local nameof_type, nameof_name = lanes.nameof(print) 486local nameof_type, nameof_name = lanes.nameof(print)
487PRINT("name of " .. nameof_type .. " print = '" .. nameof_name .. "'") 487PRINT("name of " .. nameof_type .. " print = '" .. nameof_name .. "'")
488 488-- install a finalizer that gets called upon Lanes's internal Universe is GCed.
489-- 489-- that way, we print our message after anything that can be output by lanes that are still running at that point
490io.stderr:write "Done! :)\n" 490lanes.finally(function() io.stderr:write "\n=======================================\nTEST OK\n" end)
diff --git a/tests/finalizer.lua b/tests/finalizer.lua
index 060e0a6..2acc39d 100644
--- a/tests/finalizer.lua
+++ b/tests/finalizer.lua
@@ -10,20 +10,22 @@
10 10
11local lanes = require "lanes" 11local lanes = require "lanes"
12lanes.configure{with_timers=false} 12lanes.configure{with_timers=false}
13local finally = lanes.finally
13 14
14local FN= "finalizer-test.tmp" 15local FN = "finalizer-test.tmp"
15 16
16local cleanup 17local cleanup
17 18
18local which= os.time() % 2 -- 0/1 19local function lane(error_)
19
20local function lane()
21
22 set_finalizer(cleanup) 20 set_finalizer(cleanup)
23 21
24 local f,err= io.open(FN,"w") 22 local st,err = pcall(finally, cleanup) -- should cause an error because called from a lane
23 assert(not st, "finally() should have thrown an error")
24 io.stderr:write("finally() raised error '", err, "'\n")
25
26 local f,err = io.open(FN,"w")
25 if not f then 27 if not f then
26 error( "Could not create "..FN..": "..err ) 28 error( "Could not create "..FN..": "..err)
27 end 29 end
28 30
29 f:write( "Test file that should get removed." ) 31 f:write( "Test file that should get removed." )
@@ -32,11 +34,10 @@ local function lane()
32 -- don't forget to close the file immediately, else we won't be able to delete it until f is collected 34 -- don't forget to close the file immediately, else we won't be able to delete it until f is collected
33 f:close() 35 f:close()
34 36
35 if which==0 then 37 if error_ then
36 print "you loose" 38 io.stderr:write("Raising ", tostring(error_), "\n")
37 error("aa") -- exception here; the value needs NOT be a string 39 error(error_, 0) -- exception here; the value needs NOT be a string
38 end 40 end
39
40 -- no exception 41 -- no exception
41end 42end
42 43
@@ -44,8 +45,8 @@ end
44-- This is called at the end of the lane; whether succesful or not. 45-- This is called at the end of the lane; whether succesful or not.
45-- Gets the 'error()' parameter as parameter ('nil' if normal return). 46-- Gets the 'error()' parameter as parameter ('nil' if normal return).
46-- 47--
47cleanup= function(err) 48cleanup = function(err)
48 49 io.stderr:write "------------------------ In Worker Finalizer -----------------------\n"
49 -- An error in finalizer will override an error (or success) in the main 50 -- An error in finalizer will override an error (or success) in the main
50 -- chunk. 51 -- chunk.
51 -- 52 --
@@ -57,30 +58,45 @@ cleanup= function(err)
57 io.stderr:write( "Cleanup after normal return\n" ) 58 io.stderr:write( "Cleanup after normal return\n" )
58 end 59 end
59 60
60 local _,err2= os.remove(FN) 61 local _,err2 = os.remove(FN)
61 print( "file removal result: ", tostring( err2)) 62 io.stderr:write( "file removal result: ", tostring(err2), "\n")
62 assert(not err2) -- if this fails, it will be shown in the calling script 63 assert(not err2) -- if this fails, it will be shown in the calling script
63 -- as an error from the lane itself 64 -- as an error from the lane itself
64 65
65 io.stderr:write( "Removed file "..FN.."\n" ) 66 io.stderr:write( "Removed file "..FN.."\n" )
66end 67end
67 68
68local lgen = lanes.gen("*", lane) 69-- we need error_trace_level above "minimal" to get a stack trace out of h:join()
70local lgen = lanes.gen("*", {error_trace_level = "basic"}, lane)
71
72local do_test = function(error_)
69 73
70io.stderr:write "Launching the lane!\n" 74 io.stderr:write "======================== Launching the lane! =======================\n"
71 75
72local h= lgen() 76 local h = lgen(error_)
73 77
74local _,err,stack= h:join() -- wait for the lane (no automatic error propagation) 78 local _,err,stack = h:join() -- wait for the lane (no automatic error propagation)
75if err then 79 if err then
76 assert(stack) 80 assert(stack, "no stack trace on error, check 'error_trace_level'")
77 io.stderr:write( "Lane error: "..tostring(err).."\n" ) 81 io.stderr:write( "Lane error: "..tostring(err).."\n" )
78 io.stderr:write( "\t", table.concat(stack,"\t\n"), "\n" ) 82 io.stderr:write( "\t", table.concat(stack,"\t\n"), "\n" )
83 end
79end 84end
80 85
81local f= io.open(FN,"r") 86do_test(nil)
82if f then 87do_test("An error")
83 error( "CLEANUP DID NOT WORK: "..FN.." still exists!" ) 88
89local on_exit = function()
90 finally(nil)
91 io.stderr:write "=========================In Lanes Finalizer! =======================\n"
92 local f = io.open(FN,"r")
93 if f then
94 error( "CLEANUP DID NOT WORK: "..FN.." still exists!" )
95 else
96 io.stderr:write(FN .. " was successfully removed\n")
97 end
98 io.stderr:write "Finished!\n"
84end 99end
85 100
86io.stderr:write "Finished!\n" 101-- this function is called after script exit, when the state is closed
102lanes.finally(on_exit)