Noah Watkins

github
twitter
linkedin

Lua GC and linked native heap objects

I’ve been working on a Lua project that wraps a C++ interface. Included in the interface are two objects that are created with a parent-child relationship. If a reference to the parent disappears and Lua garbage collection reclaims the parent object, using the child object will cause things to blow up.

It took me a while to find an example of how to use a weak table to record these relationships that indirectly result in the correct GC policy. Below is the final patch for reference. One open question here is what order does garbage collection impose between parent and child reclamation? Forcing a particular order may require some reference counts in the C++ objects. On the other hand, the C++ objects may not be sensitive to the issue, but this must be decided on a case-by-case basis.

diff --git a/lua_rados.cc b/lua_rados.cc
index 33a7ab7..d48d525 100644
--- a/lua_rados.cc
+++ b/lua_rados.cc
@@ -19,6 +19,8 @@
 #define LRAD_TIOCTX_T "Rados.IoctxT"
 #define LRAD_BL_T "Rados.Bufferlist"

+static char reg_key_rados_refs;
+
 typedef enum {
   CREATED,
   CONNECTED,
@@ -267,6 +269,16 @@ static int lrad_open_ioctx(lua_State *L)
   if (ret)
     return lrad_pusherror(L, ret);

+  /* record IoCtx -> Rados reference in weak key table */
+  lua_pushlightuserdata(L, ®_key_rados_refs);
+  lua_gettable(L, LUA_REGISTRYINDEX);
+  assert(!lua_isnil(L, -1));
+  assert(lua_type(L, -1) == LUA_TTABLE);
+  lua_pushvalue(L, -2); /* key = ioctx */
+  lua_pushvalue(L, 1);  /* value = cluster */
+  lua_settable(L, -3);
+  lua_pop(L, 1);
+
   /* return the userdata */
   return 1;
 }
@@ -435,6 +447,15 @@ LUALIB_API int luaopen_rados(lua_State *L)
   lua_setfield(L, -2, "__gc");
   lua_pop(L, 1);

+  /* weak table to protect IoCtx -> Rados refs */
+  lua_pushlightuserdata(L, ®_key_rados_refs);
+  lua_newtable(L);
+  lua_pushstring(L, "k");
+  lua_setfield(L, -2, "__mode");
+  lua_pushvalue(L, -1);
+  lua_setmetatable(L, -2);
+  lua_settable(L, LUA_REGISTRYINDEX);
+
   luaL_register(L, "rados", radoslib_f);

   return 0;
diff --git a/test.lua b/test.lua
index 779a30f..4722d4f 100644
--- a/test.lua
+++ b/test.lua
@@ -121,6 +121,13 @@ describe("ioctx object", function()
     ioctx = cluster:open_ioctx('data')
   end)

+  it("is usable if cluster ref disappears", function()
+    cluster = nil
+    collectgarbage()
+    local data = 'wkjeflkwjelfkjwelfkjwef'
+    assert.is_equal(#data, ioctx:write('oid', data, #data, 0))
+  end)
+
   describe("write method", function()
     it("returns number of bytes written", function()
       local data = 'wkjeflkwjelfkjwelfkjwef'
30bd762cd913e5b33d66499bed483624ef44ed89