Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

It's possible for Luv to call into Lua with an unusable state (i.e. during lua_close) #437

Open
squeek502 opened this issue Nov 3, 2019 · 1 comment
Labels

Comments

@squeek502
Copy link
Member

squeek502 commented Nov 3, 2019

See #414 and #436.

This is a more general problem than I thought. There are two things here:

  1. It's possible for Lua to be called out to during lua_close due to how handles work in Libuv. One example is in Fix uv.shutdown cb being called when canceled #414 where if there is a shutdown req on a stream handle during gc, then that gets called during uv_close of that handle (which can be during loop_gc). This can be triggered in other ways as well (like a close callback being called during loop_gc)
  2. We aren't handling handle closing properly/safely. In our handle_gc function, we free the handle memory if it's closing, but that is not safe to do, because in Libuv CLOSING and CLOSED are separate states (there can still be pending things on a closing handle like the flag UV_HANDLE_ENDGAME_QUEUED). It's only safe to free a handle once the callback from uv_close is called.

luv/src/handle.c

Lines 116 to 127 in ab4a9ad

if (handle) {
if (!uv_is_closing(handle)) {
// If the handle is not closed yet, close it first before freeing memory.
uv_close(handle, luv_gc_cb);
}
else {
// Otherwise, free the memory right away.
luv_handle_free(handle);
}
// Mark as cleaned up by wiping the dangling pointer.
*udata = NULL;
}

Running the code below will cause Luv to free the handle during GC and then Libuv will try to access it (because of UV_HANDLE_ENDGAME_QUEUED), causing an assert/crash/undefined behaviour (tested on Windows, different platforms have different closing procedures, so not sure if it happens everywhere):

local uv = require('luv')

local test = uv.new_pipe(false)
uv.close(test)
-- during loop_gc, test is marked as closing so luv frees its uv_handle_t memory,
-- but Libuv was not actually done with the handle and tries to access that memory
-- while the loop is walking the handles and closing them so that the loop can be closed

Note that adding a uv.run() at the end of the script fixes this specific case.


I'm not quite sure how to fix either of these. I'll need to do more research and maybe check into how Node handles this sort of thing if possible (Node might be different though since Luv gives direct access to uv_run and I don't think Node does?).

@squeek502
Copy link
Member Author

Partially addressed by #654. See #654 (review) for what's left to do for this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant