@@ -36,7 +36,59 @@ static void lua_timer_release_ref(LuaTimer* lt) {
3636 }
3737}
3838
39+ // Live-timer registry: registry["hv.timers"][lightuserdata(timer)] = true.
40+ // hv.clearTimer receives a raw htimer_t* as an opaque handle, but a fired
41+ // once-timer's htimer_t is freed by the loop after its callback, leaving the
42+ // script holding a stale pointer. Validating the handle against this registry
43+ // (instead of blindly dereferencing hevent_userdata) makes clearTimer on a
44+ // stale/already-freed handle a safe no-op.
45+ #define HVLUA_TIMERS_REG "hv.timers"
46+
47+ static void lua_timers_reg_get (lua_State * L ) {
48+ lua_getfield (L , LUA_REGISTRYINDEX , HVLUA_TIMERS_REG );
49+ if (!lua_istable (L , -1 )) {
50+ lua_pop (L , 1 );
51+ lua_newtable (L );
52+ lua_pushvalue (L , -1 );
53+ lua_setfield (L , LUA_REGISTRYINDEX , HVLUA_TIMERS_REG );
54+ }
55+ }
56+
57+ static void lua_timer_reg_add (lua_State * L , htimer_t * timer ) {
58+ lua_timers_reg_get (L ); // [timers]
59+ lua_pushlightuserdata (L , timer ); // [timers][key]
60+ lua_pushboolean (L , 1 ); // [timers][key][true]
61+ lua_settable (L , -3 ); // timers[key]=true
62+ lua_pop (L , 1 );
63+ }
64+
65+ static void lua_timer_reg_remove (lua_State * L , htimer_t * timer ) {
66+ if (timer == NULL ) return ;
67+ lua_timers_reg_get (L ); // [timers]
68+ lua_pushlightuserdata (L , timer ); // [timers][key]
69+ lua_pushnil (L ); // [timers][key][nil]
70+ lua_settable (L , -3 ); // timers[key]=nil
71+ lua_pop (L , 1 );
72+ }
73+
74+ // Is `timer` a currently-live lua timer handle?
75+ static int lua_timer_reg_has (lua_State * L , htimer_t * timer ) {
76+ int has ;
77+ lua_timers_reg_get (L ); // [timers]
78+ lua_pushlightuserdata (L , timer ); // [timers][key]
79+ lua_gettable (L , -2 ); // [timers][val]
80+ has = lua_toboolean (L , -1 );
81+ lua_pop (L , 2 );
82+ return has ;
83+ }
84+
3985static void lua_timer_free (LuaTimer * lt ) {
86+ // Unregister the handle so a later clearTimer on this (soon-freed) timer is
87+ // a safe no-op, and detach the timer's back-pointer to lt.
88+ if (lt -> timer ) {
89+ lua_timer_reg_remove (lt -> L , lt -> timer );
90+ hevent_set_userdata (lt -> timer , NULL );
91+ }
4092 lua_timer_release_ref (lt );
4193 HV_FREE (lt );
4294}
@@ -100,6 +152,7 @@ static htimer_t* add_lua_timer(lua_State* L, uint32_t timeout_ms, uint32_t repea
100152 }
101153 hevent_set_userdata (timer , lt );
102154 lt -> timer = timer ;
155+ lua_timer_reg_add (L , timer ); // mark handle live for clearTimer validation
103156 return timer ;
104157}
105158
@@ -128,13 +181,20 @@ static int l_hloop_clearTimer(lua_State* L) {
128181 if (!lua_islightuserdata (L , 1 )) return 0 ;
129182 timer = (htimer_t * )lua_touserdata (L , 1 );
130183 if (timer == NULL ) return 0 ;
184+ // Validate the handle: a fired once-timer's htimer_t is freed by the loop,
185+ // so the script may hold a stale pointer. Only proceed if the handle is a
186+ // currently-live timer we registered; otherwise it's a safe no-op (avoids
187+ // dereferencing freed memory via hevent_userdata).
188+ if (!lua_timer_reg_has (L , timer )) return 0 ;
131189 lt = (LuaTimer * )hevent_userdata (timer );
132190 htimer_del (timer );
133191 if (lt ) {
134192 if (lt -> in_callback ) {
135193 // Called from within this timer's own callback: defer the free to
136- // on_lua_timer so we don't free `lt` while it's still in use.
194+ // on_lua_timer so we don't free `lt` while it's still in use. Drop it
195+ // from the live registry now so no further clearTimer can match.
137196 lt -> dead = 1 ;
197+ lua_timer_reg_remove (L , timer );
138198 lua_timer_release_ref (lt );
139199 } else {
140200 lua_timer_free (lt );
@@ -208,7 +268,8 @@ static void on_dns_resolved(hdns_t* query, const hdns_result_t* result, void* us
208268 HV_FREE (d );
209269
210270 L = hvlua_coroutine_state (co );
211- if (L == NULL ) { // coroutine gone (loop teardown); nothing to resume
271+ if (L == NULL ) { // coroutine gone (loop teardown); release the token
272+ hvlua_cancel (co );
212273 return ;
213274 }
214275
@@ -289,6 +350,14 @@ typedef struct LuaConn {
289350 int closed ; // set in the close callback
290351 int connecting ; // pending op is connect (vs read), for resume shape
291352 unpack_setting_t * unpack ; // owned; hio_t only stores the pointer
353+ // Sync-read detection: hio_read_until_length/delim may invoke the read
354+ // callback INLINE (buffered data already satisfies the request) before the
355+ // coroutine yields. In that window co is not yet set, so on_conn_read must
356+ // not resume; instead it stashes the data here and l_conn_read* returns it
357+ // directly without suspending.
358+ lua_State * reading_L ; // non-NULL while arming a read (the running coroutine)
359+ int read_done ; // set true if the read completed synchronously
360+ int read_nres ; // number of results pushed on reading_L (sync path)
292361} LuaConn ;
293362
294363// forward decl: conn_push_new is defined lower (after the read helpers) but
@@ -338,7 +407,18 @@ static void on_conn_connect(hio_t* io) {
338407
339408static void on_conn_read (hio_t * io , void * buf , int len ) {
340409 LuaConn * c = (LuaConn * )hevent_userdata (io );
341- if (c == NULL || c -> co == NULL ) return ;
410+ if (c == NULL ) return ;
411+ // Synchronous completion: hio_read_until_* found buffered data and called
412+ // us inline, before the coroutine yielded. co is not set yet; push the data
413+ // onto the running coroutine and let l_conn_read* return it directly (do NOT
414+ // resume — the coroutine is still running).
415+ if (c -> reading_L ) {
416+ lua_pushlstring (c -> reading_L , (const char * )buf , len );
417+ c -> read_done = 1 ;
418+ c -> read_nres = 1 ;
419+ return ;
420+ }
421+ if (c -> co == NULL ) return ;
342422 lua_State * co = hvlua_coroutine_state (c -> co );
343423 if (co == NULL ) { hvlua_cancel (c -> co ); c -> co = NULL ; return ; }
344424 lua_pushlstring (co , (const char * )buf , len );
@@ -397,6 +477,9 @@ static LuaConn* conn_push_new(lua_State* L, hio_t* io) {
397477 c -> closed = 0 ;
398478 c -> connecting = 0 ;
399479 c -> unpack = NULL ;
480+ c -> reading_L = NULL ;
481+ c -> read_done = 0 ;
482+ c -> read_nres = 0 ;
400483 luaL_getmetatable (L , CONN_META );
401484 lua_setmetatable (L , -2 );
402485 hevent_set_userdata (io , c );
@@ -410,10 +493,33 @@ static int read_k(lua_State* L, int status, lua_KContext ctx) {
410493 return lua_gettop (L ) >= 2 && lua_isnil (L , -2 ) ? 2 : 1 ;
411494}
412495
413- // Shared: arm the read callback + suspend. The specific hio_read* call is done
414- // by the caller (read once / read_until_delim / read_until_length).
415- static int conn_suspend_read (lua_State * L , LuaConn * c ) {
496+ // Read protocol (fixes sync-callback data loss): the hio_read_until_* calls may
497+ // invoke on_conn_read INLINE when buffered data already satisfies the request.
498+ // So we (1) arm the read callback and mark reading_L BEFORE issuing hio_read*,
499+ // (2) issue hio_read*, then (3) if the callback fired synchronously (read_done),
500+ // return the data directly; otherwise suspend the coroutine.
501+ //
502+ // conn_begin_read: arm callback + enter the "reading" window. Returns 0 on ok.
503+ static void conn_begin_read (lua_State * L , LuaConn * c ) {
416504 hio_setcb_read (c -> io , on_conn_read );
505+ c -> reading_L = L ;
506+ c -> read_done = 0 ;
507+ c -> read_nres = 0 ;
508+ }
509+
510+ // conn_end_read: leave the reading window; return results if the read completed
511+ // synchronously, else suspend the coroutine and yield.
512+ static int conn_end_read (lua_State * L , LuaConn * c ) {
513+ c -> reading_L = NULL ;
514+ if (c -> read_done ) {
515+ // Data already pushed on L by on_conn_read; return it directly.
516+ return c -> read_nres ;
517+ }
518+ // Also handle the case where the read synchronously closed the connection
519+ // (on_conn_close ran inline and set closed): report it without suspending.
520+ if (c -> closed || c -> io == NULL ) {
521+ lua_pushnil (L ); lua_pushstring (L , "closed" ); return 2 ;
522+ }
417523 c -> co = hvlua_suspend (L );
418524 return lua_yieldk (L , 0 , (lua_KContext )0 , read_k );
419525}
@@ -424,8 +530,9 @@ static int l_conn_read(lua_State* L) {
424530 if (c -> closed || c -> io == NULL ) {
425531 lua_pushnil (L ); lua_pushstring (L , "closed" ); return 2 ;
426532 }
533+ conn_begin_read (L , c );
427534 hio_read (c -> io );
428- return conn_suspend_read (L , c );
535+ return conn_end_read (L , c );
429536}
430537
431538// conn:readbytes(n) -> exactly n bytes (hio_read_until_length)
@@ -435,8 +542,9 @@ static int l_conn_readbytes(lua_State* L) {
435542 if (c -> closed || c -> io == NULL ) {
436543 lua_pushnil (L ); lua_pushstring (L , "closed" ); return 2 ;
437544 }
545+ conn_begin_read (L , c );
438546 hio_read_until_length (c -> io , n );
439- return conn_suspend_read (L , c );
547+ return conn_end_read (L , c );
440548}
441549
442550// conn:readuntil(delim) -> data up to and including the 1-byte delimiter
@@ -450,8 +558,9 @@ static int l_conn_readuntil(lua_State* L) {
450558 if (dlen != 1 ) {
451559 return luaL_error (L , "conn:readuntil expects a single-byte delimiter" );
452560 }
561+ conn_begin_read (L , c );
453562 hio_read_until_delim (c -> io , (unsigned char )delim [0 ]);
454- return conn_suspend_read (L , c );
563+ return conn_end_read (L , c );
455564}
456565
457566// conn:readline() -> data up to and including '\n'
@@ -460,8 +569,9 @@ static int l_conn_readline(lua_State* L) {
460569 if (c -> closed || c -> io == NULL ) {
461570 lua_pushnil (L ); lua_pushstring (L , "closed" ); return 2 ;
462571 }
572+ conn_begin_read (L , c );
463573 hio_read_until_delim (c -> io , '\n' );
464- return conn_suspend_read (L , c );
574+ return conn_end_read (L , c );
465575}
466576
467577// conn:setUnpack(opts): configure automatic message unpacking so subsequent
0 commit comments