lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Fri, Nov 13, 2015 at 11:16 AM, Soni L. <fakedme@gmail.com> wrote:
>
>
> On 13/11/15 08:19 AM, Viacheslav Usov wrote:
>>
>> This may have  been discussed earlier; if so, kindly point me to the
>> previous discussions.
>>
>> Lua uses garbage collection for everything. User-defined objects can use
>> metatables to perform appropriate finalization. For example, Lua's built-in
>> io library use the __gc field in its files' metatable to close the
>> underlying OS file, even if the user does not call the file:close function.
>> That works very well, except that finalization is non-deterministic, i.e.,
>> it is impossible to predict when it will happen.
>>
>> Using the example of files, this may be problematic, because the file
>> remains open unpredictably long, which may interfere with the other uses of
>> the file. It could be said that if determinism is important, the user must
>> ensure that file:close is called. Unfortunately, taking into account that
>> there can be some very complicated logic between io.open and file:close,
>> which may also raise Lua errors, this could lead to extremely unwieldy code.
>>
>> This problem is not specific to Lua and probably exists in every
>> GC-collected environment, so there are some established ways of dealing with
>> it. In C#, for example, this is done via the keyword 'using', which
>> establishes a scope, upon exiting which (including exiting via an
>> exception), the object is finalized. Example from
>> https://msdn.microsoft.com/en-us/library/yh598w02.aspx
>>
>> using  (Font font1 =new  Font("Arial", 10.0f))
>> {
>>      byte  charset = font1.GdiCharSet;
>> }
>
> using(Font.new("Arial", 10.0), function(font1)
>   local charset = font1.GdiCharSet
> end)
>
> Where using() runs the function in a coroutine and hooks errors in order to
> finalize the font.
>
> local function cleanup(ret)
>   collectgarbage()
>   collectgarbage() -- twice to make sure it's collected
>   return table.unpack(ret, 1, ret.n)
> end

This double collectgarbage() is both expensive and evil. Don't
micromanage the collector. If your program logic relies on GC
behavior, you're doing it wrong.

> function using(...)
>   local f = select(-1, ...)
>   local co = coroutine.create(f)
>   local ret = table.pack(co.resume(...)) -- or something
>   local errmsg
>   local yielded = -- process coroutine.yield() or something
>   -- etc
>   while co.status() ~= "dead" do
>     ret = table.pack(co.resume(table.unpack(yielded)))
>     local status = table.remove(ret, 1) -- remove ret[1], which contains the
> status
>     if status then
>       -- process coroutine.yield() or something
>     else
>       errmsg = table.remove(ret, 1) -- remove ret[1] again, which now
> contains the error message
>     end
>   end
>   if co.status() == "dead" and errmsg then
>     return cleanup(ret) -- pop `...` from the call stack
>   end
> end

A better solution might have called a required cleanup method on the
first argument of using(...). [Or even getmetatable(o):__gc()...]

Personally, I would like language support for cleanup of objects on
errors / out-of-block jumps. It's becoming an increasingly common
feature in languages: "with" in Python, "using" in C# (OP), "defer" in
Golang (which unfortunately only executes deferred calls until
return), etc. I don't like bundling error handling with
object/resource cleanup. It leads to the common situation of programs
skipping error handling ("because I want to pass the error up to the
calller anyway") and letting the GC eventually clean up the mess.

-- 
Patrick Donnelly