Skip to content

Commit

Permalink
Implement AsyncContext class
Browse files Browse the repository at this point in the history
This class provides a wrapper for the following custom asynchronous
operation APIs.
  - napi_async_init()
  - napi_async_destroy()
  - napi_make_callback()
  • Loading branch information
romandev committed Jul 13, 2018
1 parent 11697fc commit f2b4f2a
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ still a work in progress as its not yet complete).
- [Memory Management](doc/memory_management.md)
- [Async Operations](doc/async_operations.md)
- [AsyncWorker](doc/async_worker.md)
- [AsyncContext](doc/async_context.md)
- [Promises](doc/promises.md)

<a name="examples"></a>
Expand Down
151 changes: 151 additions & 0 deletions doc/async_context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# AsyncContext

The [AsyncWorker](async_worker.md) class may not be appropriate for every scenario, because with
those the async execution still happens on the main event loop. When using any
other async mechanism, introducing a new class `AsyncContext` is necessary to
ensure an async operation is properly tracked by the runtime.
The `AsyncContext` class provides `MakeCallback()` method to properly restore
the correct async execution context.

## Methods

### MakeCallback

This method is used to call from native code back into JavaScript after
returning from an async operation (when there is no other script on the stack).

```cpp
Value MakeCallback() const
```

Returns a `Value` representing the JavaScript object returned.

### MakeCallback

This method is used to call from native code back into JavaScript after
returning from an async operation (when there is no other script on the stack).

```cpp
Value MakeCallback(const Object& receiver) const
```
- `[in] receiver`: The `this` object passed to the callback.
Returns a `Value` representing the JavaScript object returned.
### MakeCallback
This method is used to call from native code back into JavaScript after
returning from an async operation (when there is no other script on the stack).
```cpp
Value MakeCallback(const std::initializer_list<napi_value>& args) const
```

- `[in] args`: Initializer list of JavaScript values as `napi_value`
representing the arguments to the callback.

Returns a `Value` representing the JavaScript object returned.

### MakeCallback

This method is used to call from native code back into JavaScript after
returning from an async operation (when there is no other script on the stack).

```cpp
Value MakeCallback(const Object& receiver, const std::initializer_list<napi_value>& args) const
```
- `[in] receiver`: The `this` object passed to the callback.
- `[in] args`: Initializer list of JavaScript values as `napi_value`
representing the arguments to the callback.
Returns a `Value` representing the JavaScript object returned.
### Constructor
Creates a new `AsyncContext`.
```cpp
explicit AsyncContext(const char* resource_name, const Function& callback);
```

- `[in] resource_name`: Null-terminated strings that represents the
identifier for the kind of resource that is being provided for diagnostic
information exposed by the async_hooks API.
- `[in] callback`: The function which will be called when an asynchronous
operations ends.

Returns an AsyncContext instance which can later make the given callback by
`MakeCallback()` method.

### Constructor

Creates a new `AsyncContext`.

```cpp
explicit AsyncContext(const char* resource_name, const Object& resource, const Function& callback);
```
- `[in] resource_name`: Null-terminated strings that represents the
identifier for the kind of resource that is being provided for diagnostic
information exposed by the async_hooks API.
- `[in] resource`: Object associated with the asynchronous operation that
will be passed to possible async_hooks.
- `[in] callback`: The function which will be called when an asynchronous
operations ends.
Returns an AsyncContext instance which can later make the given callback by
`MakeCallback()` method.
### Destructor
The async context to be destroyed.
```cpp
virtual ~AsyncContext();
```

### Example

```cpp
#include "napi.h"
#include "uv.h"

using namespace Napi;

void HeavyWork(uv_work_t* request) {
...
}

void DidHeavyWork(uv_work_t* request, int status) {
HandleScope scope;
AsyncContext* context = static_cast<AsyncContext*>(request->data);

// Run the callback in the async context.
context->MakeCallback();

// The async context is destroyed.
delete context;
}

void RunAsync(const CallbackInfo& info) {
Function callback = info[0].As<Function>();

// Creat a new async context instance.
AsyncContext* new AsyncContext("TestResource", callback);

// You can queue a task with the async context to the custom async mechanism.
// This example is using `libuv` as custom async mechanism but it can be
// anything.
uv_work_t* request = new uv_work_t();
request->data = context;
uv_queue_work(
uv_default_loop(),
request,
HeavyWork,
reinterpret_cast<uv_after_work_cb>(DidHeavyWork));
}

}
```
6 changes: 6 additions & 0 deletions doc/async_operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ asynchronous operations:

These class helps manage asynchronous operations through an abstraction
of the concept of moving data between the **event loop** and **worker threads**.

Also, the above class may not be appropriate for every scenario. When using any
other asynchronous mechanism, the following API is necessary to ensure an
asynchronous operation is properly tracked by the runtime:

- **[AsyncContext](async_context.md)**
67 changes: 67 additions & 0 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,73 @@ inline Value EscapableHandleScope::Escape(napi_value escapee) {
return Value(_env, result);
}

////////////////////////////////////////////////////////////////////////////////
// AsyncContext class
////////////////////////////////////////////////////////////////////////////////

inline AsyncContext::AsyncContext(const char* resource_name,
const Function& callback)
: AsyncContext(resource_name, Object::New(callback.Env()), callback) {
}

inline AsyncContext::AsyncContext(const char* resource_name,
const Object& resource,
const Function& callback)
: _env(callback.Env()),
_context(nullptr),
_callback(Napi::Persistent(callback)) {
napi_value resource_id;
napi_status status = napi_create_string_utf8(
_env, resource_name, NAPI_AUTO_LENGTH, &resource_id);
NAPI_THROW_IF_FAILED(_env, status);

status = napi_async_init(_env, resource, resource_id, &_context);
NAPI_THROW_IF_FAILED(_env, status);
}

inline AsyncContext::~AsyncContext() {
if (_context != nullptr) {
napi_async_destroy(_env, _context);
_context = nullptr;
}
}

inline AsyncContext::AsyncContext(AsyncContext&& other) {
_env = other._env;
other._env = nullptr;
_context = other._context;
other._context = nullptr;
_callback = std::move(other._callback);
}

inline AsyncContext& AsyncContext::operator =(AsyncContext&& other) {
_env = other._env;
other._env = nullptr;
_context = other._context;
other._context = nullptr;
_callback = std::move(other._callback);
return *this;
}

inline Value AsyncContext::MakeCallback() const {
return MakeCallback(Object::New(_env));
}

inline Value AsyncContext::MakeCallback(const Object& receiver) const {
return _callback.MakeCallback(receiver, std::initializer_list<napi_value>{});
}

inline Value AsyncContext::MakeCallback(
const std::initializer_list<napi_value>& args) const {
return MakeCallback(Object::New(_env), args);
}

inline Value AsyncContext::MakeCallback(
const Object& receiver,
const std::initializer_list<napi_value>& args) const {
return _callback.MakeCallback(receiver, args);
}

////////////////////////////////////////////////////////////////////////////////
// AsyncWorker class
////////////////////////////////////////////////////////////////////////////////
Expand Down
25 changes: 25 additions & 0 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,31 @@ namespace Napi {
napi_escapable_handle_scope _scope;
};

class AsyncContext {
public:
explicit AsyncContext(const char* resource_name, const Function& callback);
explicit AsyncContext(const char* resource_name,
const Object& resource,
const Function& callback);
virtual ~AsyncContext();

AsyncContext(AsyncContext&& other);
AsyncContext& operator =(AsyncContext&& other);
AsyncContext(const AsyncContext&) = delete;
AsyncContext& operator =(AsyncContext&) = delete;

Value MakeCallback() const;
Value MakeCallback(const Object& receiver) const;
Value MakeCallback(const std::initializer_list<napi_value>& args) const;
Value MakeCallback(const Object& receiver,
const std::initializer_list<napi_value>& args) const;

private:
napi_env _env;
napi_async_context _context;
FunctionReference _callback;
};

class AsyncWorker {
public:
virtual ~AsyncWorker();
Expand Down
26 changes: 26 additions & 0 deletions test/asynccontext.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include "napi.h"

using namespace Napi;

namespace {

static AsyncContext* context;

static void CreateAsyncContext(const CallbackInfo& info) {
Function callback = info[0].As<Function>();
context = new AsyncContext("TestResource", callback);
}

static void MakeCallback(const CallbackInfo& info) {
context->MakeCallback();
delete context;
}

} // end anonymous namespace

Object InitAsyncContext(Env env) {
Object exports = Object::New(env);
exports["createAsyncContext"] = Function::New(env, CreateAsyncContext);
exports["makeCallback"] = Function::New(env, MakeCallback);
return exports;
}
16 changes: 16 additions & 0 deletions test/asynccontext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'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) {
let called = false;
binding.asynccontext.createAsyncContext(function() {
called = true;
});
assert.strictEqual(called, false);
binding.asynccontext.makeCallback();
assert.strictEqual(called, true);
}
2 changes: 2 additions & 0 deletions test/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using namespace Napi;

Object InitArrayBuffer(Env env);
Object InitAsyncContext(Env env);
Object InitAsyncWorker(Env env);
Object InitBasicTypesNumber(Env env);
Object InitBasicTypesValue(Env env);
Expand All @@ -22,6 +23,7 @@ Object InitObjectWrap(Env env);

Object Init(Env env, Object exports) {
exports.Set("arraybuffer", InitArrayBuffer(env));
exports.Set("asynccontext", InitAsyncContext(env));
exports.Set("asyncworker", InitAsyncWorker(env));
exports.Set("basic_types_number", InitBasicTypesNumber(env));
exports.Set("basic_types_value", InitBasicTypesValue(env));
Expand Down
1 change: 1 addition & 0 deletions test/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
'target_defaults': {
'sources': [
'arraybuffer.cc',
'asynccontext.cc',
'asyncworker.cc',
'basic_types/number.cc',
'basic_types/value.cc',
Expand Down
1 change: 1 addition & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ process.config.target_defaults.default_configuration =
// explicit declaration as follows.
let testModules = [
'arraybuffer',
'asynccontext',
'asyncworker',
'basic_types/number',
'basic_types/value',
Expand Down

0 comments on commit f2b4f2a

Please sign in to comment.