├── .gitignore ├── README.md ├── finally-scm-0.rockspec ├── finally.c ├── finally.lua └── test.lua /.gitignore: -------------------------------------------------------------------------------- 1 | # docco output directory 2 | #docs/ 3 | 4 | # local files for remembering stuff 5 | HISTO 6 | TODO 7 | 8 | # temporary files 9 | .*.swp 10 | 11 | # object files 12 | *.o 13 | *.obj 14 | 15 | # libraries 16 | *.so 17 | *.dll 18 | *.a 19 | *.lib 20 | *.exp 21 | 22 | # executables 23 | *.exe 24 | 25 | # precompiled Lua bytecode files 26 | *.luac 27 | 28 | # LuaRocks packages 29 | *.rock 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Finally -- Deterministic Cleanup of Resources in Lua # 2 | 3 | ## Introduction ## 4 | 5 | Lua's garbage collector and `__gc` metamethods can handle arbitrary 6 | resources in a reliable, yet undeterministic way. Some resources 7 | however need to be reclaimed as soon as possible, even if an error is 8 | raised while using such a resource. Other languages provide dedicated 9 | language features for this situation (`finally`, `using`, or 10 | scope-based destruction of objects -- `RAII`). On Lua you can use this 11 | **finally** module. 12 | 13 | 14 | ## Getting Started ## 15 | 16 | The interface was proposed in a [lua-l mailing list thread][1]: The 17 | `finally` function takes two Lua functions as arguments, calls the 18 | first, and then the second function even if the first function call 19 | raises an error (the error is passed as an argument to the second 20 | function call in this case): 21 | 22 | local f1, f2 23 | local same = finally( function() 24 | f1 = assert( io.open( "filename1.txt", "r" ) ) 25 | f2 = assert( io.open( "filename2.txt", "r" ) ) 26 | return f1:read( "*a" ) == f2:read( "*a" ) 27 | end, function( e ) 28 | if e then print( "there was an error!" ) end 29 | if f2 then f2:close() end 30 | if f1 then f1:close() end 31 | end ) 32 | 33 | The `finally` function call returns the results of the first function 34 | (or re-raises its error) unless an error happens during execution of 35 | the second function, in which case previous results/errors are lost. 36 | This (and the fact that an interrupted cleanup function could leak 37 | important resources) is the reason why any code that may raise errors 38 | should be avoided in the cleanup function. Unfortunately Lua allocates 39 | some memory implicitly when running Lua code (e.g. for function call 40 | frames or the Lua stack), which can cause memory allocation errors or 41 | errors in unrelated `__gc` metamethods to be raised. The `finally` 42 | function implementation in this module gives you the chance of writing 43 | cleanup code that can never be interrupted by calling the cleanup 44 | function in a coroutine (preallocated before the first function call) 45 | with reserved call frames and Lua stack slots. Unless you allocate new 46 | Lua values or raise errors explicitly in your cleanup function (or 47 | write faulty Lua code), you are fine. 48 | 49 | The defaults should be good enough for most cleanup code, but you can 50 | pass the number of reserved stack slots (default 100) as the third and 51 | the number of preallocated stack frames (default 10) as the fourth 52 | argument to `finally`. To ensure that the parameters are high enough 53 | for your cleanup code, you can pass a `true`ish value as the fifth 54 | argument to `finally` during development/testing. This will cause 55 | *any* memory allocation by Lua during the execution of the cleanup 56 | function to raise an error. 57 | 58 | And that's all. 59 | 60 | [1]: http://lua-users.org/lists/lua-l/2015-11/msg00270.html 61 | [2]: http://lua-users.org/lists/lua-l/2015-04/msg00423.html 62 | 63 | 64 | ## Quirks/Gotchas ## 65 | 66 | There are many ways to allocate memory in Lua code inadvertently, and 67 | thus to risk memory allocation errors or errors in `__gc` metamethods 68 | while running the cleanup function. What you should definitely avoid 69 | is table literals, writes to non-existing table fields, new strings 70 | (e.g. using string concatenation, by implicit coercions or `tostring` 71 | calls, or some C API functions, e.g. `luaL_error` -- string literals 72 | in Lua code are fine because they are allocated when the chunk is 73 | compiled), new Lua functions, coroutines, or userdata. 74 | 75 | This module works for Lua 5.1 (including LuaJIT) up to Lua 5.3, but 76 | the code for Lua 5.1 uses recursive Lua function calls instead of C 77 | function calls to preallocate call frames and stack slots. There is a 78 | separate limit for C function calls that could cause an error later in 79 | the cleanup function, but you should easily be able to rule this out 80 | during testing. You also cannot explicitly set the number of stack 81 | slots to preallocate. For each call frame approximately 15 extra stack 82 | slots are available. However, the number of stack slots or call frames 83 | needed by a JIT-compiled Lua function might differ from the uncompiled 84 | version of the same function, and JIT-compilation itself may happen at 85 | any time and cause memory allocations. Since the LuaJIT code is 86 | written in assembler, it is hard to figure out where exactly memory 87 | might be allocated. So when using LuaJIT you are basically on your 88 | own! 89 | 90 | 91 | ## Contact ## 92 | 93 | Philipp Janda, siffiejoe(a)gmx.net 94 | 95 | Comments and feedback are always welcome. 96 | 97 | 98 | ## License ## 99 | 100 | **finally** is *copyrighted free software* distributed under the MIT 101 | license (the same license as Lua 5.1). The full license text follows: 102 | 103 | finally (c) 2015 Philipp Janda 104 | 105 | Permission is hereby granted, free of charge, to any person obtaining 106 | a copy of this software and associated documentation files (the 107 | "Software"), to deal in the Software without restriction, including 108 | without limitation the rights to use, copy, modify, merge, publish, 109 | distribute, sublicense, and/or sell copies of the Software, and to 110 | permit persons to whom the Software is furnished to do so, subject to 111 | the following conditions: 112 | 113 | The above copyright notice and this permission notice shall be 114 | included in all copies or substantial portions of the Software. 115 | 116 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 117 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 118 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 119 | IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY 120 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 121 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 122 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 123 | 124 | -------------------------------------------------------------------------------- /finally-scm-0.rockspec: -------------------------------------------------------------------------------- 1 | package="finally" 2 | version="scm-0" 3 | 4 | source = { 5 | url = "git://github.com/siffiejoe/lua-finally.git", 6 | } 7 | 8 | description = { 9 | summary = "Provides a 'finally' function.", 10 | detailed = [[ 11 | 'finally' in other languages is used to ensure execution of a 12 | certain piece of code even if an error/exception is thrown 13 | (usually for deterministic cleanup of resources). This module 14 | provides a function that emulates the 'finally' language 15 | feature of those other programming languages in Lua. 16 | ]], 17 | homepage = "https://github.com/siffiejoe/lua-finally/", 18 | license = "MIT", 19 | } 20 | 21 | dependencies = { 22 | "lua >= 5.1, < 5.5" 23 | } 24 | 25 | build = { 26 | type = "builtin", 27 | modules = { 28 | finally = "finally.c", 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /finally.c: -------------------------------------------------------------------------------- 1 | /* Lua module providing a `finally` function for deterministic 2 | * resource cleanup. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | /* Lua version compatibility */ 11 | #if LUA_VERSION_NUM == 501 /* Lua 5.1 / LuaJIT */ 12 | 13 | #define lua_resume( L2, L, na, nr ) \ 14 | ((void)(L), (void)(nr), lua_resume( L2, na )) 15 | 16 | #elif LUA_VERSION_NUM == 502 /* Lua 5.2 */ 17 | 18 | #define lua_resume( L2, L, na, nr ) \ 19 | ((void)(nr), lua_resume( L2, L, na )) 20 | 21 | typedef int lua_KContext; 22 | 23 | #define LUA_KFUNCTION( _name ) \ 24 | static int (_name)( lua_State* L, int status, lua_KContext ctx ); \ 25 | static int (_name ## _52)( lua_State* L ) { \ 26 | lua_KContext ctx; \ 27 | int status = lua_getctx( L, &ctx ); \ 28 | return (_name)( L, status, ctx ); \ 29 | } \ 30 | static int (_name)( lua_State* L, int status, lua_KContext ctx ) 31 | 32 | #define lua_callk( L, na, nr, ctx, cont ) \ 33 | lua_callk( L, na, nr, ctx, cont ## _52 ) 34 | 35 | #ifdef lua_call 36 | #undef lua_call 37 | #define lua_call( L, na, nr ) \ 38 | (lua_callk)( L, na, nr, 0, NULL ) 39 | #endif 40 | 41 | #elif LUA_VERSION_NUM == 503 42 | 43 | #define lua_resume( L2, L, na, nr ) \ 44 | ((void)(nr), lua_resume( L2, L, na )) 45 | 46 | #define LUA_KFUNCTION( _name ) \ 47 | static int (_name)( lua_State* L, int status, lua_KContext ctx ) 48 | 49 | #elif LUA_VERSION_NUM == 504 /* Lua 5.3/5.4 */ 50 | 51 | #define LUA_KFUNCTION( _name ) \ 52 | static int (_name)( lua_State* L, int status, lua_KContext ctx ) 53 | 54 | #else 55 | 56 | #error unsupported Lua version 57 | 58 | #endif /* LUA_VERSION_NUM */ 59 | 60 | 61 | /* struct to save Lua allocator */ 62 | typedef struct { 63 | lua_Alloc alloc; 64 | void* ud; 65 | } alloc_state; 66 | 67 | 68 | /* simulate out of memory for choosing the proper preallocation 69 | * settings for the cleanup function */ 70 | static void* alloc_fail( void* ud, void* ptr, size_t osize, 71 | size_t nsize ) { 72 | alloc_state* as = ud; 73 | if( nsize > 0 && (ptr == NULL || osize < nsize) ) { 74 | #if 0 75 | fprintf( stderr, "[alloc] ptr: %p, osize: %zu, nsize: %zu\n", 76 | ptr, osize, nsize ); 77 | #endif 78 | return NULL; 79 | } 80 | return as->alloc( as->ud, ptr, osize, nsize ); 81 | } 82 | 83 | 84 | #if LUA_VERSION_NUM > 501 /* Lua 5.2+ */ 85 | 86 | /* preallocate stack frames, preallocate stack slots, change Lua 87 | * allocator (if in debug mode), and call the cleanup function */ 88 | LUA_KFUNCTION( preallocatek ) { 89 | lua_Integer calls = 0, stack = 0; 90 | (void)status; 91 | switch( ctx ) { 92 | case 0: 93 | calls = lua_tointeger( L, 2 ); 94 | stack = lua_tointeger( L, 3 ); 95 | if( stack ) 96 | luaL_checkstack( L, (int)stack, "preallocate" ); 97 | if( calls > 0 ) { 98 | lua_pushvalue( L, 1 ); 99 | lua_pushvalue( L, 1 ); 100 | lua_pushinteger( L, calls-1 ); 101 | lua_callk( L, 2, LUA_MULTRET, 1, preallocatek ); 102 | case 1: 103 | if( lua_isfunction( L, 5 ) ) { 104 | alloc_state* as = lua_touserdata( L, 4 ); 105 | if( as ) 106 | lua_setallocf( L, alloc_fail, as ); 107 | lua_call( L, lua_gettop( L )-5, 0 ); 108 | return 0; 109 | } 110 | } else 111 | return lua_yield( L, 0 ); 112 | } 113 | return lua_gettop( L ) > 2; 114 | } 115 | 116 | static int preallocate( lua_State* L ) { 117 | return preallocatek( L, 0, 0 ); 118 | } 119 | 120 | #else /* Lua 5.1 */ 121 | 122 | static int lsetalloc( lua_State* L ) { 123 | alloc_state* as = lua_touserdata( L, 1 ); 124 | if( as ) 125 | lua_setallocf( L, alloc_fail, as ); 126 | return 0; 127 | } 128 | 129 | static int lyield( lua_State* L ) { 130 | return lua_yield( L, lua_gettop( L ) ); 131 | } 132 | 133 | static char const preallocate_code[] = 134 | "local setalloc, yield = ...\n" 135 | "local function postprocess( as, cleanup, ... )\n" 136 | " if cleanup then\n" 137 | " if as then setalloc( as ) end\n" 138 | " cleanup( ... )\n" 139 | " else\n" 140 | " return ...\n" 141 | " end\n" 142 | "end\n" 143 | "return function( prealloc, calls, slots, as, f )\n" 144 | " local _1,_2,_3,_4,_5,_6,_7,_8,_9,_10\n" 145 | " if calls > 0 then\n" 146 | " return postprocess( as, f, prealloc( prealloc, calls-1 ) )\n" 147 | " else\n" 148 | " return yield()\n" 149 | " end\n" 150 | "end\n"; 151 | 152 | static void push_lua_prealloc( lua_State* L ) { 153 | lua_pushlightuserdata( L, (void*)preallocate_code ); 154 | lua_rawget( L, LUA_REGISTRYINDEX ); 155 | if( lua_type( L, -1 ) != LUA_TFUNCTION ) { 156 | lua_pop( L, 1 ); 157 | if( luaL_loadbuffer( L, preallocate_code, 158 | sizeof( preallocate_code )-1, 159 | "=(embedded)" ) ) 160 | lua_error( L ); 161 | lua_pushcfunction( L, lsetalloc ); 162 | lua_pushcfunction( L, lyield ); 163 | lua_call( L, 2, 1 ); 164 | lua_pushlightuserdata( L, (void*)preallocate_code ); 165 | lua_pushvalue( L, -2 ); 166 | lua_rawset( L, LUA_REGISTRYINDEX ); 167 | } 168 | } 169 | 170 | #endif 171 | 172 | 173 | static int lfinally( lua_State* L ) { 174 | lua_Integer minstack = 0, mincalls = 0; 175 | int debug = 0, status = 0, status2 = 0, nret = 0; 176 | alloc_state as = { 0, 0 }; 177 | lua_State* L2 = NULL; 178 | luaL_checktype( L, 1, LUA_TFUNCTION ); 179 | luaL_checktype( L, 2, LUA_TFUNCTION ); 180 | minstack = luaL_optinteger( L, 3, 100 ); 181 | luaL_argcheck( L, minstack > 0, 3, 182 | "invalid number of reserved stack slots" ); 183 | mincalls = luaL_optinteger( L, 4, 10 ); 184 | luaL_argcheck( L, mincalls > 0, 4, 185 | "invalid minimum number of call frames" ); 186 | debug = lua_toboolean( L, 5 ); 187 | lua_settop( L, 2 ); 188 | /* prepare thread to run the cleanup function */ 189 | L2 = lua_newthread( L ); 190 | #if LUA_VERSION_NUM > 501 191 | mincalls += 1; /* stack frame(s) used internally */ 192 | lua_pushcfunction( L2, preallocate ); 193 | #else 194 | /* Lua 5.1 doesn't support yieldable C functions, so we use a Lua 195 | * closure to preallocate stack and call frames. This only allocates 196 | * *Lua* call frames though, not C call frames, so you could hit a 197 | * limit there while executing the cleanup function later! 198 | * Also we can't preallocate a variable amount of stack slots so 199 | * that they won't cause a panic on memory allocation failure *and* 200 | * survive a garbage collection cycle during the main function. 201 | * The above mentioned Lua closure allocates some extra locals, and 202 | * if you need more you can increase the number of reserved calls 203 | * (each extra call will give you about 15 slots). */ 204 | push_lua_prealloc( L2 ); 205 | #endif 206 | lua_pushvalue( L2, -1 ); 207 | lua_pushinteger( L2, mincalls ); 208 | lua_pushinteger( L2, minstack ); 209 | if( debug ) { 210 | as.alloc = lua_getallocf( L, &as.ud ); 211 | lua_pushlightuserdata( L2, &as ); 212 | } else 213 | lua_pushnil( L2 ); 214 | lua_pushvalue( L, 2 ); /* clean up function */ 215 | lua_xmove( L, L2, 1 ); 216 | lua_replace( L, 2 ); /* L: [ function | thread ] */ 217 | /* preallocate stack frames and stack slots for cleanup function, 218 | * and then yield ... */ 219 | status = lua_resume( L2, L, 5, &nret ); 220 | if( status != LUA_YIELD ) { /* must be an error */ 221 | lua_xmove( L2, L, 1 ); 222 | lua_error( L ); 223 | } 224 | /* run main function */ 225 | lua_pushvalue( L, 1 ); 226 | status = lua_pcall( L, 0, LUA_MULTRET, 0 ); 227 | /* run cleanup function in the other thread by resuming yielded 228 | * coroutine */ 229 | lua_settop( L2, 0 ); 230 | if( status != 0 ) { /* pass error to cleanup function */ 231 | lua_pushvalue( L, -1 ); /* duplicate error message */ 232 | lua_xmove( L, L2, 1 ); /* move to thread */ 233 | } 234 | status2 = lua_resume( L2, L, !!status, &nret ); 235 | if( debug ) /* reset memory allocation function */ 236 | lua_setallocf( L, as.alloc, as.ud ); 237 | if( status2 == LUA_YIELD ) { 238 | /* cleanup function shouldn't yield; can only happen in Lua 5.1 */ 239 | lua_settop( L, 0 ); /* make room */ 240 | lua_pushvalue( L, lua_upvalueindex( 1 ) ); 241 | lua_error( L ); 242 | } else if( status2 != 0 ) { /* error in cleanup function */ 243 | lua_settop( L, 0 ); /* make room */ 244 | lua_xmove( L2, L, 1 ); /* error message from other thread */ 245 | lua_error( L ); 246 | } 247 | if( status != 0 ) 248 | lua_error( L ); /* re-raise error from main function */ 249 | return lua_gettop( L )-2; /* return results from main function */ 250 | } 251 | 252 | 253 | #ifndef EXPORT 254 | # define EXPORT extern 255 | #endif 256 | 257 | EXPORT int luaopen_finally( lua_State* L ) { 258 | lua_pushliteral( L, "'finally' cleanup function shouldn't yield" ); 259 | lua_pushcclosure( L, lfinally, 1 ); 260 | return 1; 261 | } 262 | 263 | -------------------------------------------------------------------------------- /finally.lua: -------------------------------------------------------------------------------- 1 | local pcall, error = pcall, error 2 | 3 | local function _finally( after, ok, ... ) 4 | if ok then 5 | after() 6 | return ... 7 | else 8 | after( (...) ) 9 | error( (...), 0 ) 10 | end 11 | end 12 | 13 | local function finally( main, after ) 14 | return _finally( after, pcall( main ) ) 15 | end 16 | 17 | return finally 18 | 19 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | if arg[ 1 ] ~= "L" then 3 | package.path = "" 4 | end 5 | 6 | local finally = require( "finally" ) 7 | 8 | 9 | local function create_a( raise ) 10 | if raise then error( "error in create a" ) end 11 | local name 12 | local o = { 13 | close = function() 14 | print( "-", "close a", name ) 15 | end 16 | } 17 | name = tostring( o ) 18 | print( "+", "create a", name ) 19 | return o 20 | end 21 | 22 | local function create_b( raise ) 23 | if raise then error( "error in create b" ) end 24 | local name 25 | local o = { 26 | clear = function() 27 | print( "-", "clear b", name ) 28 | end 29 | } 30 | name = tostring( o ) 31 | print( "+", "create b", name ) 32 | return o 33 | end 34 | 35 | local function create_c( raise ) 36 | if raise then error( "error in create c" ) end 37 | local name 38 | local o = { 39 | destroy = function() 40 | print( "-", "destroy c", name ) 41 | end 42 | } 43 | name = tostring( o ) 44 | print( "+", "create c", name ) 45 | return o 46 | end 47 | 48 | 49 | local function wastememory( n ) 50 | local a, b, c, d, e, f, g, h, i, j, k, l, m 51 | if n <= 0 then 52 | return 0 53 | else 54 | return 1+wastememory( n-1 ) 55 | end 56 | end 57 | 58 | 59 | local function main1( r1, r2, r3, r4, stack, calls, dbg ) 60 | print( r1, r2, r3, r4, stack, calls, dbg ) 61 | local a, b, c 62 | return finally( function() 63 | a = create_a( r1 ) 64 | b = create_b( r2 ) 65 | c = create_c( r3 ) 66 | print( "ok" ) 67 | return 1, 2, 3 68 | end, function( ... ) 69 | print( "error?", ... ) 70 | wastememory( 5 ) 71 | if c then c:destroy() end 72 | if r4 then error( "error in finally cleanup function" ) end 73 | if b then b:clear() end 74 | if a then a:close() end 75 | end, stack, calls, dbg ) 76 | end 77 | 78 | 79 | if _VERSION == "Lua 5.1" then 80 | local xpcall, unpack, select = xpcall, unpack, select 81 | function _G.xpcall( f, msgh, ... ) 82 | local args, n = { ... }, select( '#', ... ) 83 | return xpcall( function() return f( unpack( args, 1, n ) ) end, msgh ) 84 | end 85 | end 86 | 87 | 88 | local x = ("="):rep( 70 ) 89 | local function ___() print( x ) end 90 | local tb = debug.traceback 91 | print( xpcall( main1, tb, false, false, false, false, 80, 6, true ) ) 92 | ___() 93 | print( xpcall( main1, tb, false, false, false, false, nil, nil, true ) ) 94 | ___() 95 | print( xpcall( main1, tb, false, false, true, false, nil, nil, true ) ) 96 | ___() 97 | print( xpcall( main1, tb, false, true, false, false, nil, nil, true ) ) 98 | ___() 99 | print( xpcall( main1, tb, true, false, false, false, nil, nil, true ) ) 100 | ___() 101 | print( xpcall( main1, tb, false, false, false, true ) ) 102 | ___() 103 | print( xpcall( main1, tb, false, false, true, true ) ) 104 | ___() 105 | print( xpcall( main1, tb, false, false, false, false, 30, 6, true ) ) 106 | ___() 107 | print( xpcall( main1, tb, false, false, false, false, 80, 3, true ) ) 108 | ___() 109 | print( xpcall( main1, tb, false, false, false, false, 1000001, 6, true ) ) 110 | 111 | --------------------------------------------------------------------------------