Nanfeng

Notes on software development, code, and curious ideas

Managing Native Resources in a Lua C Module

Native resources such as file handles, sockets, and XML parsers need deterministic cleanup even when exposed to Lua’s garbage-collected world. A common design is to store the native pointer in full userdata and give it a type-specific metatable.

For an Expat parser binding:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct {
XML_Parser parser;
lua_State *L;
} ParserHandle;

#define PARSER_METATABLE "Example.ExpatParser"

static ParserHandle *check_parser(lua_State *L, int index) {
return (ParserHandle *)luaL_checkudata(
L,
index,
PARSER_METATABLE
);
}

Creation allocates userdata, initializes the native parser, and attaches the metatable. If native allocation fails, report a Lua error without leaving a partially initialized object exposed.

Idempotent close

Cleanup should be safe to call more than once:

1
2
3
4
5
6
7
8
9
10
static int parser_close(lua_State *L) {
ParserHandle *handle = check_parser(L, 1);

if (handle->parser != NULL) {
XML_ParserFree(handle->parser);
handle->parser = NULL;
}

return 0;
}

Every other method must reject a closed handle before dereferencing it. Setting the pointer to NULL prevents a second call or the finalizer from freeing it twice.

Register methods and finalization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static const luaL_Reg parser_methods[] = {
{"parse", parser_parse},
{"close", parser_close},
{"__gc", parser_close},
{NULL, NULL}
};

static const luaL_Reg module_functions[] = {
{"new", parser_new},
{NULL, NULL}
};

int luaopen_example_parser(lua_State *L) {
luaL_newmetatable(L, PARSER_METATABLE);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaL_setfuncs(L, parser_methods, 0);

luaL_newlib(L, module_functions);
return 1;
}

Expose close() so callers can release scarce resources promptly. Treat __gc as a fallback because collection timing is nondeterministic. If callbacks retain Lua references, release their registry references during close and ensure native callbacks cannot fire after the state or userdata is gone.

+