lua-users home
lua-l archive

Re: block-scope finalization

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


On 13/11/15 04:23 PM, Patrick Donnelly wrote:
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.
In Lua, you sometimes have .close(), :close(), :disconnect() and even .disconnect(). How do you handle all the different ways to do that stuff? You just force a __gc.
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()...]
__metatable issue.
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.
Unlike them, Lua doesn't actually need it.
--
Disclaimer: these emails may be made public at any given time, with or without reason. If you don't agree with this, DO NOT REPLY.

AltStyle によって変換されたページ (->オリジナル) /