Resource Acquisition Is Initialization

lua-users home
wiki

This page shall address approaches for achieving the effect of Resource Acquisition Is Initialization (RAII) [1] in Lua. RAII is a quite useful paradigm that is not directly supported in Lua 5.1, though there are some ways to approximate it. Some discussions and proposed solutions are on the Lua list:

The Problem

A very typical problem well suited to RAII is this:

function dostuff()
  local f = assert(io.open("out", "w"))
  domorestuff()  -- this may raise an error
  f:close() -- this is not called if that error was raised
end

dostuff()

If an error is raised, the file is not immediately closed (as RAII would ensure). Yes, the garbage collector will eventually close the file, but we don't know when. The program success or correctness may depend on the lock on the file being immediately released. Explicitly calling collectgarbage('collect') outside a pcall may help here though, in which case Lua calls the __gc (finalizer) metamethod, which closes the file, though you may have to call collectgarbage more than once [*1]. Furthermore, Lua doesn't allow objects implemented in pure Lua (without the help of C userdata) to define their own __gc metamethods.

Simulating RAII by maintaining a stack of destructible objects

Here is one approach in pure Lua that maintains a stack of all objects that need to be reclaimed. On scope exit or upon handling an exception, the objects to be reclaimed are removed from the stack and finalized (i.e. close, if exists, is called; otherwise, it is called as a function) to release their resources.

-- raii.lua
local M = {}

local frame_marker = {} -- unique value delimiting stack frames

local running = coroutine.running

-- Close current stack frame for RAII, releasing all objects.
local function close_frame(stack, e)
  assert(#stack ~= 0, 'RAII stack empty')
  for i=#stack,1,-1 do  -- release in reverse order of acquire
    local v; v, stack[i] = stack[i], nil
    if v == frame_marker then
      break
    else
      -- note: assume finalizer never raises error
      if type(v) == "table" and v.close then
        v:close()
      else
        v(e)
      end
    end
  end
end

local function helper1(stack, ...) close_frame(stack); return ... end
-- Allow self to be used as a function modifier
-- to add RAII support to function.
function M.__call(self, f)
  return function(...)
    local stack, co = self, running()
    if co then  -- each coroutine gets its own stack
      stack = self[co]
      if not stack then
        stack = {}
        self[co] = stack
      end
    end
    stack[#stack+1] = frame_marker -- new frame
    return helper1(stack, f(...))
  end
end

-- Show variables in all stack frames.
function M.__tostring(self)
  local stack, co = self, running()
  if co then stack = stack[co] end
  local ss = {}
  local level = 0
  for i,val in ipairs(stack) do
    if val == frame_marker then
      level = level + 1
    else
      ss[#ss+1] = string.format('[%s][%d] %s', tostring(co), level, tostring(val))
    end
  end
  return table.concat(ss, '\n')
end

local function helper2(stack, level, ok, ...)
  local e; if not ok then e = select(1, ...) end
  while #stack > level do close_frame(stack, e) end
  return ...
end

-- Construct new RAII stack set.
function M.new()
  local self = setmetatable({}, M)

  -- Register new resource(s), preserving order of registration.
  function self.scoped(...)
    local stack, co = self, running()
    if co then stack = stack[co] end
    for n=1,select('#', ...) do
      stack[#stack+1] = select(n, ...)
    end
    return ...
  end

  -- a variant of pcall
  -- that ensures the RAII stack is unwound.
  function self.pcall(f, ...)
    local stack, co = self, running()
    if co then stack = stack[co] end
    local level = #stack
    return helper2(stack, level, pcall(f, ...))
  end

  -- Note: it's somewhat convenient having scoped and pcall be
  -- closures....  local scoped = raii.scoped

  return self
end

-- singleton.
local raii = M.new()

return raii

Example usage:

local raii = require "raii"
local scoped, pcall = raii.scoped, raii.pcall

-- Define some resource type for testing.
-- In practice, this is a resource we acquire and
-- release (e.g. a file, database handle, Win32 handle, etc.).
local Resource = {}; do
  Resource.__index = Resource
  function Resource:__tostring() return self.name end
  function Resource.open(name)
    local self = setmetatable({name=name}, Resource)
    print("open", name)
    return self
  end
  function Resource:close() print("close", self.name) end
  function Resource:foo()   print("hello", self.name) end
end

local test3 = raii(function()
  local f = scoped(Resource.open('D'))
  f:foo()
  print(raii)
  error("opps")
end)

local test2 = raii(function()
  scoped(function(e) print("leaving", e) end)
  local f = scoped(Resource.open('C'))
  test3(st)
end)

local test1 = raii(function()
  local g1 = scoped(Resource.open('A'))
  local g2 = scoped(Resource.open('B'))
  print(pcall(test2))
end)

test1()


--[[ OUTPUT:
open    A
open    B
open    C
open    D
hello   D
[nil][1] A
[nil][1] B
[nil][2] function: 0x68a818
[nil][2] C
[nil][3] D
close   D
close   C
leaving complex2.lua:23: opps
complex2.lua:23: opps
close   B
close   A
]]

Example using coroutines:

local raii = require "raii"
local scoped, pcall = raii.scoped, raii.pcall

-- Define some resource type for testing.
-- In practice, this is a resource we acquire and
-- release (e.g. a file, database handle, Win32 handle, etc.).
local Resource = {}; do
  Resource.__index = Resource
  local running = coroutine.running
  function Resource:__tostring() return self.name end
  function Resource.open(name)
    local self = setmetatable({name=name}, Resource)
    print(running(), "open", self.name)
    return self
  end
  function Resource:close() print(running(), "close", self.name) end
  function Resource:foo()   print(running(), "hello", self.name) end
end

local test3 = raii(function(n)
  local f = scoped(Resource.open('D' .. n))
  f:foo()
  print(raii)
  error("opps")
end)

local test2 = raii(function(n)
  scoped(function(e) print(coroutine.running(), "leaving", e) end)
  local f = scoped(Resource.open('C' .. n))
  test3(n)
end)

local test1 = raii(function(n)
  local g1 = scoped(Resource.open('A' .. n))
  coroutine.yield()
  local g2 = scoped(Resource.open('B' .. n))
  coroutine.yield()
  print(coroutine.running(), pcall(test2, n))
  coroutine.yield()
end)

local cos = {coroutine.create(test1), coroutine.create(test1)}
while true do
  local is_done = true
  for n=1,#cos do
    if coroutine.status(cos[n]) ~= "dead" then
      coroutine.resume(cos[n], n)
      is_done = false
    end
  end
  if is_done then break end
end
-- Note: all coroutines must terminate for RAII to work.

--[[ OUTPUT:
thread: 0x68a7f0        open    A1
thread: 0x68ac10        open    A2
thread: 0x68a7f0        open    B1
thread: 0x68ac10        open    B2
thread: 0x68a7f0        open    C1
thread: 0x68a7f0        open    D1
thread: 0x68a7f0        hello   D1
[thread: 0x68a7f0][1] A1
[thread: 0x68a7f0][1] B1
[thread: 0x68a7f0][2] function: 0x68ada0
[thread: 0x68a7f0][2] C1
[thread: 0x68a7f0][3] D1
thread: 0x68a7f0        close   D1
thread: 0x68a7f0        close   C1
thread: 0x68a7f0        leaving complex3.lua:24: opps
thread: 0x68a7f0        complex3.lua:24: opps
thread: 0x68ac10        open    C2
thread: 0x68ac10        open    D2
thread: 0x68ac10        hello   D2
[thread: 0x68ac10][1] A2
[thread: 0x68ac10][1] B2
[thread: 0x68ac10][2] function: 0x684258
[thread: 0x68ac10][2] C2
[thread: 0x68ac10][3] D2
thread: 0x68ac10        close   D2
thread: 0x68ac10        close   C2
thread: 0x68ac10        leaving complex3.lua:24: opps
thread: 0x68ac10        complex3.lua:24: opps
thread: 0x68a7f0        close   B1
thread: 0x68a7f0        close   A1
thread: 0x68ac10        close   B2
thread: 0x68ac10        close   A2
]]

--DavidManura

Scope Manager

JohnBelmonte suggested in LuaList:2007-05/msg00354.html [*2] implementing something like a D scope guard statement [3][4] construct in Lua. The idea was for variable class (like local) named scoped that when provided a function (or callable table), it would call it on scope exit:

function test()
  local fh = io:open()
  scoped function() fh:close() end
  foo()
end

It is possible to implement this in plain Lua. This is described in Lua Programming Gems, Gem #13 "Exceptions in Lua" [5] to permit something like this:

function dostuff()
  scope(function()
    local fh1 = assert(io.open('file1'))
    on_exit(function() fh1:close() end)
    ...
    local fh2 = assert(io.open('file2'))
    on_exit(function() fh2:close() end)
    ...
  end)
end

A Possible Syntax Extension for Scope Guard Statement

This requires the construction of an anonymous function, but there are advantages to avoid that from an efficiency standpoint.

Here's another idea ("finally ... end" construct) that is very basic:

function load(filename)
   local h = io.open (filename)
   finally if h then h:close() end end
   ...
end

Note that the scope construct as implemented in D syntactically resembles an if statement that executes at the end of the scope. That is, provided we consider exit, success, and failure to be real conditional expressions; in fact, it might be useful to make that generalization. I had proposed the following syntax extension for Lua:

stat :: scopeif exp then block {elseif exp then block} [else block] end

where err is an implicit variable (like self) that can be used inside exp or block and represents the error being raised, or nil if no error was raised. (Comment: after revisiting that syntax again many months later, I found the semantics not very intuitive, particularly concerning the special usage of err.)

The examples in "Exception Safe Programming" [3] translate into Lua as

function abc()
  local f = dofoo();
  scopeif err then dofoo_undo(f) end

  local b = dobar();
  scopeif err then dobar_undo(b) end

  local d = dodef();

  return Transaction(f, b, d)
end
-----
function bar()
  local verbose_save = verbose
  verbose = false
  scopeif true then verbose = verbose_save end

  ...lots of code...
end
-----
function send(msg)
  do
    local origTitle = msg.Title()
    scopeif true then msg.SetTitle(origTitle) end
    msg.SetTitle("[Sending] " .. origTitle)
    Copy(msg, "Sent")
  end
  scopeif err then
    Remove(msg.ID(), "Sent")
  else
    SetTitle(msg.ID(), "Sent", msg.Title)
  end
  SmtpSend(msg)	-- do the least reliable part last
end

The scopeif true then ... end is somewhat verbose though not unlike while true do ... end. The use of scopeif rather than scope if follows the pattern of elseif.

JohnBelmonte's database example becomes shortened to

function Database:commit()
  for attempt = 1, MAX_TRIES do
    scopeif instance_of(err, DatabaseConflictError) then
      if attempt < MAX_TRIES then
        log('Database conflict (attempt '..attempt..')')
      else
        error('Commit failed after '..attempt..' tries.')
      end
    end -- note: else no-op
    self.commit()
    return
  end
end

Here's how a regular RAII would be simulated (the D article doesn't say that RAII is never useful):

function test()
  local resource = Resource(); scope if true then resource:close() end
  foo()
end

That is, however, more verbose than the proposed

function test()
  scoped resource = Resource()
  foo()
end

Perhaps this can be prototyped in Metalua [6].

--DavidManura

Try/finally/scoped-guard patches

A few patches to Lua have been posted to handle this type of thing:

(2008-01-31) PATCH: for try/catch/finally support posted by Hu Qiwei [10][11][12]. return and break are prohibited in the try block.

(2008-01-07) PATCH: finalization of objects posted by Nodir Temirhodzhaev. [13] This is an alternative to the above "try/catch/finally" exception handling mechanism and is related to [3] and ResourceAcquisitionIsInitialization.

Update 2009-02-14: LuaList:2009-02/msg00258.html ; LuaList:2009-03/msg00418.html

(2008-02-12) PATCH: experimental finalize/guard posted by Alex Mania [14] supports finalize and guard blocks for RAII.

LuaList:2009-03/msg00418.html

with statement (Metalua, Python)

MetaLua 0.4 offers an RAII extension called "withdo". It works with every resources that are released by calling a method :close(). It protects against normal termination of the protected block, returns from within the block, and errors. The following would return the sum of the sizes of files filename1 and filename2 *after* having closed their handles:

with h1, h2 = io.open 'filename1', io.open 'filename2' do
   local total = #h1:read'*a' + #h2:read'*a'
   return total
end

Note that the Metalua design is limited by the requirement that resource objects have a certain method ("close()" in this case). In Python it was rejected in favor of "with ... as ..." syntax allowing a resource management object separate from the resource itself [7]. Furthermore the Python statement allows the assignment to be elided, since in many situations the resource variable is not needed-- for example if you just want to hold a lock during the block.

Additional Comments

[*1] It could be twice or more, depending on how intertwined the userdata are, but it definitely takes two collects to get rid of a userdata with a __gc meta if the userdata is the last reference to some other object which itself is/refers to a userdata, then the cycle continues. (noted by RiciLake)

[*2] The RAII pattern suffers from the need to create ad-hoc classes to manage resources, and from the clunky nesting needed when acquiring sequential resources. See http://www.digitalmars.com/d/exception-safe.html. A better pattern is in Lua gem #13 "Exceptions in Lua" [5]. --JohnBelmonte

Here's my proposed example code illustrating a Google Go defer and D scope(exit) like syntax. --DavidManura

-- Lua 5.1, example without exceptions
local function readfile(filename)
  local fh, err = io.open(filename)
  if not fh then return false, err end
  local data, err = fh:read'*a'
  -- note: in this case, the two fh:close()'s may be moved here, but in general that is not possible
  if not data then fh:close(); return false, err end
  fh:close()
  return data
end
-- Lua 5.1, example with exceptions, under suitable definitions of given functions.
local function readfile(filename)
  return scoped(function(onexit)  -- based on pcall
    local fh = assert(io.open(filename)); onexit(function() fh:close() end)
    return assert(fh:read'*a')
  end)
end
-- proposal, example without exceptions
local function readfile(filename)
  local fh, err = io.open(filename); if not fh then return false, err end
  defer fh:close()
  local data, err = fh:read'*a'; if not data then return false, err end
  return data
end
  -- note: "local val, err = io.open(filename); if not val then return false, err end" is a common
  -- pattern and perhaps warrants a syntax like "local val = returnunless io.open(filename)".
-- proposal, example with exceptions
local function readfile(filename)
  local fh = assert(io.open(filename)); defer fh:close()
  return assert(fh:read'*a')
end
-- proposal, example catching exceptions
do
  defer if class(err) == 'FileError' then
    print(err)
    err:suppress()
  end
  print(readfile("test.txt"))
end

-- alternate proposal - cleanup code by metamechanism
local function readfile(filename)
  scoped fh = assert(io.open(filename)) -- note: fh:close() or getmetatable(fh).__close(fh) called on scope exit
  return assert(fh:read'*a')
end

See Also


RecentChanges · preferences
edit · history
Last edited March 13, 2012 4:36 am GMT (diff)