Search code examples
c++lualuajit

Passing shared_ptr to Lua


Some time ago I have decided to go for a reference count methodology to manage the lifetime of the resource and it did a trick. Later I have faced a new challenge, to preserve an object lifetime till Lua executes a long-running task in a separate thread ( which is achieved with turbo library ).

The idea I came up was to pass a copy of the shared_ptr to the Lua and let garbage collector decide whenever the resource is not needed anymore. But the problem is Lua performing clear of the data twice and I have no idea why.

Here I will describe a few steps I performed. At first, I am creating a table, setting metatable for it and putting it as global

static const struct luaL_reg FileTableDefinition[] = {
  { "__gc",     destroy },
  { NULL,       NULL } };

const char* id = "PointerManager";
lua_newtable( state );
luaL_newmetatable( state, id );
luaL_register( state, NULL, FileTableDefinition );
lua_pushliteral( state, "__index" );
lua_pushvalue( state, -2 );
lua_rawset( state, -3 );
lua_setglobal( state, id );

During the step number two, I am allocating a shared_ptr with an object in the user data and setting a destructor from the table defined in step 2. The File is a class which is exported to the LUA so user actually can perform a call to that class.

void *userData = lua_newuserdata( state, sizeof( std::shared_ptr<File> ) );

// Handle allocation failure
if ( ! userData) { return; }

// Use the placement parameters of the new operator to allocate
// shared_ptr within the userdata provided by Lua. Copy our other
// shared_ptr into it, increasing the reference count
new(userData) std::shared_ptr<File>( aFile );

// Now just set the metatable on this new object
luaL_getmetatable( state, "PointerManager" );
lua_setmetatable( state, -2 );

The destructor of the object defined as follows:

int destroy( lua_State* state )
{
    void* resourcePtr = luaL_checkudata( state, 1, "PointerManager" );  
    if( resourcePtr )
    {
        auto resource = static_cast<std::shared_ptr<File>*>( resourcePtr );
        resource->reset();
        return 1;
    }
    return 0;
}

I am doing a script call (lua_pcall) of the event. Script defined as follows:

local turbo = require("turbo")

function sleep(n)
  os.execute("sleep " .. tonumber(n))
end

function on_file_event ( file )
    print("on_file_event enter.")
    turbo.ioloop.instance():add_callback(function()
        local thread = turbo.thread.Thread(function(th)
            print("executing")
            sleep(1)
            print( file:filepath() )
            file = nil
            collectgarbage("collect")
            print("stopped executing")
            th:stop()
        end)

        turbo.ioloop.instance():close()
    end):start()
    print("on_file_event exit.")
end

I put a cout into the class destructor and somehow it is called twice. Both times destroy function is called (once when I am calling garbage collector and once on program exit) and regardless I am doing a reset of shared pointer destructor of the underlying object reached twice. The memory of the shared pointer and pointer itself is the same, thread id is same as well. I did as well a Valgrind dive but no corruption was found.

Calling destroy file 
        >> ~File object deleted (/opt/nids/artifacts/generic/32). Address: 0x1bec850

Calling destroy file 
        >> ~File object deleted (/opt/nids/artifacts/generic/32). Address: 0x1bec850

Am I doing something completely wrong?


Solution

  • I found the reason. It was an underlying fork inside of the turbo library. When I created a new thread in lua - C fork occurred so addresses were the same but in fact they were in the different memory space. Later on memory was freed and two set of prints appeared.

    I was able to confirm that with straces.