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

test: add basic tests and doc for scopes #250

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
test: add basic tests and doc for scopes
  • Loading branch information
mhdawson committed May 3, 2018
commit 3e4e1a3c8c42eb3f4dcf447ddb5273ab8f3829e1
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -64,7 +64,7 @@ values. Concepts and operations generally map to ideas specified in the
- [PropertyDescriptor](doc/property_descriptor.md)
- [Error Handling](doc/error_handling.md)
- [Error](doc/error.md)
- [Object Lifettime Management](doc/object_lifetime_management.md)
- [Object Lifetime Management](doc/object_lifetime_management.md)
- [HandleScope](doc/handle_scope.md)
- [EscapableHandleScope](doc/escapable_handle_scope.md)
- [Working with JavaScript Values](doc/working_with_javascript_values.md)
82 changes: 82 additions & 0 deletions doc/escapable_handle_scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# EscapableHandleScope

The EscapableHandleScope class is used to manage the lifetime of object handles
which are created through the use of node-addon-api. These handles
keep an object alive in the heap in order to ensure that the objects
are not collected by the garbage collector while native code is using them.
A handle may be created when any new node-addon-api Value or one
of its subclasses is created or returned.

An EscapableHandleScope is a special type of HandleScope
which allows a single handle to be "promoted" to an outer scope.

For more details refer to the section titled
(Object lifetime management)[object_lifetime_management].

## Methods

### Constructor

Creates a new escapable handle scope.

```cpp
EscapableHandleScope EscapableHandleScope::New(Napi:Env env);
```

- `[in] Env`: The environment in which to construct the EscapableHandleScope object.

Returns a new EscapableHandleScope

### Constructor

Creates a new escapable handle scope.

```cpp
EscapableHandleScope EscapableHandleScope::New(napi_env env, napi_handle_scope scope);
```

- `[in] env`: napi_env in which the scope passed in was created.
- `[in] scope`: pre-existing napi_handle_scope.

Returns a new EscapableHandleScope instance which wraps the
napi_escapable_handle_scope handle passed in. This can be used
to mix usage of the C N-API and node-addon-api.

operator EscapableHandleScope::napi_escapable_handle_scope

```cpp
operator EscapableHandleScope::napi_escapable_handle_scope() const
```

Returns the N-API napi_escapable_handle_scope wrapped by the EscapableHandleScope object.
This can be used to mix usage of the C N-API and node-addon-api by allowing
the class to be used be converted to a napi_escapable_handle_scope.

### Destructor
```cpp
~EscapableHandleScope();
```

Deletes the EscapableHandleScope instance and allows any objects/handles created
in the scope to be collected by the garbage collector. There is no
guarantee as to when the gargbage collector will do this.

### Escape

```cpp
napi::Value EscapableHandleScope::Escape(napi_value escapee);
```

- `[in] escapee`: Napi::Value or napi_env to promote to the outer scope

Returns Napi:Value which can be used in the outer scope. This method can
be called at most once on a given EscapableHandleScope. If it is called
more than once an exception will be thrown.

### Env

```cpp
Napi::Env Env() const;
```

Returns the Napi:Env associated with the EscapableHandleScope.
5 changes: 0 additions & 5 deletions doc/escapable_handle_sope.md

This file was deleted.

69 changes: 65 additions & 4 deletions doc/handle_scope.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,66 @@
# Handle scope
# HandleScope

You are reading a draft of the next documentation and it's in continuos update so
if you don't find what you need please refer to:
[C++ wrapper classes for the ABI-stable C APIs for Node.js](https://nodejs.github.io/node-addon-api/)
The HandleScope class is used to manage the lifetime of object handles
which are created through the use of node-addon-api. These handles
keep an object alive in the heap in order to ensure that the objects
are not collected while native code is using them.
A handle may be created when any new node-addon-api Value or one
of its subclasses is created or returned. For more details refer to
the section titled (Object lifetime management)[object_lifetime_management].

## Methods

### Constructor

Creates a new handle scope.

```cpp
HandleScope HandleScope::New(Napi:Env env);
```

- `[in] Env`: The environment in which to construct the HandleScope object.

Returns a new HandleScope


### Constructor

Creates a new handle scope.

```cpp
HandleScope HandleScope::New(napi_env env, napi_handle_scope scope);
```

- `[in] env`: napi_env in which the scope passed in was created.
- `[in] scope`: pre-existing napi_handle_scope.

Returns a new HandleScope instance which wraps the napi_handle_scope
handle passed in. This can be used to mix usage of the C N-API
and node-addon-api.

operator HandleScope::napi_handle_scope

```cpp
operator HandleScope::napi_handle_scope() const
```

Returns the N-API napi_handle_scope wrapped by the EscapableHandleScope object.
This can be used to mix usage of the C N-API and node-addon-api by allowing
the class to be used be converted to a napi_handle_scope.

### Destructor
```cpp
~HandleScope();
```

Deletes the HandleScope instance and allows any objects/handles created
in the scope to be collected by the garbage collector. There is no
guarantee as to when the gargbage collector will do this.

### Env

```cpp
Napi::Env Env() const;
```

Returns the Napi:Env associated with the HandleScope.
84 changes: 84 additions & 0 deletions doc/object_lifetime_management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Object lifetime management

A handle may be created when any new node-addon-api Value and
its subclasses is created or returned.

As the methods and classes within the node-addon-api are used,
handles to objects in the heap for the underlying
VM may be created. A handle may be created when any new
node-addon-api Value or one of its subclasses is created or returned.
These handles must hold the objects 'live' until they are no
longer required by the native code, otherwise the objects could be
collected by the garbage collector before the native code was
finished using them.

As handles are created they are associated with a
'scope'. The lifespan for the default scope is tied to the lifespan
of the native method call. The result is that, by default, handles
remain valid and the objects associated with these handles will be
held live for the lifespan of the native method call.

In many cases, however, it is necessary that the handles remain valid for
either a shorter or longer lifespan than that of the native method.
The sections which follow describe the node-addon-api classes and
methods that than can be used to change the handle lifespan from
the default.

## Making handle lifespan shorter than that of the native method

It is often necessary to make the lifespan of handles shorter than
the lifespan of a native method. For example, consider a native method
that has a loop which creates a number of values and does something
with each of the values, one at a time:

```C++
for (int i = 0; i < LOOP_MAX; i++) {
std::string name = std::string("inner-scope") + std::to_string(i);
Value newValue = String::New(info.Env(), name.c_str());
// do something with neValue
};
```

This would result in a large number of handles being created, consuming
substantial resources. In addition, even though the native code could only
use the most recently created value, all of the previously created
values would also be kept alive since they all share the same scope.

To handle this case, node-addon-api provides the ability to establish
a new 'scope' to which newly created handles will be associated. Once those
handles are no longer required, the scope can be deleted and any handles
associated with the scope are invalidated. The `HandleScope`
and `EscapableHandleScope` classes are provided by node-addon-api for
creating additional scopes.

node-addon-api only supports a single nested hierarchy of scopes. There is
only one active scope at any time, and all new handles will be associated
with that scope while it is active. Scopes must be deleted in the reverse
order from which they are opened. In addition, all scopes created within
a native method must be deleted before returning from that method. Since
HandleScopes are typically stack allocated the compiler will take care of
deletion, however, care must be taken to create the scope in the right
place such that you achieve the desired lifetime.

Taking the earlier example, creating a HandleScope in the innner loop
would ensure that at most a single new value is held alive throughout the
execution of the loop:

```C
for (int i = 0; i < LOOP_MAX; i++) {
HandleScope scope(info.Env());
std::string name = std::string("inner-scope") + std::to_string(i);
Value newValue = String::New(info.Env(), name.c_str());
// do something with neValue
};
```

When nesting scopes, there are cases where a handle from an
inner scope needs to live beyond the lifespan of that scope. node-addon-api
provides the `EscapableHandleScope` with the Escape method
in order to support this case. An escapable scope
allows one object to be 'promoted' so that it 'escapes' the
current scope and the lifespan of the handle changes from the current
scope to that of the outer scope. The Escape method can only be called
once for a given EscapableHandleScope.

5 changes: 0 additions & 5 deletions doc/onject_lifetime_management.md

This file was deleted.

2 changes: 2 additions & 0 deletions test/binding.cc
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ Object InitDataViewReadWrite(Env env);
Object InitError(Env env);
Object InitExternal(Env env);
Object InitFunction(Env env);
Object InitHandleScope(Env env);
Object InitName(Env env);
Object InitObject(Env env);
Object InitPromise(Env env);
@@ -31,6 +32,7 @@ Object Init(Env env, Object exports) {
exports.Set("external", InitExternal(env));
exports.Set("function", InitFunction(env));
exports.Set("name", InitName(env));
exports.Set("handlescope", InitHandleScope(env));
exports.Set("object", InitObject(env));
exports.Set("promise", InitPromise(env));
exports.Set("typedarray", InitTypedArray(env));
1 change: 1 addition & 0 deletions test/binding.gyp
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
'error.cc',
'external.cc',
'function.cc',
'handlescope.cc',
'name.cc',
'object/delete_property.cc',
'object/get_property.cc',
56 changes: 56 additions & 0 deletions test/handlescope.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include "napi.h"
#include "string.h"

using namespace Napi;

Value createScope(const CallbackInfo& info) {
{
HandleScope scope(info.Env());
String::New(info.Env(), "inner-scope");
}
return String::New(info.Env(), "scope");
}

Value escapeFromScope(const CallbackInfo& info) {
Value result;
{
EscapableHandleScope scope(info.Env());
result = scope.Escape(String::New(info.Env(), "inner-scope"));
}
return result;
}

#define LOOP_MAX 1000000
Value stressEscapeFromScope(const CallbackInfo& info) {
Value result;
for (int i = 0; i < LOOP_MAX; i++) {
EscapableHandleScope scope(info.Env());
std::string name = std::string("inner-scope") + std::to_string(i);
Value newValue = String::New(info.Env(), name.c_str());
if (i == (LOOP_MAX -1)) {
result = scope.Escape(newValue);
}
}
return result;
}

Value doubleEscapeFromScope(const CallbackInfo& info) {
Value result;
{
EscapableHandleScope scope(info.Env());
result = scope.Escape(String::New(info.Env(), "inner-scope"));
result = scope.Escape(String::New(info.Env(), "inner-scope"));
}
return result;
}

Object InitHandleScope(Env env) {
Object exports = Object::New(env);

exports["createScope"] = Function::New(env, createScope);
exports["escapeFromScope"] = Function::New(env, escapeFromScope);
exports["stressEscapeFromScope"] = Function::New(env, stressEscapeFromScope);
exports["doubleEscapeFromScope"] = Function::New(env, doubleEscapeFromScope);

return exports;
}
15 changes: 15 additions & 0 deletions test/handlescope.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';
const buildType = process.config.target_defaults.default_configuration;
const assert = require('assert');

test(require(`./build/${buildType}/binding.node`));
test(require(`./build/${buildType}/binding_noexcept.node`));

function test(binding) {
assert.strictEqual(binding.handlescope.createScope(), 'scope');
assert.strictEqual(binding.handlescope.escapeFromScope(), 'inner-scope');
assert.strictEqual(binding.handlescope.stressEscapeFromScope(), 'inner-scope999999');
assert.throws(() => binding.handlescope.doubleEscapeFromScope(),
Error,
' napi_escape_handle already called on scope');
}
1 change: 1 addition & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ let testModules = [
'error',
'external',
'function',
'handlescope',
'name',
'object/delete_property',
'object/get_property',