Skip to content

Commit 75086da

Browse files
committed
test: add basic tests and doc for scopes
PR-URL: #250 Reviewed-By: Kyle Farnung <[email protected]> Reviewed-By: Nicola Del Gobbo <[email protected]>
1 parent 341dbd2 commit 75086da

11 files changed

+306
-15
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ values. Concepts and operations generally map to ideas specified in the
6464
- [PropertyDescriptor](doc/property_descriptor.md)
6565
- [Error Handling](doc/error_handling.md)
6666
- [Error](doc/error.md)
67-
- [Object Lifettime Management](doc/object_lifetime_management.md)
67+
- [Object Lifetime Management](doc/object_lifetime_management.md)
6868
- [HandleScope](doc/handle_scope.md)
6969
- [EscapableHandleScope](doc/escapable_handle_scope.md)
7070
- [Working with JavaScript Values](doc/working_with_javascript_values.md)

doc/escapable_handle_scope.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# EscapableHandleScope
2+
3+
The EscapableHandleScope class is used to manage the lifetime of object handles
4+
which are created through the use of node-addon-api. These handles
5+
keep an object alive in the heap in order to ensure that the objects
6+
are not collected by the garbage collector while native code is using them.
7+
A handle may be created when any new node-addon-api Value or one
8+
of its subclasses is created or returned.
9+
10+
An EscapableHandleScope is a special type of HandleScope
11+
which allows a single handle to be "promoted" to an outer scope.
12+
13+
For more details refer to the section titled
14+
(Object lifetime management)[object_lifetime_management].
15+
16+
## Methods
17+
18+
### Constructor
19+
20+
Creates a new escapable handle scope.
21+
22+
```cpp
23+
EscapableHandleScope EscapableHandleScope::New(Napi:Env env);
24+
```
25+
26+
- `[in] Env`: The environment in which to construct the EscapableHandleScope object.
27+
28+
Returns a new EscapableHandleScope
29+
30+
### Constructor
31+
32+
Creates a new escapable handle scope.
33+
34+
```cpp
35+
EscapableHandleScope EscapableHandleScope::New(napi_env env, napi_handle_scope scope);
36+
```
37+
38+
- `[in] env`: napi_env in which the scope passed in was created.
39+
- `[in] scope`: pre-existing napi_handle_scope.
40+
41+
Returns a new EscapableHandleScope instance which wraps the
42+
napi_escapable_handle_scope handle passed in. This can be used
43+
to mix usage of the C N-API and node-addon-api.
44+
45+
operator EscapableHandleScope::napi_escapable_handle_scope
46+
47+
```cpp
48+
operator EscapableHandleScope::napi_escapable_handle_scope() const
49+
```
50+
51+
Returns the N-API napi_escapable_handle_scope wrapped by the EscapableHandleScope object.
52+
This can be used to mix usage of the C N-API and node-addon-api by allowing
53+
the class to be used be converted to a napi_escapable_handle_scope.
54+
55+
### Destructor
56+
```cpp
57+
~EscapableHandleScope();
58+
```
59+
60+
Deletes the EscapableHandleScope instance and allows any objects/handles created
61+
in the scope to be collected by the garbage collector. There is no
62+
guarantee as to when the gargbage collector will do this.
63+
64+
### Escape
65+
66+
```cpp
67+
napi::Value EscapableHandleScope::Escape(napi_value escapee);
68+
```
69+
70+
- `[in] escapee`: Napi::Value or napi_env to promote to the outer scope
71+
72+
Returns Napi:Value which can be used in the outer scope. This method can
73+
be called at most once on a given EscapableHandleScope. If it is called
74+
more than once an exception will be thrown.
75+
76+
### Env
77+
78+
```cpp
79+
Napi::Env Env() const;
80+
```
81+
82+
Returns the Napi:Env associated with the EscapableHandleScope.

doc/escapable_handle_sope.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

doc/handle_scope.md

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,66 @@
1-
# Handle scope
1+
# HandleScope
22

3-
You are reading a draft of the next documentation and it's in continuos update so
4-
if you don't find what you need please refer to:
5-
[C++ wrapper classes for the ABI-stable C APIs for Node.js](https://nodejs.github.io/node-addon-api/)
3+
The HandleScope class is used to manage the lifetime of object handles
4+
which are created through the use of node-addon-api. These handles
5+
keep an object alive in the heap in order to ensure that the objects
6+
are not collected while native code is using them.
7+
A handle may be created when any new node-addon-api Value or one
8+
of its subclasses is created or returned. For more details refer to
9+
the section titled (Object lifetime management)[object_lifetime_management].
10+
11+
## Methods
12+
13+
### Constructor
14+
15+
Creates a new handle scope.
16+
17+
```cpp
18+
HandleScope HandleScope::New(Napi:Env env);
19+
```
20+
21+
- `[in] Env`: The environment in which to construct the HandleScope object.
22+
23+
Returns a new HandleScope
24+
25+
26+
### Constructor
27+
28+
Creates a new handle scope.
29+
30+
```cpp
31+
HandleScope HandleScope::New(napi_env env, napi_handle_scope scope);
32+
```
33+
34+
- `[in] env`: napi_env in which the scope passed in was created.
35+
- `[in] scope`: pre-existing napi_handle_scope.
36+
37+
Returns a new HandleScope instance which wraps the napi_handle_scope
38+
handle passed in. This can be used to mix usage of the C N-API
39+
and node-addon-api.
40+
41+
operator HandleScope::napi_handle_scope
42+
43+
```cpp
44+
operator HandleScope::napi_handle_scope() const
45+
```
46+
47+
Returns the N-API napi_handle_scope wrapped by the EscapableHandleScope object.
48+
This can be used to mix usage of the C N-API and node-addon-api by allowing
49+
the class to be used be converted to a napi_handle_scope.
50+
51+
### Destructor
52+
```cpp
53+
~HandleScope();
54+
```
55+
56+
Deletes the HandleScope instance and allows any objects/handles created
57+
in the scope to be collected by the garbage collector. There is no
58+
guarantee as to when the gargbage collector will do this.
59+
60+
### Env
61+
62+
```cpp
63+
Napi::Env Env() const;
64+
```
65+
66+
Returns the Napi:Env associated with the HandleScope.

doc/object_lifetime_management.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Object lifetime management
2+
3+
A handle may be created when any new node-addon-api Value and
4+
its subclasses is created or returned.
5+
6+
As the methods and classes within the node-addon-api are used,
7+
handles to objects in the heap for the underlying
8+
VM may be created. A handle may be created when any new
9+
node-addon-api Value or one of its subclasses is created or returned.
10+
These handles must hold the objects 'live' until they are no
11+
longer required by the native code, otherwise the objects could be
12+
collected by the garbage collector before the native code was
13+
finished using them.
14+
15+
As handles are created they are associated with a
16+
'scope'. The lifespan for the default scope is tied to the lifespan
17+
of the native method call. The result is that, by default, handles
18+
remain valid and the objects associated with these handles will be
19+
held live for the lifespan of the native method call.
20+
21+
In many cases, however, it is necessary that the handles remain valid for
22+
either a shorter or longer lifespan than that of the native method.
23+
The sections which follow describe the node-addon-api classes and
24+
methods that than can be used to change the handle lifespan from
25+
the default.
26+
27+
## Making handle lifespan shorter than that of the native method
28+
29+
It is often necessary to make the lifespan of handles shorter than
30+
the lifespan of a native method. For example, consider a native method
31+
that has a loop which creates a number of values and does something
32+
with each of the values, one at a time:
33+
34+
```C++
35+
for (int i = 0; i < LOOP_MAX; i++) {
36+
std::string name = std::string("inner-scope") + std::to_string(i);
37+
Value newValue = String::New(info.Env(), name.c_str());
38+
// do something with neValue
39+
};
40+
```
41+
42+
This would result in a large number of handles being created, consuming
43+
substantial resources. In addition, even though the native code could only
44+
use the most recently created value, all of the previously created
45+
values would also be kept alive since they all share the same scope.
46+
47+
To handle this case, node-addon-api provides the ability to establish
48+
a new 'scope' to which newly created handles will be associated. Once those
49+
handles are no longer required, the scope can be deleted and any handles
50+
associated with the scope are invalidated. The `HandleScope`
51+
and `EscapableHandleScope` classes are provided by node-addon-api for
52+
creating additional scopes.
53+
54+
node-addon-api only supports a single nested hierarchy of scopes. There is
55+
only one active scope at any time, and all new handles will be associated
56+
with that scope while it is active. Scopes must be deleted in the reverse
57+
order from which they are opened. In addition, all scopes created within
58+
a native method must be deleted before returning from that method. Since
59+
HandleScopes are typically stack allocated the compiler will take care of
60+
deletion, however, care must be taken to create the scope in the right
61+
place such that you achieve the desired lifetime.
62+
63+
Taking the earlier example, creating a HandleScope in the innner loop
64+
would ensure that at most a single new value is held alive throughout the
65+
execution of the loop:
66+
67+
```C
68+
for (int i = 0; i < LOOP_MAX; i++) {
69+
HandleScope scope(info.Env());
70+
std::string name = std::string("inner-scope") + std::to_string(i);
71+
Value newValue = String::New(info.Env(), name.c_str());
72+
// do something with neValue
73+
};
74+
```
75+
76+
When nesting scopes, there are cases where a handle from an
77+
inner scope needs to live beyond the lifespan of that scope. node-addon-api
78+
provides the `EscapableHandleScope` with the Escape method
79+
in order to support this case. An escapable scope
80+
allows one object to be 'promoted' so that it 'escapes' the
81+
current scope and the lifespan of the handle changes from the current
82+
scope to that of the outer scope. The Escape method can only be called
83+
once for a given EscapableHandleScope.

doc/onject_lifetime_management.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

test/binding.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Object InitDataViewReadWrite(Env env);
1212
Object InitError(Env env);
1313
Object InitExternal(Env env);
1414
Object InitFunction(Env env);
15+
Object InitHandleScope(Env env);
1516
Object InitName(Env env);
1617
Object InitObject(Env env);
1718
Object InitPromise(Env env);
@@ -31,6 +32,7 @@ Object Init(Env env, Object exports) {
3132
exports.Set("external", InitExternal(env));
3233
exports.Set("function", InitFunction(env));
3334
exports.Set("name", InitName(env));
35+
exports.Set("handlescope", InitHandleScope(env));
3436
exports.Set("object", InitObject(env));
3537
exports.Set("promise", InitPromise(env));
3638
exports.Set("typedarray", InitTypedArray(env));

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
'error.cc',
1313
'external.cc',
1414
'function.cc',
15+
'handlescope.cc',
1516
'name.cc',
1617
'object/delete_property.cc',
1718
'object/get_property.cc',

test/handlescope.cc

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include "napi.h"
2+
#include "string.h"
3+
4+
using namespace Napi;
5+
6+
Value createScope(const CallbackInfo& info) {
7+
{
8+
HandleScope scope(info.Env());
9+
String::New(info.Env(), "inner-scope");
10+
}
11+
return String::New(info.Env(), "scope");
12+
}
13+
14+
Value escapeFromScope(const CallbackInfo& info) {
15+
Value result;
16+
{
17+
EscapableHandleScope scope(info.Env());
18+
result = scope.Escape(String::New(info.Env(), "inner-scope"));
19+
}
20+
return result;
21+
}
22+
23+
#define LOOP_MAX 1000000
24+
Value stressEscapeFromScope(const CallbackInfo& info) {
25+
Value result;
26+
for (int i = 0; i < LOOP_MAX; i++) {
27+
EscapableHandleScope scope(info.Env());
28+
std::string name = std::string("inner-scope") + std::to_string(i);
29+
Value newValue = String::New(info.Env(), name.c_str());
30+
if (i == (LOOP_MAX -1)) {
31+
result = scope.Escape(newValue);
32+
}
33+
}
34+
return result;
35+
}
36+
37+
Value doubleEscapeFromScope(const CallbackInfo& info) {
38+
Value result;
39+
{
40+
EscapableHandleScope scope(info.Env());
41+
result = scope.Escape(String::New(info.Env(), "inner-scope"));
42+
result = scope.Escape(String::New(info.Env(), "inner-scope"));
43+
}
44+
return result;
45+
}
46+
47+
Object InitHandleScope(Env env) {
48+
Object exports = Object::New(env);
49+
50+
exports["createScope"] = Function::New(env, createScope);
51+
exports["escapeFromScope"] = Function::New(env, escapeFromScope);
52+
exports["stressEscapeFromScope"] = Function::New(env, stressEscapeFromScope);
53+
exports["doubleEscapeFromScope"] = Function::New(env, doubleEscapeFromScope);
54+
55+
return exports;
56+
}

test/handlescope.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
const buildType = process.config.target_defaults.default_configuration;
3+
const assert = require('assert');
4+
5+
test(require(`./build/${buildType}/binding.node`));
6+
test(require(`./build/${buildType}/binding_noexcept.node`));
7+
8+
function test(binding) {
9+
assert.strictEqual(binding.handlescope.createScope(), 'scope');
10+
assert.strictEqual(binding.handlescope.escapeFromScope(), 'inner-scope');
11+
assert.strictEqual(binding.handlescope.stressEscapeFromScope(), 'inner-scope999999');
12+
assert.throws(() => binding.handlescope.doubleEscapeFromScope(),
13+
Error,
14+
' napi_escape_handle already called on scope');
15+
}

0 commit comments

Comments
 (0)