lua-users home
lua-l archive

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


On Jan 16, 2010, at 5:07 AM, Cosmin Apreutesei wrote:

>> Having also found newproxy() useful for finalization of Lua objects, I
>> wonder why this is such a show-stopper. We all know that __gc happens
>> in its own sweet time. But for Lua objects, __gc is an opportunity to
>> ensure that some resource is released, eventually.
> 
> I find __gc useful only for releasing small memory objects allocated
> by external allocators. For many types of resources it's useless:
> - for some types of resources (db transactions, graphics objects) the
> release has visible effects or consequences on program behavior so
> deterministic release is part of the program logic, which btw is not
> an argument for forcing me to be explicit about the release and bloat
> my program with release_this() and release_that() when lexical scoping
> already tells me when the resources should go.
> - since __gc is non-deterministic you're not allowed to break the
> program in a __gc handler, which also brings the irritating
> restriction on not allowing __gc hooks from Lua; for some resources
> breaking on release is a required feature.
> 
> That being said, I can't find a reason for newproxy() in Lua 5.2, now
> that __len is checked for tables (my only use for it). Even if I
> wanted to use newproxy() for __gc, I can't convert my table-based OOP
> implementation to userdata-based cuz of virtualization holes.
> 
> I think a deterministic __immediate_gc hook would solve these
> problems. I also wish the `for` protocol had included an exit hook
> that would be called no matter how you exit the iteration (break,
> return, error).

If you really can lexically scope the lifespan for something then it's pretty easy to write routines like the following:

	local function _closeTransaction( self, transaction, success, ... )
		if not success then
			abortTransaction( db, transaction )
			error( ( ... ) )
		else
			commitTransaction( db, transaction )
			return ...
		end
	end

	function DB:transaction( ... ) do
		local transaction = openTransaction( self )
		return _closeTransaction( self, transaction, pcall( ... ) )
	end

And then in your code write:

	db:transaction( function()
		-- modify db inside the transaction
	end )

(Dynamic scoping together with in-do-end gets interesting here in that it could do things like revealing the database tables as globals within the transaction if that were deemed useful, but that's a different discussion.)

Immediate GC depends on knowing when all of the references to an object go away and in general we can't know that. Reference counting could catch most of them but comes with a fairly hefty performance cost. What you can do in a collected language is specify that a particular message be sent on scope exit. Here is a version of the C# using statement which sends dispose on exit (warning code has not been executed):

	local function pcall2call( success, ... )
		if success then
			return ...
		else
			error( ( ... ) )	-- The clever programmer will figure out how many levels up to report the error...
		end
	end

	local function _disposeObj( obj )
		obj:dispose()
	end

	local function _using_helper( objs, ... )
		for i = objs.n, 1, -1 do
			obj = obs[ i ]
			if obj then pcall( _disposeObj, obj ) end
				-- pcall to avoid having one object disposal stop others
				-- we eat errors because disposal is viewed as a courtesy
		end
		return pcall2call( ... )
	end

	function using( ... )
		local objs = { n = select( '#', ... ), ... }
		return function( ... )
			return _using_helper( objs, pcall( ... ) )
		end
	end

	using( lock_mutex( mtx1 ), lock_mutex( mtx2 ) )( function()
		-- do stuff with both mutexes having been locked
	end ) 

Where one would want syntactic help is in setting up lexical bindings for the variables that will be destructed. Again this is something that might be easier with dynamically scoped environments:

	using2{ f = io.open( filepath ) }( function()
		-- do stuff with file f. assumes that files support a dispose method (or using closes rather than disposing)
	end )

One doesn't need to write these constructs all that often. Less often if the destruction routine has a standard name and you can use using. Use of these constructs has a cost in closure construction and a syntactic cost in the relative weight of "function()" and "end )".

Mark