Better handling of lifetime of WS object

This commit is contained in:
Philip Gladstone 2022-05-02 20:24:02 -04:00
parent ff116b760a
commit 6899d8ed0c
1 changed files with 94 additions and 36 deletions

View File

@ -124,7 +124,8 @@ typedef struct {
bool closed; bool closed;
httpd_handle_t handle; httpd_handle_t handle;
int fd; int fd;
int self_ref; int self_ref; // only present if at least one callback registered
int self_weak_ref;
int text_fn_ref; int text_fn_ref;
int binary_fn_ref; int binary_fn_ref;
int close_fn_ref; int close_fn_ref;
@ -392,6 +393,48 @@ static esp_err_t dynamic_handler_httpd(httpd_req_t *req)
} }
#ifdef CONFIG_NODEMCU_CMODULE_HTTPD_WS #ifdef CONFIG_NODEMCU_CMODULE_HTTPD_WS
// returns a reference to a table with the [1] value being a weak
// ref to the top of the stack
static int register_weak_ref(lua_State *L)
{
lua_newtable(L);
lua_newtable(L); // metatable={}
lua_pushliteral(L, "__mode");
lua_pushliteral(L, "v");
lua_rawset(L, -3); // metatable._mode='v'
lua_setmetatable(L, -2); // setmetatable(new_table,metatable)
lua_pushvalue(L, -2); // push the previous top of stack
lua_rawseti(L, -2, 1); // new_table[1]=original value on top of the stack
lua_remove(L, -2); // Remove the original ivalue
return luaL_ref(L, LUA_REGISTRYINDEX); // this pops the new_table
}
// Returns the value of the weak ref on the stack
// but returns false with nothing on the stack has been GC'ed
static bool deref_weak_ref(lua_State *L, int ref)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return false;
}
lua_rawgeti(L, -1, 1);
// Either we have nil, or we have the underlying object
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return false;
}
return true;
}
static esp_err_t websocket_handler_httpd(httpd_req_t *req) static esp_err_t websocket_handler_httpd(httpd_req_t *req)
{ {
if (req->method == HTTP_GET) { if (req->method == HTTP_GET) {
@ -415,9 +458,9 @@ static void free_sess_ctx(void *ctx) {
xSemaphoreGive(done); xSemaphoreGive(done);
} }
static void ws_clear(lua_State *L, ws_connection_t *ws) static void ws_clear(lua_State *L, ws_connection_t *ws)
{ {
luaL_unref2(L, LUA_REGISTRYINDEX, ws->self_ref); luaL_unref2(L, LUA_REGISTRYINDEX, ws->self_weak_ref);
if (ws->text_fn_ref > 0) { if (ws->text_fn_ref > 0) {
luaL_unref2(L, LUA_REGISTRYINDEX, ws->text_fn_ref); luaL_unref2(L, LUA_REGISTRYINDEX, ws->text_fn_ref);
} }
@ -559,20 +602,21 @@ static void dynamic_handler_lvm(task_param_t param, task_prio_t prio)
#ifdef CONFIG_NODEMCU_CMODULE_HTTPD_WS #ifdef CONFIG_NODEMCU_CMODULE_HTTPD_WS
if (req_info->method == FREE_WS_OBJECT) { if (req_info->method == FREE_WS_OBJECT) {
printf("Freeing WS Object %d\n", req_info->reference); printf("Freeing WS Object %d\n", req_info->reference);
lua_rawgeti(L, LUA_REGISTRYINDEX, req_info->reference); if (deref_weak_ref(L, req_info->reference)) {
ws_connection_t *ws = (ws_connection_t *) luaL_checkudata(L, -1, WS_METATABLE); ws_connection_t *ws = (ws_connection_t *) luaL_checkudata(L, -1, WS_METATABLE);
if (!ws->closed) { if (!ws->closed) {
printf("FIrst close\n"); printf("First close\n");
ws->closed = true; ws->closed = true;
if (ws->close_fn_ref > 0) { if (ws->close_fn_ref > 0) {
printf("Calling close handler\n"); printf("Calling close handler\n");
lua_rawgeti(L, LUA_REGISTRYINDEX, ws->close_fn_ref); lua_rawgeti(L, LUA_REGISTRYINDEX, ws->close_fn_ref);
luaL_pcallx(L, 0, 0); luaL_pcallx(L, 0, 0);
}
} }
}
ws_clear(L, ws); ws_clear(L, ws);
}
tr.request_type = SEND_OK; tr.request_type = SEND_OK;
} else if (req_info->method == HTTP_WEBSOCKET) { } else if (req_info->method == HTTP_WEBSOCKET) {
@ -581,26 +625,27 @@ static void dynamic_handler_lvm(task_param_t param, task_prio_t prio)
if (req_info->req->sess_ctx) { if (req_info->req->sess_ctx) {
// Websocket event arrived // Websocket event arrived
printf("Sess_ctx = %d\n", (int) req_info->req->sess_ctx); printf("Sess_ctx = %d\n", (int) req_info->req->sess_ctx);
lua_rawgeti(L, LUA_REGISTRYINDEX, (int) req_info->req->sess_ctx); if (deref_weak_ref(L, (int) req_info->req->sess_ctx)) {
ws_connection_t *ws = (ws_connection_t *) luaL_checkudata(L, -1, WS_METATABLE); ws_connection_t *ws = (ws_connection_t *) luaL_checkudata(L, -1, WS_METATABLE);
int fn = 0; int fn = 0;
if (req_info->ws_pkt.type == HTTPD_WS_TYPE_TEXT) {
fn = ws->text_fn_ref;
} else if (req_info->ws_pkt.type == HTTPD_WS_TYPE_BINARY) {
fn = ws->binary_fn_ref;
}
if (fn) {
lua_rawgeti(L, LUA_REGISTRYINDEX, fn);
lua_pushlstring(L, (const char *) req_info->ws_pkt.payload, (size_t) req_info->ws_pkt.len); if (req_info->ws_pkt.type == HTTPD_WS_TYPE_TEXT) {
fn = ws->text_fn_ref;
} else if (req_info->ws_pkt.type == HTTPD_WS_TYPE_BINARY) {
fn = ws->binary_fn_ref;
}
luaL_pcallx(L, 1, 0); if (fn) {
lua_rawgeti(L, LUA_REGISTRYINDEX, fn);
lua_pushlstring(L, (const char *) req_info->ws_pkt.payload, (size_t) req_info->ws_pkt.len);
luaL_pcallx(L, 1, 0);
}
} }
} }
tr.request_type = SEND_OK; tr.request_type = SEND_OK;
} else } else
#endif #endif
{ {
lua_rawgeti(L, LUA_REGISTRYINDEX, dynamic_handlers_table_ref); // +1 lua_rawgeti(L, LUA_REGISTRYINDEX, dynamic_handlers_table_ref); // +1
@ -624,12 +669,13 @@ static void dynamic_handler_lvm(task_param_t param, task_prio_t prio)
luaL_getmetatable(L, WS_METATABLE); luaL_getmetatable(L, WS_METATABLE);
lua_setmetatable(L, -2); lua_setmetatable(L, -2);
lua_pushvalue(L, -1); lua_pushvalue(L, -1);
ws->self_ref = luaL_ref(L, LUA_REGISTRYINDEX); ws->self_weak_ref = register_weak_ref(L);
ws->handle = req_info->req->handle; ws->handle = req_info->req->handle;
ws->fd = httpd_req_to_sockfd(req_info->req); ws->fd = httpd_req_to_sockfd(req_info->req);
ws->self_ref = LUA_NOREF;
// Set the session context so we know what is going on. // Set the session context so we know what is going on.
req_info->req->sess_ctx = (void *) ws->self_ref; req_info->req->sess_ctx = (void *) ws->self_weak_ref;
req_info->req->free_ctx = free_sess_ctx; req_info->req->free_ctx = free_sess_ctx;
int err = luaL_pcallx(L, 2, 0); int err = luaL_pcallx(L, 2, 0);
@ -639,8 +685,8 @@ static void dynamic_handler_lvm(task_param_t param, task_prio_t prio)
} else { } else {
tr.request_type = SEND_OK; tr.request_type = SEND_OK;
} }
} }
} else } else
#endif #endif
{ {
int err = luaL_pcallx(L, 1, 1); // -1 +1 int err = luaL_pcallx(L, 1, 1); // -1 +1
@ -981,7 +1027,9 @@ static void ws_async_close(void *arg) {
printf("About to trigger close on %d\n", async_close->fd); printf("About to trigger close on %d\n", async_close->fd);
httpd_sess_trigger_close(async_close->hd, async_close->fd); if (httpd_sess_trigger_close(async_close->hd, async_close->fd) != ESP_OK) {
printf("Failed to trigger close\n");
}
free(async_close); free(async_close);
} }
@ -1000,7 +1048,7 @@ static int ws_close(lua_State *L) {
return 0; return 0;
} }
// event types: text, binary, close // event types: text, binary, close
static int ws_on(lua_State *L) { static int ws_on(lua_State *L) {
ws_connection_t *ws = (ws_connection_t*)luaL_checkudata(L, 1, WS_METATABLE); ws_connection_t *ws = (ws_connection_t*)luaL_checkudata(L, 1, WS_METATABLE);
const char *event = lua_tostring(L, 2); const char *event = lua_tostring(L, 2);
@ -1027,6 +1075,16 @@ static int ws_on(lua_State *L) {
*slot = luaL_ref(L, LUA_REGISTRYINDEX); *slot = luaL_ref(L, LUA_REGISTRYINDEX);
} }
if (ws->text_fn_ref || ws->binary_fn_ref || ws->close_fn_ref) {
// We need a self_ref
if (ws->self_ref <= 0) {
lua_pushvalue(L, 1);
ws->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);
}
} else {
luaL_unref2(L, LUA_REGISTRYINDEX, ws->self_ref);
}
return 0; return 0;
} }