@@ -24,9 +24,14 @@ typedef struct {
2424} luv_dir_t ;
2525#endif
2626
27+ typedef struct {
28+ uv_fs_t * req ;
29+ } luv_fs_scandir_t ;
30+
2731static uv_fs_t * luv_check_fs (lua_State * L , int index ) {
28- if (luaL_testudata (L , index , "uv_fs" ) != NULL ) {
29- return (uv_fs_t * )lua_touserdata (L , index );
32+ if (luaL_testudata (L , index , "uv_fs_scandir" ) != NULL ) {
33+ luv_fs_scandir_t * scandir_req = (luv_fs_scandir_t * )lua_touserdata (L , index );
34+ return (uv_fs_t * )(scandir_req -> req );
3035 }
3136 uv_fs_t * req = (uv_fs_t * )luaL_checkudata (L , index , "uv_req" );
3237 luaL_argcheck (L , req -> type == UV_FS && req -> data , index , "Expected uv_fs_t" );
@@ -251,6 +256,13 @@ static int push_fs_result(lua_State* L, uv_fs_t* req) {
251256 }
252257
253258 if (req -> result < 0 ) {
259+ if (req -> fs_type == UV_FS_SCANDIR ) {
260+ // We need to unref the luv_fs_scandir_t userdata to allow it to be garbage collected.
261+ // The scandir callback can only be called once, so we now know that the
262+ // req can be safely garbage collected.
263+ luaL_unref (L , LUA_REGISTRYINDEX , data -> data_ref );
264+ data -> data_ref = LUA_NOREF ;
265+ }
254266 lua_pushnil (L );
255267 if (fs_req_has_dest_path (req )) {
256268 lua_rawgeti (L , LUA_REGISTRYINDEX , data -> data_ref );
@@ -336,8 +348,15 @@ static int push_fs_result(lua_State* L, uv_fs_t* req) {
336348 return 1 ;
337349
338350 case UV_FS_SCANDIR :
339- // Expose the userdata for the request.
340- lua_rawgeti (L , LUA_REGISTRYINDEX , data -> req_ref );
351+ // The luv_fs_scandir_t userdata is stored in data_ref.
352+ // We want to return this instead of the uv_req_t because the
353+ // luv_fs_scandir_t userdata has a gc method.
354+ lua_rawgeti (L , LUA_REGISTRYINDEX , data -> data_ref );
355+ // We now want to unref the userdata to allow it to be garbage collected.
356+ // The scandir callback can only be called once, so we now know that the
357+ // req can be safely garbage collected.
358+ luaL_unref (L , LUA_REGISTRYINDEX , data -> data_ref );
359+ data -> data_ref = LUA_NOREF ;
341360 return 1 ;
342361
343362#if LUV_UV_VERSION_GEQ (1 , 28 , 0 )
@@ -470,9 +489,6 @@ static void luv_fs_cb(uv_fs_t* req) {
470489 luv_cleanup_req(L, lreq); \
471490 req->data = NULL; \
472491 uv_fs_req_cleanup(req); \
473- } else { \
474- luaL_unref(L, LUA_REGISTRYINDEX, lreq->req_ref); \
475- lreq->req_ref = LUA_NOREF; \
476492 } \
477493 } \
478494 else { \
@@ -614,8 +630,34 @@ static int luv_fs_scandir(lua_State* L) {
614630 int flags = 0 ; // TODO: find out what these flags are.
615631 int ref = luv_check_continuation (L , 2 );
616632 uv_fs_t * req = (uv_fs_t * )lua_newuserdata (L , uv_req_size (UV_FS ));
617- req -> data = luv_setup_req_with_mt (L , ctx , ref , "uv_fs" );
618- FS_CALL (uv_fs_scandir , req , path , flags );
633+ req -> data = luv_setup_req (L , ctx , ref );
634+ int sync = ref == LUA_NOREF ;
635+
636+ // Wrap the req in a garbage-collectable wrapper.
637+ // This allows us to separate the lifetime of the uv_req_t from the lifetime
638+ // of the userdata that gets returned here, since otherwise the returned uv_req_t
639+ // would hold a reference to itself making it ineligible for garbage collection before
640+ // the process ends.
641+ luv_fs_scandir_t * scandir_req = (luv_fs_scandir_t * )lua_newuserdata (L , sizeof (luv_fs_scandir_t ));
642+ scandir_req -> req = req ;
643+ luaL_getmetatable (L , "uv_fs_scandir" );
644+ lua_setmetatable (L , -2 );
645+ int scandir_req_index = lua_gettop (L );
646+
647+ int nargs ;
648+ FS_CALL_NORETURN (uv_fs_scandir , req , path , flags );
649+ // This indicates an error, so we want to return immediately
650+ if (nargs != 1 ) return nargs ;
651+
652+ // Ref the return if this is async, since we don't want this to be garbage collected
653+ // before the callback is called.
654+ if (!sync ) {
655+ lua_pushvalue (L , scandir_req_index );
656+ ((luv_req_t * )req -> data )-> data_ref = luaL_ref (L , LUA_REGISTRYINDEX );
657+ }
658+
659+ lua_pushvalue (L , scandir_req_index );
660+ return 1 ;
619661}
620662
621663static int luv_fs_scandir_next (lua_State * L ) {
0 commit comments