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

n-api: re-implement async env cleanup hooks #34819

Conversation

gabrielschulhof
Copy link
Contributor

  • Avoid passing core void* and function pointers into add-on.
  • Document napi_async_cleanup_hook_handle type.
  • Render receipt of the handle mandatory. Removal of the handle remains
    mandatory.

Fixes: #34715
Signed-off-by: Gabriel Schulhof gabriel.schulhof@intel.com

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/n-api

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. labels Aug 18, 2020
@@ -1612,13 +1650,15 @@ added: REPLACEME

```c
NAPI_EXTERN napi_status napi_remove_async_cleanup_hook(
napi_env env,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the need to pass napi_env here because we wish to discourage JS execution in environment cleanup hooks and this way it should prevent add-ons from having to store the env for the sole purpose of calling this function from a uv_close callback.

We store napi_env internally and it's guaranteed to be usable for the lifetime of a remove_handle, because we Ref() it during its construction and Unref() it asynchronously during its destruction.

@gabrielschulhof gabrielschulhof added the node-api Issues and PRs related to the Node-API. label Aug 18, 2020
Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in the issue, I'm missing the motivation for this change here.

I, personally, feel like the previous interface was a bit clearer because the signature of the cleanup hook implied that the callback function needed to be called.

In any case, the documentation needs to be updated with changes: sections.

src/node_api.cc Outdated Show resolved Hide resolved
src/node_api.cc Outdated Show resolved Hide resolved
@@ -1585,23 +1616,30 @@ added: REPLACEME
```c
NAPI_EXTERN napi_status napi_add_async_cleanup_hook(
napi_env env,
void (*fun)(void* arg, void(* cb)(void*), void* cbarg),
napi_async_cleanup_hook hook,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're removing the signature here, please add a link to the definition of napi_async_cleanup_hook

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the link below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like how this better hides the internal implementation. It seemed like we were leaking a bit more than needed in the N-API APIs wrapping the internal node api.

@gabrielschulhof
Copy link
Contributor Author

@addaleax I have addressed your review comments.

Qualitatively, I think this approach makes napi_add_async_cleanup_hook() and napi_remove_async_cleanup_hook more into bookends for a specific cleanup operation. Furthermore, there is a nice symmetry produced by the fact that the entirety of the setup for a hook happens in its constructor, and the entirety of its teardown happens in its destructor. Additionally, it leaves only one value for the add-on maintainer to track.

I understand that these are all qualitative arguments and, as such, more or less a matter of taste. Quantitatively, perhaps, we can look at the test and see that it is simplified by 18 lines, though, since the change removes support for optional storage of the handle, the six removed lines that tested for that feature should perhaps not be considered. That still leaves 12 lines that are an indication that perhaps using async cleanup hooks by way of this API rather than the previous API results in less add-on code that needs writing and maintaining.

@addaleax
Copy link
Member

@gabrielschulhof I mean, I think any high-level wrapper like the C++ one would expose these two versions the same way anyway :)

Ultimately, I'm good with this if you think it's worth the breakage.

@gabrielschulhof
Copy link
Contributor Author

This is an overview for those who might wish to review of the current implementation vs. the proposed implementation showing how each would handle the most common use case:

Current implementation                               Proposed implementation
|                                                    |
v                                                    v
+------------------------------------------------+   +----------------------------------------------+
| Create async thing (e.g. uv handle)            |   | Create async thing (e.g. uv handle)          |
| napi_add_async_cleanup_hook(hook_cb)           |   | napi_add_async_cleanup_hook(hook_cb)         |
| Optional: Store returned hook_handle           | ! | Mandatory: Store returned hook_handle        |
+------------------------------------------------+   +----------------------------------------------+
|                                                    |
v                                                    v
+- hook_cb: -------------------------------------+   +- hook_cb: -----------------------------------+
| store done_cb (function ptr)                   | ! |                                              |
| store done_arg (void*)                         | ! |                                              |
| if a handle was stored                         | ! |                                              |
|   call napi_remove_async_cleanup_hook(handle)  | ! |                                              |
| Start closing the async thing (e.g uv_close)   |   | Start closing the async thing (e.g uv_close) |
+------------------------------------------------+   +----------------------------------------------+
|                                                    |
v                                                    v
+- uv_close cb: ---------------------------------+   +- uv_close cb: -------------------------------+
| call done_cb(done_arg)                         | ! | call napi_remove_async_cleanup_hook(handle)  |
+------------------------------------------------+   +----------------------------------------------+


+--+
|  | <-- an iteration of the event loop
+--+

Gabriel Schulhof and others added 5 commits August 19, 2020 11:17
* Avoid passing core `void*` and function pointers into add-on.
* Document `napi_async_cleanup_hook_handle` type.
* Render receipt of the handle mandatory. Removal of the handle remains
  mandatory.

Fixes: nodejs#34715
Signed-off-by: Gabriel Schulhof <gabriel.schulhof@intel.com>
Co-authored-by: Anna Henningsen <github@addaleax.net>
@gabrielschulhof
Copy link
Contributor Author

@addaleax I added the changes: sections you requested.

Copy link
Member

@himself65 himself65 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nodejs-github-bot
Copy link
Collaborator


node::RemoveEnvironmentCleanupHook(std::move(remove_handle->handle));
delete remove_handle;
if (remove_handle == nullptr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can still be optional to get the handle when the hook is added. When the hook is called it is passed the handle so unless it wants to call remove before the hook runs, it does not need to get/store the handle. I'd suggest we change back to this being optional.

Copy link
Member

@mhdawson mhdawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM after being updated to make getting the handle on additional optional like it was before.

@mhdawson
Copy link
Member

The assumption is that there should be little to no usage of the existing API as it's only be out for ~ 3weeks from what Gabriel tells me and as an Experimental API would should be able to change it in this case.

@gabrielschulhof
Copy link
Contributor Author

gabrielschulhof commented Aug 26, 2020

@mhdawson it is once more optional to receive and store the handle during creation. It is nevertheless still mandatory to store the handle when receiving it in the cleanup hook, because it must be passed to napi_remove_async_cleanup_hook when the handle cleanup completes:

Current implementation                               Proposed implementation
|                                                    |
v                                                    v
+------------------------------------------------+   +----------------------------------------------+
| Create async thing (e.g. uv handle)            |   | Create async thing (e.g. uv handle)          |
| napi_add_async_cleanup_hook(hook_cb)           |   | napi_add_async_cleanup_hook(hook_cb)         |
| Optional: Store returned hook_handle           |   | Optional: Store returned hook_handle         |
+------------------------------------------------+   +----------------------------------------------+
|                                                    |
v                                                    v
+- hook_cb: -------------------------------------+   +- hook_cb: -----------------------------------+
| store done_cb (function ptr)                   | ! |                                              |
| store done_arg (void*)                         | ! |                                              |
| if a handle was stored                         | ! | if a handle was not stored                   |
|   call napi_remove_async_cleanup_hook(handle)  | ! |   store it for use in the uv_close cb        |
| Start closing the async thing (e.g uv_close)   |   | Start closing the async thing (e.g uv_close) |
+------------------------------------------------+   +----------------------------------------------+
|                                                    |
v                                                    v
+- uv_close cb: ---------------------------------+   +- uv_close cb: -------------------------------+
| call done_cb(done_arg)                         | ! | call napi_remove_async_cleanup_hook(handle)  |
+------------------------------------------------+   +----------------------------------------------+


+--+
|  | <-- an iteration of the event loop
+--+

This actually reduces the breakage slightly because the first step is now identical to the current implementation.

@gabrielschulhof
Copy link
Contributor Author

Please mind the edit above, sorry:

- "...the first step is not identical..."
+ "...the first step is now identical..."

@nodejs-github-bot
Copy link
Collaborator

@nodejs-github-bot
Copy link
Collaborator

Copy link
Member

@mhdawson mhdawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@gabrielschulhof
Copy link
Contributor Author

Landed in 0848f56.

gabrielschulhof pushed a commit that referenced this pull request Aug 27, 2020
* Avoid passing core `void*` and function pointers into add-on.
* Document `napi_async_cleanup_hook_handle` type.
* Render receipt of the handle mandatory from the point where the
  hook gets called. Removal of the handle remains mandatory.

Fixes: #34715
Signed-off-by: Gabriel Schulhof <gabriel.schulhof@intel.com>
Co-authored-by: Anna Henningsen <github@addaleax.net>
PR-URL: #34819
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Zeyu Yang <himself65@outlook.com>
@gabrielschulhof gabrielschulhof deleted the re-implement-async-cleanup-hooks branch August 27, 2020 15:21
richardlau pushed a commit that referenced this pull request Sep 1, 2020
* Avoid passing core `void*` and function pointers into add-on.
* Document `napi_async_cleanup_hook_handle` type.
* Render receipt of the handle mandatory from the point where the
  hook gets called. Removal of the handle remains mandatory.

Fixes: #34715
Signed-off-by: Gabriel Schulhof <gabriel.schulhof@intel.com>
Co-authored-by: Anna Henningsen <github@addaleax.net>
PR-URL: #34819
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Zeyu Yang <himself65@outlook.com>
@richardlau richardlau mentioned this pull request Sep 2, 2020
4 tasks
ianhattendorf added a commit to ianhattendorf/nodegit that referenced this pull request Sep 11, 2020
codebytere added a commit to electron/electron that referenced this pull request Sep 21, 2020
codebytere added a commit to electron/electron that referenced this pull request Sep 21, 2020
codebytere added a commit to electron/electron that referenced this pull request Sep 21, 2020
codebytere added a commit to electron/electron that referenced this pull request Sep 21, 2020
addaleax added a commit that referenced this pull request Sep 26, 2020
* Avoid passing core `void*` and function pointers into add-on.
* Document `napi_async_cleanup_hook_handle` type.
* Render receipt of the handle mandatory from the point where the
  hook gets called. Removal of the handle remains mandatory.

Fixes: #34715
Signed-off-by: Gabriel Schulhof <gabriel.schulhof@intel.com>
Co-authored-by: Anna Henningsen <github@addaleax.net>
PR-URL: #34819
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Zeyu Yang <himself65@outlook.com>
@codebytere codebytere mentioned this pull request Sep 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. node-api Issues and PRs related to the Node-API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

N-API: Some issues with the new async cleanup hooks
5 participants