lua-users home
lua-l archive

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


On Sun, May 31, 2009 at 11:22 PM, Cosmin Apreutesei
<cosmin.apreutesei@gmail.com> wrote:
> On Mon, Jun 1, 2009 at 00:58, Peter Cawley <lua@corsix.org> wrote:
>> On Sun, May 31, 2009 at 10:42 PM, Cosmin Apreutesei
>> <cosmin.apreutesei@gmail.com> wrote:
>>> With immediately-collected resources, the functionality of f:close()
>>> remains, as replaced by f = nil. I don't think any control is taken
>>> away.
>>>
>>
>> Expect "f = nil" would not provide the solid guarantee that the
>> resource was collected, whereas "f:close()" does. Consider the example
>> of a database API which only allows one (non-closed) cursor to exist
>> at any one time. After running a query and using a cursor to iterate
>> the results, you want to be absolutely sure that the cursor is
>> collected, as otherwise no other database calls will work. If you
>> accidently make a copy of "f" to some place, then "f = nil" would not
>> collect the cursor, and your application would start failing all over
>> the place. Using "f:close()" would guarantee that the cursor is
>> closed, and any further operations on this one cursor object would
>> result in a Lua error, which is (IMO) a much better result than having
>> the rest of the application's future DB interactions failing.
>
> But that's a feature any library could provide if so designed. That
> database API would surely have a close() method. And if it doesn't,
> you could encapsulate the handler with a few lines of lua code and
> provide the close() method yourself.
>
Consider the simple implementation of a hypothetical DB handling function:

function DoStuff()
  local query = DB.execute"select * from blah"
  for row in query do
    -- do something with row
  end
  query:close()

  query = DB.execute"select * from meh"
  -- etc.
  query:close()
end

This is fine, unless the "do something with row" code causes an error
to be thrown, or includes a "return" statement, at which point, the
execution of DoStuff is terminated, and "query:close()" is never run.
The next iteration might then look something like the following:

function DoStuff()
  local query = DB.execute"select * from blah"
  local status, err = pcall(function()
    for row in query do
      -- do something with row
      -- NB: return statements no longer cause DoStuff to return
    end
  end)
  query:close()
  if err then error(err) end -- NB: location of error moved, stack
trace will be affected

  query = DB.execute"select * from meh"
  -- etc.
  query:close()
end

This implementation would cause "query:close()" to run even if "do
something with row" causes an error, or if it contains a return
statement. It still has a number of shortcomings though:
  * return statements within the for loop no longer do the same thing
as they did before
  * the location at which the error happens has changed, which will
affect any debug stack traces
  * the resource clean-up code is still separated from resource
acquisition (image the "do something with row" code being very long)
  * the code complexity has increased a fair bit
  * the "do something with row" code cannot call coroutine.yield, as
it would be yielding across a C call (the pcall)

You could consider another Lua solution which would preserve the
behaviour of return statements, but this would increase the complexity
significantly (a robust soluation would have to save a vararg list of
the return values and all sorts of other stuff). On the other hand,
consider a hypothetical language-level "with" construct:

function DoStuff()
  local query = DB.execute"select * from blah"
  with query do
    for row in query do
      -- do something with row
      -- NB: return statements work as intended, and error location
information is preserved
    end
  end

  query = DB.execute"select * from meh"
  -- etc.
  query:close()
end

This achieves the initial goal of ensuring that query:close() is
called, regardless of how execution leaves the for loop. It does so in
a short and elegant manner, addressing all of the shortcomings of the
previous method.