lua-users home
lua-l archive

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



These changes are planned for an upcoming Lanes 2.0.3 version.

Is there something to improve in the below way of adding finalizers to Lanes:

	func= lanes.gen( [libs_str,] [opt_tbl,] lane_func )

This is the usual Lanes generator function. Now, 'opt_tbl' can have a 'finalizer' field such that:

	void= function ([err])

It gets the parameter passed to 'error' if error is run in the 'lane_func'. In case of normal return, it's given no parameter.

Documentation will say:

<<
A finalizer is "piece of code that ensures certain necessary actions are taken when an acquired resource (such as a file or access to a hardware device) is no longer being used" (Wikipedia [link]). The finalizer procedure will be called, within the lane's Lua state, either after a succesful return or an error call. This allows i.e. a temporary file being removed from the disc even if the script ended abruptly.

Errors happening in the finalizer will behave as errors in the lane script (from the main script's viewpoint). The finalizer won't be called recursively for them, of course.
<<

Here is a sample of its intended usage:

<<
require "lanes"

local FN= "finalizer-test.tmp"

local function lane()
    local f= io.open(FN,"w")
    f:write( "Test file that should get removed." )

    io.stderr:write( "File "..FN.." created\n" )

    error(f)    -- exception here; the value needs not be a string

    -- no exception
end

--
-- This is called at the end of the lane; whether succesful or not.
-- Gets the 'error()' parameter as parameter ('nil' if lane was succesful).
--
local function cleanup(err)
    if err then
        io.stderr:write( "Cleanup after error: "..tostring(err).."\n" )
    else
        io.stderr:write( "Cleanup after normal return\n" )
    end

    local _,err2= os.remove(FN)
    assert(not err2)

    io.stderr:write( "Removed file "..FN.."\n" )
end

local lgen = lanes.gen("*", { finalizer=cleanup }, lane)

io.stderr:write "Launching the lane!\n"

local h= lgen()

local _,err= h:join() -- wait for the lane (no automatic error propagation)
if err then
    io.stderr:write( "Lane error: "..tostring(error).."\n" )
end

local f= io.open(FN,"r")
if f then
    error( "CLEANUP DID NOT WORK: "..FN.." still exists!" )
end

io.stderr:write "Finished!\n"

<<





Asko Kauppi kirjoitti 21.1.2009 kello 11:20:


I would see some synergy benefits with Lanes and these needs.

With Lanes, pcall() is already used to run particular functions in separate Lua states. This essentially creates a "scope" for such functions.

Using traditional GC mechanism for these scopes is deterministic, since the whole Lua state is cleared at return/error. However, I could survey this a little further and maybe add some ease of use way to add "finalizers" to the lanes. They would be run when the lane has returned or errorred (just before its Lua state is closed).

Considering a case where a project _is_ already using Lanes for its multithreading benefits, is there anything that remains a problem after this? Performance is not downgraded, since pcall() is anyways used.

-asko


David Manura kirjoitti 21.1.2009 kello 7:42:

On Tue, Jan 20, 2009 at 8:48 PM, John Belmonte wrote:
On Tue, Jan 20, 2009 at 5:56 PM, Asko Kauppi wrote:
In other words, what exactly does it _not_ cover, in your and/or David's
opinion?  Give us a list.

Repeated from the previous message for you:

Many applications expose scarce resources to the scripting
environment such as locks; handles for files, memory, and database
transactions; etc.  Such resources need to be released promptly and
deterministically after use, and despite any exceptional condition in
the program.  Of course this can be accomplished with Lua as it is,
but the resulting code is hard to follow [1], highly error prone, and
makes heavy use of protected calls-- limiting potential use of
coroutines.

Decision #1 proposes some more convenient mechanism for deterministic
resource cleanup (a.k.a. "deterministic finalization").  In contrast,
Lua's garbage collector, like most garbage collectors, provides
non-deterministic resource cleanup through finalizers, which in Lua
are the "__gc" metamethods on userdata (accessible in pure Lua code
only through newproxy[8]).  The finalizer of an object typically
closes any resources the object owns, such as to release memory or
close a file handle.  The potential problem is that Lua can delay
running the finalizers as long as it wants to or until you explicitly
call collectgarbage().  In many cases, such as with managed resources
(i.e. memory managed by Lua), this may not be a problem.  In other
cases, as in non-managed resources noted by John above,
non-deterministic resource cleanup can negatively affect program
correctness.  A typical solution to this is for the object to provide
a manually callable dispose method that releases resources.  In the
Lua file object, this is the file:close() method.  This may be fine
except when a block of code contains a number of exit locations via
returns or exceptions.  You may need to take care to call the dispose
method prior to each return and after each exception (i.e. following a
pcall).  In Lua, numerous pcalls and disposals before returns can be
unwieldy.

It's a well recognized problem.  Some .NET descriptions are in [2-5]
for background (.NET is a garbage collected framework that provides a
special mechanism for deterministic finalization).  C# and Python
provide the using and with clauses.  D provides the scope statement.
Java provides a somewhat invasive try/finally.  Traditionally
non-garbage-collected languages may provide other ways of
deterministic cleanup: C++ has RAII and Perl 5 has reference
counting.[1]

Now, personally, in practice, I've only found myself wanting this time
of capability in Lua on a few occasions (but perhaps others with
different applications will have greater need for it):

- closing file handles upon exceptions
- closing COM objects on exceptions
- releasing native OS handles on exceptions, e.g. as once in
   http://lua-users.org/wiki/SerialCommunication
- and if I used Lua more for database and socket work, quite
possibly that too.

Workarounds I've used are explicit collectgarbage() calls judiciously
placed, adding pcalls (often ugly, affects error tracebacks, and can
conflict with coroutines), and ignoring the problem (sometimes
acceptable).  In the case of file objects, they return on error
(rather than raise like in Java), so pcall can often be avoided unless
intervening code raises errors, but you can still need to explicitly
close on scope exits.

I'll also add that exception safety in Lua may require some practical
assumptions concerning out-of-memory errors[6][7].


[1] http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
[2] http://www.deez.info/sengelha/2005/02/11/deterministic-finalization-in-garbage-collected-languages/
[3] http://davidhayden.com/blog/dave/archive/2005/01/13/773.aspx
[4] http://www.codeproject.com/KB/dotnet/idisposable.aspx
[5] http://startbigthinksmall.wordpress.com/2008/04/10/dispose_correctly_with_the_using_statement/
[6] http://lua-users.org/lists/lua-l/2008-10/msg00405.html
[7] http://lua-users.org/lists/lua-l/2008-10/msg00426.html
[8] http://lua-users.org/wiki/HiddenFeatures
[9] http://lua-users.org/wiki/ErrorHandling