-
Notifications
You must be signed in to change notification settings - Fork 3.7k
sunos: fs-event callback can be called after uv_close() #3542
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
Conversation
|
@jclulow - would appreciate your review here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
|
I've added a test for this issue, which fails without the patch: and succeeds afterwards |
5bc1ffc to
c7d5b3e
Compare
On illumos and Solaris, fs events are implemented with PORT_SOURCE_FILE type event ports. These are one-shot so need re-arming each time they fire. Once they are armed and an event occurs, the kernel removes them from the current cache list and puts them on an event queue to be read by the application. There's a window in closing one of these ports when it could have triggered and be pending delivery. In that case, the attempt to disarm (dissociate) the event will fail with ENOENT but libuv still goes ahead and closes down the handle. In particular, the close callback (uv_close() argument) will be called but then the event will subsequently be delivered if the loop is still active; this should not happen.
since the loop went inactive.
On illumos and Solaris, fs events are implemented with PORT_SOURCE_FILE type event ports. These are one-shot so need re-arming each time they fire. Once they are armed and an event occurs, the kernel removes them from the current cache list and puts them on an event queue to be read by the application. There's a window in closing one of these ports when it could have triggered and be pending delivery. In that case, the attempt to disarm (dissociate) the event will fail with ENOENT but libuv still goes ahead and closes down the handle. In particular, the close callback (uv_close() argument) will be called but then the event will subsequently be delivered if the loop is still active; this should not happen.
| uv__handle_stop(handle); | ||
| uv__make_close_pending((uv_handle_t*) handle); | ||
| break; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In reviewing #3620, I noticed a possible defect introduced here, and wanted to check my understanding. Doesn't this need to now check if the user attempted to stop the handle here?
| } | |
| } else if (handle->fd == PORT_DELETED) { | |
| uv__handle_stop(handle); | |
| break; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Secondly, there is a uv__is_active check in uv_fs_event_start, and I think that is now incorrect (or at least insufficient) and also needs to permit the case where uv_fs_event_stop didn't finish yet. But also while maintaining this fix (so that if you do a sequence of stop/event/start/close, it will still remember to defer the close).
I am mildly tempted to suggest we should instead soft-deprecate uv_fs_event_stop (in favor of uv_close)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a while since I looked at this now but, yes, I think you're right for the us_fs_event_stop() case when the event has already been despatched and is awaiting delivery. It will need a uv__make_close_pending() too though, wouldn't it? It could then just be added to the condition above:
if (uv__is_closing(handle) || handle->fd == PORT_DELETED) {
...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uv__make_close_pending shouldn't be triggered, since the handle is only paused, not closed
On illumos and Solaris, fs events are implemented with
PORT_SOURCE_FILE type event ports. These are one-shot so
need re-arming each time they fire. Once they are armed
and an event occurs, the kernel removes them from the current
cache list and puts them on an event queue to be read by
the application.
There's a window in closing one of these ports when it could
have triggered and be pending delivery. In that case, the
attempt to disarm (dissociate) the event will fail with ENOENT
but libuv still goes ahead and closes down the handle. In
particular, the close callback (uv_close() argument) will be
called but then the event will subsequently be delivered if
the loop is still active; this should not happen.
This was originally reported to me as a problem with using
nodejs with the nodemon package. When monitoring a tree of
files and making a lot of quick changes (for example switching
a project's branch), node crashes:
Using dtrace, I was able to see what was happening here:
First, the fs event is started and associated.
Then, when the file is removed from the filesystem, the kernel moves
the event to the pending list
Here is
uv_close()attempting to dissociate the event, which returnsENOENT:
and, finally, the handle is freed and then the event is returned,
now pointing to the freed memory (ev 10 == file deleted). Note that
the handle is in the closing list on the call to uv__fs_event_read():
All tests pass successfully with this change in place, on both illumos and Solaris.