[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: block-scope finalization
- From: Patrick Donnelly <batrick@...>
- Date: Fri, 13 Nov 2015 13:23:12 -0500
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