From 9d38f61afbd9f574c96b6a3ee401399a3e4bbb21 Mon Sep 17 00:00:00 2001 From: Jim Schlight Date: Thu, 29 Mar 2018 12:04:31 -0700 Subject: [PATCH 001/696] doc: New Promise and Reference docs PR-URL: https://github.com/nodejs/node-addon-api/pull/243 Reviewed-By: Michael Dawson Reviewed-By: Kyle Farnung --- doc/promises.md | 71 +++++++++++++++++++++++++++-- doc/reference.md | 116 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 181 insertions(+), 6 deletions(-) diff --git a/doc/promises.md b/doc/promises.md index 40fc9fa69..627705387 100644 --- a/doc/promises.md +++ b/doc/promises.md @@ -1,5 +1,70 @@ -# Promise - -You are reading a draft of the next documentation and it's in continuous update so +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/) + +# Promise + +The Promise class, along with its Promise::Deferred class, implement the ability to create, resolve, and reject Promise objects. + +The basic approach is to create a Promise::Deferred object and return to your caller the value returned by the Promise::Deferred::Promise method. For example: + +```cpp +Value YourFunction(const CallbackInfo& info) { + // your code goes here... + Promise::Deferred deferred = Promise::Deferred::New(info.Env()); + // deferred needs to survive this call... + return deferred.Promise(); +} +``` + +Later, when the asynchronous process completes, call either the `Resolve` or `Reject` method on the Promise::Deferred object created earlier: + +```cpp + deferred.Resolve(String::New(info.Env(), "OK")); +``` + +## Promise::Deferred Methods + +### Factory Method + +```cpp +static Promise::Deferred Promise::Deferred::New(napi_env env); +``` + +* `[in] env`: The `napi_env` environment in which to create the Deferred object. + +### Constructor + +```cpp +Promise::Deferred(napi_env env); +``` + +* `[in] env`: The `napi_env` environment in which to construct the Deferred object. + +### Promise + +```cpp +Promise Promise::Deferred::Promise() const; +``` + +Returns the Promise object held by the Promise::Deferred object. + +### Resolve + +```cpp +void Promise::Deferred::Resolve(napi_value value) const; +``` + +Resolves the Promise object held by the Promise::Deferred object. + +* `[in] value`: The N-API primitive value with which to resolve the Promise. + +### Reject + +```cpp +void Promise::Deferred::Reject(napi_value value) const; +``` + +Rejects the Promise object held by the Promise::Deferred object. + +* `[in] value`: The N-API primitive value with which to reject the Promise. diff --git a/doc/reference.md b/doc/reference.md index e7580befa..c25f98d8e 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -1,5 +1,115 @@ -# Reference - -You are reading a draft of the next documentation and it's in continuous update so +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/) + +# Reference (template) + +Holds a counted reference to a [Value](value.md) object; initially a weak reference unless otherwise specified, may be changed to/from a strong reference by adjusting the refcount. + +The referenced Value is not immediately destroyed when the reference count is zero; it is merely then eligible for garbage-collection if there are no other references to the Value. + +Reference objects allocated in static space, such as a global static instance, must call the `SuppressDestruct` method to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. + +The following classes inherit, either directly or indirectly, from Reference: + +* [ObjectWrap](object_wrap.md) +* [ObjectReference](object_reference.md) +* [FunctionReference](function_reference.md) + +## Methods + +### Factory Method + +```cpp +static Reference New(const T& value, uint32_t initialRefcount = 0); +``` + +* `[in] value`: The value which is to be referenced. + +* `[in] initialRefcount`: The initial reference count. + +### Empty Constructor + +```cpp +Reference(); +``` + +Creates a new _empty_ Reference instance. + +### Constructor + +```cpp +Reference(napi_env env, napi_value value); +``` + +* `[in] env`: The `napi_env` environment in which to construct the Reference object. + +* `[in] value`: The N-API primitive value to be held by the Reference. + +### Env + +```cpp +Napi::Env Env() const; +``` + +Returns the `Env` value in which the Reference was instantiated. + +### IsEmpty + +```cpp +bool IsEmpty() const; +``` + +Determines whether the value held by the Reference is empty. + +### Value + +```cpp +T Value() const; +``` + +Returns the value held by the Reference. + +### Ref + +```cpp +uint32_t Ref(); +``` + +Increments the reference count for the Reference and returns the resulting reference count. Throws an error if the increment fails. + +### Unref + +```cpp +uint32_t Unref(); +``` + +Decrements the reference count for the Reference and returns the resulting reference count. Throws an error if the decrement fails. + +### Reset (Empty) + +```cpp +void Reset(); +``` + +Sets the value held by the Reference to be empty. + +### Reset + +```cpp +void Reset(const T& value, uint32_t refcount = 0); +``` + +* `[in] value`: The value which is to be referenced. + +* `[in] initialRefcount`: The initial reference count. + +Sets the value held by the Reference. + +### SuppressDestruct + +```cpp +void SuppressDestruct(); +``` + +Call this method on a Reference that is declared as static data to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. From 5a63f45eda5e395de15a3e14d2406051b8d981cd Mon Sep 17 00:00:00 2001 From: NickNaso Date: Thu, 8 Mar 2018 20:16:01 +0100 Subject: [PATCH 002/696] doc: First step of error and async doc PR-URL: https://github.com/nodejs/node-addon-api/pull/272 Reviewed-By: Kyle Farnung Reviewed-By: Michael Dawson --- README.md | 2 + doc/async_operations.md | 24 +++- doc/async_worker.md | 309 +++++++++++++++++++++++++++++++++++++++- doc/error.md | 116 ++++++++++++++- doc/error_handling.md | 157 +++++++++++++++++++- doc/range_error.md | 59 ++++++++ doc/setup.md | 2 +- doc/type_error.md | 59 ++++++++ 8 files changed, 714 insertions(+), 14 deletions(-) create mode 100644 doc/range_error.md create mode 100644 doc/type_error.md diff --git a/README.md b/README.md index e4bfd25a8..c4fba45f2 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ 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) + - [TypeError](doc/type_error.md) + - [RangeError](doc/range_error.md) - [Object Lifetime Management](doc/object_lifetime_management.md) - [HandleScope](doc/handle_scope.md) - [EscapableHandleScope](doc/escapable_handle_scope.md) diff --git a/doc/async_operations.md b/doc/async_operations.md index 399346862..b8dec37cf 100644 --- a/doc/async_operations.md +++ b/doc/async_operations.md @@ -1,5 +1,23 @@ # Asynchronous operations -You are reading a draft of the next documentation and it's in continuous 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/) +Node.js native add-ons often need to execute long running tasks and to avoid +blocking the **event loop** they have to run them asynchronously from the +**event loop**. +In the Node.js model of execution the event loop thread represents the thread +where JavaScript code is executing. The node.js guidance is to avoid blocking +other work queued on the event loop thread. Therefore, we need to do this work on +another thread. + +All this means that native add-ons need to leverage async helpers from libuv as +part of their implementation. This allows them to schedule work to be executed +asynchronously so that their methods can return in advance of the work being +completed. + +Node Addon API provides an interface to support functions that cover +the most common asynchronous use cases. There is an abstract classes to implement +asynchronous operations: + +- **[AsyncWorker](async_worker.md)** + +These class helps manage asynchronous operations through an abstraction +of the concept of moving data between the **event loop** and **worker threads**. diff --git a/doc/async_worker.md b/doc/async_worker.md index a1a96890d..0d78c1b62 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -1,5 +1,306 @@ -# Async worker +# AsyncWorker -You are reading a draft of the next documentation and it's in continuous 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/) +`AsyncWorker` is an abstract class that you can subclass to remove many of the +tedious tasks of moving data between the event loop and worker threads. This +class internally handles all the details of creating and executing an asynchronous +operation. + +Once created, execution is requested by calling `Queue`. When a thread is +available for execution the `Execute` method will be invoked. Once `Execute` +complets either `OnOK` or `OnError` will be invoked. Once the `OnOK` or +`OnError` methods are complete the AsyncWorker instance is destructed. + +For the most basic use, only the `Execute` method must be implemented in a +subclass. + +## Methods + +### Env + +Requests the environment in which the async worker has been initially created. + +```cpp +Env Env() const; +``` + +Returns the environment in which the async worker has been created. + +### Queue + +Requests that the work be queued for execution. + +```cpp +void Queue(); +``` + +### Cancel + +Cancels queued work if it has not yet been started. If it has already started +executing, it cannot be cancelled. If cancelled successfully neither +`OnOK` nor `OnError` will be called. + +```cpp +void Cancel(); +``` + +### Receiver + +```cpp +ObjectReference& Receiver(); +``` + +Returns the persistent object reference of the receiver object set when the async +worker was created. + +### Callback + +```cpp +FunctionReference& Callback(); +``` + +Returns the persistent function reference of the callback set when the async +worker was created. The returned function reference will receive the results of +the computation that happened in the `Execute` method, unless the default +implementation of `OnOK` or `OnError` is overridden. + +### SetError + +Sets the error message for the error that happened during the execution. Setting +an error message will cause the `OnError` method to be invoked instead of `OnOK` +once the `Execute` method completes. + +```cpp +void SetError(const std::string& error); +``` + +- `[in] error`: The reference to the string that represent the message of the error. + +### Execute + +This method is used to execute some tasks out of the **event loop** on a libuv +worker thread. Subclasses must implement this method and the method is run on +a thread other than that running the main event loop. As the method is not +running on the main event loop, it must avoid calling any methods from node-addon-api +or running any code that might invoke JavaScript. Instead once this method is +complete any interaction through node-addon-api with JavaScript should be implemented +in the `OnOK` method which runs on the main thread and is invoked when the `Execute` +method completes. + +```cpp +virtual void Execute() = 0; +``` + +### OnOK + +This method is invoked when the computation in the `Excecute` method ends. +The default implementation runs the Callback provided when the AsyncWorker class +was created. + +```cpp +virtual void OnOK(); +``` + +### OnError + +This method is invoked afer Execute() completes if an error occurs +while `Execute` is running and C++ exceptions are enabled or if an +error was set through a call to `SetError`. The default implementation +calls the callback provided when the AsyncWorker class was created, passing +in the error as the first parameter. + +```cpp +virtual void OnError(const Error& e); +``` + +### Constructor + +Creates a new `AsyncWorker`. + +```cpp +explicit AsyncWorker(const Function& callback); +``` + +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. + +Returns an AsyncWork instance which can later be queued for execution by calling +`Queue`. + +### Constructor + +Creates a new `AsyncWorker`. + +```cpp +explicit AsyncWorker(const Function& callback, const char* resource_name); +``` + +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[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. + +Returns an AsyncWork instance which can later be queued for execution by calling +`Queue`. + + +### Constructor + +Creates a new `AsyncWorker`. + +```cpp +explicit AsyncWorker(const Function& callback, const char* resource_name, const Object& resource); +``` + +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[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. + +Returns an AsyncWork instance which can later be queued for execution by calling +`Queue`. + +### Constructor + +Creates a new `AsyncWorker`. + +```cpp +explicit AsyncWorker(const Object& receiver, const Function& callback); +``` + +- `[in] receiver`: The `this` object passed to the called function. +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. + +Returns an AsyncWork instance which can later be queued for execution by calling +`Queue`. + + +### Constructor + +Creates a new `AsyncWorker`. + +```cpp +explicit AsyncWorker(const Object& receiver, const Function& callback,const char* resource_name); +``` + +- `[in] receiver`: The `this` object passed to the called function. +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[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. + +Returns an AsyncWork instance which can later be queued for execution by calling +`Queue`. + + +### Constructor + +Creates a new `AsyncWorker`. + +```cpp +explicit AsyncWorker(const Object& receiver, const Function& callback, const char* resource_name, const Object& resource); +``` + +- `[in] receiver`: The `this` object passed to the called function. +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[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. + +Returns an AsyncWork instance which can later be queued for execution by calling +`Queue`. + +### Destructor + +Deletes the created work object that is used to execute logic asynchronously. + +```cpp +virtual ~AsyncWorker(); +``` + +## Operator + +```cpp +operator napi_async_work() const; +``` + +Returns the N-API napi_async_work wrapped by the AsyncWorker object. This can be +used to mix usage of the C N-API and node-addon-api. + +## Example + +The first step to use the `AsyncWorker` class is to create a new class that inherit +from it and implement the `Execute` abstract method. Typically input to your +worker will be saved within class' fields generally passed in through its +constructor. + +When the `Execute` method completes without errors the `OnOK` function callback +will be invoked. In this function the results of the computation will be +reassembled and returned back to the initial JavaScript context. + +`AsyncWorker` ensures that all the code in the `Execute` function runs in the +background out of the **event loop** thread and at the end the `OnOK` or `OnError` +function will be called and are executed as part of the event loop. + +The code below show a basic example of `AsyncWorker` the implementation: + +```cpp +#include + +#include +#include + +use namespace Napi; + +class EchoWorker : public AsyncWorker { + public: + EchoWorker(Function& callback, std::string& echo) + : AsyncWorker(callback), echo(echo) {} + + ~EchoWorker() {} + // This code will be executed on the worker thread + void Execute() { + // Need to simulate cpu heavy task + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + void OnOK() { + HandleScope scope(Env()); + Callback().Call({Env().Null(), String::New(Env(), echo)}); + } + + private: + std::string echo; +}; +``` + +The `EchoWorker`'s contructor calls the base class' constructor to pass in the +callback that the `AsyncWorker` base class will store persistently. When the work +on the `Execute` method is done the `OnOk` method is called and the results return +back to JavaScript invoking the stored callback with its associated environment. + +The following code shows an example on how to create and and use an `AsyncWorker` + +```cpp +Value Echo(const CallbackInfo& info) { + // You need to check the input data here + Function cb = info[1].As(); + std::string in = info[0].As(); + EchoWorker* wk = new EchoWorker(cb, in); + wk->Queue(); + return info.Env().Undefined(); +``` + +Using the implementation of an `AsyncWorker` is straight forward. You need only create +a new instance and pass to its constructor the callback you want to execute when +your asynchronous task ends and other data you need for your computation. Once created the +only other action you have to do is to call the `Queue` method that will that will +queue the created worker for execution. diff --git a/doc/error.md b/doc/error.md index 6ffa8842c..dc5e7ea07 100644 --- a/doc/error.md +++ b/doc/error.md @@ -1,5 +1,115 @@ # Error -You are reading a draft of the next documentation and it's in continuous 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 **Error** class is a representation of the JavaScript Error object that is thrown +when runtime errors occur. The Error object can also be used as a base object for +user-defined exceptions. + +The **Error** class is a persistent reference to a JavaScript error object thus +inherits its behavior from the `ObjectReference` class (for more info see: [ObjectReference](object_reference.md)). + +If C++ exceptions are enabled (for more info see: [Setup](setup.md)), then the +**Error** class extends `std::exception` and enables integrated +error-handling for C++ exceptions and JavaScript exceptions. + +For more details about error handling refer to the section titled [Error handling](error_handling.md). + +## Methods + +### New + +Creates empty instance of an `Error` object for the specified environment. + +```cpp +Error::New(Napi:Env env); +``` + +- `[in] Env`: The environment in which to construct the Error object. + +Returns an instance of `Error` object. + +### New + +Creates instance of an `Error` object. + +```cpp +Error::New(Napi:Env env, const char* message); +``` + +- `[in] Env`: The environment in which to construct the Error object. +- `[in] message`: Null-terminated string to be used as the message for the Error. + +Returns instance of an `Error` object. + +### New + +Creates instance of an `Error` object + +```cpp +Error::New(Napi:Env env, const std::string& message); +``` + +- `[in] Env`: The environment in which to construct the `Error` object. +- `[in] message`: Reference string to be used as the message for the `Error`. + +Returns instance of an `Error` object. + +### Fatal + +In case of an unrecoverable error in a native module, a fatal error can be thrown +to immediately terminate the process. + +```cpp +static NAPI_NO_RETURN void Fatal(const char* location, const char* message); +``` + +The function call does not return, the process will be terminated. + +### Constructor + +Creates empty instance of an `Error`. + +```cpp +Error(); +``` + +Returns an instance of `Error` object. + +### Constructor + +Initializes an `Error` instance from an existing JavaScript error object. + +```cpp +Error(napi_env env, napi_value value); +``` + +- `[in] Env`: The environment in which to construct the Error object. +- `[in] value`: The `Error` reference to wrap. + +Returns instance of an `Error` object. + +### Message + +```cpp +std::string& Message() const NAPI_NOEXCEPT; +``` + +Returns the reference to the string that represent the message of the error. + +### ThrowAsJavaScriptException + +Throw the error as JavaScript exception. + +```cpp +void ThrowAsJavaScriptException() const; +``` + +Throws the error as a JavaScript exception. + +### what + +```cpp +const char* what() const NAPI_NOEXCEPT override; +``` + +Returns a pointer to a null-terminated string that is used to identify the +exception. This method can be used only if the exception mechanism is enabled. diff --git a/doc/error_handling.md b/doc/error_handling.md index c54040823..d56281575 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -1,5 +1,156 @@ # Error handling -You are reading a draft of the next documentation and it's in continuous 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/) +Error handling represents one of the most important considerations when +implementing a Node.js native add-on. When an error occurs in your C++ code you +have to handle and dispatch it correctly. **node-addon-api** uses return values and +JavaScript exceptions for error handling. You can choose return values or +exception handling based on the mechanism that works best for your add-on. + +The **Error** is a persistent reference (for more info see: [Object reference](object_reference.md)) +to a JavaScript error object. Use of this class depends on whether C++ +exceptions are enabled at compile time. + +If C++ exceptions are enabled (for more info see: [Setup](setup.md)), then the +**Error** class extends `std::exception` and enables integrated +error-handling for C++ exceptions and JavaScript exceptions. + +The following sections explain the approach for each case: + +- [Handling Errors With C++ Exceptions](#exceptions) +- [Handling Errors Without C++ Exceptions](#noexceptions) + + + +In most cases when an error occurs, the addon should do whatever clean is possible +and then return to JavaScript so that they error can be propagated. In less frequent +cases the addon may be able to recover from the error, clear the error and then +continue. + +## Handling Errors With C++ Exceptions + +When C++ exceptions are enabled try/catch can be used to catch exceptions thrown +from calls to JavaScript and then they can either be handled or rethrown before +returning from a native method. + +If a node-addon-api call fails without executing any JavaScript code (for example due to +an invalid argument), then node-addon-api automatically converts and throws +the error as a C++ exception of type **Error**. + +If a JavaScript function called by C++ code via node-addon-api throws a JavaScript +exception, then node-addon-api automatically converts and throws it as a C++ +exception of type **Error** on return from the JavaScript code to the native +method. + +If a C++ exception of type **Error** escapes from a N-API C++ callback, then +the N-API wrapper automatically converts and throws it as a JavaScript exception. + +On return from a native method, node-addon-api will automatically convert a pending C++ +exception to a JavaScript exception. + +When C++ exceptions are enabled try/catch can be used to catch exceptions thrown +from calls to JavaScript and then they can either be handled or rethrown before +returning from a native method. + +## Examples with C++ exceptions enabled + +### Throwing a C++ exception + +```cpp +Env env = ... +throw Error::New(env, "Example exception"); +// other C++ statements +// ... +``` + +The statements following the throw statement will not be executed. The exception +will bubble up as a C++ exception of type **Error**, until it is either caught +while still in C++, or else automatically propagated as a JavaScript exception +when returning to JavaScript. + +### Propagating a N-API C++ exception + +```cpp +Function jsFunctionThatThrows = someObj.As(); +Value result = jsFunctionThatThrows({ arg1, arg2 }); +// other C++ statements +// ... +``` + +The C++ statements following the call to the JavaScript function will not be +executed. The exception will bubble up as a C++ exception of type **Error**, +until it is either caught while still in C++, or else automatically propagated as +a JavaScript exception when returning to JavaScript. + +### Handling a N-API C++ exception + +```cpp +Function jsFunctionThatThrows = someObj.As(); +Value result; +try { + result = jsFunctionThatThrows({ arg1, arg2 }); +} catch (const Error& e) { + cerr << "Caught JavaScript exception: " + e.what(); +} +``` + +Since the exception was caught here, it will not be propagated as a JavaScript +exception. + + + +## Handling Errors Without C++ Exceptions + +If C++ exceptions are disabled (for more info see: [Setup](setup.md)), then the +**Error** class does not extend `std::exception`. This means that any calls to +node-addon-api function do not throw a C++ exceptions. Instead, it raises +_pending_ JavaScript exceptions and returns an _empty_ **Value**. +The calling code should check `env.IsExceptionPending()` before attempting to use a +returned value, and may use methods on the **Env** class +to check for, get, and clear a pending JavaScript exception (for more info see: [Env](env.md)). +If the pending exception is not cleared, it will be thrown when the native code +returns to JavaScript. + +## Examples with C++ exceptions disabled + +### Throwing a JS exception + +```cpp +Env env = ... +Error::New(env, "Example exception").ThrowAsJavaScriptException(); +return; +``` + +After throwing a JavaScript exception, the code should generally return +immediately from the native callback, after performing any necessary cleanup. + +### Propagating a N-API JS exception + +```cpp +Env env = ... +Function jsFunctionThatThrows = someObj.As(); +Value result = jsFunctionThatThrows({ arg1, arg2 }); +if (env.IsExceptionPending()) { + Error e = env.GetAndClearPendingException(); + return e.Value(); +} +``` + +If env.IsExceptionPending() is returns true a +JavaScript exception is pending. To let the exception propagate, the code should +generally return immediately from the native callback, after performing any +necessary cleanup. + +### Handling a N-API JS exception + +```cpp +Env env = ... +Function jsFunctionThatThrows = someObj.As(); +Value result = jsFunctionThatThrows({ arg1, arg2 }); +if (env.IsExceptionPending()) { + Error e = env.GetAndClearPendingException(); + cerr << "Caught JavaScript exception: " + e.Message(); +} +``` + +Since the exception was cleared here, it will not be propagated as a JavaScript +exception after the native callback returns. diff --git a/doc/range_error.md b/doc/range_error.md new file mode 100644 index 000000000..e0bd14d30 --- /dev/null +++ b/doc/range_error.md @@ -0,0 +1,59 @@ +# RangeError + +The **RangeError** class is a representation of the JavaScript RangeError that is +thrown when trying to pass a value as an argument to a function that does not allow +a range that includes the value. + +The **RangeError** class inherits its behaviors from the **Error** class (for +more info see: [Error](error.md)). + +For more details about error handling refer to the section titled [Error handling](error_handling.md). + +## Methods + +### New + +Creates a new instance of a `RangeError` object. + +```cpp +RangeError::New(Napi:Env env, const char* message); +``` + +- `[in] Env`: The environment in which to construct the `RangeError` object. +- `[in] message`: Null-terminated string to be used as the message for the `RangeError`. + +Returns an instance of a `RangeError` object. + +### New + +Creates a new instance of a `RangeError` object. + +```cpp +RangeError::New(Napi:Env env, const std::string& message); +``` + +- `[in] Env`: The environment in which to construct the `RangeError` object. +- `[in] message`: Reference string to be used as the message for the `RangeError`. + +Returns an instance of a `RangeError` object. + +### Constructor + +Creates a new empty instance of a `RangeError`. + +```cpp +RangeError(); +``` + +### Constructor + +Initializes a `RangeError` instance from an existing Javascript error object. + +```cpp +RangeError(napi_env env, napi_value value); +``` + +- `[in] Env`: The environment in which to construct the `RangeError` object. +- `[in] value`: The `Error` reference to wrap. + +Returns an instance of a `RangeError` object. \ No newline at end of file diff --git a/doc/setup.md b/doc/setup.md index be95e747e..176815a96 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -19,7 +19,7 @@ To use **N-API** in a native module: ```json "dependencies": { - "node-addon-api": "1.1.0", + "node-addon-api": "1.2.0", } ``` diff --git a/doc/type_error.md b/doc/type_error.md new file mode 100644 index 000000000..7cfb9d1bf --- /dev/null +++ b/doc/type_error.md @@ -0,0 +1,59 @@ +# TypeError + +The **TypeError** class is a representation of the JavaScript `TypeError` that is +thrown when an operand or argument passed to a function is incompatible with the +type expected by the operator or function. + +The **TypeError** class inherits its behaviors from the **Error** class (for more info +see: [Error](error.md)). + +For more details about error handling refer to the section titled [Error handling](error_handling.md). + +## Methods + +### New + +Creates a new instance of the `TypeError` object. + +```cpp +TypeError::New(Napi:Env env, const char* message); +``` + +- `[in] Env`: The environment in which to construct the `TypeError` object. +- `[in] message`: Null-terminated string to be used as the message for the `TypeError`. + +Returns an instance of a `TypeError` object. + +### New + +Creates a new instance of a `TypeError` object. + +```cpp +TypeError::New(Napi:Env env, const std::string& message); +``` + +- `[in] Env`: The environment in which to construct the `TypeError` object. +- `[in] message`: Reference string to be used as the message for the `TypeError`. + +Returns an instance of a `TypeError` object. + +### Constructor + +Creates a new empty instance of a `TypeError`. + +```cpp +TypeError(); +``` + +### Constructor + +Initializes a ```TypeError``` instance from an existing JavaScript error object. + +```cpp +TypeError(napi_env env, napi_value value); +``` + +- `[in] Env`: The environment in which to construct the `TypeError` object. +- `[in] value`: The `Error` reference to wrap. + +Returns an instance of a `TypeError` object. \ No newline at end of file From 74ff79717ee70d4ec7a06d9f738a0f2f60e17c06 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Thu, 14 Jun 2018 17:35:41 -0400 Subject: [PATCH 003/696] doc: fix link to async_worker.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4fba45f2..fb6308ad7 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ values. Concepts and operations generally map to ideas specified in the - [TypedArray](doc/typed_array.md) - [TypedArrayOf](doc/typed_array_of.md) - [Async Operations](doc/async_operations.md) - - [AsyncWorker](async_worker.md) + - [AsyncWorker](doc/async_worker.md) - [Promises](doc/promises.md) From e029a076c6bc5ae2303f4027c3c556ddc976918d Mon Sep 17 00:00:00 2001 From: Hitesh Kanwathirtha Date: Thu, 10 May 2018 02:33:17 -0700 Subject: [PATCH 004/696] doc: First pass at basic Node Addon API docs PR-URL: https://github.com/nodejs/node-addon-api/pull/268 Reviewed-By: Michael Dawson --- doc/array.md | 5 - doc/basic_types.md | 410 ++++++++++++++++++++++++++++++++++++++++++++- doc/name.md | 5 - doc/string.md | 79 ++++++--- doc/symbol.md | 45 ++++- 5 files changed, 506 insertions(+), 38 deletions(-) delete mode 100644 doc/array.md delete mode 100644 doc/name.md diff --git a/doc/array.md b/doc/array.md deleted file mode 100644 index 0672fdb04..000000000 --- a/doc/array.md +++ /dev/null @@ -1,5 +0,0 @@ -# Array - -You are reading a draft of the next documentation and it's in continuous 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/) diff --git a/doc/basic_types.md b/doc/basic_types.md index 38e9d3e82..9a4fc71eb 100644 --- a/doc/basic_types.md +++ b/doc/basic_types.md @@ -1,5 +1,409 @@ # Basic Types -You are reading a draft of the next documentation and it's in continuous 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/) +Node Addon API consists of a few fundamental data types. These allow a user of +the API to create, convert and introspect fundamental JavaScript types, and +interoperate with their C++ counterparts. + +## Value + +Value is the base class of Node Addon API's fundamental object type hierarchy. +It represents a JavaScript value of an unknown type. It is a thin wrapper around +the N-API datatype `napi_value`. Methods on this class can be used to check +the JavaScript type of the underlying N-API `napi_value` and also to convert to +C++ types. + +### Constructor + +```cpp +Value(); +``` + +Used to create a Node Addon API `Value` that represents an **empty** value. + +```cpp +Value(napi_env env, napi_value value); +``` + +- `[in] env` - The `napi_env` environment in which to construct the Value +object. +- `[in] value` - The underlying JavaScript value that the `Value` instance +represents. + +Returns a Node.js Addon API `Value` that represents the `napi_value` passed +in. + +### Operators + +#### operator napi_value + +```cpp +operator napi_value() const; +``` + +Returns the underlying N-API `napi_value`. If the instance is _empty_, this +returns `nullptr`. + +#### operator == + +```cpp +bool operator ==(const Value& other) const; +``` + +Returns `true` if this value strictly equals another value, or `false` otherwise. + +#### operator != + +```cpp +bool operator !=(const Value& other) const; +``` + +Returns `false` if this value strictly equals another value, or `true` otherwise. + +### Methods + +#### From +```cpp +template +static Value From(napi_env env, const T& value); +``` + +- `[in] env` - The `napi_env` environment in which to construct the Value object. +- `[in] value` - The C++ type to represent in JavaScript. + +Returns a `Napi::Value` representing the input C++ type in JavaScript. + +This method is used to convert from a C++ type to a JavaScript value. +Here, `value` may be any of: +- `bool` - returns a `Napi::Boolean`. +- Any integer type - returns a `Napi::Number`. +- Any floating point type - returns a `Napi::Number`. +- `const char*` (encoded using UTF-8, null-terminated) - returns a `Napi::String`. +- `const char16_t*` (encoded using UTF-16-LE, null-terminated) - returns a `Napi::String`. +- `std::string` (encoded using UTF-8) - returns a `Napi::String`. +- `std::u16string` - returns a `Napi::String`. +- `napi::Value` - returns a `Napi::Value`. +- `napi_value` - returns a `Napi::Value`. + +#### As +```cpp +template T As() const; +``` + +Returns the `Napi::Value` cast to a desired C++ type. + +Use this when the actual type is known or assumed. + +Note: +This conversion does NOT coerce the type. Calling any methods inappropriate for +the actual value type will throw `Napi::Error`. + +#### StrictEquals +```cpp +bool StrictEquals(const Value& other) const; +``` + +- `[in] other` - The value to compare against. + +Returns true if the other `Napi::Value` is strictly equal to this one. + +#### Env +```cpp +Napi::Env Env() const; +``` + +Returns the environment that the value is associated with. See +[`Napi::Env`](env.md) for more details about environments. + +#### IsEmpty +```cpp +bool IsEmpty() const; +``` + +Returns `true` if the value is uninitialized. + +An empty value is invalid, and most attempts to perform an operation on an +empty value will result in an exception. An empty value is distinct from +JavaScript `null` or `undefined`, which are valid values. + +When C++ exceptions are disabled at compile time, a method with a `Value` +return type may return an empty value to indicate a pending exception. If C++ +exceptions are not being used, callers should check the result of +`Env::IsExceptionPending` before attempting to use the value. + +#### Type +```cpp +napi_valuetype Type() const; +``` + +Returns the underlying N-API `napi_valuetype` of the value. + +#### IsUndefined +```cpp +bool IsUndefined() const; +``` + +Returns `true` if the underlying value is a JavaScript `undefined` or `false` +otherwise. + +#### IsNull +```cpp +bool IsNull() const; +``` + +Returns `true` if the underlying value is a JavaScript `null` or `false` +otherwise. + +#### IsBoolean +```cpp +bool IsBoolean() const; +``` + +Returns `true` if the underlying value is a JavaScript `true` or JavaScript +`false`, or `false` if the value is not a Boolean value in JavaScript. + +#### IsNumber +```cpp +bool IsNumber() const; +``` + +Returns `true` if the underlying value is a JavaScript `Number` or `false` +otherwise. + +#### IsString +```cpp +bool IsString() const; +``` + +Returns `true` if the underlying value is a JavaScript `String` or `false` +otherwise. + +#### IsSymbol +```cpp +bool IsSymbol() const; +``` + +Returns `true` if the underlying value is a JavaScript `Symbol` or `false` +otherwise. + +#### IsArray +```cpp +bool IsArray() const; +``` + +Returns `true` if the underlying value is a JavaScript `Array` or `false` +otherwise. + +#### IsArrayBuffer +```cpp +bool IsArrayBuffer() const; +``` + +Returns `true` if the underlying value is a JavaScript `ArrayBuffer` or `false` +otherwise. + +#### IsTypedArray +```cpp +bool IsTypedArray() const; +``` + +Returns `true` if the underlying value is a JavaScript `TypedArray` or `false` +otherwise. + +#### IsObject +```cpp +bool IsObject() const; +``` + +Returns `true` if the underlying value is a JavaScript `Object` or `false` +otherwise. + +#### IsFunction +```cpp +bool IsFunction() const; +``` + +Returns `true` if the underlying value is a JavaScript `Function` or `false` +otherwise. + +#### IsPromise +```cpp +bool IsPromise() const; +``` + +Returns `true` if the underlying value is a JavaScript `Promise` or `false` +otherwise. + +#### IsDataView +```cpp +bool IsDataView() const; +``` + +Returns `true` if the underlying value is a JavaScript `DataView` or `false` +otherwise. + +#### IsBuffer +```cpp +bool IsBuffer() const; +``` + +Returns `true` if the underlying value is a Node.js `Buffer` or `false` +otherwise. + +#### IsExternal +```cpp +bool IsExternal() const; +``` + +Returns `true` if the underlying value is a N-API external object or `false` +otherwise. + +#### ToBoolean +```cpp +Boolean ToBoolean() const; +``` + +Returns a `Napi::Boolean` representing the `Napi::Value`. + +This is a wrapper around `napi_coerce_to_boolean`. This will throw a JavaScript +exception if the coercion fails. If C++ exceptions are not being used, callers +should check the result of `Env::IsExceptionPending` before attempting to use +the returned value. + + +#### ToNumber +```cpp +Number ToNumber() const; +``` + +Returns a `Napi::Number` representing the `Napi::Value`. + +Note: +This can cause script code to be executed according to JavaScript semantics. +This is a wrapper around `napi_coerce_to_number`. This will throw a JavaScript +exception if the coercion fails. If C++ exceptions are not being used, callers +should check the result of `Env::IsExceptionPending` before attempting to use +the returned value. + + +#### ToString +```cpp +String ToString() const; +``` + +Returns a `Napi::String` representing the `Napi::Value`. + +Note that this can cause script code to be executed according to JavaScript +semantics. This is a wrapper around `napi_coerce_to_string`. This will throw a +JavaScript exception if the coercion fails. If C++ exceptions are not being +used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + + +#### ToObject +```cpp +Object ToObject() const; +``` + +Returns a `Napi::Object` representing the `Napi::Value`. + +This is a wrapper around `napi_coerce_to_object`. This will throw a JavaScript +exception if the coercion fails. If C++ exceptions are not being used, callers +should check the result of `Env::IsExceptionPending` before attempting to use +the returned value. + + +## Name + +Names are JavaScript values that can be used as a property name. There are two +specialized types of names supported in Node.js Addon API- [`String`](String.md) +and [`Symbol`](Symbol.md). + +### Methods + +#### Constructor +```cpp +Name(); +``` + +Returns an empty `Name`. + +```cpp +Name(napi_env env, napi_value value); +``` +- `[in] env` - The environment in which to create the array. +- `[in] value` - The primitive to wrap. + +Returns a Name created from the JavaScript primitive. + +Note: +The value is not coerced to a string. + +## Array + +Arrays are native representations of JavaScript Arrays. `Napi::Array` is a wrapper +around `napi_value` representing a JavaScript Array. + +### Constructor +```cpp +Array(); +``` + +Returns an empty array. + +If an error occurs, a `Napi::Error` will be thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + + +```cpp +Array(napi_env env, napi_value value); +``` +- `[in] env` - The environment in which to create the array. +- `[in] value` - The primitive to wrap. + +Returns a `Napi::Array` wrapping a `napi_value`. + +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + +### Methods + +#### New +```cpp +static Array New(napi_env env); +``` +- `[in] env` - The environment in which to create the array. + +Returns a new `Napi::Array`. + +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + +#### New + +```cpp +static Array New(napi_env env, size_t length); +``` +- `[in] env` - The environment in which to create the array. +- `[in] length` - The length of the array. + +Returns a new `Napi::Array` with the given length. + +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + +#### New +```cpp +uint32_t Length() const; +``` + +Returns the length of the array. + +Note: +This can execute JavaScript code implicitly according to JavaScript semantics. +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. diff --git a/doc/name.md b/doc/name.md deleted file mode 100644 index 1563f2b8b..000000000 --- a/doc/name.md +++ /dev/null @@ -1,5 +0,0 @@ -# Name - -You are reading a draft of the next documentation and it's in continuous 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/) diff --git a/doc/string.md b/doc/string.md index 19324cf4a..fd1ff8b13 100644 --- a/doc/string.md +++ b/doc/string.md @@ -1,51 +1,86 @@ # String -You are reading a draft of the next documentation and it's in continuous 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/) -# Methods - -### Constructor - -Creates a new String value from a UTF-8 encoded c++ string. +## Constructor ```cpp -String::New(napi_env env, const std::string& value); +String(); ``` -- `[in] env`: The `napi_env` environment in which to construct the Value object. -- `[in] value`: The C++ primitive from which to instantiate the Value. `value` may be any of: - - std::string& - - std::u16string& - - const char* - - const char16_t* +Returns a new **empty** String instance. -Creates a new empty String +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. -```cpp -String::New(); ``` +String(napi_env env, napi_value value); ///< Wraps a N-API value primitive. +``` +- `[in] env` - The environment in which to create the string. +- `[in] value` - The primitive to wrap. + +Returns a `Napi::String` wrapping a `napi_value`. + +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + +## Operators ### operator std::string ```cpp operator std::string() const; ``` -Converts a String value to a UTF-8 encoded C++ string. + +Returns a UTF-8 encoded C++ string. ### operator std::u16string ```cpp operator std::u16string() const; ``` -Converts a String value to a UTF-16 encoded C++ string. + +Returns a UTF-16 encoded C++ string. + +## Methods + +### New +```cpp +String::New(); +``` + +Returns a new empty String + +### New +```cpp +String::New(napi_env env, const std::string& value); +String::New(napi_env env, const std::u16string& value); +String::New(napi_env env, const char* value); +String::New(napi_env env, const char16_t* value); +``` + +- `[in] env`: The `napi_env` environment in which to construct the Value object. +- `[in] value`: The C++ primitive from which to instantiate the Value. `value` may be any of: + - `std::string&` - represents an ANSI string. + - `std::u16string&` - represents a UTF16-LE string. + - `const char*` - represents a UTF8 string. + - `const char16_t*` - represents a UTF16-LE string. + +Returns a new `Napi::String` that represents the passed in C++ string. + +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. ### Utf8Value ```cpp std::string Utf8Value() const; ``` -Converts a String value to a UTF-8 encoded C++ string. + +Returns a UTF-8 encoded C++ string. + ### Utf16Value ```cpp std::u16string Utf16Value() const; ``` -Converts a String value to a UTF-16 encoded C++ string. \ No newline at end of file + +Returns a UTF-16 encoded C++ string. \ No newline at end of file diff --git a/doc/symbol.md b/doc/symbol.md index f644d137f..fe7ee9c03 100644 --- a/doc/symbol.md +++ b/doc/symbol.md @@ -1,5 +1,44 @@ # Symbol -You are reading a draft of the next documentation and it's in continuous 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/) +## Methods + +### Constructor + +Instantiates a new `Symbol` value + +```cpp +Symbol(); +``` + +Returns a new empty Symbol. + +### New +```cpp +Symbol::New(napi_env env, const std::string& description); +Symbol::New(napi_env env, const char* description); +Symbol::New(napi_env env, String description); +Symbol::New(napi_env env, napi_value description); +``` + +- `[in] env`: The `napi_env` environment in which to construct the Symbol object. +- `[in] value`: The C++ primitive which represents the description hint for the Symbol. + `description` may be any of: + - `std::string&` - ANSI string description. + - `const char*` - represents a UTF8 string description. + - `String` - Node addon API String description. + - `napi_value` - N-API `napi_value` description. + +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + +### Utf8Value +```cpp +static Symbol WellKnown(napi_env env, const std::string& name); +``` + +- `[in] env`: The `napi_env` environment in which to construct the Symbol object. +- `[in] name`: The C++ string representing the `Symbol` to retrieve. + +Returns a `Napi::Symbol` representing a well-known Symbol from the +Symbol registry. From 9ab6607242017732cc92d14afa7de3d5228d9934 Mon Sep 17 00:00:00 2001 From: joshgarde Date: Fri, 8 Jun 2018 17:19:13 -0700 Subject: [PATCH 005/696] doc: Update Doc Version Number PR-URL: https://github.com/nodejs/node-addon-api/pull/277 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- doc/Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 4994d9991..995090d1e 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = N-API # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.3.3 +PROJECT_NUMBER = 1.3.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a From 12b2cdeed3230ebeaa32caa67ec269e4f4d85388 Mon Sep 17 00:00:00 2001 From: Kyle Farnung Date: Wed, 2 May 2018 16:56:15 -0700 Subject: [PATCH 006/696] fix test files Fix missing `pip` in macOS builds Add new builds to the matrix PR-URL: https://github.com/nodejs/node-addon-api/pull/257 Reviewed-By: Michael Dawson --- .travis.yml | 8 ++++++-- appveyor.yml | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c37486fff..7821a7223 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,11 +10,15 @@ os: env: global: # https://github.com/jasongin/nvs/blob/master/doc/CI.md - - NVS_VERSION=1.2.0 + - NVS_VERSION=1.4.2 matrix: - NODEJS_VERSION=node/4 - NODEJS_VERSION=node/6 - NODEJS_VERSION=node/8 + - NODEJS_VERSION=node/9 + - NODEJS_VERSION=node/10 + - NODEJS_VERSION=chakracore/8 + - NODEJS_VERSION=chakracore/10 - NODEJS_VERSION=nightly - NODEJS_VERSION=chakracore-nightly matrix: @@ -35,7 +39,7 @@ addons: - g++-4.9 before_install: # coveralls - - pip install --user cpp-coveralls + - pip2 install --user cpp-coveralls # compilers - if [ "$CXX" = "g++" -a "$TRAVIS_OS_NAME" = "linux" ]; then export CXX="g++-4.9" CC="gcc-4.9" AR="gcc-ar-4.9" RANLIB="gcc-ranlib-4.9" NM="gcc-nm-4.9" ; fi - if [ "$CXX" = "clang++" ]; then export NPMOPT=--clang=1 ; fi diff --git a/appveyor.yml b/appveyor.yml index 6addabfbd..439b91df0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,11 +1,15 @@ environment: # https://github.com/jasongin/nvs/blob/master/doc/CI.md - NVS_VERSION: 1.2.0 + NVS_VERSION: 1.4.2 fast_finish: true matrix: - NODEJS_VERSION: node/4 - NODEJS_VERSION: node/6 - NODEJS_VERSION: node/8 + - NODEJS_VERSION: node/9 + - NODEJS_VERSION: node/10 + - NODEJS_VERSION: chakracore/8 + - NODEJS_VERSION: chakracore/10 - NODEJS_VERSION: nightly - NODEJS_VERSION: chakracore-nightly From 7394bfd154392859086467e36a15b757a01b5fa7 Mon Sep 17 00:00:00 2001 From: Ben Berman Date: Mon, 18 Jun 2018 15:08:02 -0400 Subject: [PATCH 007/696] doc: Fix typo in docs PR-URL: https://github.com/nodejs/node-addon-api/pull/285 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- doc/promises.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/promises.md b/doc/promises.md index 627705387..f0bf8a61d 100644 --- a/doc/promises.md +++ b/doc/promises.md @@ -1,4 +1,4 @@ -You are reading a draft of the next documentation and it's in continuos update so +You are reading a draft of the next documentation and it's in continuous 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/) From 6cff890ee5b0a764d52b2ad5114be6e14c9538d4 Mon Sep 17 00:00:00 2001 From: Ben Berman Date: Mon, 18 Jun 2018 10:59:55 -0400 Subject: [PATCH 008/696] doc: Fix typo in docs PR-URL: https://github.com/nodejs/node-addon-api/pull/284 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- doc/object_lifetime_management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/object_lifetime_management.md b/doc/object_lifetime_management.md index b6ea39db0..5c7a66c28 100644 --- a/doc/object_lifetime_management.md +++ b/doc/object_lifetime_management.md @@ -35,7 +35,7 @@ with each of the values, one at a time: 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 + // do something with newValue }; ``` From c2a620dc111765d20d8e958cdca024dd6618e234 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Tue, 19 Jun 2018 16:35:54 -0400 Subject: [PATCH 009/696] doc: Clarify positioning versus N-API PR-URL: https://github.com/nodejs/node-addon-api/pull/288 Reviewed-By: Nicola Del Gobbo --- README.md | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index fb6308ad7..f8bf992ee 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,32 @@ -# **Node.js API (N-API) Package** - -This package contains **header-only C++ wrapper classes** for the -**ABI-stable Node.js API** also known as **N-API**, providing C++ object -model and exception handling semantics with low overhead. It guarantees -backward compatibility when used with older versions of Node.js that do -not have N-API built-in. - -N-API is an API for building native addons. It is independent from the -underlying JavaScript runtime (e.g. V8 or ChakraCore) and is maintained as -part of Node.js itself. This API will be Application Binary Interface (ABI) -stable across versions and flavors of Node.js. It is intended to insulate +# **node-addon-api module** +This module contains **header-only C++ wrapper classes** which simplify +the use of the C based [N-API](https://nodejs.org/dist/latest/docs/api/n-api.html) +provided by Node.js when using C++. It provides a C++ object model +and exception handling semantics with low overhead. + +N-API is an ABI stable C interface provided by Node.js for building native +addons. It is independent from the underlying JavaScript runtime (e.g. V8 or ChakraCore) +and is maintained as part of Node.js itself. It is intended to insulate native addons from changes in the underlying JavaScript engine and allow modules compiled for one version to run on later versions of Node.js without -recompilation. N-API guarantees the **API** and **ABI** compatibility across -different versions of Node.js. So if you switched to a different version of -Node.js, you would not need to reinstall or recompile the native addon. +recompilation. -APIs exposed by N-API are generally used to create and manipulate JavaScript -values. Concepts and operations generally map to ideas specified in the -**ECMA262 Language Specification**. +The `node-addon-api` module, which is not part of Node.js, preserves the benefits +of the N-API as it consists only of inline code that depends only on the stable API +provided by N-API. As such, modules built against one version of Node.js +using node-addon-api should run without having to be rebuilt with newer versions +of Node.js. + +As new APIs are added to N-API, node-addon-api must be updated to provide +wrappers for those new APIs. For this reason node-addon-api provides +methods that allow callers to obtain the underlying N-API handles so +direct calls to N-API and the use of the objects/methods provided by +node-addon-api can be used together. For example, in order to be able +to use an API for which the node-add-api does not yet provide a wrapper. + +APIs exposed by node-addon-api are generally used to create and +manipulate JavaScript values. Concepts and operations generally map +to ideas specified in the **ECMA262 Language Specification**. - **[Setup](#setup)** - **[API Documentation](#api)** @@ -47,6 +55,10 @@ values. Concepts and operations generally map to ideas specified in the ### **API Documentation** + +The following is the documentation for node-addon-api (NOTE: +still a work in progress as its not yet complete). + - [Basic Types](doc/basic_types.md) - [Array](doc/array.md) - [Symbol](doc/symbol.md) @@ -86,7 +98,7 @@ values. Concepts and operations generally map to ideas specified in the ### **Examples** -Are you new to **N-API**? Take a look at our **[examples](https://github.com/nodejs/abi-stable-node-addon-examples)** +Are you new to **node-addon-api**? Take a look at our **[examples](https://github.com/nodejs/abi-stable-node-addon-examples)** - **[Hello World](https://github.com/nodejs/abi-stable-node-addon-examples/tree/master/1_hello_world/node-addon-api)** - **[Pass arguments to a function](https://github.com/nodejs/abi-stable-node-addon-examples/tree/master/2_function_arguments/node-addon-api)** @@ -101,7 +113,7 @@ Are you new to **N-API**? Take a look at our **[examples](https://github.com/nod ### **Tests** -To run the **N-API** tests do: +To run the **node-addon-api** tests do: ``` npm install From 90f92c4dc035fe8ad05bc8b3ea3d05ba9f855f0e Mon Sep 17 00:00:00 2001 From: Hitesh Kanwathirtha Date: Thu, 21 Jun 2018 01:17:34 -0700 Subject: [PATCH 010/696] doc: Update broken links in README.md PR-URL: https://github.com/nodejs/node-addon-api/pull/290 Reviewed-By: Michael Dawson --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f8bf992ee..5b812b052 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,10 @@ The following is the documentation for node-addon-api (NOTE: still a work in progress as its not yet complete). - [Basic Types](doc/basic_types.md) - - [Array](doc/array.md) + - [Array](doc/basic_types.md#array) - [Symbol](doc/symbol.md) - [String](doc/string.md) - - [Name](doc/name.md) + - [Name](doc/basic_types.md#name) - [Number](doc/number.md) - [Boolean](doc/boolean.md) - [Env](doc/env.md) From 86be13a611c97f18dacedd689af0be08d5ad691e Mon Sep 17 00:00:00 2001 From: Ben Berman Date: Mon, 18 Jun 2018 19:02:21 -0400 Subject: [PATCH 011/696] doc: Fix HandleScope docs It turns out `HandleScope::New` doesn't actually exist, fix up doc. PR-URL: https://github.com/nodejs/node-addon-api/pull/287 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- doc/handle_scope.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/handle_scope.md b/doc/handle_scope.md index 898c6e21d..81a63fa28 100644 --- a/doc/handle_scope.md +++ b/doc/handle_scope.md @@ -12,27 +12,27 @@ the section titled (Object lifetime management)[object_lifetime_management]. ### Constructor -Creates a new handle scope. +Creates a new handle scope on the stack. ```cpp -HandleScope HandleScope::New(Napi:Env env); +HandleScope(Napi:Env env); ``` -- `[in] Env`: The environment in which to construct the HandleScope object. +- `[in] env`: The environment in which to construct the HandleScope object. Returns a new HandleScope ### Constructor -Creates a new handle scope. +Creates a new handle scope on the stack. ```cpp -HandleScope HandleScope::New(napi_env env, napi_handle_scope scope); +HandleScope(Napi::Env env, Napi::HandleScope scope); ``` -- `[in] env`: napi_env in which the scope passed in was created. -- `[in] scope`: pre-existing napi_handle_scope. +- `[in] env`: Napi::Env in which the scope passed in was created. +- `[in] scope`: pre-existing Napi::HandleScope. 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 @@ -41,7 +41,7 @@ and node-addon-api. operator HandleScope::napi_handle_scope ```cpp -operator HandleScope::napi_handle_scope() const +operator napi_handle_scope() const ``` Returns the N-API napi_handle_scope wrapped by the EscapableHandleScope object. From 605aa2babffb58e7b87b816da4324af619dc58e2 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Tue, 19 Jun 2018 01:04:43 +0200 Subject: [PATCH 012/696] Add memory management feature Add memory mangament feature For more info see the following issue: https://github.com/nodejs/node-addon-api/issues/260 PR-URL: https://github.com/nodejs/node-addon-api/pull/286 Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- README.md | 1 + doc/memory_management.md | 27 +++++++++++++++++++++++++++ napi-inl.h | 11 +++++++++++ napi.h | 8 ++++++++ package.json | 2 +- test/binding.cc | 2 ++ test/binding.gyp | 1 + test/index.js | 1 + test/memory_management.cc | 17 +++++++++++++++++ test/memory_management.js | 10 ++++++++++ 10 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 doc/memory_management.md create mode 100644 test/memory_management.cc create mode 100644 test/memory_management.js diff --git a/README.md b/README.md index 5b812b052..b3a97d0ac 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ still a work in progress as its not yet complete). - [ArrayBuffer](doc/array_buffer.md) - [TypedArray](doc/typed_array.md) - [TypedArrayOf](doc/typed_array_of.md) + - [Memory Management](doc/memory_management.md) - [Async Operations](doc/async_operations.md) - [AsyncWorker](doc/async_worker.md) - [Promises](doc/promises.md) diff --git a/doc/memory_management.md b/doc/memory_management.md new file mode 100644 index 000000000..2cabd57ca --- /dev/null +++ b/doc/memory_management.md @@ -0,0 +1,27 @@ +# MemoryManagement + +The `MemoryManagement` class contains functions that give the JavaScript engine +an indication of the amount of externally allocated memory that is kept alive by +JavaScript objects. + +## Methods + +### AdjustExternalMemory + +The function `AdjustExternalMemory` adjusts the amount of registered external +memory used to give the JavaScript engine an indication of the amount of externally +allocated memory that is kept alive by JavaScript objects. +The JavaScript engine uses this to decide when to perform global garbage collections. +Registering externally allocated memory will trigger global garbage collections +more often than it would otherwise in an attempt to garbage collect the JavaScript +objects that keep the externally allocated memory alive. + +```cpp +static int64_t MemoryManagement::AdjustExternalMemory(Env env, int64_t change_in_bytes); +``` + +- `[in] env`: The environment in which the API is invoked under. +- `[in] change_in_bytes`: The change in externally allocated memory that is kept +alive by JavaScript objects expressed in bytes. + +Returns the adjusted memory value. diff --git a/napi-inl.h b/napi-inl.h index 77e4d5584..46ba2c0b9 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3224,6 +3224,17 @@ inline void AsyncWorker::OnWorkComplete( delete self; } +//////////////////////////////////////////////////////////////////////////////// +// Memory Management class +//////////////////////////////////////////////////////////////////////////////// + +inline int64_t MemoryManagement::AdjustExternalMemory(Env env, int64_t change_in_bytes) { + int64_t result; + napi_status status = napi_adjust_external_memory(env, change_in_bytes, &result); + NAPI_THROW_IF_FAILED(env, status, 0); + return result; +} + // These macros shouldn't be useful in user code. #undef NAPI_THROW #undef NAPI_THROW_IF_FAILED diff --git a/napi.h b/napi.h index 0aa9a6af1..f9852968b 100644 --- a/napi.h +++ b/napi.h @@ -76,6 +76,8 @@ namespace Napi { /// Defines the signature of a N-API C++ module's registration callback (init) function. typedef Object (*ModuleRegisterCallback)(Env env, Object exports); + class MemoryManagement; + /// Environment for N-API values and operations. /// /// All N-API values and operations must be associated with an environment. An environment @@ -1549,6 +1551,12 @@ namespace Napi { std::string _error; }; + // Memory management. + class MemoryManagement { + public: + static int64_t AdjustExternalMemory(Env env, int64_t change_in_bytes); + }; + } // namespace Napi // Inline implementations of all the above class methods are included here. diff --git a/package.json b/package.json index 352cfc382..f1fdeb73a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "Anna Henningsen (https://github.com/addaleax)", "Arunesh Chandra (https://github.com/aruneshchandra)", "Benjamin Byholm (https://github.com/kkoopa)", - "Cory Mickelson (https://github.com/corymickelson)", + "Cory Mickelson (https://github.com/corymickelson)", "David Halls (https://github.com/davedoesdev)", "Eric Bickle (https://github.com/ebickle)", "Gabriel Schulhof (https://github.com/gabrielschulhof)", diff --git a/test/binding.cc b/test/binding.cc index 8e6a1e9ec..0032c8e30 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -13,6 +13,7 @@ Object InitError(Env env); Object InitExternal(Env env); Object InitFunction(Env env); Object InitHandleScope(Env env); +Object InitMemoryManagement(Env env); Object InitName(Env env); Object InitObject(Env env); Object InitPromise(Env env); @@ -33,6 +34,7 @@ Object Init(Env env, Object exports) { exports.Set("function", InitFunction(env)); exports.Set("name", InitName(env)); exports.Set("handlescope", InitHandleScope(env)); + exports.Set("memory_management", InitMemoryManagement(env)); exports.Set("object", InitObject(env)); exports.Set("promise", InitPromise(env)); exports.Set("typedarray", InitTypedArray(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 4e0050480..acbbc0a12 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -13,6 +13,7 @@ 'external.cc', 'function.cc', 'handlescope.cc', + 'memory_management.cc', 'name.cc', 'object/delete_property.cc', 'object/get_property.cc', diff --git a/test/index.js b/test/index.js index 5211bb467..c9d7fb0c2 100644 --- a/test/index.js +++ b/test/index.js @@ -19,6 +19,7 @@ let testModules = [ 'external', 'function', 'handlescope', + 'memory_management', 'name', 'object/delete_property', 'object/get_property', diff --git a/test/memory_management.cc b/test/memory_management.cc new file mode 100644 index 000000000..a42357539 --- /dev/null +++ b/test/memory_management.cc @@ -0,0 +1,17 @@ +#include "napi.h" + +using namespace Napi; + +Value externalAllocatedMemory(const CallbackInfo& info) { + int64_t kSize = 1024 * 1024; + int64_t baseline = MemoryManagement::AdjustExternalMemory(info.Env(), 0); + int64_t tmp = MemoryManagement::AdjustExternalMemory(info.Env(), kSize); + tmp = MemoryManagement::AdjustExternalMemory(info.Env(), -kSize); + return Boolean::New(info.Env(), tmp == baseline); +} + +Object InitMemoryManagement(Env env) { + Object exports = Object::New(env); + exports["externalAllocatedMemory"] = Function::New(env, externalAllocatedMemory); + return exports; +} diff --git a/test/memory_management.js b/test/memory_management.js new file mode 100644 index 000000000..f4911a2a6 --- /dev/null +++ b/test/memory_management.js @@ -0,0 +1,10 @@ +'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.memory_management.externalAllocatedMemory(), true) +} From 11697fcecd6e6b7519fda09a135397cea14dd701 Mon Sep 17 00:00:00 2001 From: Kyle Farnung Date: Tue, 1 May 2018 18:09:09 -0700 Subject: [PATCH 013/696] doc: ArrayBuffer and Buffer documentation PR-URL: https://github.com/nodejs/node-addon-api/pull/256 Reviewed-By: Michael Dawson --- doc/array_buffer.md | 130 ++++++++++++++++++++++++++++++++++++++-- doc/buffer.md | 141 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 264 insertions(+), 7 deletions(-) diff --git a/doc/array_buffer.md b/doc/array_buffer.md index c6823b95a..d6a682a82 100644 --- a/doc/array_buffer.md +++ b/doc/array_buffer.md @@ -1,5 +1,127 @@ -# Array buffer +# ArrayBuffer -You are reading a draft of the next documentation and it's in continuous 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 `ArrayBuffer` class corresponds to the JavaScript `ArrayBuffer` class. + +## Methods + +### New + +Allocates a new `ArrayBuffer` object with a given length. + +```cpp +static ArrayBuffer New(napi_env env, size_t byteLength); +``` + +- `[in] env`: The environment in which to create the ArrayBuffer object. +- `[in] byteLength`: The length to be allocated, in bytes. + +Returns a new `ArrayBuffer` object. + +### New + +Wraps the provided external data into a new `ArrayBuffer` object. + +The `ArrayBuffer` object does not assume ownership for the data and expects it +to be valid for the lifetime of the object. Since the `ArrayBuffer` is subject +to garbage collection this overload is only suitable for data which is static +and never needs to be freed. + +```cpp +static ArrayBuffer New(napi_env env, void* externalData, size_t byteLength); +``` + +- `[in] env`: The environment in which to create the `ArrayBuffer` object. +- `[in] externalData`: The pointer to the external data to wrap. +- `[in] byteLength`: The length of the `externalData`, in bytes. + +Returns a new `ArrayBuffer` object. + +### New + +Wraps the provided external data into a new `ArrayBuffer` object. + +The `ArrayBuffer` object does not assume ownership for the data and expects it +to be valid for the lifetime of the object. The data can only be freed once the +`finalizeCallback` is invoked to indicate that the `ArrayBuffer` has been +released. + +```cpp +template +static ArrayBuffer New(napi_env env, + void* externalData, + size_t byteLength, + Finalizer finalizeCallback); +``` + +- `[in] env`: The environment in which to create the `ArrayBuffer` object. +- `[in] externalData`: The pointer to the external data to wrap. +- `[in] byteLength`: The length of the `externalData`, in bytes. +- `[in] finalizeCallback`: A function to be called when the `ArrayBuffer` is + destroyed. It must implement `operator()`, accept a `void*` (which is the + `externalData` pointer), and return `void`. + +Returns a new `ArrayBuffer` object. + +### New + +Wraps the provided external data into a new `ArrayBuffer` object. + +The `ArrayBuffer` object does not assume ownership for the data and expects it +to be valid for the lifetime of the object. The data can only be freed once the +`finalizeCallback` is invoked to indicate that the `ArrayBuffer` has been +released. + +```cpp +template +static ArrayBuffer New(napi_env env, + void* externalData, + size_t byteLength, + Finalizer finalizeCallback, + Hint* finalizeHint); +``` + +- `[in] env`: The environment in which to create the `ArrayBuffer` object. +- `[in] externalData`: The pointer to the external data to wrap. +- `[in] byteLength`: The length of the `externalData`, in bytes. +- `[in] finalizeCallback`: The function to be called when the `ArrayBuffer` is + destroyed. It must implement `operator()`, accept a `void*` (which is the + `externalData` pointer) and `Hint*`, and return `void`. +- `[in] finalizeHint`: The hint to be passed as the second parameter of the + finalize callback. + +Returns a new `ArrayBuffer` object. + +### Constructor + +Initializes an empty instance of the `ArrayBuffer` class. + +```cpp +ArrayBuffer(); +``` + +### Constructor + +Initializes a wrapper instance of an existing `ArrayBuffer` object. + +```cpp +ArrayBuffer(napi_env env, napi_value value); +``` + +- `[in] env`: The environment in which to create the `ArrayBuffer` object. +- `[in] value`: The `ArrayBuffer` reference to wrap. + +### ByteLength + +```cpp +size_t ByteLength() const; +``` + +Returns the length of the wrapped data, in bytes. + +### Data + +```cpp +T* Data() const; +``` + +Returns a pointer the wrapped data. diff --git a/doc/buffer.md b/doc/buffer.md index 87d07e38f..e37f7d980 100644 --- a/doc/buffer.md +++ b/doc/buffer.md @@ -1,5 +1,140 @@ # Buffer -You are reading a draft of the next documentation and it's in continuous 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 `Buffer` class creates a projection of raw data that can be consumed by +script. + +## Methods + +### New + +Allocates a new `Buffer` object with a given length. + +```cpp +static Buffer New(napi_env env, size_t length); +``` + +- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] length`: The number of `T` elements to allocate. + +Returns a new `Buffer` object. + +### New + +Wraps the provided external data into a new `Buffer` object. + +The `Buffer` object does not assume ownership for the data and expects it to be +valid for the lifetime of the object. Since the `Buffer` is subject to garbage +collection this overload is only suitable for data which is static and never +needs to be freed. + +```cpp +static Buffer New(napi_env env, T* data, size_t length); +``` + +- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] data`: The pointer to the external data to expose. +- `[in] length`: The number of `T` elements in the external data. + +Returns a new `Buffer` object. + +### New + +Wraps the provided external data into a new `Buffer` object. + +The `Buffer` object does not assume ownership for the data and expects it +to be valid for the lifetime of the object. The data can only be freed once the +`finalizeCallback` is invoked to indicate that the `Buffer` has been released. + +```cpp +template +static Buffer New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback); +``` + +- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] data`: The pointer to the external data to expose. +- `[in] length`: The number of `T` elements in the external data. +- `[in] finalizeCallback`: The function to be called when the `Buffer` is + destroyed. It must implement `operator()`, accept a `T*` (which is the + external data pointer), and return `void`. + +Returns a new `Buffer` object. + +### New + +Wraps the provided external data into a new `Buffer` object. + +The `Buffer` object does not assume ownership for the data and expects it to be +valid for the lifetime of the object. The data can only be freed once the +`finalizeCallback` is invoked to indicate that the `Buffer` has been released. + +```cpp +template +static Buffer New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint); +``` + +- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] data`: The pointer to the external data to expose. +- `[in] length`: The number of `T` elements in the external data. +- `[in] finalizeCallback`: The function to be called when the `Buffer` is + destroyed. It must implement `operator()`, accept a `T*` (which is the + external data pointer) and `Hint*`, and return `void`. +- `[in] finalizeHint`: The hint to be passed as the second parameter of the + finalize callback. + +Returns a new `Buffer` object. + +### Copy + +Allocates a new `Buffer` object and copies the provided external data into it. + +```cpp +static Buffer Copy(napi_env env, const T* data, size_t length); +``` + +- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] data`: The pointer to the external data to copy. +- `[in] length`: The number of `T` elements in the external data. + +Returns a new `Buffer` object containing a copy of the data. + +### Constructor + +Initializes an empty instance of the `Buffer` class. + +```cpp +Buffer(); +``` + +### Constructor + +Initializes the `Buffer` object using an existing Uint8Array. + +```cpp +Buffer(napi_env env, napi_value value); +``` + +- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] value`: The Uint8Array reference to wrap. + +### Data + +```cpp +T* Data() const; +``` + +Returns a pointer the external data. + +### Length + +```cpp +size_t Length() const; +``` + +Returns the number of `T` elements in the external data. From dd1191e086fc4beb42de8d359ede9ad3e6776c56 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Wed, 11 Jul 2018 16:51:48 -0400 Subject: [PATCH 014/696] test: fix asyncworker test so it runs on 6.x PR-URL: https://github.com/nodejs/node-addon-api/pull/298 Fixes: https://github.com/nodejs/node-addon-api/issues/296 Reviewed-By: Gabriel Schulhof Reviewed-By: Nicola Del Gobbo --- test/asyncworker.js | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/test/asyncworker.js b/test/asyncworker.js index 05195e48e..676afd537 100644 --- a/test/asyncworker.js +++ b/test/asyncworker.js @@ -1,9 +1,22 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -const async_hooks = require('async_hooks'); const common = require('./common'); +// we only check async hooks on 8.x an higher were +// they are closer to working properly +const nodeVersion = process.versions.node.split('.')[0] +let async_hooks = undefined; +function checkAsyncHooks() { + if (nodeVersion >=8) { + if (async_hooks == undefined) { + async_hooks = require('async_hooks'); + } + return true; + } + return false; +} + test(require(`./build/${buildType}/binding.node`)); test(require(`./build/${buildType}/binding_noexcept.node`)); @@ -40,6 +53,22 @@ function installAsyncHooksForTest() { } function test(binding) { + if (!checkAsyncHooks()) { + binding.asyncworker.doWork(true, {}, function (e) { + assert.strictEqual(typeof e, 'undefined'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + }, 'test data'); + + binding.asyncworker.doWork(false, {}, function (e) { + assert.ok(e instanceof Error); + assert.strictEqual(e.message, 'test error'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + }, 'test data'); + return; + } + { const hooks = installAsyncHooksForTest(); const triggerAsyncId = async_hooks.executionAsyncId(); From 98161970c9a3c2aee1a4a0aaf57ae920ec215edb Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Mon, 9 Jul 2018 15:57:46 -0400 Subject: [PATCH 015/696] Backport perf, crash and exception handling fixes The following commits are backported: e41ccd4e2de6deb03e277fa9d6baff685ab31689 - performance fix 2a08925896872c345e2948778eab01e9853c22cb - performance fix follow-up 978d89f5e3cf238a0e42303acc9493993ede9c98 - make functions gc-able c1695d8bad09fc61922ec91101736debb2d165db - add napi_fatal_exception() 9e226475e8e28b8664643d6644503ccd06bceb9a - exception handling fix 1a5a19d6d475578eec355a8b3079b4a96fc77489 - crash fix PR-URL: https://github.com/nodejs/node-addon-api/pull/295 Reviewed-By: Nicola Del Gobbo Reviewed-By: Michael Dawson --- src/node_api.cc | 267 ++++++++++++++++++++++++++---------------------- src/node_api.h | 2 + 2 files changed, 145 insertions(+), 124 deletions(-) diff --git a/src/node_api.cc b/src/node_api.cc index 20fada908..e717a3835 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -34,15 +34,11 @@ struct napi_env__ { last_exception.Reset(); has_instance.Reset(); wrap_template.Reset(); - function_data_template.Reset(); - accessor_data_template.Reset(); } v8::Isolate* isolate; v8::Persistent last_exception; v8::Persistent has_instance; v8::Persistent wrap_template; - v8::Persistent function_data_template; - v8::Persistent accessor_data_template; bool has_instance_available; napi_extended_error_info last_error; int open_handle_scopes = 0; @@ -60,7 +56,6 @@ struct napi_env__ { } \ } while (0) - #define RETURN_STATUS_IF_FALSE(env, condition, status) \ do { \ if (!(condition)) { \ @@ -168,6 +163,22 @@ struct napi_env__ { (out) = v8::type::New((buffer), (byte_offset), (length)); \ } while (0) +#define NAPI_CALL_INTO_MODULE(env, call, handle_exception) \ + do { \ + int open_handle_scopes = (env)->open_handle_scopes; \ + napi_clear_last_error((env)); \ + call; \ + CHECK_EQ((env)->open_handle_scopes, open_handle_scopes); \ + if (!(env)->last_exception.IsEmpty()) { \ + handle_exception( \ + v8::Local::New((env)->isolate, (env)->last_exception)); \ + (env)->last_exception.Reset(); \ + } \ + } while (0) + +#define NAPI_CALL_INTO_MODULE_THROW(env, call) \ + NAPI_CALL_INTO_MODULE((env), call, (env)->isolate->ThrowException) + namespace { namespace v8impl { @@ -278,6 +289,13 @@ v8::Local V8LocalValueFromJsValue(napi_value v) { return local; } +static inline void trigger_fatal_exception( + napi_env env, v8::Local local_err) { + v8::TryCatch try_catch(env->isolate); + env->isolate->ThrowException(local_err); + node::FatalException(env->isolate, try_catch); +} + static inline napi_status V8NameFromPropertyDescriptor(napi_env env, const napi_property_descriptor* p, v8::Local* result) { @@ -327,10 +345,11 @@ class Finalizer { static void FinalizeBufferCallback(char* data, void* hint) { Finalizer* finalizer = static_cast(hint); if (finalizer->_finalize_callback != nullptr) { - finalizer->_finalize_callback( - finalizer->_env, - data, - finalizer->_finalize_hint); + NAPI_CALL_INTO_MODULE_THROW(finalizer->_env, + finalizer->_finalize_callback( + finalizer->_env, + data, + finalizer->_finalize_hint)); } Delete(finalizer); @@ -436,12 +455,14 @@ class Reference : private Finalizer { // Check before calling the finalize callback, because the callback might // delete it. bool delete_self = reference->_delete_self; + napi_env env = reference->_env; if (reference->_finalize_callback != nullptr) { - reference->_finalize_callback( - reference->_env, - reference->_finalize_data, - reference->_finalize_hint); + NAPI_CALL_INTO_MODULE_THROW(env, + reference->_finalize_callback( + reference->_env, + reference->_finalize_data, + reference->_finalize_hint)); } if (delete_self) { @@ -471,15 +492,42 @@ class TryCatch : public v8::TryCatch { //=== Function napi_callback wrapper ================================= -static const int kDataIndex = 0; -static const int kEnvIndex = 1; +// Use this data structure to associate callback data with each N-API function +// exposed to JavaScript. The structure is stored in a v8::External which gets +// passed into our callback wrapper. This reduces the performance impact of +// calling through N-API. +// Ref: benchmark/misc/function_call +// Discussion (incl. perf. data): https://github.com/nodejs/node/pull/21072 +class CallbackBundle { + public: -static const int kFunctionIndex = 2; -static const int kFunctionFieldCount = 3; + ~CallbackBundle() { + handle.ClearWeak(); + handle.Reset(); + } + // Bind the lifecycle of `this` C++ object to a JavaScript object. + // We never delete a CallbackBundle C++ object directly. + void BindLifecycleTo(v8::Isolate* isolate, v8::Local target) { + handle.Reset(isolate, target); + handle.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); + } -static const int kGetterIndex = 2; -static const int kSetterIndex = 3; -static const int kAccessorFieldCount = 4; + napi_env env; // Necessary to invoke C++ NAPI callback + void* cb_data; // The user provided callback data + napi_callback function_or_getter; + napi_callback setter; + v8::Persistent handle; // Die with this JavaScript object + private: + static void WeakCallback(v8::WeakCallbackInfo const& info) { + // Use the "WeakCallback mechanism" to delete the C++ `bundle` object. + // This will be called when the v8::External containing `this` pointer + // is being GC-ed. + CallbackBundle* bundle = info.GetParameter(); + if (bundle != nullptr) { + delete bundle; + } + } +}; // Base class extended by classes that wrap V8 function and property callback // info. @@ -504,17 +552,17 @@ class CallbackWrapper { void* _data; }; -template +template class CallbackWrapperBase : public CallbackWrapper { public: CallbackWrapperBase(const Info& cbinfo, const size_t args_length) : CallbackWrapper(JsValueFromV8LocalValue(cbinfo.This()), args_length, nullptr), - _cbinfo(cbinfo), - _cbdata(v8::Local::Cast(cbinfo.Data())) { - _data = v8::Local::Cast(_cbdata->GetInternalField(kDataIndex)) - ->Value(); + _cbinfo(cbinfo) { + _bundle = reinterpret_cast( + v8::Local::Cast(cbinfo.Data())->Value()); + _data = _bundle->cb_data; } napi_value GetNewTarget() override { return nullptr; } @@ -523,42 +571,26 @@ class CallbackWrapperBase : public CallbackWrapper { void InvokeCallback() { napi_callback_info cbinfo_wrapper = reinterpret_cast( static_cast(this)); - napi_callback cb = reinterpret_cast( - v8::Local::Cast( - _cbdata->GetInternalField(kInternalFieldIndex))->Value()); - v8::Isolate* isolate = _cbinfo.GetIsolate(); - - napi_env env = static_cast( - v8::Local::Cast( - _cbdata->GetInternalField(kEnvIndex))->Value()); - // Make sure any errors encountered last time we were in N-API are gone. - napi_clear_last_error(env); + // All other pointers we need are stored in `_bundle` + napi_env env = _bundle->env; + napi_callback cb = _bundle->*FunctionField; - int open_handle_scopes = env->open_handle_scopes; - - napi_value result = cb(env, cbinfo_wrapper); + napi_value result; + NAPI_CALL_INTO_MODULE_THROW(env, result = cb(env, cbinfo_wrapper)); if (result != nullptr) { this->SetReturnValue(result); } - - CHECK_EQ(env->open_handle_scopes, open_handle_scopes); - - if (!env->last_exception.IsEmpty()) { - isolate->ThrowException( - v8::Local::New(isolate, env->last_exception)); - env->last_exception.Reset(); - } } const Info& _cbinfo; - const v8::Local _cbdata; + CallbackBundle* _bundle; }; class FunctionCallbackWrapper : public CallbackWrapperBase, - kFunctionIndex> { + &CallbackBundle::function_or_getter> { public: static void Invoke(const v8::FunctionCallbackInfo& info) { FunctionCallbackWrapper cbwrapper(info); @@ -604,7 +636,7 @@ class FunctionCallbackWrapper class GetterCallbackWrapper : public CallbackWrapperBase, - kGetterIndex> { + &CallbackBundle::function_or_getter> { public: static void Invoke(v8::Local property, const v8::PropertyCallbackInfo& info) { @@ -635,7 +667,8 @@ class GetterCallbackWrapper }; class SetterCallbackWrapper - : public CallbackWrapperBase, kSetterIndex> { + : public CallbackWrapperBase, + &CallbackBundle::setter> { public: static void Invoke(v8::Local property, v8::Local value, @@ -675,25 +708,16 @@ class SetterCallbackWrapper // Creates an object to be made available to the static function callback // wrapper, used to retrieve the native callback function and data pointer. static -v8::Local CreateFunctionCallbackData(napi_env env, - napi_callback cb, - void* data) { - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); +v8::Local CreateFunctionCallbackData(napi_env env, + napi_callback cb, + void* data) { + CallbackBundle* bundle = new CallbackBundle(); + bundle->function_or_getter = cb; + bundle->cb_data = data; + bundle->env = env; + v8::Local cbdata = v8::External::New(env->isolate, bundle); + bundle->BindLifecycleTo(env->isolate, cbdata); - v8::Local otpl; - ENV_OBJECT_TEMPLATE(env, function_data, otpl, v8impl::kFunctionFieldCount); - v8::Local cbdata = otpl->NewInstance(context).ToLocalChecked(); - - cbdata->SetInternalField( - v8impl::kEnvIndex, - v8::External::New(isolate, static_cast(env))); - cbdata->SetInternalField( - v8impl::kFunctionIndex, - v8::External::New(isolate, reinterpret_cast(cb))); - cbdata->SetInternalField( - v8impl::kDataIndex, - v8::External::New(isolate, data)); return cbdata; } @@ -701,36 +725,18 @@ v8::Local CreateFunctionCallbackData(napi_env env, // callback wrapper, used to retrieve the native getter/setter callback // function and data pointer. static -v8::Local CreateAccessorCallbackData(napi_env env, - napi_callback getter, - napi_callback setter, - void* data) { - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - - v8::Local otpl; - ENV_OBJECT_TEMPLATE(env, accessor_data, otpl, v8impl::kAccessorFieldCount); - v8::Local cbdata = otpl->NewInstance(context).ToLocalChecked(); - - cbdata->SetInternalField( - v8impl::kEnvIndex, - v8::External::New(isolate, static_cast(env))); - - if (getter != nullptr) { - cbdata->SetInternalField( - v8impl::kGetterIndex, - v8::External::New(isolate, reinterpret_cast(getter))); - } +v8::Local CreateAccessorCallbackData(napi_env env, + napi_callback getter, + napi_callback setter, + void* data) { + CallbackBundle* bundle = new CallbackBundle(); + bundle->function_or_getter = getter; + bundle->setter = setter; + bundle->cb_data = data; + bundle->env = env; + v8::Local cbdata = v8::External::New(env->isolate, bundle); + bundle->BindLifecycleTo(env->isolate, cbdata); - if (setter != nullptr) { - cbdata->SetInternalField( - v8impl::kSetterIndex, - v8::External::New(isolate, reinterpret_cast(setter))); - } - - cbdata->SetInternalField( - v8impl::kDataIndex, - v8::External::New(isolate, data)); return cbdata; } @@ -880,8 +886,10 @@ void napi_module_register_cb(v8::Local exports, // one is found. napi_env env = v8impl::GetEnv(context); - napi_value _exports = - mod->nm_register_func(env, v8impl::JsValueFromV8LocalValue(exports)); + napi_value _exports; + NAPI_CALL_INTO_MODULE_THROW(env, + _exports = mod->nm_register_func(env, + v8impl::JsValueFromV8LocalValue(exports))); // If register function returned a non-null exports object different from // the exports object we passed it, set that as the "exports" property of @@ -969,6 +977,16 @@ napi_status napi_get_last_error_info(napi_env env, return napi_ok; } +napi_status napi_fatal_exception(napi_env env, napi_value err) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, err); + + v8::Local local_err = v8impl::V8LocalValueFromJsValue(err); + v8impl::trigger_fatal_exception(env, local_err); + + return napi_clear_last_error(env); +} + NAPI_NO_RETURN void napi_fatal_error(const char* location, size_t location_len, const char* message, @@ -1008,16 +1026,16 @@ napi_status napi_create_function(napi_env env, v8::Isolate* isolate = env->isolate; v8::Local return_value; v8::EscapableHandleScope scope(isolate); - v8::Local cbdata = + v8::Local cbdata = v8impl::CreateFunctionCallbackData(env, cb, callback_data); RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); - v8::Local tpl = v8::FunctionTemplate::New( - isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); - v8::Local context = isolate->GetCurrentContext(); - v8::MaybeLocal maybe_function = tpl->GetFunction(context); + v8::MaybeLocal maybe_function = + v8::Function::New(context, + v8impl::FunctionCallbackWrapper::Invoke, + cbdata); CHECK_MAYBE_EMPTY(env, maybe_function, napi_generic_failure); return_value = scope.Escape(maybe_function.ToLocalChecked()); @@ -1048,7 +1066,7 @@ napi_status napi_define_class(napi_env env, v8::Isolate* isolate = env->isolate; v8::EscapableHandleScope scope(isolate); - v8::Local cbdata = + v8::Local cbdata = v8impl::CreateFunctionCallbackData(env, constructor, callback_data); RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); @@ -1084,7 +1102,7 @@ napi_status napi_define_class(napi_env env, // This code is similar to that in napi_define_properties(); the // difference is it applies to a template instead of an object. if (p->getter != nullptr || p->setter != nullptr) { - v8::Local cbdata = v8impl::CreateAccessorCallbackData( + v8::Local cbdata = v8impl::CreateAccessorCallbackData( env, p->getter, p->setter, p->data); tpl->PrototypeTemplate()->SetAccessor( @@ -1095,7 +1113,7 @@ napi_status napi_define_class(napi_env env, v8::AccessControl::DEFAULT, attributes); } else if (p->method != nullptr) { - v8::Local cbdata = + v8::Local cbdata = v8impl::CreateFunctionCallbackData(env, p->method, p->data); RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); @@ -1457,7 +1475,7 @@ napi_status napi_define_properties(napi_env env, v8impl::V8PropertyAttributesFromDescriptor(p); if (p->getter != nullptr || p->setter != nullptr) { - v8::Local cbdata = v8impl::CreateAccessorCallbackData( + v8::Local cbdata = v8impl::CreateAccessorCallbackData( env, p->getter, p->setter, @@ -1476,16 +1494,20 @@ napi_status napi_define_properties(napi_env env, return napi_set_last_error(env, napi_invalid_arg); } } else if (p->method != nullptr) { - v8::Local cbdata = + v8::Local cbdata = v8impl::CreateFunctionCallbackData(env, p->method, p->data); - RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); + CHECK_MAYBE_EMPTY(env, cbdata, napi_generic_failure); + + v8::MaybeLocal maybe_fn = + v8::Function::New(context, + v8impl::FunctionCallbackWrapper::Invoke, + cbdata); - v8::Local t = v8::FunctionTemplate::New( - isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); + CHECK_MAYBE_EMPTY(env, maybe_fn, napi_generic_failure); auto define_maybe = obj->DefineOwnProperty( - context, property_name, t->GetFunction(), attributes); + context, property_name, maybe_fn.ToLocalChecked(), attributes); if (!define_maybe.FromMaybe(false)) { return napi_set_last_error(env, napi_generic_failure); @@ -3451,20 +3473,17 @@ class Work : public node::AsyncResource { v8::HandleScope scope(env->isolate); CallbackScope callback_scope(work); - work->_complete(env, ConvertUVErrorCode(status), work->_data); + NAPI_CALL_INTO_MODULE(env, + work->_complete(env, ConvertUVErrorCode(status), work->_data), + [env] (v8::Local local_err) { + // If there was an unhandled exception in the complete callback, + // report it as a fatal exception. (There is no JavaScript on the + // callstack that can possibly handle it.) + v8impl::trigger_fatal_exception(env, local_err); + }); // Note: Don't access `work` after this point because it was // likely deleted by the complete callback. - - // If there was an unhandled exception in the complete callback, - // report it as a fatal exception. (There is no JavaScript on the - // callstack that can possibly handle it.) - if (!env->last_exception.IsEmpty()) { - v8::TryCatch try_catch(env->isolate); - env->isolate->ThrowException( - v8::Local::New(env->isolate, env->last_exception)); - node::FatalException(env->isolate, try_catch); - } } } diff --git a/src/node_api.h b/src/node_api.h index a3a07a646..27028f75c 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -110,6 +110,8 @@ NAPI_EXTERN napi_status napi_get_last_error_info(napi_env env, const napi_extended_error_info** result); +NAPI_EXTERN napi_status napi_fatal_exception(napi_env env, napi_value err); + NAPI_EXTERN NAPI_NO_RETURN void napi_fatal_error(const char* location, size_t location_len, const char* message, From 2ff776ffe36cb694506678edc68ebf17f8eacd89 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Thu, 12 Jul 2018 09:15:48 -0400 Subject: [PATCH 016/696] backport node::Persistent This reduces the delta in src/node_api.cc for the definition of the `CallbackBundle` structure back to zero wrt. upstream by copying the `node::Persistent` class template from upstream's src/node_persistent.h. PR-URL: https://github.com/nodejs/node-addon-api/pull/300 Reviewed-By: Michael Dawson --- src/node_api.cc | 11 +++-------- src/node_internals.h | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/node_api.cc b/src/node_api.cc index e717a3835..dd16016f3 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -498,13 +498,7 @@ class TryCatch : public v8::TryCatch { // calling through N-API. // Ref: benchmark/misc/function_call // Discussion (incl. perf. data): https://github.com/nodejs/node/pull/21072 -class CallbackBundle { - public: - - ~CallbackBundle() { - handle.ClearWeak(); - handle.Reset(); - } +struct CallbackBundle { // Bind the lifecycle of `this` C++ object to a JavaScript object. // We never delete a CallbackBundle C++ object directly. void BindLifecycleTo(v8::Isolate* isolate, v8::Local target) { @@ -516,7 +510,8 @@ class CallbackBundle { void* cb_data; // The user provided callback data napi_callback function_or_getter; napi_callback setter; - v8::Persistent handle; // Die with this JavaScript object + node::Persistent handle; // Die with this JavaScript object + private: static void WeakCallback(v8::WeakCallbackInfo const& info) { // Use the "WeakCallback mechanism" to delete the C++ `bundle` object. diff --git a/src/node_internals.h b/src/node_internals.h index 7444fbdd9..bacccffb7 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -70,6 +70,23 @@ class CallbackScope { namespace node { +// Copied from Node.js' src/node_persistent.h +template +struct ResetInDestructorPersistentTraits { + static const bool kResetInDestructor = true; + template + // Disallow copy semantics by leaving this unimplemented. + inline static void Copy( + const v8::Persistent&, + v8::Persistent>*); +}; + +// v8::Persistent does not reset the object slot in its destructor. That is +// acknowledged as a flaw in the V8 API and expected to change in the future +// but for now node::Persistent is the easier and safer alternative. +template +using Persistent = v8::Persistent>; + #if NODE_MAJOR_VERSION < 8 || NODE_MAJOR_VERSION == 8 && NODE_MINOR_VERSION < 2 typedef int async_id; From 908cdc314ce49eef7f48c0e57358504ecd5755ac Mon Sep 17 00:00:00 2001 From: Kyle Farnung Date: Mon, 16 Jul 2018 14:07:10 -0700 Subject: [PATCH 017/696] doc: add `TypedArray` and `TypedArrayOf` * Added documentation for `TypedArray` and `TypedArrayOf` * Tweaked the documentation for `ArrayBuffer` PR-URL: https://github.com/nodejs/node-addon-api/pull/305 Reviewed-By: Michael Dawson --- doc/array_buffer.md | 46 +++++++------- doc/typed_array.md | 77 ++++++++++++++++++++++-- doc/typed_array_of.md | 136 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 229 insertions(+), 30 deletions(-) diff --git a/doc/array_buffer.md b/doc/array_buffer.md index d6a682a82..754983686 100644 --- a/doc/array_buffer.md +++ b/doc/array_buffer.md @@ -1,28 +1,30 @@ # ArrayBuffer -The `ArrayBuffer` class corresponds to the JavaScript `ArrayBuffer` class. +The `ArrayBuffer` class corresponds to the +[JavaScript `ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) +class. ## Methods ### New -Allocates a new `ArrayBuffer` object with a given length. +Allocates a new `ArrayBuffer` instance with a given length. ```cpp static ArrayBuffer New(napi_env env, size_t byteLength); ``` -- `[in] env`: The environment in which to create the ArrayBuffer object. +- `[in] env`: The environment in which to create the `ArrayBuffer` instance. - `[in] byteLength`: The length to be allocated, in bytes. -Returns a new `ArrayBuffer` object. +Returns a new `ArrayBuffer` instance. ### New -Wraps the provided external data into a new `ArrayBuffer` object. +Wraps the provided external data into a new `ArrayBuffer` instance. -The `ArrayBuffer` object does not assume ownership for the data and expects it -to be valid for the lifetime of the object. Since the `ArrayBuffer` is subject +The `ArrayBuffer` instance does not assume ownership for the data and expects it +to be valid for the lifetime of the instance. Since the `ArrayBuffer` is subject to garbage collection this overload is only suitable for data which is static and never needs to be freed. @@ -30,19 +32,19 @@ and never needs to be freed. static ArrayBuffer New(napi_env env, void* externalData, size_t byteLength); ``` -- `[in] env`: The environment in which to create the `ArrayBuffer` object. +- `[in] env`: The environment in which to create the `ArrayBuffer` instance. - `[in] externalData`: The pointer to the external data to wrap. - `[in] byteLength`: The length of the `externalData`, in bytes. -Returns a new `ArrayBuffer` object. +Returns a new `ArrayBuffer` instance. ### New -Wraps the provided external data into a new `ArrayBuffer` object. +Wraps the provided external data into a new `ArrayBuffer` instance. -The `ArrayBuffer` object does not assume ownership for the data and expects it -to be valid for the lifetime of the object. The data can only be freed once the -`finalizeCallback` is invoked to indicate that the `ArrayBuffer` has been +The `ArrayBuffer` instance does not assume ownership for the data and expects it +to be valid for the lifetime of the instance. The data can only be freed once +the `finalizeCallback` is invoked to indicate that the `ArrayBuffer` has been released. ```cpp @@ -53,22 +55,22 @@ static ArrayBuffer New(napi_env env, Finalizer finalizeCallback); ``` -- `[in] env`: The environment in which to create the `ArrayBuffer` object. +- `[in] env`: The environment in which to create the `ArrayBuffer` instance. - `[in] externalData`: The pointer to the external data to wrap. - `[in] byteLength`: The length of the `externalData`, in bytes. - `[in] finalizeCallback`: A function to be called when the `ArrayBuffer` is destroyed. It must implement `operator()`, accept a `void*` (which is the `externalData` pointer), and return `void`. -Returns a new `ArrayBuffer` object. +Returns a new `ArrayBuffer` instance. ### New -Wraps the provided external data into a new `ArrayBuffer` object. +Wraps the provided external data into a new `ArrayBuffer` instance. -The `ArrayBuffer` object does not assume ownership for the data and expects it -to be valid for the lifetime of the object. The data can only be freed once the -`finalizeCallback` is invoked to indicate that the `ArrayBuffer` has been +The `ArrayBuffer` instance does not assume ownership for the data and expects it +to be valid for the lifetime of the instance. The data can only be freed once +the `finalizeCallback` is invoked to indicate that the `ArrayBuffer` has been released. ```cpp @@ -80,7 +82,7 @@ static ArrayBuffer New(napi_env env, Hint* finalizeHint); ``` -- `[in] env`: The environment in which to create the `ArrayBuffer` object. +- `[in] env`: The environment in which to create the `ArrayBuffer` instance. - `[in] externalData`: The pointer to the external data to wrap. - `[in] byteLength`: The length of the `externalData`, in bytes. - `[in] finalizeCallback`: The function to be called when the `ArrayBuffer` is @@ -89,7 +91,7 @@ static ArrayBuffer New(napi_env env, - `[in] finalizeHint`: The hint to be passed as the second parameter of the finalize callback. -Returns a new `ArrayBuffer` object. +Returns a new `ArrayBuffer` instance. ### Constructor @@ -107,7 +109,7 @@ Initializes a wrapper instance of an existing `ArrayBuffer` object. ArrayBuffer(napi_env env, napi_value value); ``` -- `[in] env`: The environment in which to create the `ArrayBuffer` object. +- `[in] env`: The environment in which to create the `ArrayBuffer` instance. - `[in] value`: The `ArrayBuffer` reference to wrap. ### ByteLength diff --git a/doc/typed_array.md b/doc/typed_array.md index 43f5bfea7..f96d36499 100644 --- a/doc/typed_array.md +++ b/doc/typed_array.md @@ -1,5 +1,74 @@ -# Typed array +# TypedArray -You are reading a draft of the next documentation and it's in continuous 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 `TypedArray` class corresponds to the +[JavaScript `TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) +class. + +## Methods + +### Constructor + +Initializes an empty instance of the `TypedArray` class. + +```cpp +TypedArray(); +``` + +### Constructor + +Initializes a wrapper instance of an existing `TypedArray` instance. + +```cpp +TypedArray(napi_env env, napi_value value); +``` + +- `[in] env`: The environment in which to create the `TypedArray` instance. +- `[in] value`: The `TypedArray` reference to wrap. + +### TypedArrayType + +```cpp +napi_typedarray_type TypedArrayType() const; +``` + +Returns the type of this instance. + +### ArrayBuffer + +```cpp +Napi::ArrayBuffer ArrayBuffer() const; +``` + +Returns the backing array buffer. + +### ElementSize + +```cpp +uint8_t ElementSize() const; +``` + +Returns the size of one element, in bytes. + +### ElementLength + +```cpp +size_t ElementLength() const; +``` + +Returns the number of elements. + +### ByteOffset + +```cpp +size_t ByteOffset() const; +``` + +Returns the offset into the `ArrayBuffer` where the array starts, in bytes. + +### ByteLength + +```cpp +size_t ByteLength() const; +``` + +Returns the length of the array, in bytes. diff --git a/doc/typed_array_of.md b/doc/typed_array_of.md index 4ed65485a..868e5ec44 100644 --- a/doc/typed_array_of.md +++ b/doc/typed_array_of.md @@ -1,5 +1,133 @@ -# Typed array of +# TypedArrayOf -You are reading a draft of the next documentation and it's in continuous 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 `TypedArrayOf` class corresponds to the various +[JavaScript `TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) +classes. + +## Typedefs + +The common JavaScript `TypedArray` types are pre-defined for each of use: + +```cpp +typedef TypedArrayOf Int8Array; +typedef TypedArrayOf Uint8Array; +typedef TypedArrayOf Int16Array; +typedef TypedArrayOf Uint16Array; +typedef TypedArrayOf Int32Array; +typedef TypedArrayOf Uint32Array; +typedef TypedArrayOf Float32Array; +typedef TypedArrayOf Float64Array; +``` + +The one exception is the `Uint8ClampedArray` which requires explicit +initialization: + +```cpp +Uint8Array::New(env, length, napi_uint8_clamped_array) +``` + +Note that while it's possible to create a "clamped" array the _clamping_ +behavior is only applied in JavaScript. + +## Methods + +### New + +Allocates a new `TypedArray` instance with a given length. The underlying +`ArrayBuffer` is allocated automatically to the desired number of elements. + +The array type parameter can normally be omitted (because it is inferred from +the template parameter T), except when creating a "clamped" array. + +```cpp +static TypedArrayOf New(napi_env env, + size_t elementLength, + napi_typedarray_type type); +``` + +- `[in] env`: The environment in which to create the `TypedArrayOf` instance. +- `[in] elementLength`: The length to be allocated, in elements. +- `[in] type`: The type of array to allocate (optional). + +Returns a new `TypedArrayOf` instance. + +### New + +Wraps the provided `ArrayBuffer` into a new `TypedArray` instance. + +The array `type` parameter can normally be omitted (because it is inferred from +the template parameter `T`), except when creating a "clamped" array. + +```cpp +static TypedArrayOf New(napi_env env, + size_t elementLength, + Napi::ArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type); +``` + +- `[in] env`: The environment in which to create the `TypedArrayOf` instance. +- `[in] elementLength`: The length to array, in elements. +- `[in] arrayBuffer`: The backing `ArrayBuffer` instance. +- `[in] bufferOffset`: The offset into the `ArrayBuffer` where the array starts, + in bytes. +- `[in] type`: The type of array to allocate (optional). + +Returns a new `TypedArrayOf` instance. + +### Constructor + +Initializes an empty instance of the `TypedArrayOf` class. + +```cpp +TypedArrayOf(); +``` + +### Constructor + +Initializes a wrapper instance of an existing `TypedArrayOf` object. + +```cpp +TypedArrayOf(napi_env env, napi_value value); +``` + +- `[in] env`: The environment in which to create the `TypedArrayOf` object. +- `[in] value`: The `TypedArrayOf` reference to wrap. + +### operator [] + +```cpp +T& operator [](size_t index); +``` + +- `[in] index: The element index into the array. + +Returns the element found at the given index. + +### operator [] + +```cpp +const T& operator [](size_t index) const; +``` + +- `[in] index: The element index into the array. + +Returns the element found at the given index. + +### Data + +```cpp +T* Data() const; +``` + +Returns a pointer into the backing `ArrayBuffer` which is offset to point to the +start of the array. + +### Data + +```cpp +const T* Data() const +``` + +Returns a pointer into the backing `ArrayBuffer` which is offset to point to the +start of the array. From 968a5f2000ac8117bce59c29f790740023dc803b Mon Sep 17 00:00:00 2001 From: Anisha Rohra Date: Wed, 18 Jul 2018 02:20:33 -0400 Subject: [PATCH 018/696] doc: Add documentation for ObjectReference.md PR-URL: https://github.com/nodejs/node-addon-api/pull/307 Reviewed-By: Michael Dawson --- doc/object_reference.md | 120 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 4 deletions(-) diff --git a/doc/object_reference.md b/doc/object_reference.md index fcc4d9d45..da826f109 100644 --- a/doc/object_reference.md +++ b/doc/object_reference.md @@ -1,5 +1,117 @@ -# Object reference +# Object Reference + +ObjectReference is a subclass of [Reference](reference.md), and is equivalent to an instance of `Reference`. This means that an ObjectReference holds an [Object](object.md), and a count of the number of references to that Object. When the count is greater than 0, an ObjectReference is not eligible for garbage collection. This ensures that the Object being held as a value of the ObjectReference will remain accessible, even if the original Object no longer is. However, ObjectReference is unique from a Reference since properties can be set and get to the Object itself that can be accessed through the ObjectReference. + +For more general information on references, please consult [Reference](referenc.md). + +## Example +```cpp +#include + +using namescape Napi; + +void Init(Env env) { + + // Create an empty ObjectReference that has an initial reference count of 2. + ObjectReference obj_ref = Reference::New(Object::New(env), 2); + + // Set a couple of different properties on the reference. + obj_ref.Set("hello", String::New(env, "world")); + obj_ref.Set(42, "The Answer to Life, the Universe, and Everything"); + + // Get the properties using the keys. + Value val1 = obj_ref.Get("hello"); + Value val2 = obj_ref.Get(42); +} +``` + +## Methods + +### Initialization + +```cpp +static ObjectReference New(const Object& value, uint32_t initialRefcount = 0); +``` + +* `[in] value`: The Object which is to be referenced. + +* `[in] initialRefcount`: The initial reference count. + +Returns the newly created reference. + +```cpp +static ObjectReference Weak(const Object& value); +``` + +Creates a "weak" reference to the value, in that the initial count of number of references is set to 0. + +* `[in] value`: The value which is to be referenced. + +Returns the newly created reference. + +```cpp +static ObjectReference Persistent(const Object& value); +``` + +Creates a "persistent" reference to the value, in that the initial count of number of references is set to 1. + +* `[in] value`: The value which is to be referenced. + +Returns the newly created reference. + +### Empty Constructor + +```cpp +ObjectReference(); +``` + +Returns a new _empty_ ObjectReference instance. + +### Constructor + +```cpp +ObjectReference(napi_env env, napi_value value); +``` + +* `[in] env`: The `napi_env` environment in which to construct the ObjectReference object. + +* `[in] value`: The N-API primitive value to be held by the ObjectReference. + +Returns the newly created reference. + +### Set +```cpp +void Set(___ key, ___ value); +``` + +* `[in] key`: The name for the property being assigned. + +* `[in] value`: The value being assigned to the property. + +The `key` can be any of the following types: +- `const char*` +- `const std::string` +- `uint32_t` + +The `value` can be any of the following types: +- `napi_value` +- `Napi::Value` +- `const char*` +- `bool` +- `double` + +### Get + +```cpp +Value Get(___ key); +``` + +* `[in] key`: The name of the property to return the value for. + +Returns the [Value](value.md) associated with the key property. Returns NULL if no such key exists. + +The `key` can be any of the following types: +- `const char*` +- `const std::string` +- `uint32_t` -You are reading a draft of the next documentation and it's in continuous 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/) From d68e86adb44da0eebed85a88833a97d4a1a7c5f2 Mon Sep 17 00:00:00 2001 From: Anisha Rohra Date: Thu, 19 Jul 2018 00:21:32 -0400 Subject: [PATCH 019/696] doc: Added documentation for PropertyDescriptor PR-URL: https://github.com/nodejs/node-addon-api/pull/309 Reviewed-By: Michael Dawson --- doc/property_descriptor.md | 144 +++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 4 deletions(-) diff --git a/doc/property_descriptor.md b/doc/property_descriptor.md index 65bf400ad..02c022fcb 100644 --- a/doc/property_descriptor.md +++ b/doc/property_descriptor.md @@ -1,5 +1,141 @@ -# property descriptor +# Property Descriptor + +An [Object](object.md) can be assigned properites via its [DefineProperty](object.md#defineproperty) and [DefineProperties](object.md#defineproperties) function, which take PropertyDescrptor(s) as their parameters. The PropertyDescriptor can contain either values or functions, which are then assigned to the Object. Note that a single instance of a PropertyDescriptor class can only contain either one value, or at most two functions. PropertyDescriptors can only be created through the class methods [Accessor](#accessor), [Function](#function), or [Value](#value), each of which return a new static instance of a PropertyDescriptor. + +## Example + +```cpp +#include + +using namespace Napi; + +Value TestGetter(const CallbackInfo& info) { + return Boolean::New(info.Env(), testValue); +} + +void TestSetter(const CallbackInfo& info) { + testValue = info[0].As(); +} + +Value TestFunction(const CallbackInfo& info) { + return Boolean::New(info.Env(), true); +} + +Void Init(Env env) { + // Accessor + PropertyDescriptor pd1 = PropertyDescriptor::Accessor("pd1", TestGetter); + PropertyDescriptor pd2 = PropertyDescriptor::Accessor("pd2", TestGetter, TestSetter); + + // Function + PropertyDescriptor pd3 = PropertyDescriptor::Function("function", TestFunction); + + // Value + Boolean true_bool = Boolean::New(env, true); + PropertyDescriptor pd4 = PropertyDescriptor::Value("boolean value", TestFunction, napi_writable); + + // Assign to an Object + Object obj = Object::New(env); + obj.DefineProperties({pd1, pd2, pd3, pd4}); +} +``` + +## Methods + +### Constructor + +```cpp +Napi::PropertyDescriptor::PropertyDescriptor (napi_property_descriptor desc); +``` + +* `[in] desc`: A PropertyDescriptor that is needed in order to create another PropertyDescriptor. + +### Accessor + +```cpp +static PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, + Getter getter, + napi_property_attributes attributes = napi_default, + void *data = nullptr); +``` + +* `[in] name`: The name used for the getter function. +* `[in] getter`: A getter function. +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a PropertyDescriptor that contains a function. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `napi_value value` +- `Name` + +```cpp +static PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void *data = nullptr); +``` + +* `[in] name`: The name of the getter and setter function. +* `[in] getter`: The getter function. +* `[in] setter`: The setter function. +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a PropertyDescriptor that contains a Getter and Setter function. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `napi_value value` +- `Name` + +### Function + +```cpp +static PropertyDescriptor Napi::PropertyDescriptor::Function (___ name, + Callable cb, + napi_property_attributes attributes = napi_default, + void *data = nullptr); +``` + +* `[in] name`: The name of the Callable function. +* `[in] cb`: The function +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a PropertyDescriptor that contains a callable Function. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `napi_value value` +- `Name` + +### Value + +```cpp +static PropertyDescriptor Napi::PropertyDescriptor::Value (___ name, + napi_value value, + napi_property_attributes attributes = napi_default); +``` + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `napi_value value` +- `Name` + +## Related Information + +### napi\_property\_attributes +`napi_property_attributes` are flags used to indicate to JavaScript certain permissions that the property is meant to have. The following are the flag options: +- napi\_default, +- napi\_writable, +- napi\_enumerable, +- napi\_configurable +For more information on the flags and on napi\_property\_attributes, please read the documentation [here](https://github.com/nodejs/node/blob/master/doc/api/n-api.md#napi_property_attributes). -You are reading a draft of the next documentation and it's in continuous 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/) From 7dc5ac8bc3ac3db844b3b313b5a91575fd258d8f Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Thu, 19 Jul 2018 20:40:19 +0200 Subject: [PATCH 020/696] doc: update metadata for release --- README.md | 2 +- package.json | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b3a97d0ac..700b4db72 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 1.3** +## **Current version: 1.4** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index f1fdeb73a..3ba21b326 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "Anisha Rohra (https://github.com/anisha-rohra)", "Anna Henningsen (https://github.com/addaleax)", "Arunesh Chandra (https://github.com/aruneshchandra)", + "Ben Berman (https://github.com/rivertam)", "Benjamin Byholm (https://github.com/kkoopa)", "Cory Mickelson (https://github.com/corymickelson)", "David Halls (https://github.com/davedoesdev)", @@ -16,12 +17,14 @@ "Jason Ginchereau (https://github.com/jasongin)", "Jim Schlight (https://github.com/jschlight)", "Jinho Bang (https://github.com/romandev)", + "joshgarde (https://github.com/joshgarde)", "Konstantin Tarkus (https://github.com/koistya)", "Kyle Farnung (https://github.com/kfarnung)", "Matteo Collina (https://github.com/mcollina)", "Michael Dawson (https://github.com/mhdawson)", "Michele Campus (https://github.com/kYroL01)", "Nicola Del Gobbo (https://github.com/NickNaso)", + "Nick Soggin (https://github.com/iSkore)", "Rolf Timmermans (https://github.com/rolftimmermans)", "Sampson Gao (https://github.com/sampsongao)", "Taylor Woll (https://github.com/boingoing)" @@ -47,5 +50,5 @@ "test": "node test", "doc": "doxygen doc/Doxyfile" }, - "version": "1.3.0" + "version": "1.4.0" } From 2885c185912655076e0eb01de6d7e5e6393cbd19 Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Thu, 19 Jul 2018 20:56:47 +0200 Subject: [PATCH 021/696] doc: Create changelog for release 1.4.0 --- CHANGELOG.md | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf1289344..216ca2545 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,48 @@ # node-addon-api Changelog -## 2018-05-08 Version 1.3.0 (Current), @mhdawson +## 2018-07-19 Version 1.4.0 (Current), @NickNasso + +### Notable changes: + +#### Documentation + +- Numerous additions to the documentation, filling out coverage + of API surface + +#### API + +- Add resource parameters to AsyncWorker constructor +- Add memory management feature + +### Commits + +* [[`7dc5ac8bc3`](https://github.com/nodejs/node-addon-api/commit/7dc5ac8bc3)] - **doc**: update metadata for release (Nicola Del Gobbo) +* [[`d68e86adb4`](https://github.com/nodejs/node-addon-api/commit/d68e86adb4)] - **doc**: Added documentation for PropertyDescriptor (Anisha Rohra) [#309](https://github.com/nodejs/node-addon-api/pull/309) +* [[`968a5f2000`](https://github.com/nodejs/node-addon-api/commit/968a5f2000)] - **doc**: Add documentation for ObjectReference.md (Anisha Rohra) [#307](https://github.com/nodejs/node-addon-api/pull/307) +* [[`908cdc314c`](https://github.com/nodejs/node-addon-api/commit/908cdc314c)] - **doc**: add `TypedArray` and `TypedArrayOf` (Kyle Farnung) [#305](https://github.com/nodejs/node-addon-api/pull/305) +* [[`2ff776ffe3`](https://github.com/nodejs/node-addon-api/commit/2ff776ffe3)] - backport node::Persistent (Gabriel Schulhof) [#300](https://github.com/nodejs/node-addon-api/pull/300) +* [[`98161970c9`](https://github.com/nodejs/node-addon-api/commit/98161970c9)] - Backport perf, crash and exception handling fixes (Gabriel Schulhof) [#295](https://github.com/nodejs/node-addon-api/pull/295) +* [[`dd1191e086`](https://github.com/nodejs/node-addon-api/commit/dd1191e086)] - **test**: fix asyncworker test so it runs on 6.x (Michael Dawson) [#298](https://github.com/nodejs/node-addon-api/pull/298) +* [[`11697fcecd`](https://github.com/nodejs/node-addon-api/commit/11697fcecd)] - **doc**: ArrayBuffer and Buffer documentation (Kyle Farnung) [#256](https://github.com/nodejs/node-addon-api/pull/256) +* [[`605aa2babf`](https://github.com/nodejs/node-addon-api/commit/605aa2babf)] - Add memory management feature (NickNaso) [#286](https://github.com/nodejs/node-addon-api/pull/286) +* [[`86be13a611`](https://github.com/nodejs/node-addon-api/commit/86be13a611)] - **doc**: Fix HandleScope docs (Ben Berman) [#287](https://github.com/nodejs/node-addon-api/pull/287) +* [[`90f92c4dc0`](https://github.com/nodejs/node-addon-api/commit/90f92c4dc0)] - **doc**: Update broken links in README.md (Hitesh Kanwathirtha) [#290](https://github.com/nodejs/node-addon-api/pull/290) +* [[`c2a620dc11`](https://github.com/nodejs/node-addon-api/commit/c2a620dc11)] - **doc**: Clarify positioning versus N-API (Michael Dawson) [#288](https://github.com/nodejs/node-addon-api/pull/288) +* [[`6cff890ee5`](https://github.com/nodejs/node-addon-api/commit/6cff890ee5)] - **doc**: Fix typo in docs (Ben Berman) [#284](https://github.com/nodejs/node-addon-api/pull/284) +* [[`7394bfd154`](https://github.com/nodejs/node-addon-api/commit/7394bfd154)] - **doc**: Fix typo in docs (Ben Berman) [#285](https://github.com/nodejs/node-addon-api/pull/285) +* [[`12b2cdeed3`](https://github.com/nodejs/node-addon-api/commit/12b2cdeed3)] - fix test files (Kyle Farnung) [#257](https://github.com/nodejs/node-addon-api/pull/257) +* [[`9ab6607242`](https://github.com/nodejs/node-addon-api/commit/9ab6607242)] - **doc**: Update Doc Version Number (joshgarde) [#277](https://github.com/nodejs/node-addon-api/pull/277) +* [[`e029a076c6`](https://github.com/nodejs/node-addon-api/commit/e029a076c6)] - **doc**: First pass at basic Node Addon API docs (Hitesh Kanwathirtha) [#268](https://github.com/nodejs/node-addon-api/pull/268) +* [[`74ff79717e`](https://github.com/nodejs/node-addon-api/commit/74ff79717e)] - **doc**: fix link to async\_worker.md (Michael Dawson) +* [[`5a63f45eda`](https://github.com/nodejs/node-addon-api/commit/5a63f45eda)] - **doc**: First step of error and async doc (NickNaso) [#272](https://github.com/nodejs/node-addon-api/pull/272) +* [[`9d38f61afb`](https://github.com/nodejs/node-addon-api/commit/9d38f61afb)] - **doc**: New Promise and Reference docs (Jim Schlight) [#243](https://github.com/nodejs/node-addon-api/pull/243) +* [[`43ff9fa836`](https://github.com/nodejs/node-addon-api/commit/43ff9fa836)] - **doc**: Updated Object documentation (Anisha Rohra) [#254](https://github.com/nodejs/node-addon-api/pull/254) +* [[`b197f7cc8b`](https://github.com/nodejs/node-addon-api/commit/b197f7cc8b)] - **doc**: minor typos (Nick Soggin) [#248](https://github.com/nodejs/node-addon-api/pull/248) +* [[`4b8918b352`](https://github.com/nodejs/node-addon-api/commit/4b8918b352)] - Add resource parameters to AsyncWorker constructor (Jinho Bang) [#253](https://github.com/nodejs/node-addon-api/pull/253) +* [[`1ecf7c19b6`](https://github.com/nodejs/node-addon-api/commit/1ecf7c19b6)] - **doc**: fix wrong link in readme (miloas) [#255](https://github.com/nodejs/node-addon-api/pull/255) +* [[`a750ed1932`](https://github.com/nodejs/node-addon-api/commit/a750ed1932)] - **release**: updates to metadata for next release (Michael Dawson) + +## 2018-05-08 Version 1.3.0, @mhdawson ### Notable changes: From 4d92a6066f9d0c3dffe9349728558433a55bec02 Mon Sep 17 00:00:00 2001 From: Anisha Rohra Date: Fri, 12 Jan 2018 11:23:56 -0500 Subject: [PATCH 022/696] src: Add ObjectReference test case PR-URL: https://github.com/nodejs/node-addon-api/pull/212 Reviewed-By: Michael Dawson --- test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 3 +- test/objectreference.cc | 218 +++++++++++++++++++++++++++++++++ test/objectreference.js | 260 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 test/objectreference.cc create mode 100644 test/objectreference.js diff --git a/test/binding.cc b/test/binding.cc index 0032c8e30..3b4bb7b0a 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -19,6 +19,7 @@ Object InitObject(Env env); Object InitPromise(Env env); Object InitTypedArray(Env env); Object InitObjectWrap(Env env); +Object InitObjectReference(Env env); Object Init(Env env, Object exports) { exports.Set("arraybuffer", InitArrayBuffer(env)); @@ -39,6 +40,7 @@ Object Init(Env env, Object exports) { exports.Set("promise", InitPromise(env)); exports.Set("typedarray", InitTypedArray(env)); exports.Set("objectwrap", InitObjectWrap(env)); + exports.Set("objectreference", InitObjectReference(env)); return exports; } diff --git a/test/binding.gyp b/test/binding.gyp index acbbc0a12..698eb45b7 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -24,6 +24,7 @@ 'promise.cc', 'typedarray.cc', 'objectwrap.cc', + 'objectreference.cc', ], 'include_dirs': ["::New(Object::New(env), 2); + reference.SuppressDestruct(); + + if (info[0].IsString()) { + if (info[2].As() == String::New(env, "javascript")) { + weak.Set(info[0].As(), info[1]); + persistent.Set(info[0].As(), info[1]); + reference.Set(info[0].As(), info[1]); + } else { + weak.Set(info[0].As().Utf8Value(), info[1]); + persistent.Set(info[0].As().Utf8Value(), info[1]); + reference.Set(info[0].As().Utf8Value(), info[1]); + } + } else if (info[0].IsNumber()) { + weak.Set(info[0].As(), info[1]); + persistent.Set(info[0].As(), info[1]); + reference.Set(info[0].As(), info[1]); + } +} + +void SetCastedObjects(const CallbackInfo& info) { + Env env = info.Env(); + HandleScope scope(env); + + Array ex = Array::New(env); + ex.Set((uint32_t)0, String::New(env, "hello")); + ex.Set(1, String::New(env, "world")); + ex.Set(2, String::New(env, "!")); + + casted_weak = Weak(ex.As()); + casted_weak.SuppressDestruct(); + + casted_persistent = Persistent(ex.As()); + casted_persistent.SuppressDestruct(); + + casted_reference = Reference::New(ex.As(), 2); + casted_reference.SuppressDestruct(); +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +Value GetFromValue(const CallbackInfo& info) { + Env env = info.Env(); + + if (info[0].As() == String::New(env, "weak")) { + if (weak.IsEmpty()) { + return String::New(env, "No Referenced Value"); + } else { + return weak.Value(); + } + } else if (info[0].As() == String::New(env, "persistent")) { + return persistent.Value(); + } else { + return reference.Value(); + } +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +// info[1] is the key, and it be either a String or a Number. +Value GetFromGetter(const CallbackInfo& info) { + Env env = info.Env(); + + if (info[0].As() == String::New(env, "weak")) { + if (weak.IsEmpty()) { + return String::New(env, "No Referenced Value"); + } else { + if (info[1].IsString()) { + return weak.Get(info[1].As().Utf8Value()); + } else if (info[1].IsNumber()) { + return weak.Get(info[1].As().Uint32Value()); + } + } + } else if (info[0].As() == String::New(env, "persistent")) { + if (info[1].IsString()) { + return persistent.Get(info[1].As().Utf8Value()); + } else if (info[1].IsNumber()) { + return persistent.Get(info[1].As().Uint32Value()); + } + } else { + if (info[0].IsString()) { + return reference.Get(info[0].As().Utf8Value()); + } else if (info[0].IsNumber()) { + return reference.Get(info[0].As().Uint32Value()); + } + } + + return String::New(env, "Error: Reached end of getter"); +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +Value GetCastedFromValue(const CallbackInfo& info) { + Env env = info.Env(); + + if (info[0].As() == String::New(env, "weak")) { + if (casted_weak.IsEmpty()) { + return String::New(env, "No Referenced Value"); + } else { + return casted_weak.Value(); + } + } else if (info[0].As() == String::New(env, "persistent")) { + return casted_persistent.Value(); + } else { + return casted_reference.Value(); + } +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +// info[1] is the key and it must be a Number. +Value GetCastedFromGetter(const CallbackInfo& info) { + Env env = info.Env(); + + if (info[0].As() == String::New(env, "weak")) { + if (casted_weak.IsEmpty()) { + return String::New(env, "No Referenced Value"); + } else { + return casted_weak.Get(info[1].As()); + } + } else if (info[0].As() == String::New(env, "persistent")) { + return casted_persistent.Get(info[1].As()); + } else { + return casted_reference.Get(info[1].As()); + } +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +Number UnrefObjects(const CallbackInfo& info) { + Env env = info.Env(); + uint32_t num; + + if (info[0].As() == String::New(env, "weak")) { + num = weak.Unref(); + } else if (info[0].As() == String::New(env, "persistent")) { + num = persistent.Unref(); + } else if (info[0].As() == String::New(env, "references")) { + num = reference.Unref(); + } else if (info[0].As() == String::New(env, "casted weak")) { + num = casted_weak.Unref(); + } else if (info[0].As() == String::New(env, "casted persistent")) { + num = casted_persistent.Unref(); + } else { + num = casted_reference.Unref(); + } + + return Number::New(env, num); +} + +// info[0] is a flag to determine if the weak, persistent, or +// multiple reference ObjectReference is being requested. +Number RefObjects(const CallbackInfo& info) { + Env env = info.Env(); + uint32_t num; + + if (info[0].As() == String::New(env, "weak")) { + num = weak.Ref(); + } else if (info[0].As() == String::New(env, "persistent")) { + num = persistent.Ref(); + } else if (info[0].As() == String::New(env, "references")) { + num = reference.Ref(); + } else if (info[0].As() == String::New(env, "casted weak")) { + num = casted_weak.Ref(); + } else if (info[0].As() == String::New(env, "casted persistent")) { + num = casted_persistent.Ref(); + } else { + num = casted_reference.Ref(); + } + + return Number::New(env, num); +} + +Object InitObjectReference(Env env) { + Object exports = Object::New(env); + + exports["setCastedObjects"] = Function::New(env, SetCastedObjects); + exports["setObjects"] = Function::New(env, SetObjects); + exports["getCastedFromValue"] = Function::New(env, GetCastedFromValue); + exports["getFromGetter"] = Function::New(env, GetFromGetter); + exports["getCastedFromGetter"] = Function::New(env, GetCastedFromGetter); + exports["getFromValue"] = Function::New(env, GetFromValue); + exports["unrefObjects"] = Function::New(env, UnrefObjects); + exports["refObjects"] = Function::New(env, RefObjects); + + return exports; +} diff --git a/test/objectreference.js b/test/objectreference.js new file mode 100644 index 000000000..07de0bc77 --- /dev/null +++ b/test/objectreference.js @@ -0,0 +1,260 @@ +/* + * First tests are for setting and getting the ObjectReference on the + * casted Array as Object. Then the tests are for the ObjectReference + * to an empty Object. They test setting the ObjectReference with a C + * string, a JavaScript string, and a JavaScript Number as the keys. + * Then getting the value of those keys through the Reference function + * Value() and through the ObjectReference getters. Finally, they test + * Unref() and Ref() to determine if the reference count is as + * expected and errors are thrown when expected. + */ + +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const testUtil = require('./testUtil'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + function testCastedEqual(testToCompare) { + var compare_test = ["hello", "world", "!"]; + if (testToCompare instanceof Array) { + assert.deepEqual(compare_test, testToCompare); + } else if (testToCompare instanceof String) { + assert.deepEqual("No Referenced Value", testToCompare); + } else { + assert.fail(); + } + } + + testUtil.runGCTests([ + 'Weak Casted Array', + () => { + binding.objectreference.setCastedObjects(); + var test = binding.objectreference.getCastedFromValue("weak"); + var test2 = new Array(); + test2[0] = binding.objectreference.getCastedFromGetter("weak", 0); + test2[1] = binding.objectreference.getCastedFromGetter("weak", 1); + test2[2] = binding.objectreference.getCastedFromGetter("weak", 2); + + testCastedEqual(test); + testCastedEqual(test2); + }, + + 'Persistent Casted Array', + () => { + binding.objectreference.setCastedObjects(); + const test = binding.objectreference.getCastedFromValue("persistent"); + const test2 = new Array(); + test2[0] = binding.objectreference.getCastedFromGetter("persistent", 0); + test2[1] = binding.objectreference.getCastedFromGetter("persistent", 1); + test2[2] = binding.objectreference.getCastedFromGetter("persistent", 2); + + assert.ok(test instanceof Array); + assert.ok(test2 instanceof Array); + testCastedEqual(test); + testCastedEqual(test2); + }, + + 'References Casted Array', + () => { + binding.objectreference.setCastedObjects(); + const test = binding.objectreference.getCastedFromValue(); + const test2 = new Array(); + test2[0] = binding.objectreference.getCastedFromGetter("reference", 0); + test2[1] = binding.objectreference.getCastedFromGetter("reference", 1); + test2[2] = binding.objectreference.getCastedFromGetter("reference", 2); + + assert.ok(test instanceof Array); + assert.ok(test2 instanceof Array); + testCastedEqual(test); + testCastedEqual(test2); + }, + + 'Weak', + () => { + binding.objectreference.setObjects("hello", "world"); + const test = binding.objectreference.getFromValue("weak"); + const test2 = binding.objectreference.getFromGetter("weak", "hello"); + + assert.deepEqual({ hello: "world"}, test); + assert.equal("world", test2); + assert.equal(test["hello"], test2); + }, + () => { + binding.objectreference.setObjects("hello", "world", "javascript"); + const test = binding.objectreference.getFromValue("weak"); + const test2 = binding.objectreference.getFromValue("weak", "hello"); + + assert.deepEqual({ hello: "world" }, test); + assert.deepEqual({ hello: "world" }, test2); + assert.equal(test, test2); + }, + () => { + binding.objectreference.setObjects(1, "hello world"); + const test = binding.objectreference.getFromValue("weak"); + const test2 = binding.objectreference.getFromGetter("weak", 1); + + assert.deepEqual({ 1: "hello world" }, test); + assert.equal("hello world", test2); + assert.equal(test[1], test2); + }, + () => { + binding.objectreference.setObjects(0, "hello"); + binding.objectreference.setObjects(1, "world"); + const test = binding.objectreference.getFromValue("weak"); + const test2 = binding.objectreference.getFromGetter("weak", 0); + const test3 = binding.objectreference.getFromGetter("weak", 1); + + assert.deepEqual({ 1: "world" }, test); + assert.equal(undefined, test2); + assert.equal("world", test3); + }, + () => { + binding.objectreference.setObjects("hello", "world"); + assert.doesNotThrow( + () => { + var rcount = binding.objectreference.refObjects("weak"); + assert.equal(rcount, 1); + rcount = binding.objectreference.unrefObjects("weak"); + assert.equal(rcount, 0); + }, + Error + ); + assert.throws( + () => { + binding.objectreference.unrefObjects("weak"); + }, + Error + ); + }, + + 'Persistent', + () => { + binding.objectreference.setObjects("hello", "world"); + const test = binding.objectreference.getFromValue("persistent"); + const test2 = binding.objectreference.getFromGetter("persistent", "hello"); + + assert.deepEqual({ hello: "world" }, test); + assert.equal("world", test2); + assert.equal(test["hello"], test2); + }, + () => { + binding.objectreference.setObjects("hello", "world", "javascript"); + const test = binding.objectreference.getFromValue("persistent"); + const test2 = binding.objectreference.getFromValue("persistent", "hello"); + + assert.deepEqual({ hello: "world" }, test); + assert.deepEqual({ hello: "world" }, test2); + assert.deepEqual(test, test2); + }, + () => { + binding.objectreference.setObjects(1, "hello world"); + const test = binding.objectreference.getFromValue("persistent"); + const test2 = binding.objectreference.getFromGetter("persistent", 1); + + assert.deepEqual({ 1: "hello world"}, test); + assert.equal("hello world", test2); + assert.equal(test[1], test2); + }, + () => { + binding.objectreference.setObjects(0, "hello"); + binding.objectreference.setObjects(1, "world"); + const test = binding.objectreference.getFromValue("persistent"); + const test2 = binding.objectreference.getFromGetter("persistent", 0); + const test3 = binding.objectreference.getFromGetter("persistent", 1); + + assert.deepEqual({ 1: "world"}, test); + assert.equal(undefined, test2); + assert.equal("world", test3); + }, + () => { + binding.objectreference.setObjects("hello", "world"); + assert.doesNotThrow( + () => { + var rcount = binding.objectreference.unrefObjects("persistent"); + assert.equal(rcount, 0); + rcount = binding.objectreference.refObjects("persistent"); + assert.equal(rcount, 1); + rcount = binding.objectreference.unrefObjects("persistent"); + assert.equal(rcount, 0); + rcount = binding.objectreference.refObjects("persistent"); + assert.equal(rcount, 1); + rcount = binding.objectreference.unrefObjects("persistent"); + assert.equal(rcount, 0); + }, + Error + ); + assert.throws( + () => { + binding.objectreference.unrefObjects("persistent"); + }, + Error + ); + }, + + 'References', + () => { + binding.objectreference.setObjects("hello", "world"); + const test = binding.objectreference.getFromValue(); + const test2 = binding.objectreference.getFromGetter("hello"); + + assert.deepEqual({ hello: "world" }, test); + assert.equal("world", test2); + assert.equal(test["hello"], test2); + }, + () => { + binding.objectreference.setObjects("hello", "world", "javascript"); + const test = binding.objectreference.getFromValue(); + const test2 = binding.objectreference.getFromValue("hello"); + + assert.deepEqual({ hello: "world" }, test); + assert.deepEqual({ hello: "world" }, test2); + assert.deepEqual(test, test2); + }, + () => { + binding.objectreference.setObjects(1, "hello world"); + const test = binding.objectreference.getFromValue(); + const test2 = binding.objectreference.getFromGetter(1); + + assert.deepEqual({ 1: "hello world"}, test); + assert.equal("hello world", test2); + assert.equal(test[1], test2); + }, + () => { + binding.objectreference.setObjects(0, "hello"); + binding.objectreference.setObjects(1, "world"); + const test = binding.objectreference.getFromValue(); + const test2 = binding.objectreference.getFromGetter(0); + const test3 = binding.objectreference.getFromGetter(1); + + assert.deepEqual({ 1: "world"}, test); + assert.equal(undefined, test2); + assert.equal("world", test3); + }, + () => { + binding.objectreference.setObjects("hello", "world"); + assert.doesNotThrow( + () => { + var rcount = binding.objectreference.unrefObjects("references"); + assert.equal(rcount, 1); + rcount = binding.objectreference.refObjects("references"); + assert.equal(rcount, 2); + rcount = binding.objectreference.unrefObjects("references"); + assert.equal(rcount, 1); + rcount = binding.objectreference.unrefObjects("references"); + assert.equal(rcount, 0); + }, + Error + ); + assert.throws( + () => { + binding.objectreference.unrefObjects("references"); + }, + Error + ); + } + ]) +}; From b0ecd38d76fac57edd953c183cffcace274d5b37 Mon Sep 17 00:00:00 2001 From: Jake Yoon Date: Thu, 6 Sep 2018 02:48:38 +0900 Subject: [PATCH 023/696] Fix Code of conduct link properly (#323) PR-URL: https://github.com/nodejs/node-addon-api/pull/323 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 60fac9ffd..eb07a975e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ # Code of Conduct The Node.js Code of Conduct, which applies to this project, can be found at -https://github.com/nodejs/TSC/blob/master/CODE_OF_CONDUCT.md. +https://github.com/nodejs/admin/blob/master/CODE_OF_CONDUCT.md. From 0097e96b9279bc0624412edb9f71755e69ea6aea Mon Sep 17 00:00:00 2001 From: NickNaso Date: Wed, 29 Aug 2018 15:47:20 +0200 Subject: [PATCH 024/696] Fixed broken links for Symbol and String --- doc/basic_types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/basic_types.md b/doc/basic_types.md index 9a4fc71eb..43b041dc8 100644 --- a/doc/basic_types.md +++ b/doc/basic_types.md @@ -315,8 +315,8 @@ the returned value. ## Name Names are JavaScript values that can be used as a property name. There are two -specialized types of names supported in Node.js Addon API- [`String`](String.md) -and [`Symbol`](Symbol.md). +specialized types of names supported in Node.js Addon API- [`String`](string.md) +and [`Symbol`](symbol.md). ### Methods From a6f7a6ad51387d6d2c7ddf357b39a9e2b4696401 Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Thu, 23 Aug 2018 11:36:59 +0200 Subject: [PATCH 025/696] Add Env() to Promise::Deferred. --- napi-inl.h | 4 ++++ napi.h | 1 + 2 files changed, 5 insertions(+) diff --git a/napi-inl.h b/napi-inl.h index 46ba2c0b9..620cac501 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1622,6 +1622,10 @@ inline Promise Promise::Deferred::Promise() const { return Napi::Promise(_env, _promise); } +inline Napi::Env Promise::Deferred::Env() const { + return Napi::Env(_env); +} + inline void Promise::Deferred::Resolve(napi_value value) const { napi_status status = napi_resolve_deferred(_env, _deferred, value); NAPI_THROW_IF_FAILED(_env, status); diff --git a/napi.h b/napi.h index f9852968b..531d1c358 100644 --- a/napi.h +++ b/napi.h @@ -881,6 +881,7 @@ namespace Napi { Deferred(napi_env env); Napi::Promise Promise() const; + Napi::Env Env() const; void Resolve(napi_value value) const; void Reject(napi_value value) const; From a3951ab973b7f65723b5ce64b58417e2a44a8ea2 Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Thu, 30 Aug 2018 08:39:10 +0200 Subject: [PATCH 026/696] Add documentation for Env(). PR-URL: https://github.com/nodejs/node-addon-api/pull/318 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- doc/promises.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/promises.md b/doc/promises.md index f0bf8a61d..e67483bbb 100644 --- a/doc/promises.md +++ b/doc/promises.md @@ -41,6 +41,14 @@ Promise::Deferred(napi_env env); * `[in] env`: The `napi_env` environment in which to construct the Deferred object. +### Env + +```cpp +Napi::Env Env() const; +``` + +Returns the Env environment this Promise::Deferred object is associated with. + ### Promise ```cpp From 757eb1f5a368d7f52af6faf279b3075d37cf337f Mon Sep 17 00:00:00 2001 From: NickNaso Date: Tue, 19 Jun 2018 01:44:11 +0200 Subject: [PATCH 027/696] doc: add function and function reference doc PR-URL: https://github.com/nodejs/node-addon-api/pull/299 Reviewed-By: Michael Dawson --- doc/function.md | 283 +++++++++++++++++++++++++++++++++++++- doc/function_reference.md | 195 +++++++++++++++++++++++++- 2 files changed, 471 insertions(+), 7 deletions(-) diff --git a/doc/function.md b/doc/function.md index e4d7c92fc..ca85ef05a 100644 --- a/doc/function.md +++ b/doc/function.md @@ -1,5 +1,282 @@ # Function -You are reading a draft of the next documentation and it's in continuous 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 `Napi::Function` class provides a set of methods for creating a function object in +native code that can later be called from JavaScript. The created function is not +automatically visible from JavaScript. Instead it needs to be part of the add-on's +module exports or be returned by one of the module's exported functions. + +In addition the `Function` class also provides methods that can be used to call +functions that were created in JavaScript and passed to the native add-on. + +The `Napi::Function` class inherits its behavior from the `Napi::Object` class (for more info +see: [`Napi::Object`](object.md)). + +## Example + +```cpp +#include + +using namespace Napi; + +Value Fn(const CallbackInfo& info) { + Env env = info.Env(); + // ... + return String::New(env, "Hello World"); +} + +Object Init(Env env, Object exports) { + exports.Set(String::New(env, "fn"), Function::New(env, Fn)); +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) +``` + +The above code can be used from JavaScript as follows: + +```js +const addon = require('./addon'); +addon.fn(); +``` + +With the `Napi::Function` class it is possible to call a JavaScript function object +from a native add-on with two different methods: `Call` and `MakeCallback`. +The API of these two methods is very similar, but they are used in different +contexts. The `MakeCallback` method is used to call from native code back into +JavaScript after returning from an [asynchronous operation](async_operations.md) +and in general in situations which don't have an existing JavaScript function on +the stack. The `Call` method is used when there is already a JavaScript function +on the stack (for example when running a native method called from JavaScript). + +## Methods + +### Constructor + +Creates a new empty instance of `Napi::Function`. + +```cpp +Function(); +``` + +### Constructor + +Creates a new instance of the `Napi::Function` object. + +```cpp +Function(napi_env env, napi_value value); +``` + +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. +- `[in] value`: The `napi_value` which is a handle for a JavaScript function. + +Returns a non-empty `Napi::Function` instance. + +### New + +Creates an instance of a `Napi::Function` object. + +```cpp +template +static Function New(napi_env env, Callable cb, const char* utf8name = nullptr, void* data = nullptr); +``` + +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. +- `[in] cb`: Object that implements `Callable`. +- `[in] utf8name`: Null-terminated string to be used as the name of the function. +- `[in] data`: User-provided data context. This will be passed back into the +function when invoked later. + +Returns an instance of a `Napi::Function` object. + +### New + +```cpp +template +static Function New(napi_env env, Callable cb, const std::string& utf8name, void* data = nullptr); +``` + +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. +- `[in] cb`: Object that implements `Callable`. +- `[in] utf8name`: String to be used as the name of the function. +- `[in] data`: User-provided data context. This will be passed back into the +function when invoked later. + +Returns an instance of a `Napi::Function` object. + +### New + +Creates a new JavaScript value from one that represents the constructor for the +object. + +```cpp +Napi::Object New(const std::initializer_list& args) const; +``` + +- `[in] args`: Initializer list of JavaScript values as `napi_value` representing +the arguments of the contructor function. + +Returns a new JavaScript object. + +### New + +Creates a new JavaScript value from one that represents the constructor for the +object. + +```cpp +Napi::Object New(const std::vector& args) const; +``` + +- `[in] args`: Vector of JavaScript values as `napi_value` representing the +arguments of the constructor function. + +Returns a new JavaScript object. + +### New + +Creates a new JavaScript value from one that represents the constructor for the +object. + +```cpp +Napi::Object New(size_t argc, const napi_value* args) const; +``` + +- `[in] argc`: The number of the arguments passed to the contructor function. +- `[in] args`: Array of JavaScript values as `napi_value` representing the +arguments of the constructor function. + +Returns a new JavaScript object. + +### Call + +Calls a Javascript function from a native add-on. + +```cpp +Napi::Value Call(const std::initializer_list& args) const; +``` + +- `[in] args`: Initializer list of JavaScript values as `napi_value` representing +the arguments of the function. + +Returns a `Napi::Value` representing the JavaScript value returned by the function. + +### Call + +Calls a JavaScript function from a native add-on. + +```cpp +Napi::Value Call(const std::vector& args) const; +``` + +- `[in] args`: Vector of JavaScript values as `napi_value` representing the +arguments of the function. + +Returns a `Napi::Value` representing the JavaScript value returned by the function. + +### Call + +Calls a Javascript function from a native add-on. + +```cpp +Napi::Value Call(size_t argc, const napi_value* args) const; +``` + +- `[in] argc`: The number of the arguments passed to the function. +- `[in] args`: Array of JavaScript values as `napi_value` representing the +arguments of the function. + +Returns a `Napi::Value` representing the JavaScript value returned by the function. + +### Call + +Calls a Javascript function from a native add-on. + +```cpp +Napi::Value Call(napi_value recv, const std::initializer_list& args) const; +``` + +- `[in] recv`: The `this` object passed to the called function. +- `[in] args`: Initializer list of JavaScript values as `napi_value` representing +the arguments of the function. + +Returns a `Napi::Value` representing the JavaScript value returned by the function. + +### Call + +Calls a Javascript function from a native add-on. + +```cpp +Napi::Value Call(napi_value recv, const std::vector& args) const; +``` + +- `[in] recv`: The `this` object passed to the called function. +- `[in] args`: Vector of JavaScript values as `napi_value` representing the +arguments of the function. + +Returns a `Napi::Value` representing the JavaScript value returned by the function. + +### Call + +Calls a Javascript function from a native add-on. + +```cpp +Napi::Value Call(napi_value recv, size_t argc, const napi_value* args) const; +``` + +- `[in] recv`: The `this` object passed to the called function. +- `[in] argc`: The number of the arguments passed to the function. +- `[in] args`: Array of JavaScript values as `napi_value` representing the +arguments of the function. + +Returns a `Napi::Value` representing the JavaScript value returned by the function. + +### MakeCallback + +Calls a Javascript function from a native add-on after an asynchronous operation. + +```cpp +Napi::Value MakeCallback(napi_value recv, const std::initializer_list& args) const; +``` + +- `[in] recv`: The `this` object passed to the called function. +- `[in] args`: Initializer list of JavaScript values as `napi_value` representing +the arguments of the function. + +Returns a `Napi::Value` representing the JavaScript value returned by the function. + +### MakeCallback + +Calls a Javascript function from a native add-on after an asynchronous operation. + +```cpp +Napi::Value MakeCallback(napi_value recv, const std::vector& args) const; +``` + +- `[in] recv`: The `this` object passed to the called function. +- `[in] args`: List of JavaScript values as `napi_value` representing the +arguments of the function. + +Returns a `Napi::Value` representing the JavaScript value returned by the function. + +### MakeCallback + +Calls a Javascript function from a native add-on after an asynchronous operation. + +```cpp +Napi::Value MakeCallback(napi_value recv, size_t argc, const napi_value* args) const; +``` + +- `[in] recv`: The `this` object passed to the called function. +- `[in] argc`: The number of the arguments passed to the function. +- `[in] args`: Array of JavaScript values as `napi_value` representing the +arguments of the function. + +Returns a `Napi::Value` representing the JavaScript value returned by the function. + +## Operator + +```cpp +Napi::Value operator ()(const std::initializer_list& args) const; +``` + +- `[in] args`: Initializer list of JavaScript values as `napi_value`. + +Returns a `Napi::Value` representing the JavaScript value returned by the function. diff --git a/doc/function_reference.md b/doc/function_reference.md index 1978b81b4..e555f8b04 100644 --- a/doc/function_reference.md +++ b/doc/function_reference.md @@ -1,5 +1,192 @@ -# Function reference +# FunctionReference -You are reading a draft of the next documentation and it's in continuous 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/) +`Napi::FunctionReference` is a subclass of [`Napi::Reference`](reference.md), and +is equivalent to an instance of `Napi::Reference`. This means +that a `Napi::FunctionReference` holds a [`Napi::Function`](function.md), and a +count of the number of references to that `Napi::Function`. When the count is +greater than 0, a `Napi::FunctionReference` is not eligible for garbage collection. +This ensures that the `Function` will remain accessible, even if the original +reference to it is no longer available. +`Napi::FunctionReference` allows the referenced JavaScript function object to be +called from a native add-on with two different methods: `Call` and `MakeCallback`. +See the documentation for [`Napi::Function`](function.md) for when `Call` should +be used instead of `MakeCallback` and vice-versa. + +The `Napi::FunctionReference` class inherits its behavior from the `Napi::Reference` +class (for more info see: [`Napi::Reference`](reference.md)). + +## Methods + +### Weak + +Creates a "weak" reference to the value, in that the initial reference count is +set to 0. + +```cpp +static FunctionReference Weak(const Function& value); +``` + +- `[in] value`: The value which is to be referenced. + +Returns the newly created reference. + +### Persistent + +Creates a "persistent" reference to the value, in that the initial reference +count is set to 1. + +```cpp +static FunctionReference Persistent(const Function& value); +``` + +- `[in] value`: The value which is to be referenced. + +Returns the newly created reference. + +### Constructor + +Creates a new empty instance of `Napi::FunctionReference`. + +```cpp +FunctionReference(); +``` + +### Constructor + +Creates a new instance of the `Napi::FunctionReference`. + +```cpp +FunctionReference(napi_env env, napi_ref ref); +``` + +- `[in] env`: The environment in which to construct the `Napi::FunctionReference` object. +- `[in] ref`: The N-API reference to be held by the `Napi::FunctionReference`. + +Returns a newly created `Napi::FunctionReference` object. + +### New + +Constructs a new instance by calling the constructor held by this reference. + +```cpp +Napi::Object New(const std::initializer_list& args) const; +``` + +- `[in] args`: Initializer list of JavaScript values as `napi_value` representing +the arguments of the contructor function. + +Returns a new JavaScript object. + +### New + +Constructs a new instance by calling the constructor held by this reference. + +```cpp +Napi::Object New(const std::vector& args) const; +``` + +- `[in] args`: Vector of JavaScript values as `napi_value` representing the +arguments of the constructor function. + +Returns a new JavaScript object. + +### Call + +Calls a referenced Javascript function from a native add-on. + +```cpp +Napi::Value Call(const std::initializer_list& args) const; +``` + +- `[in] args`: Initializer list of JavaScript values as `napi_value` representing +the arguments of the referenced function. + +Returns a `Napi::Value` representing the JavaScript object returned by the referenced +function. + +### Call + +Calls a referenced Javascript function from a native add-on. + +```cpp +Napi::Value Call(const std::vector& args) const; +``` + +- `[in] args`: Vector of JavaScript values as `napi_value` representing the +arguments of the referenced function. + +Returns a `Napi::Value` representing the JavaScript object returned by the referenced +function. + +### Call + +Calls a referenced Javascript function from a native add-on. + +```cpp +Napi::Value Call(napi_value recv, const std::initializer_list& args) const; +``` + +- `[in] recv`: The `this` object passed to the referenced function when it's called. +- `[in] args`: Initializer list of JavaScript values as `napi_value` representing +the arguments of the referenced function. + +Returns a `Napi::Value` representing the JavaScript object returned by the referenced +function. + +### Call + +Calls a referenced Javascript function from a native add-on. + +```cpp +Napi::Value Call(napi_value recv, const std::vector& args) const; +``` + +- `[in] recv`: The `this` object passed to the referenced function when it's called. +- `[in] args`: Vector of JavaScript values as `napi_value` representing the +arguments of the referenced function. + +Returns a `Napi::Value` representing the JavaScript object returned by the referenced +function. + +### MakeCallback + +Calls a referenced Javascript function from a native add-on after an asynchronous +operation. + +```cpp +Napi::Value MakeCallback(napi_value recv, const std::initializer_list& args) const; +``` + +- `[in] recv`: The `this` object passed to the referenced function when it's called. +- `[in] args`: Initializer list of JavaScript values as `napi_value` representing +the arguments of the referenced function. + +Returns a `Napi::Value` representing the JavaScript object returned by the referenced +function. + +### MakeCallback + +Calls a referenced Javascript function from a native add-on after an asynchronous +operation. + +```cpp +Napi::Value MakeCallback(napi_value recv, const std::vector& args) const; +``` + +- `[in] recv`: The `this` object passed to the referenced function when it's called. +- `[in] args`: Vector of JavaScript values as `napi_value` representing the +arguments of the referenced function. + +Returns a `Napi::Value` representing the JavaScript object returned by the referenced +function. + +## Operator + +```cpp +Napi::Value operator ()(const std::initializer_list& args) const; +``` + +- `[in] args`: Initializer list of reference to JavaScript values as `napi_value` + +Returns a `Napi::Value` representing the JavaScript value returned by the referenced +function. From 8ed29f547c84c1d59ecc6b931fa1e15a260c4f3b Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Tue, 4 Sep 2018 10:03:50 -0400 Subject: [PATCH 028/696] doc: add blurb about ABI stability PR-URL: https://github.com/nodejs/node-addon-api/pull/326 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 700b4db72..9d4011575 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,13 @@ provided by N-API. As such, modules built against one version of Node.js using node-addon-api should run without having to be rebuilt with newer versions of Node.js. +It is important to remember that *other* Node.js interfaces such as +`libuv` (included in a project via `#include `) are not ABI-stable across +Node.js major versions. Thus, and addon must use N-API and/or `node-addon-api` +exclusively and build against a version of Node.js that includes an +implementation of N-API (meaning a version of Node.js newer than 6.14.2) in +order to benefit from ABI stability across Node.js major versions. + As new APIs are added to N-API, node-addon-api must be updated to provide wrappers for those new APIs. For this reason node-addon-api provides methods that allow callers to obtain the underlying N-API handles so From 7cdd78726afeacb2d61c98fbc0ea7f8caacebe91 Mon Sep 17 00:00:00 2001 From: Jaeseok Yoon Date: Thu, 6 Sep 2018 09:53:31 +0900 Subject: [PATCH 029/696] doc: added cpp highlight for string.md There is no cpp highlight for some function in string.md file. PR-URL: https://github.com/nodejs/node-addon-api/pull/329 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- doc/string.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/string.md b/doc/string.md index fd1ff8b13..faa308a2d 100644 --- a/doc/string.md +++ b/doc/string.md @@ -12,7 +12,7 @@ If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not being used, callers should check the result of `Env::IsExceptionPending` before attempting to use the returned value. -``` +```cpp String(napi_env env, napi_value value); ///< Wraps a N-API value primitive. ``` - `[in] env` - The environment in which to create the string. @@ -83,4 +83,4 @@ Returns a UTF-8 encoded C++ string. std::u16string Utf16Value() const; ``` -Returns a UTF-16 encoded C++ string. \ No newline at end of file +Returns a UTF-16 encoded C++ string. From 622ffaea76ffc21f1e8ecf659a77d79dd66a5e46 Mon Sep 17 00:00:00 2001 From: Mikhail Cheshkov Date: Tue, 14 Aug 2018 16:51:51 +0300 Subject: [PATCH 030/696] test: Tighten up compiler warnings PR-URL: https://github.com/nodejs/node-addon-api/pull/315 Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- napi-inl.h | 81 +++++++++++++++++++++++++-------------------- test/arraybuffer.cc | 4 +-- test/binding.gyp | 2 ++ test/buffer.cc | 4 +-- test/error.cc | 2 +- test/external.cc | 4 +-- test/objectwrap.cc | 4 +-- 7 files changed, 57 insertions(+), 44 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 620cac501..59362e704 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -27,6 +27,9 @@ namespace details { #define NAPI_THROW_IF_FAILED(env, status, ...) \ if ((status) != napi_ok) throw Error::New(env); +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) throw Error::New(env); + #else // NAPI_CPP_EXCEPTIONS #define NAPI_THROW(e) (e).ThrowAsJavaScriptException(); @@ -40,6 +43,14 @@ namespace details { return __VA_ARGS__; \ } +// We need a _VOID version of this macro to avoid warnings resulting from +// leaving the NAPI_THROW_IF_FAILED `...` argument empty. +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) { \ + Error::New(env).ThrowAsJavaScriptException(); \ + return; \ + } + #endif // NAPI_CPP_EXCEPTIONS #define NAPI_FATAL_IF_FAILED(status, location, message) \ @@ -160,7 +171,7 @@ struct AccessorCallbackData { napi_value exports) { \ return Napi::RegisterModule(env, exports, regfunc); \ } \ - NAPI_MODULE(modname, __napi_ ## regfunc); + NAPI_MODULE(modname, __napi_ ## regfunc) // Adapt the NAPI_MODULE registration function: // - Wrap the arguments in NAPI wrappers. @@ -867,21 +878,21 @@ template inline void Object::Set(napi_value key, const ValueType& value) { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } template inline void Object::Set(Value key, const ValueType& value) { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } template inline void Object::Set(const char* utf8name, const ValueType& value) { napi_status status = napi_set_named_property(_env, _value, utf8name, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } template @@ -929,7 +940,7 @@ template inline void Object::Set(uint32_t index, const ValueType& value) { napi_status status = napi_set_element(_env, _value, index, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline bool Object::Delete(uint32_t index) { @@ -949,19 +960,19 @@ inline Array Object::GetPropertyNames() { inline void Object::DefineProperty(const PropertyDescriptor& property) { napi_status status = napi_define_properties(_env, _value, 1, reinterpret_cast(&property)); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline void Object::DefineProperties(const std::initializer_list& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.begin())); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline void Object::DefineProperties(const std::vector& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.data())); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline bool Object::InstanceOf(const Function& constructor) const { @@ -1171,7 +1182,7 @@ inline void ArrayBuffer::EnsureInfo() const { // since they can never change during the lifetime of the ArrayBuffer. if (_data == nullptr) { napi_status status = napi_get_arraybuffer_info(_env, _value, &_data, &_length); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } } @@ -1222,7 +1233,7 @@ inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) { &_data /* data */, nullptr /* arrayBuffer */, nullptr /* byteOffset */); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline Napi::ArrayBuffer DataView::ArrayBuffer() const { @@ -1466,7 +1477,7 @@ inline TypedArrayOf::TypedArrayOf(napi_env env, napi_value value) : TypedArray(env, value), _data(nullptr) { napi_status status = napi_get_typedarray_info( _env, _value, &_type, &_length, reinterpret_cast(&_data), nullptr, nullptr); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } template @@ -1615,7 +1626,7 @@ inline Promise::Deferred Promise::Deferred::New(napi_env env) { inline Promise::Deferred::Deferred(napi_env env) : _env(env) { napi_status status = napi_create_promise(_env, &_deferred, &_promise); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline Promise Promise::Deferred::Promise() const { @@ -1628,12 +1639,12 @@ inline Napi::Env Promise::Deferred::Env() const { inline void Promise::Deferred::Resolve(napi_value value) const { napi_status status = napi_resolve_deferred(_env, _deferred, value); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline void Promise::Deferred::Reject(napi_value value) const { napi_status status = napi_reject_deferred(_env, _deferred, value); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) { @@ -1752,7 +1763,7 @@ inline void Buffer::EnsureInfo() const { size_t byteLength; void* voidData; napi_status status = napi_get_buffer_info(_env, _value, &voidData, &byteLength); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); _length = byteLength / sizeof (T); _data = static_cast(voidData); } @@ -1888,7 +1899,7 @@ inline void Error::ThrowAsJavaScriptException() const { HandleScope scope(_env); if (!IsEmpty()) { napi_status status = napi_throw(_env, Value()); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } } @@ -2077,7 +2088,7 @@ template inline void Reference::Reset() { if (_ref != nullptr) { napi_status status = napi_delete_reference(_env, _ref); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); _ref = nullptr; } } @@ -2090,7 +2101,7 @@ inline void Reference::Reset(const T& value, uint32_t refcount) { napi_value val = value; if (val != nullptr) { napi_status status = napi_create_reference(_env, value, refcount, &_ref); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } } @@ -2364,7 +2375,7 @@ inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) _argc = _staticArgCount; _argv = _staticArgs; napi_status status = napi_get_cb_info(env, info, &_argc, _argv, &_this, &_data); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); if (_argc > _staticArgCount) { // Use either a fixed-size array (on the stack) or a dynamically-allocated @@ -2373,7 +2384,7 @@ inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) _argv = _dynamicArgs; status = napi_get_cb_info(env, info, &_argc, _argv, nullptr, nullptr); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } } @@ -2430,7 +2441,7 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(const char* utf8name, Getter getter, napi_property_attributes attributes, - void* data) { + void* /*data*/) { typedef details::CallbackData CbData; // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, nullptr }); @@ -2459,7 +2470,7 @@ template inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, Getter getter, napi_property_attributes attributes, - void* data) { + void* /*data*/) { typedef details::CallbackData CbData; // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, nullptr }); @@ -2490,7 +2501,7 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(const char* utf8name, Getter getter, Setter setter, napi_property_attributes attributes, - void* data) { + void* /*data*/) { typedef details::AccessorCallbackData CbData; // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, setter }); @@ -2521,7 +2532,7 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, Getter getter, Setter setter, napi_property_attributes attributes, - void* data) { + void* /*data*/) { typedef details::AccessorCallbackData CbData; // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, setter }); @@ -2552,7 +2563,7 @@ template inline PropertyDescriptor PropertyDescriptor::Function(const char* utf8name, Callable cb, napi_property_attributes attributes, - void* data) { + void* /*data*/) { typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; typedef details::CallbackData CbData; // TODO: Delete when the function is destroyed @@ -2582,7 +2593,7 @@ template inline PropertyDescriptor PropertyDescriptor::Function(napi_value name, Callable cb, napi_property_attributes attributes, - void* data) { + void* /*data*/) { typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; typedef details::CallbackData CbData; // TODO: Delete when the function is destroyed @@ -2663,7 +2674,7 @@ inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { napi_ref ref; T* instance = static_cast(this); status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); - NAPI_THROW_IF_FAILED(env, status) + NAPI_THROW_IF_FAILED_VOID(env, status); Reference* instanceRef = instance; *instanceRef = Reference(env, ref); @@ -3031,7 +3042,7 @@ inline HandleScope::HandleScope(napi_env env, napi_handle_scope scope) inline HandleScope::HandleScope(Napi::Env env) : _env(env) { napi_status status = napi_open_handle_scope(_env, &_scope); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline HandleScope::~HandleScope() { @@ -3056,7 +3067,7 @@ inline EscapableHandleScope::EscapableHandleScope( inline EscapableHandleScope::EscapableHandleScope(Napi::Env env) : _env(env) { napi_status status = napi_open_escapable_handle_scope(_env, &_scope); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline EscapableHandleScope::~EscapableHandleScope() { @@ -3124,11 +3135,11 @@ inline AsyncWorker::AsyncWorker(const Object& receiver, napi_value resource_id; napi_status status = napi_create_string_latin1( _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); status = napi_create_async_work(_env, resource, resource_id, OnExecute, OnWorkComplete, this, &_work); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline AsyncWorker::~AsyncWorker() { @@ -3169,12 +3180,12 @@ inline Napi::Env AsyncWorker::Env() const { inline void AsyncWorker::Queue() { napi_status status = napi_queue_async_work(_env, _work); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline void AsyncWorker::Cancel() { napi_status status = napi_cancel_async_work(_env, _work); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } inline ObjectReference& AsyncWorker::Receiver() { @@ -3197,7 +3208,7 @@ inline void AsyncWorker::SetError(const std::string& error) { _error = error; } -inline void AsyncWorker::OnExecute(napi_env env, void* this_pointer) { +inline void AsyncWorker::OnExecute(napi_env /*env*/, void* this_pointer) { AsyncWorker* self = static_cast(this_pointer); #ifdef NAPI_CPP_EXCEPTIONS try { @@ -3211,7 +3222,7 @@ inline void AsyncWorker::OnExecute(napi_env env, void* this_pointer) { } inline void AsyncWorker::OnWorkComplete( - napi_env env, napi_status status, void* this_pointer) { + napi_env /*env*/, napi_status status, void* this_pointer) { AsyncWorker* self = static_cast(this_pointer); if (status != napi_cancelled) { HandleScope scope(self->_env); diff --git a/test/arraybuffer.cc b/test/arraybuffer.cc index b03492d43..27bb993fe 100644 --- a/test/arraybuffer.cc +++ b/test/arraybuffer.cc @@ -63,7 +63,7 @@ Value CreateExternalBufferWithFinalize(const CallbackInfo& info) { info.Env(), data, testLength, - [](Env env, void* finalizeData) { + [](Env /*env*/, void* finalizeData) { delete[] static_cast(finalizeData); finalizeCount++; }); @@ -92,7 +92,7 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) { info.Env(), data, testLength, - [](Env env, void* finalizeData, char* finalizeHint) { + [](Env /*env*/, void* finalizeData, char* /*finalizeHint*/) { delete[] static_cast(finalizeData); finalizeCount++; }, diff --git a/test/binding.gyp b/test/binding.gyp index 698eb45b7..52cb7c225 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -28,6 +28,8 @@ ], 'include_dirs': ["::New(info.Env(), new int(1), - [](Env env, int* data) { + [](Env /*env*/, int* data) { delete data; finalizeCount++; }); @@ -25,7 +25,7 @@ Value CreateExternalWithFinalizeHint(const CallbackInfo& info) { finalizeCount = 0; char* hint = nullptr; return External::New(info.Env(), new int(1), - [](Env env, int* data, char* hint) { + [](Env /*env*/, int* data, char* /*hint*/) { delete data; finalizeCount++; }, diff --git a/test/objectwrap.cc b/test/objectwrap.cc index 7dc5a8f63..fae7b2652 100644 --- a/test/objectwrap.cc +++ b/test/objectwrap.cc @@ -32,11 +32,11 @@ class Test : public Napi::ObjectWrap { return Napi::Number::New(info.Env(), value); } - Napi::Value Iter(const Napi::CallbackInfo& info) { + Napi::Value Iter(const Napi::CallbackInfo& /*info*/) { return Constructor.New({}); } - void Setter(const Napi::CallbackInfo& info, const Napi::Value& new_value) { + void Setter(const Napi::CallbackInfo& /*info*/, const Napi::Value& new_value) { value = new_value.As(); } From e44aca985ebf1b71a1d532a63c2ecaa61a34b736 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Thu, 28 Jun 2018 10:06:34 -0500 Subject: [PATCH 031/696] add bigint class --- doc/bigint.md | 94 ++++++++++++++++++++++++++++++++++++++++++++++ napi-inl.h | 69 ++++++++++++++++++++++++++++++++++ napi.h | 59 ++++++++++++++++++++++++++++- test/bigint.cc | 76 +++++++++++++++++++++++++++++++++++++ test/bigint.js | 52 +++++++++++++++++++++++++ test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 1 + test/typedarray.cc | 27 +++++++++++++ test/typedarray.js | 47 +++++++++++++++++++++++ 10 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 doc/bigint.md create mode 100644 test/bigint.cc create mode 100644 test/bigint.js diff --git a/doc/bigint.md b/doc/bigint.md new file mode 100644 index 000000000..6d3b0afc0 --- /dev/null +++ b/doc/bigint.md @@ -0,0 +1,94 @@ +# BigInt + +A JavaScript BigInt value. + +## Methods + +### New + +```cpp +static BigInt New(Napi::Env env, int64_t value); +static BigInt New(Napi::Env env, uint64_t value); +``` + + - `[in] env`: The environment in which to construct the `BigInt` object. + - `[in] value`: The value the JavaScript `BigInt` will contain + +These APIs convert the C `int64_t` and `uint64_t` types to the JavaScript +`BigInt` type. + +```cpp +static BigInt New(Napi::Env env, + int sign_bit, + size_t word_count, + const uint64_t* words); +``` + + - `[in] env`: The environment in which to construct the `BigInt` object. + - `[in] sign_bit`: Determines if the resulting `BigInt` will be positive or negative. + - `[in] word_count`: The length of the words array. + - `[in] words`: An array of `uint64_t` little-endian 64-bit words. + +This API converts an array of unsigned 64-bit words into a single `BigInt` +value. + +The resulting `BigInt` is calculated as: (–1)`sign_bit` (`words[0]` +× (264)0 + `words[1]` × (264)1 + …) + +Returns a new JavaScript `BigInt`. + +### Constructor + +```cpp +Napi::BigInt(); +``` + +Returns a new empty JavaScript `BigInt`. + +### Int64Value + +```cpp +int64_t Int64Value(bool* lossless) const; +``` + + - `[out] lossless`: Indicates whether the `BigInt` value was converted + losslessly. + +Returns the C `int64_t` primitive equivalent of the given JavaScript +`BigInt`. If needed it will truncate the value, setting lossless to false. + +### Uint64Value + +```cpp +uint64_t Uint64Value(bool* lossless) const; +``` + + - `[out] lossless`: Indicates whether the `BigInt` value was converted + losslessly. + +Returns the C `uint64_t` primitive equivalent of the given JavaScript +`BigInt`. If needed it will truncate the value, setting lossless to false. + +### WordCount + +```cpp +size_t WordCount() const; +``` + +Returns the number of words needed to store this `BigInt` value. + +### ToWords + +```cpp +void ToWords(size_t* word_count, int* sign_bit, uint64_t* words); +``` + + - `[out] sign_bit`: Integer representing if the JavaScript `BigInt` is positive + or negative. + - `[in/out] word_count`: Must be initialized to the length of the words array. + Upon return, it will be set to the actual number of words that would be + needed to store this `BigInt`. + - `[out] words`: Pointer to a pre-allocated 64-bit word array. + +Returns a single `BigInt` value into a sign bit, 64-bit little-endian array, +and the number of elements in the array. diff --git a/napi-inl.h b/napi-inl.h index 46ba2c0b9..c5ae15eb8 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -287,6 +287,12 @@ inline bool Value::IsNumber() const { return Type() == napi_number; } +#ifdef NAPI_EXPERIMENTAL +inline bool Value::IsBigInt() const { + return Type() == napi_bigint; +} +#endif // NAPI_EXPERIMENTAL + inline bool Value::IsString() const { return Type() == napi_string; } @@ -505,6 +511,69 @@ inline double Number::DoubleValue() const { return result; } +#ifdef NAPI_EXPERIMENTAL +//////////////////////////////////////////////////////////////////////////////// +// BigInt Class +//////////////////////////////////////////////////////////////////////////////// + +inline BigInt BigInt::New(napi_env env, int64_t val) { + napi_value value; + napi_status status = napi_create_bigint_int64(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline BigInt BigInt::New(napi_env env, uint64_t val) { + napi_value value; + napi_status status = napi_create_bigint_uint64(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline BigInt BigInt::New(napi_env env, int sign_bit, size_t word_count, const uint64_t* words) { + napi_value value; + napi_status status = napi_create_bigint_words(env, sign_bit, word_count, words, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline BigInt::BigInt() : Value() { +} + +inline BigInt::BigInt(napi_env env, napi_value value) : Value(env, value) { +} + +inline int64_t BigInt::Int64Value(bool* lossless) const { + int64_t result; + napi_status status = napi_get_value_bigint_int64( + _env, _value, &result, lossless); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline uint64_t BigInt::Uint64Value(bool* lossless) const { + uint64_t result; + napi_status status = napi_get_value_bigint_uint64( + _env, _value, &result, lossless); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline size_t BigInt::WordCount() const { + size_t word_count; + napi_status status = napi_get_value_bigint_words( + _env, _value, nullptr, &word_count, nullptr); + NAPI_THROW_IF_FAILED(_env, status, 0); + return word_count; +} + +inline void BigInt::ToWords(int* sign_bit, size_t* word_count, uint64_t* words) { + napi_status status = napi_get_value_bigint_words( + _env, _value, sign_bit, word_count, words); + NAPI_THROW_IF_FAILED(_env, status); +} +#endif // NAPI_EXPERIMENTAL + //////////////////////////////////////////////////////////////////////////////// // Name class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index f9852968b..0f95d9a0c 100644 --- a/napi.h +++ b/napi.h @@ -52,6 +52,9 @@ namespace Napi { class Value; class Boolean; class Number; +#ifdef NAPI_EXPERIMENTAL + class BigInt; +#endif // NAPI_EXPERIMENTAL class String; class Object; class Array; @@ -72,6 +75,10 @@ namespace Napi { typedef TypedArrayOf Uint32Array; ///< Typed-array of unsigned 32-bit integers typedef TypedArrayOf Float32Array; ///< Typed-array of 32-bit floating-point values typedef TypedArrayOf Float64Array; ///< Typed-array of 64-bit floating-point values +#ifdef NAPI_EXPERIMENTAL + typedef TypedArrayOf BigInt64Array; ///< Typed array of signed 64-bit integers + typedef TypedArrayOf BigUint64Array; ///< Typed array of unsigned 64-bit integers +#endif // NAPI_EXPERIMENTAL /// Defines the signature of a N-API C++ module's registration callback (init) function. typedef Object (*ModuleRegisterCallback)(Env env, Object exports); @@ -171,6 +178,9 @@ namespace Napi { bool IsNull() const; ///< Tests if a value is a null JavaScript value. bool IsBoolean() const; ///< Tests if a value is a JavaScript boolean. bool IsNumber() const; ///< Tests if a value is a JavaScript number. +#ifdef NAPI_EXPERIMENTAL + bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint. +#endif // NAPI_EXPERIMENTAL bool IsString() const; ///< Tests if a value is a JavaScript string. bool IsSymbol() const; ///< Tests if a value is a JavaScript symbol. bool IsArray() const; ///< Tests if a value is a JavaScript array. @@ -242,6 +252,47 @@ namespace Napi { double DoubleValue() const; ///< Converts a Number value to a 64-bit floating-point value. }; +#ifdef NAPI_EXPERIMENTAL + /// A JavaScript bigint value. + class BigInt : public Value { + public: + static BigInt New( + napi_env env, ///< N-API environment + int64_t value ///< Number value + ); + static BigInt New( + napi_env env, ///< N-API environment + uint64_t value ///< Number value + ); + + /// Creates a new BigInt object using a specified sign bit and a + /// specified list of digits/words. + /// The resulting number is calculated as: + /// (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...) + static BigInt New( + napi_env env, ///< N-API environment + int sign_bit, ///< Sign bit. 1 if negative. + size_t word_count, ///< Number of words in array + const uint64_t* words ///< Array of words + ); + + BigInt(); ///< Creates a new _empty_ BigInt instance. + BigInt(napi_env env, napi_value value); ///< Wraps a N-API value primitive. + + int64_t Int64Value(bool* lossless) const; ///< Converts a BigInt value to a 64-bit signed integer value. + uint64_t Uint64Value(bool* lossless) const; ///< Converts a BigInt value to a 64-bit unsigned integer value. + + size_t WordCount() const; ///< The number of 64-bit words needed to store the result of ToWords(). + + /// Writes the contents of this BigInt to a specified memory location. + /// `sign_bit` must be provided and will be set to 1 if this BigInt is negative. + /// `*word_count` has to be initialized to the length of the `words` array. + /// Upon return, it will be set to the actual number of words that would + /// be needed to store this BigInt (i.e. the return value of `WordCount()`). + void ToWords(int* sign_bit, size_t* word_count, uint64_t* words); + }; +#endif // NAPI_EXPERIMENTAL + /// A JavaScript string or symbol value (that can be used as a property name). class Name : public Value { public: @@ -705,6 +756,10 @@ namespace Napi { : std::is_same::value ? napi_uint32_array : std::is_same::value ? napi_float32_array : std::is_same::value ? napi_float64_array +#ifdef NAPI_EXPERIMENTAL + : std::is_same::value ? napi_bigint64_array + : std::is_same::value ? napi_biguint64_array +#endif // NAPI_EXPERIMENTAL : unknown_array_type; } /// !endcond @@ -1551,9 +1606,9 @@ namespace Napi { std::string _error; }; - // Memory management. + // Memory management. class MemoryManagement { - public: + public: static int64_t AdjustExternalMemory(Env env, int64_t change_in_bytes); }; diff --git a/test/bigint.cc b/test/bigint.cc new file mode 100644 index 000000000..48e44ac18 --- /dev/null +++ b/test/bigint.cc @@ -0,0 +1,76 @@ +#define NAPI_EXPERIMENTAL +#include "napi.h" + +using namespace Napi; + +namespace { + +Value IsLossless(const CallbackInfo& info) { + Env env = info.Env(); + + BigInt big = info[0].As(); + bool is_signed = info[1].ToBoolean().Value(); + + bool lossless; + if (is_signed) { + big.Int64Value(&lossless); + } else { + big.Uint64Value(&lossless); + } + + return Boolean::New(env, lossless); +} + +Value TestInt64(const CallbackInfo& info) { + bool lossless; + int64_t input = info[0].As().Int64Value(&lossless); + + return BigInt::New(info.Env(), input); +} + +Value TestUint64(const CallbackInfo& info) { + bool lossless; + uint64_t input = info[0].As().Uint64Value(&lossless); + + return BigInt::New(info.Env(), input); +} + +Value TestWords(const CallbackInfo& info) { + BigInt big = info[0].As(); + + size_t expected_word_count = big.WordCount(); + + int sign_bit; + size_t word_count = 10; + uint64_t words[10]; + + big.ToWords(&sign_bit, &word_count, words); + + if (word_count != expected_word_count) { + Error::New(info.Env(), "word count did not match").ThrowAsJavaScriptException(); + return BigInt(); + } + + return BigInt::New(info.Env(), sign_bit, word_count, words); +} + +Value TestTooBigBigInt(const CallbackInfo& info) { + int sign_bit = 0; + size_t word_count = SIZE_MAX; + uint64_t words[10]; + + return BigInt::New(info.Env(), sign_bit, word_count, words); +} + +} // anonymous namespace + +Object InitBigInt(Env env) { + Object exports = Object::New(env); + exports["IsLossless"] = Function::New(env, IsLossless); + exports["TestInt64"] = Function::New(env, TestInt64); + exports["TestUint64"] = Function::New(env, TestUint64); + exports["TestWords"] = Function::New(env, TestWords); + exports["TestTooBigBigInt"] = Function::New(env, TestTooBigBigInt); + + return exports; +} diff --git a/test/bigint.js b/test/bigint.js new file mode 100644 index 000000000..e4255172c --- /dev/null +++ b/test/bigint.js @@ -0,0 +1,52 @@ +'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) { + const { + TestInt64, + TestUint64, + TestWords, + IsLossless, + TestTooBigBigInt, + } = binding.bigint; + + [ + 0n, + -0n, + 1n, + -1n, + 100n, + 2121n, + -1233n, + 986583n, + -976675n, + 98765432213456789876546896323445679887645323232436587988766545658n, + -4350987086545760976737453646576078997096876957864353245245769809n, + ].forEach((num) => { + if (num > -(2n ** 63n) && num < 2n ** 63n) { + assert.strictEqual(TestInt64(num), num); + assert.strictEqual(IsLossless(num, true), true); + } else { + assert.strictEqual(IsLossless(num, true), false); + } + + if (num >= 0 && num < 2n ** 64n) { + assert.strictEqual(TestUint64(num), num); + assert.strictEqual(IsLossless(num, false), true); + } else { + assert.strictEqual(IsLossless(num, false), false); + } + + assert.strictEqual(num, TestWords(num)); + }); + + assert.throws(TestTooBigBigInt, { + name: 'RangeError', + message: 'Maximum BigInt size exceeded', + }); +} diff --git a/test/binding.cc b/test/binding.cc index 0032c8e30..b91367d30 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -6,6 +6,7 @@ Object InitArrayBuffer(Env env); Object InitAsyncWorker(Env env); Object InitBasicTypesNumber(Env env); Object InitBasicTypesValue(Env env); +Object InitBigInt(Env env); Object InitBuffer(Env env); Object InitDataView(Env env); Object InitDataViewReadWrite(Env env); @@ -25,6 +26,7 @@ Object Init(Env env, Object exports) { exports.Set("asyncworker", InitAsyncWorker(env)); exports.Set("basic_types_number", InitBasicTypesNumber(env)); exports.Set("basic_types_value", InitBasicTypesValue(env)); + exports.Set("bigint", InitBigInt(env)); exports.Set("buffer", InitBuffer(env)); exports.Set("dataview", InitDataView(env)); exports.Set("dataview_read_write", InitDataView(env)); diff --git a/test/binding.gyp b/test/binding.gyp index acbbc0a12..e81b00f60 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -5,6 +5,7 @@ 'asyncworker.cc', 'basic_types/number.cc', 'basic_types/value.cc', + 'bigint.cc', 'binding.cc', 'buffer.cc', 'dataview/dataview.cc', diff --git a/test/index.js b/test/index.js index c9d7fb0c2..1181dbf15 100644 --- a/test/index.js +++ b/test/index.js @@ -12,6 +12,7 @@ let testModules = [ 'asyncworker', 'basic_types/number', 'basic_types/value', + 'bigint', 'buffer', 'dataview/dataview', 'dataview/dataview_read_write', diff --git a/test/typedarray.cc b/test/typedarray.cc index b556a80a6..d231f69f6 100644 --- a/test/typedarray.cc +++ b/test/typedarray.cc @@ -1,3 +1,4 @@ +#define NAPI_EXPERIMENTAL #include "napi.h" using namespace Napi; @@ -64,6 +65,16 @@ Value CreateTypedArray(const CallbackInfo& info) { NAPI_TYPEDARRAY_NEW(Float64Array, info.Env(), length, napi_float64_array) : NAPI_TYPEDARRAY_NEW_BUFFER(Float64Array, info.Env(), length, buffer, bufferOffset, napi_float64_array); + } else if (arrayType == "bigint64") { + return buffer.IsUndefined() ? + NAPI_TYPEDARRAY_NEW(BigInt64Array, info.Env(), length, napi_bigint64_array) : + NAPI_TYPEDARRAY_NEW_BUFFER(BigInt64Array, info.Env(), length, buffer, bufferOffset, + napi_bigint64_array); + } else if (arrayType == "biguint64") { + return buffer.IsUndefined() ? + NAPI_TYPEDARRAY_NEW(BigUint64Array, info.Env(), length, napi_biguint64_array) : + NAPI_TYPEDARRAY_NEW_BUFFER(BigUint64Array, info.Env(), length, buffer, bufferOffset, + napi_biguint64_array); } else { Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); return Value(); @@ -86,6 +97,8 @@ Value GetTypedArrayType(const CallbackInfo& info) { case napi_uint32_array: return String::New(info.Env(), "uint32"); case napi_float32_array: return String::New(info.Env(), "float32"); case napi_float64_array: return String::New(info.Env(), "float64"); + case napi_bigint64_array: return String::New(info.Env(), "bigint64"); + case napi_biguint64_array: return String::New(info.Env(), "biguint64"); default: return String::New(info.Env(), "invalid"); } } @@ -122,6 +135,10 @@ Value GetTypedArrayElement(const CallbackInfo& info) { return Number::New(info.Env(), array.As()[index]); case napi_float64_array: return Number::New(info.Env(), array.As()[index]); + case napi_bigint64_array: + return BigInt::New(info.Env(), array.As()[index]); + case napi_biguint64_array: + return BigInt::New(info.Env(), array.As()[index]); default: Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); return Value(); @@ -160,6 +177,16 @@ void SetTypedArrayElement(const CallbackInfo& info) { case napi_float64_array: array.As()[index] = value.DoubleValue(); break; + case napi_bigint64_array: { + bool lossless; + array.As()[index] = value.As().Int64Value(&lossless); + break; + } + case napi_biguint64_array: { + bool lossless; + array.As()[index] = value.As().Uint64Value(&lossless); + break; + } default: Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); } diff --git a/test/typedarray.js b/test/typedarray.js index 9aa880c16..680cf856e 100644 --- a/test/typedarray.js +++ b/test/typedarray.js @@ -64,6 +64,53 @@ function test(binding) { } }); + [ + ['bigint64', BigInt64Array], + ['biguint64', BigUint64Array], + ].forEach(([type, Constructor]) => { + try { + const length = 4; + const t = binding.typedarray.createTypedArray(type, length); + assert.ok(t instanceof Constructor); + assert.strictEqual(binding.typedarray.getTypedArrayType(t), type); + assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); + + t[3] = 11n; + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11n); + binding.typedarray.setTypedArrayElement(t, 3, 22n); + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 22n); + assert.strictEqual(t[3], 22n); + + const b = binding.typedarray.getTypedArrayBuffer(t); + assert.ok(b instanceof ArrayBuffer); + } catch (e) { + console.log(type, Constructor); + throw e; + } + + try { + const length = 4; + const offset = 8; + const b = new ArrayBuffer(offset + 64 * 4); + + const t = binding.typedarray.createTypedArray(type, length, b, offset); + assert.ok(t instanceof Constructor); + assert.strictEqual(binding.typedarray.getTypedArrayType(t), type); + assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); + + t[3] = 11n; + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11n); + binding.typedarray.setTypedArrayElement(t, 3, 22n); + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 22n); + assert.strictEqual(t[3], 22n); + + assert.strictEqual(binding.typedarray.getTypedArrayBuffer(t), b); + } catch (e) { + console.log(type, Constructor); + throw e; + } + }); + assert.throws(() => { binding.typedarray.createInvalidTypedArray(); }, /Invalid (pointer passed as )?argument/); From 79ee8381d2fc49b5bb7c338ceb3559b8a7e6b489 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Tue, 18 Sep 2018 09:28:54 -0400 Subject: [PATCH 032/696] src: fix compile failure in test Since original submit for https://github.com/nodejs/node-addon-api/pull/292 warnings were tightened through https://github.com/nodejs/node-addon-api/pull/315 causing the bigint test to fail to compile Fix the compile failure PR-URL: https://github.com/nodejs/node-addon-api/pull/345 Reviewed-By: : None, landed to unbreak CI --- napi-inl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napi-inl.h b/napi-inl.h index e2319ac04..77f403e0c 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -581,7 +581,7 @@ inline size_t BigInt::WordCount() const { inline void BigInt::ToWords(int* sign_bit, size_t* word_count, uint64_t* words) { napi_status status = napi_get_value_bigint_words( _env, _value, sign_bit, word_count, words); - NAPI_THROW_IF_FAILED(_env, status); + NAPI_THROW_IF_FAILED_VOID(_env, status); } #endif // NAPI_EXPERIMENTAL From 2ad47a83b1c4da09fefa1983688b149caf7c909f Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Thu, 13 Sep 2018 18:14:18 -0400 Subject: [PATCH 033/696] test: explicitly cast to uint32_t in test Explicitly casting to `uint32_t` prevents a template resolution ambiguity on 32-bit platforms. Fixes: https://github.com/nodejs/node-addon-api/issues/337 PR-URL: https://github.com/nodejs/node-addon-api/pull/341 Fixes: https://github.com/nodejs/node-addon-api/issues/337 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- test/object/object.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/object/object.cc b/test/object/object.cc index 50fac9722..713ed5827 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -126,7 +126,7 @@ Value CreateObjectUsingMagic(const CallbackInfo& info) { obj[std::string("s_true")] = true; obj[std::string("s_false")] = false; obj["0"] = 0; - obj[42] = 120; + obj[(uint32_t)42] = 120; obj["0.0f"] = 0.0f; obj["0.0"] = 0.0; obj["-1"] = -1; From 14c69abd46787b89b125e9c5bde6616385f7750b Mon Sep 17 00:00:00 2001 From: Jaeseok Yoon Date: Thu, 6 Sep 2018 09:42:27 +0900 Subject: [PATCH 034/696] test: write tests for Boolean class The new tests cover a part of the basic type conversion from JavaScript type to native type. PR-URL: https://github.com/nodejs/node-addon-api/pull/328 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- test/basic_types/boolean.cc | 15 +++++++++++++++ test/basic_types/boolean.js | 14 ++++++++++++++ test/binding.cc | 2 ++ test/binding.gyp | 1 + test/index.js | 1 + 5 files changed, 33 insertions(+) create mode 100644 test/basic_types/boolean.cc create mode 100644 test/basic_types/boolean.js diff --git a/test/basic_types/boolean.cc b/test/basic_types/boolean.cc new file mode 100644 index 000000000..c874845eb --- /dev/null +++ b/test/basic_types/boolean.cc @@ -0,0 +1,15 @@ +#include "napi.h" + +using namespace Napi; + +Value CreateBoolean(const CallbackInfo& info) { + return Boolean::New(info.Env(), info[0].As().Value()); +} + +Object InitBasicTypesBoolean(Env env) { + Object exports = Object::New(env); + + exports["createBoolean"] = Function::New(env, CreateBoolean); + + return exports; +} diff --git a/test/basic_types/boolean.js b/test/basic_types/boolean.js new file mode 100644 index 000000000..3a9c88da8 --- /dev/null +++ b/test/basic_types/boolean.js @@ -0,0 +1,14 @@ +'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) { + const bool1 = binding.basic_types_boolean.createBoolean(true); + assert.strictEqual(bool1, true); + + const bool2 = binding.basic_types_boolean.createBoolean(false); + assert.strictEqual(bool2, false); +} diff --git a/test/binding.cc b/test/binding.cc index 6142ea0a6..aa807e7f5 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -4,6 +4,7 @@ using namespace Napi; Object InitArrayBuffer(Env env); Object InitAsyncWorker(Env env); +Object InitBasicTypesBoolean(Env env); Object InitBasicTypesNumber(Env env); Object InitBasicTypesValue(Env env); Object InitBigInt(Env env); @@ -25,6 +26,7 @@ Object InitObjectReference(Env env); Object Init(Env env, Object exports) { exports.Set("arraybuffer", InitArrayBuffer(env)); exports.Set("asyncworker", InitAsyncWorker(env)); + exports.Set("basic_types_boolean", InitBasicTypesBoolean(env)); exports.Set("basic_types_number", InitBasicTypesNumber(env)); exports.Set("basic_types_value", InitBasicTypesValue(env)); exports.Set("bigint", InitBigInt(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 3ca18ea87..cc75c09f0 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -3,6 +3,7 @@ 'sources': [ 'arraybuffer.cc', 'asyncworker.cc', + 'basic_types/boolean.cc', 'basic_types/number.cc', 'basic_types/value.cc', 'bigint.cc', diff --git a/test/index.js b/test/index.js index 580625008..5ae55400b 100644 --- a/test/index.js +++ b/test/index.js @@ -10,6 +10,7 @@ process.config.target_defaults.default_configuration = let testModules = [ 'arraybuffer', 'asyncworker', + 'basic_types/boolean', 'basic_types/number', 'basic_types/value', 'bigint', From c7d54180ff0ef742445ed39ba20bd66aae815c96 Mon Sep 17 00:00:00 2001 From: Arnaud Botella Date: Wed, 12 Sep 2018 17:06:54 +0200 Subject: [PATCH 035/696] doc: the Napi::ObjectWrap example does not compile The current example does not compile using GCC 4.8. PR-URL: https://github.com/nodejs/node-addon-api/pull/339 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- napi.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/napi.h b/napi.h index b2dce0ec5..3ad095485 100644 --- a/napi.h +++ b/napi.h @@ -1407,8 +1407,8 @@ namespace Napi { /// public: /// static void Initialize(Napi::Env& env, Napi::Object& target) { /// Napi::Function constructor = DefineClass(env, "Example", { - /// InstanceAccessor("value", &GetSomething, &SetSomething), - /// InstanceMethod("doSomething", &DoSomething), + /// InstanceAccessor("value", &Example::GetSomething, &Example::SetSomething), + /// InstanceMethod("doSomething", &Example::DoSomething), /// }); /// target.Set("Example", constructor); /// } From 100d0a7cb20de705f71b77be68f2dc2075ec13ae Mon Sep 17 00:00:00 2001 From: NickNaso Date: Wed, 29 Aug 2018 15:36:07 +0200 Subject: [PATCH 036/696] doc: first pass on objectwrap documentation PR-URL: https://github.com/nodejs/node-addon-api/pull/321 Reviewed-By: Michael Dawson --- doc/class_property_descriptor.md | 39 ++- doc/object_wrap.md | 426 ++++++++++++++++++++++++++++++- 2 files changed, 448 insertions(+), 17 deletions(-) diff --git a/doc/class_property_descriptor.md b/doc/class_property_descriptor.md index cca90d186..5df283491 100644 --- a/doc/class_property_descriptor.md +++ b/doc/class_property_descriptor.md @@ -1,5 +1,36 @@ -# Class propertry and descriptior +# Class propertry and descriptor -You are reading a draft of the next documentation and it's in continuous 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/) +Property descriptor for use with `Napi::ObjectWrap::DefineClass()`. +This is different from the standalone `Napi::PropertyDescriptor` because it is +specific to each `Napi::ObjectWrap` subclass. +This prevents using descriptors from a different class when defining a new class +(preventing the callbacks from having incorrect `this` pointers). + +## Methods + +### Contructor + +Creates new instance of `Napi::ClassPropertyDescriptor` descriptor object. + +```cpp +Napi::ClassPropertyDescriptor(napi_property_descriptor desc) : _desc(desc) {} +``` + +- `[in] desc`: The `napi_property_descriptor` + +Returns new instance of `Napi::ClassPropertyDescriptor` that is used as property descriptor +inside the `Napi::ObjectWrap` class. + +### Operator + +```cpp +operator napi_property_descriptor&() { return _desc; } +``` + +Returns the original N-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` + +```cpp +operator const napi_property_descriptor&() const { return _desc; } +``` + +Returns the original N-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` \ No newline at end of file diff --git a/doc/object_wrap.md b/doc/object_wrap.md index 9f4422e61..d422b4e8e 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -1,13 +1,413 @@ -## Object Wrap - -The ```ObjectWrap``` class can be used to expose C++ code to JavaScript. To do -this you need to extend the ObjectWrap class that contain all the plumbing to connect -JavaScript code to a C++ object. -Classes extending ```ObjectWrap``` can be instantiated from JavaScript using the -**new** operator, and their methods can be directly invoked from JavaScript. -The **wrap** word refers to a way to group methods and state of your class because it -will be your responsibility write custom code to bridge each of your C++ class methods. - -You are reading a draft of the next documentation and it's in continuous 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/) +# Object Wrap + +The `Napi::ObjectWrap` class is used to bind the lifetime of C++ code to a +JavaScript object. Once bound, each time an instance of the JavaScript object +is created, an instance of the C++ class will also be created. When a method +is called on the JavaScript object which is defined as an InstanceMethod, the +corresponding C++ method on the wrapped C++ class will be invoked. + +In order to create a wrapper it's necessary to extend the +`Napi::ObjectWrap`class which contains all the plumbing to connect JavaScript code +with a C++ object. Classes extending `Napi::ObjectWrap` can be instantiated from +JavaScript using the **new** operator, and their methods can be directly invoked +from JavaScript. The **wrap** word refers to a way of grouping methods and state +of the class because it will be necessary write custom code to bridge each of +your C++ class methods. + +## Example + +```cpp +#include + +class Example : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object exports); + Example(const Napi::CallbackInfo &info); + + private: + static Napi::FunctionReference constructor; + double _value; + Napi::Value GetValue(const Napi::CallbackInfo &info); + Napi::Value SetValue(const Napi::CallbackInfo &info); +}; + +Napi::Object Example::Init(Napi::Env env, Napi::Object exports) { + // This method is used to hook the accessor and method callbacks + Napi::Function func = DefineClass(env, "Example", { + InstanceMethod("GetValue", &Example::GetValue), + InstanceMethod("SetValue", &Example::SetValue) + }); + + // Create a peristent reference to the class constructor. This will allow + // a function called on a class prototype and a function + // called on instance of a class to be distinguished from each other. + constructor = Napi::Persistent(func); + // Call the SuppressDestruct() method on the static data prevent the calling + // to this destructor to reset the reference when the environment is no longer + // available. + constructor.SuppressDestruct(); + exports.Set("Example", func); + return exports; +} + +Example::Example(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) { + Napi::Env env = info.Env(); + // ... + Napi::Number value = info[0].As(); + this->_value = value.DoubleValue(); +} + +Napi::FunctionReference Example::constructor; + +Napi::Value Example::GetValue(const Napi::CallbackInfo &info){ + Napi::Env env = info.Env(); + return Napi::Number::New(env, this->_value); +} + +Napi::Value Example::SetValue(const Napi::CallbackInfo &info){ + Napi::Env env = info.Env(); + // ... + Napi::Number value = info[0].As(); + this->_value = value.DoubleValue(); + return this->GetValue(info); +} + +// Initialize native add-on +Napi::Object Init (Napi::Env env, Napi::Object exports) { + Example::Init(env, exports); + return exports; +} + +// Regisgter and initialize native add-on +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) +``` + +The above code can be used from JavaScript as follows: + +```js +'use strict' + +const { Example } = require('bindings')('addon') + +const example = new Example(11) +console.log(example.GetValue()) +// It prints 11 +example.SetValue(19) +console.log(example.GetValue()); +// It prints 19 +``` + +At initialization time, the `Napi::ObjectWrap::DefineClass()` method must be used +to hook up the accessor and method callbacks. It takes a list of property +descriptors, which can be constructed via the various static methods on the base +class. + +When JavaScript code invokes the constructor, the constructor callback will create +a new C++ instance and "wrap" it into the newly created JavaScript object. + +When JavaScript code invokes a method or a property accessor on the class the +corresponding C++ callback function will be executed. + +For a wrapped object it could be difficult to distinguish between a function called +on a class prototype and a function called on instance of a class. Therefore it is +good practice to save a persistent reference to the class constructor. This allows +the two cases to be distinguished from each other by checking the this object +against the class constructor. + +## Methods + +### Contructor + +Creates a new instance of a JavaScript object that wraps native instance. + +```cpp +Napi::ObjectWrap(const Napi::CallbackInfo& callbackInfo); +``` + +- `[in] callbackInfo`: The object representing the components of the JavaScript +request being made. + +### Unwrap + +Retrieves a native instance wrapped in a JavaScript object. + +```cpp +static T* Napi::ObjectWrap::Unwrap(Napi::Object wrapper); +``` + +* `[in] wrapper`: The JavaScript object that wraps the native instance. + +Returns a native instace wrapped in a JavaScript object. Given the +Napi:Object, this allows a method to get a pointer to the wrapped +C++ object and then reference fields, call methods, etc. within that class. +In many cases calling Unwrap is not required, as methods can +use the `this` field for ObjectWrap when running in a method on a +class that extends ObjectWrap. + +### DefineClass + +Defnines a JavaScript class with constructor, static and instance properties and +methods. + +```cpp +static Napi::Function Napi::ObjectWrap::DefineClass(Napi::Env env, + const char* utf8name, + const std::initializer_list& properties, + void* data = nullptr); +``` + +* `[in] env`: The environment in which to construct a JavaScript class. +* `[in] utf8name`: Null-terminated string that represents the name of the +JavaScript constructor function. +* `[in] properties`: Initializer list of class property descriptor describing +static and instance properties and methods of the class. +See: [`Class propertry and descriptor`](class_property_descriptor.md). +* `[in] data`: User-provided data passed to the constructor callback as `data` +property of the `Napi::CallbackInfo`. + +Returns a `Napi::Function` representing the constructor function for the class. + +### DefineClass + +Defnines a JavaScript class with constructor, static and instance properties and +methods. + +```cpp +static Napi::Function Napi::ObjectWrap::DefineClass(Napi::Env env, + const char* utf8name, + const std::vector& properties, + void* data = nullptr); +``` + +* `[in] env`: The environment in which to construct a JavaScript class. +* `[in] utf8name`: Null-terminated string that represents the name of the +JavaScript constructor function. +* `[in] properties`: Vector of class property descriptor describing static and +instance properties and methods of the class. +See: [`Class propertry and descriptor`](class_property_descriptor.md). +* `[in] data`: User-provided data passed to the constructor callback as `data` +property of the `Napi::CallbackInfo`. + +Returns a `Napi::Function` representing the constructor function for the class. + +### StaticMethod + +Creates property descriptor that represents a static method of a JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(const char* utf8name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of a static +method for the class. +- `[in] method`: The native function that represents a static method of a +JavaScript class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents the static method of a +JavaScript class. + +### StaticMethod + +Creates property descriptor that represents a static method of a JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(const char* utf8name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of a static +method for the class. +- `[in] method`: The native function that represents a static method of a +JavaScript class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents a static method of a +JavaScript class. + +### StaticAccessor + +Creates property descriptor that represents a static accessor property of a +JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticAccessor(const char* utf8name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of a static +accessor property for the class. +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into getter or setter when +is invoked. + +Returns `Napi::PropertyDescriptor` object that represents a static accessor +property of a JavaScript class. + +### InstanceMethod + +Creates property descriptor that represents an instance method of a JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceMethod(const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of an instance +method for the class. +- `[in] method`: The native function that represents an instance method of a +JavaScript class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance method of a +JavaScript class. + +### InstanceMethod + +Creates property descriptor that represents an instance method of a JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceMethod(const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of an instance +method for the class. +- `[in] method`: The native function that represents an instance method of a +JavaScript class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance method of a +JavaScript class. + +### InstanceMethod + +Creates property descriptor that represents an instance method of a JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceMethod(Napi::Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance method for the class. +- `[in] method`: The native function that represents an instance method of a +JavaScript class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance method of a +JavaScript class. + +### InstanceMethod + +Creates property descriptor that represents an instance method of a JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceMethod(Napi::Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance method for the class. +- `[in] method`: The native function that represents an instance method of a +JavaScript class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance method of a +JavaScript class. + +### InstanceAccessor + +Creates property descriptor that represents an instance accessor property of a +JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceAccessor(const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of an instance +accessor property for the class. +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] attributes`: The attributes associated with the particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into getter or setter when this is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance accessor +property of a JavaScript class. + +### StaticValue + +Creates property descriptor that represents an static value property of a +JavaScript class. +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticValue(const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the static +property. +- `[in] value`: The value that's retrieved by a get access of the property. +- `[in] attributes`: The attributes to be associated with the property in addition +to the napi_static attribute. One or more of `napi_property_attributes`. + +Returns `Napi::PropertyDescriptor` object that represents an static value +property of a JavaScript class + +### InstanceValue + +Creates property descriptor that represents an instance value property of a +JavaScript class. +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceValue(const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the property. +- `[in] value`: The value that's retrieved by a get access of the property. +- `[in] attributes`: The attributes to be associated with the property. +One or more of `napi_property_attributes`. + +Returns `Napi::PropertyDescriptor` object that represents an instance value +property of a JavaScript class. From 38e01b7e3bbef31f0f7c8e4256e5d8b7641b6b6c Mon Sep 17 00:00:00 2001 From: NickNaso Date: Fri, 31 Aug 2018 18:36:02 +0200 Subject: [PATCH 037/696] src: first pass on adding version management apis PR-URL: https://github.com/nodejs/node-addon-api/pull/325 Reviewed-By: Michael Dawson --- README.md | 1 + doc/version_management.md | 43 ++++++++++++++++++++++++++++++++++++++ napi-inl.h | 18 ++++++++++++++++ napi.h | 7 +++++++ test/binding.cc | 2 ++ test/binding.gyp | 1 + test/index.js | 1 + test/version_management.cc | 27 ++++++++++++++++++++++++ test/version_management.js | 32 ++++++++++++++++++++++++++++ 9 files changed, 132 insertions(+) create mode 100644 doc/version_management.md create mode 100644 test/version_management.cc create mode 100644 test/version_management.js diff --git a/README.md b/README.md index 9d4011575..bdb8b9389 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ still a work in progress as its not yet complete). - [Async Operations](doc/async_operations.md) - [AsyncWorker](doc/async_worker.md) - [Promises](doc/promises.md) + - [Version management](doc/version_management.md) diff --git a/doc/version_management.md b/doc/version_management.md new file mode 100644 index 000000000..e41663661 --- /dev/null +++ b/doc/version_management.md @@ -0,0 +1,43 @@ +# VersionManagement + +The `Napi::VersionManagement` class contains methods that allow information +to be retrieved about the version of N-API and Node.js. In some cases it is +important to make decisions based on different versions of the system. + +## Methods + +### GetNapiVersion + +Retrieves the highest N-API version supported by Node.js runtime. + +```cpp +static uint32_t GetNapiVersion(Env env); +``` + +- `[in] env`: The environment in which the API is invoked under. + +Returns the highest N-API version supported by Node.js runtime. + +### GetNodeVersion + +Retrives information about Node.js version present on the system. All the +information is stored in the `napi_node_version` structrue that is defined as +shown below: + +```cpp +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t patch; + const char* release; +} napi_node_version; +```` + +```cpp +static const napi_node_version* GetNodeVersion(Env env); +``` + +- `[in] env`: The environment in which the API is invoked under. + +Returns the structure a pointer to the structure `napi_node_version` populated by +the version information of Node.js runtime. diff --git a/napi-inl.h b/napi-inl.h index 77f403e0c..813a27adc 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3319,6 +3319,24 @@ inline int64_t MemoryManagement::AdjustExternalMemory(Env env, int64_t change_in return result; } +//////////////////////////////////////////////////////////////////////////////// +// Version Management class +//////////////////////////////////////////////////////////////////////////////// + +inline uint32_t VersionManagement::GetNapiVersion(Env env) { + uint32_t result; + napi_status status = napi_get_version(env, &result); + NAPI_THROW_IF_FAILED(env, status, 0); + return result; +} + +inline const napi_node_version* VersionManagement::GetNodeVersion(Env env) { + const napi_node_version* result; + napi_status status = napi_get_node_version(env, &result); + NAPI_THROW_IF_FAILED(env, status, 0); + return result; +} + // These macros shouldn't be useful in user code. #undef NAPI_THROW #undef NAPI_THROW_IF_FAILED diff --git a/napi.h b/napi.h index 3ad095485..e142ee79b 100644 --- a/napi.h +++ b/napi.h @@ -1613,6 +1613,13 @@ namespace Napi { static int64_t AdjustExternalMemory(Env env, int64_t change_in_bytes); }; + // Version management + class VersionManagement { + public: + static uint32_t GetNapiVersion(Env env); + static const napi_node_version* GetNodeVersion(Env env); + }; + } // namespace Napi // Inline implementations of all the above class methods are included here. diff --git a/test/binding.cc b/test/binding.cc index aa807e7f5..071b75172 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -22,6 +22,7 @@ Object InitPromise(Env env); Object InitTypedArray(Env env); Object InitObjectWrap(Env env); Object InitObjectReference(Env env); +Object InitVersionManagement(Env env); Object Init(Env env, Object exports) { exports.Set("arraybuffer", InitArrayBuffer(env)); @@ -45,6 +46,7 @@ Object Init(Env env, Object exports) { exports.Set("typedarray", InitTypedArray(env)); exports.Set("objectwrap", InitObjectWrap(env)); exports.Set("objectreference", InitObjectReference(env)); + exports.Set("version_management", InitVersionManagement(env)); return exports; } diff --git a/test/binding.gyp b/test/binding.gyp index cc75c09f0..3945a8085 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -27,6 +27,7 @@ 'typedarray.cc', 'objectwrap.cc', 'objectreference.cc', + 'version_management.cc' ], 'include_dirs': ["major)); + version.Set("minor", Number::New(env, node_version->minor)); + version.Set("patch", Number::New(env, node_version->patch)); + version.Set("release", String::New(env, node_version->release)); + return version; +} + +Object InitVersionManagement(Env env) { + Object exports = Object::New(env); + exports["getNapiVersion"] = Function::New(env, getNapiVersion); + exports["getNodeVersion"] = Function::New(env, getNodeVersion); + return exports; +} diff --git a/test/version_management.js b/test/version_management.js new file mode 100644 index 000000000..f52db2f73 --- /dev/null +++ b/test/version_management.js @@ -0,0 +1,32 @@ +'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 parseVersion() { + const expected = {}; + expected.napi = parseInt(process.versions.napi); + expected.release = process.release.name; + const nodeVersion = process.versions.node.split('.'); + expected.major = parseInt(nodeVersion[0]); + expected.minor = parseInt(nodeVersion[1]); + expected.patch = parseInt(nodeVersion[2]); + return expected; +} + +function test(binding) { + + const expected = parseVersion(); + + const napiVersion = binding.version_management.getNapiVersion(); + assert.strictEqual(napiVersion, expected.napi); + + const nodeVersion = binding.version_management.getNodeVersion(); + assert.strictEqual(nodeVersion.major, expected.major); + assert.strictEqual(nodeVersion.minor, expected.minor); + assert.strictEqual(nodeVersion.patch, expected.patch); + assert.strictEqual(nodeVersion.release, expected.release); + +} From 0a00e7c97bd72aa3b81d4c5fd4bd1218d66e994d Mon Sep 17 00:00:00 2001 From: Philipp Renoth Date: Mon, 11 Jun 2018 03:00:11 +0200 Subject: [PATCH 038/696] src: implement missing descriptor defs for symbols Implements descriptor definitions with symbols for `StaticMethod`, `StaticAccessor`, `InstanceAccessor`, `StaticValue`, `InstanceValue`. Ref: https://github.com/nodejs/node-addon-api/issues/279 PR-URL: https://github.com/nodejs/node-addon-api/pull/280 Refs: https://github.com/nodejs/node-addon-api/issues/279 Reviewed-By: Michael Dawson --- doc/object_wrap.md | 133 ++++++++++++++++++++++++++ napi-inl.h | 96 +++++++++++++++++++ napi.h | 24 +++++ test/objectwrap.cc | 120 ++++++++++++++++------- test/objectwrap.js | 234 ++++++++++++++++++++++++++++++++++++--------- 5 files changed, 530 insertions(+), 77 deletions(-) diff --git a/doc/object_wrap.md b/doc/object_wrap.md index d422b4e8e..93773da69 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -234,6 +234,50 @@ One or more of `napi_property_attributes`. Returns `Napi::PropertyDescriptor` object that represents a static method of a JavaScript class. +### StaticMethod + +Creates property descriptor that represents a static method of a JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: Napi:Symbol that represents the name of a static +method for the class. +- `[in] method`: The native function that represents a static method of a +JavaScript class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents the static method of a +JavaScript class. + +### StaticMethod + +Creates property descriptor that represents a static method of a JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +method for the class. +- `[in] name`: Napi:Symbol that represents the name of a static. +- `[in] method`: The native function that represents a static method of a +JavaScript class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents a static method of a +JavaScript class. + ### StaticAccessor Creates property descriptor that represents a static accessor property of a @@ -261,6 +305,32 @@ is invoked. Returns `Napi::PropertyDescriptor` object that represents a static accessor property of a JavaScript class. +### StaticAccessor + +Creates property descriptor that represents a static accessor property of a +JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticAccessor(Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: Napi:Symbol that represents the name of a static accessor. +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into getter or setter when +is invoked. + +Returns `Napi::PropertyDescriptor` object that represents a static accessor +property of a JavaScript class. + ### InstanceMethod Creates property descriptor that represents an instance method of a JavaScript class. @@ -375,6 +445,32 @@ One or more of `napi_property_attributes`. Returns `Napi::PropertyDescriptor` object that represents an instance accessor property of a JavaScript class. +### InstanceAccessor + +Creates property descriptor that represents an instance accessor property of a +JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceAccessor(Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance accessor. +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] attributes`: The attributes associated with the particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into getter or setter when this is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance accessor +property of a JavaScript class. + ### StaticValue Creates property descriptor that represents an static value property of a @@ -394,6 +490,25 @@ to the napi_static attribute. One or more of `napi_property_attributes`. Returns `Napi::PropertyDescriptor` object that represents an static value property of a JavaScript class +### StaticValue + +Creates property descriptor that represents an static value property of a +JavaScript class. +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +name of the static property. +- `[in] value`: The value that's retrieved by a get access of the property. +- `[in] attributes`: The attributes to be associated with the property in addition +to the napi_static attribute. One or more of `napi_property_attributes`. + +Returns `Napi::PropertyDescriptor` object that represents an static value +property of a JavaScript class + ### InstanceValue Creates property descriptor that represents an instance value property of a @@ -411,3 +526,21 @@ One or more of `napi_property_attributes`. Returns `Napi::PropertyDescriptor` object that represents an instance value property of a JavaScript class. + +### InstanceValue + +Creates property descriptor that represents an instance value property of a +JavaScript class. +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +name of the property. +- `[in] value`: The value that's retrieved by a get access of the property. +- `[in] attributes`: The attributes to be associated with the property. +One or more of `napi_property_attributes`. + +Returns `Napi::PropertyDescriptor` object that represents an instance value diff --git a/napi-inl.h b/napi-inl.h index 813a27adc..59eacf9f8 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2823,6 +2823,40 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( return desc; } +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + // TODO: Delete when the class is destroyed + StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::StaticVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes, + void* data) { + // TODO: Delete when the class is destroyed + StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::StaticMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( const char* utf8name, @@ -2843,6 +2877,26 @@ inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( return desc; } +template +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes, + void* data) { + // TODO: Delete when the class is destroyed + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({ getter, setter, data }); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( const char* utf8name, @@ -2933,6 +2987,26 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( return desc; } +template +inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( + Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes, + void* data) { + // TODO: Delete when the class is destroyed + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({ getter, setter, data }); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::StaticValue(const char* utf8name, Napi::Value value, napi_property_attributes attributes) { @@ -2943,6 +3017,16 @@ inline ClassPropertyDescriptor ObjectWrap::StaticValue(const char* utf8nam return desc; } +template +inline ClassPropertyDescriptor ObjectWrap::StaticValue(Symbol name, + Napi::Value value, napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::InstanceValue( const char* utf8name, @@ -2955,6 +3039,18 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceValue( return desc; } +template +inline ClassPropertyDescriptor ObjectWrap::InstanceValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = attributes; + return desc; +} + template inline napi_value ObjectWrap::ConstructorCallbackWrapper( napi_env env, diff --git a/napi.h b/napi.h index e142ee79b..a8eafd312 100644 --- a/napi.h +++ b/napi.h @@ -1453,11 +1453,24 @@ namespace Napi { StaticMethodCallback method, napi_property_attributes attributes = napi_default, void* data = nullptr); + static PropertyDescriptor StaticMethod(Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod(Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor StaticAccessor(const char* utf8name, StaticGetterCallback getter, StaticSetterCallback setter, napi_property_attributes attributes = napi_default, void* data = nullptr); + static PropertyDescriptor StaticAccessor(Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor InstanceMethod(const char* utf8name, InstanceVoidMethodCallback method, napi_property_attributes attributes = napi_default, @@ -1479,12 +1492,23 @@ namespace Napi { InstanceSetterCallback setter, napi_property_attributes attributes = napi_default, void* data = nullptr); + static PropertyDescriptor InstanceAccessor(Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor StaticValue(const char* utf8name, Napi::Value value, napi_property_attributes attributes = napi_default); + static PropertyDescriptor StaticValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); static PropertyDescriptor InstanceValue(const char* utf8name, Napi::Value value, napi_property_attributes attributes = napi_default); + static PropertyDescriptor InstanceValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); private: static napi_value ConstructorCallbackWrapper(napi_env env, napi_callback_info info); diff --git a/test/objectwrap.cc b/test/objectwrap.cc index fae7b2652..ef1cfa73e 100644 --- a/test/objectwrap.cc +++ b/test/objectwrap.cc @@ -1,66 +1,118 @@ #include -class TestIter : public Napi::ObjectWrap { -public: - TestIter(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) {} +Napi::ObjectReference testStaticContextRef; - Napi::Value Next(const Napi::CallbackInfo& info) { - auto object = Napi::Object::New(info.Env()); - object.Set("done", Napi::Boolean::New(info.Env(), true)); - return object; - } +Napi::Value StaticGetter(const Napi::CallbackInfo& /*info*/) { + return testStaticContextRef.Value().Get("value"); +} - static Napi::FunctionReference Initialize(Napi::Env env) { - return Napi::Persistent(DefineClass(env, "TestIter", { - InstanceMethod("next", &TestIter::Next), - })); - } -}; +void StaticSetter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { + testStaticContextRef.Value().Set("value", value); +} + +Napi::Value TestStaticMethod(const Napi::CallbackInfo& info) { + std::string str = info[0].ToString(); + return Napi::String::New(info.Env(), str + " static"); +} + +Napi::Value TestStaticMethodInternal(const Napi::CallbackInfo& info) { + std::string str = info[0].ToString(); + return Napi::String::New(info.Env(), str + " static internal"); +} class Test : public Napi::ObjectWrap { public: Test(const Napi::CallbackInfo& info) : - Napi::ObjectWrap(info), - Constructor(TestIter::Initialize(info.Env())) { + Napi::ObjectWrap(info) { } - void SetMethod(const Napi::CallbackInfo& info) { - value = info[0].As(); + void Setter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { + value_ = value.ToString(); } - Napi::Value GetMethod(const Napi::CallbackInfo& info) { - return Napi::Number::New(info.Env(), value); + Napi::Value Getter(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), value_); } - Napi::Value Iter(const Napi::CallbackInfo& /*info*/) { - return Constructor.New({}); + Napi::Value TestMethod(const Napi::CallbackInfo& info) { + std::string str = info[0].ToString(); + return Napi::String::New(info.Env(), str + " instance"); } - void Setter(const Napi::CallbackInfo& /*info*/, const Napi::Value& new_value) { - value = new_value.As(); + Napi::Value TestMethodInternal(const Napi::CallbackInfo& info) { + std::string str = info[0].ToString(); + return Napi::String::New(info.Env(), str + " instance internal"); } - Napi::Value Getter(const Napi::CallbackInfo& info) { - return Napi::Number::New(info.Env(), value); + Napi::Value ToStringTag(const Napi::CallbackInfo& info) { + return Napi::String::From(info.Env(), "TestTag"); + } + + // creates dummy array, returns `([value])[Symbol.iterator]()` + Napi::Value Iterator(const Napi::CallbackInfo& info) { + Napi::Array array = Napi::Array::New(info.Env()); + array.Set(array.Length(), Napi::String::From(info.Env(), value_)); + return array.Get(Napi::Symbol::WellKnown(info.Env(), "iterator")).As().Call(array, {}); } static void Initialize(Napi::Env env, Napi::Object exports) { + + Napi::Symbol kTestStaticValueInternal = Napi::Symbol::New(env, "kTestStaticValueInternal"); + Napi::Symbol kTestStaticAccessorInternal = Napi::Symbol::New(env, "kTestStaticAccessorInternal"); + Napi::Symbol kTestStaticMethodInternal = Napi::Symbol::New(env, "kTestStaticMethodInternal"); + + Napi::Symbol kTestValueInternal = Napi::Symbol::New(env, "kTestValueInternal"); + Napi::Symbol kTestAccessorInternal = Napi::Symbol::New(env, "kTestAccessorInternal"); + Napi::Symbol kTestMethodInternal = Napi::Symbol::New(env, "kTestMethodInternal"); + exports.Set("Test", DefineClass(env, "Test", { - InstanceMethod("test_set_method", &Test::SetMethod), - InstanceMethod("test_get_method", &Test::GetMethod), - InstanceMethod(Napi::Symbol::WellKnown(env, "iterator"), &Test::Iter), - InstanceAccessor("test_getter_only", &Test::Getter, nullptr), - InstanceAccessor("test_setter_only", nullptr, &Test::Setter), - InstanceAccessor("test_getter_setter", &Test::Getter, &Test::Setter), + + // expose symbols for testing + StaticValue("kTestStaticValueInternal", kTestStaticValueInternal), + StaticValue("kTestStaticAccessorInternal", kTestStaticAccessorInternal), + StaticValue("kTestStaticMethodInternal", kTestStaticMethodInternal), + StaticValue("kTestValueInternal", kTestValueInternal), + StaticValue("kTestAccessorInternal", kTestAccessorInternal), + StaticValue("kTestMethodInternal", kTestMethodInternal), + + // test data + StaticValue("testStaticValue", Napi::String::New(env, "value"), napi_enumerable), + StaticValue(kTestStaticValueInternal, Napi::Number::New(env, 5), napi_default), + + StaticAccessor("testStaticGetter", &StaticGetter, nullptr, napi_enumerable), + StaticAccessor("testStaticSetter", nullptr, &StaticSetter, napi_default), + StaticAccessor("testStaticGetSet", &StaticGetter, &StaticSetter, napi_enumerable), + StaticAccessor(kTestStaticAccessorInternal, &StaticGetter, &StaticSetter, napi_enumerable), + + StaticMethod("testStaticMethod", &TestStaticMethod, napi_enumerable), + StaticMethod(kTestStaticMethodInternal, &TestStaticMethodInternal, napi_default), + + InstanceValue("testValue", Napi::Boolean::New(env, true), napi_enumerable), + InstanceValue(kTestValueInternal, Napi::Boolean::New(env, false), napi_enumerable), + + InstanceAccessor("testGetter", &Test::Getter, nullptr, napi_enumerable), + InstanceAccessor("testSetter", nullptr, &Test::Setter, napi_default), + InstanceAccessor("testGetSet", &Test::Getter, &Test::Setter, napi_enumerable), + InstanceAccessor(kTestAccessorInternal, &Test::Getter, &Test::Setter, napi_enumerable), + + InstanceMethod("testMethod", &Test::TestMethod, napi_enumerable), + InstanceMethod(kTestMethodInternal, &Test::TestMethodInternal, napi_default), + + // conventions + InstanceAccessor(Napi::Symbol::WellKnown(env, "toStringTag"), &Test::ToStringTag, nullptr, napi_enumerable), + InstanceMethod(Napi::Symbol::WellKnown(env, "iterator"), &Test::Iterator, napi_default), + })); } private: - uint32_t value; - Napi::FunctionReference Constructor; + std::string value_; }; Napi::Object InitObjectWrap(Napi::Env env) { + testStaticContextRef = Napi::Persistent(Napi::Object::New(env)); + testStaticContextRef.SuppressDestruct(); + Napi::Object exports = Napi::Object::New(env); Test::Initialize(env, exports); return exports; diff --git a/test/objectwrap.js b/test/objectwrap.js index 4098c7e32..72805000d 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -2,61 +2,209 @@ 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`)); +const test = (binding) => { + const Test = binding.objectwrap.Test; -function test(binding) { - var Test = binding.objectwrap.Test; + const testValue = (obj, clazz) => { + assert.strictEqual(obj.testValue, true); + assert.strictEqual(obj[clazz.kTestValueInternal], false); + }; - function testSetGetMethod(obj) { - obj.test_set_method(90); - assert.strictEqual(obj.test_get_method(), 90); - } + const testAccessor = (obj, clazz) => { + // read-only, write-only + { + obj.testSetter = 'instance getter'; + assert.strictEqual(obj.testGetter, 'instance getter'); - function testIter(obj) { - for (const value of obj) { + obj.testSetter = 'instance getter 2'; + assert.strictEqual(obj.testGetter, 'instance getter 2'); + } + + // read write-only + { + let error; + try { const read = obj.testSetter; } catch (e) { error = e; } + // no error + assert.strictEqual(error, undefined); + + // read is undefined + assert.strictEqual(obj.testSetter, undefined); } - } - function testGetterOnly(obj) { - obj.test_set_method(91); - assert.strictEqual(obj.test_getter_only, 91); - - let error; - try { - // Can not assign to read only property. - obj.test_getter_only = 92; - } catch(e) { - error = e; - } finally { + // write read-only + { + let error; + try { obj.testGetter = 'write'; } catch (e) { error = e; } assert.strictEqual(error.name, 'TypeError'); } - } - function testSetterOnly(obj) { - obj.test_setter_only = 93; - assert.strictEqual(obj.test_setter_only, undefined); - assert.strictEqual(obj.test_getter_only, 93); - } + // rw + { + obj.testGetSet = 'instance getset'; + assert.strictEqual(obj.testGetSet, 'instance getset'); + + obj.testGetSet = 'instance getset 2'; + assert.strictEqual(obj.testGetSet, 'instance getset 2'); + } + + // rw symbol + { + obj[clazz.kTestAccessorInternal] = 'instance internal getset'; + assert.strictEqual(obj[clazz.kTestAccessorInternal], 'instance internal getset'); + + obj[clazz.kTestAccessorInternal] = 'instance internal getset 2'; + assert.strictEqual(obj[clazz.kTestAccessorInternal], 'instance internal getset 2'); + } + }; + + const testMethod = (obj, clazz) => { + assert.strictEqual(obj.testMethod('method'), 'method instance'); + assert.strictEqual(obj[clazz.kTestMethodInternal]('method'), 'method instance internal'); + }; + + const testEnumerables = (obj, clazz) => { + // Object.keys: only object + assert.deepEqual(Object.keys(obj), []); + + // for..in: object + prototype + { + const keys = []; + for (let key in obj) { + keys.push(key); + } + + assert.deepEqual(keys, [ + 'testGetSet', + 'testGetter', + 'testValue', + 'testMethod' + ]); + } + }; - function testGetterSetter(obj) { - obj.test_getter_setter = 94; - assert.strictEqual(obj.test_getter_setter, 94); + const testConventions = (obj, clazz) => { + // test @@toStringTag + { + assert.strictEqual(obj[Symbol.toStringTag], 'TestTag'); + assert.strictEqual('' + obj, '[object TestTag]'); + } + + // test @@iterator + { + obj.testSetter = 'iterator'; + const values = []; + + for (let item of obj) { + values.push(item); + } - obj.test_getter_setter = 95; - assert.strictEqual(obj.test_getter_setter, 95); + assert.deepEqual(values, ['iterator']); + } + }; + + const testStaticValue = (clazz) => { + assert.strictEqual(clazz.testStaticValue, 'value'); + assert.strictEqual(clazz[clazz.kTestStaticValueInternal], 5); } - function testObj(obj) { - testSetGetMethod(obj); - testIter(obj); - testGetterOnly(obj); - testSetterOnly(obj); - testGetterSetter(obj); + const testStaticAccessor = (clazz) => { + // read-only, write-only + { + const tempObj = {}; + clazz.testStaticSetter = tempObj; + assert.strictEqual(clazz.testStaticGetter, tempObj); + + const tempArray = []; + clazz.testStaticSetter = tempArray; + assert.strictEqual(clazz.testStaticGetter, tempArray); + } + + // read write-only + { + let error; + try { const read = clazz.testStaticSetter; } catch (e) { error = e; } + // no error + assert.strictEqual(error, undefined); + + // read is undefined + assert.strictEqual(clazz.testStaticSetter, undefined); + } + + // write-read-only + { + let error; + try { clazz.testStaticGetter = 'write'; } catch (e) { error = e; } + assert.strictEqual(error.name, 'TypeError'); + } + + // rw + { + clazz.testStaticGetSet = 9; + assert.strictEqual(clazz.testStaticGetSet, 9); + + clazz.testStaticGetSet = 4; + assert.strictEqual(clazz.testStaticGetSet, 4); + } + + // rw symbol + { + clazz[clazz.kTestStaticAccessorInternal] = 'static internal getset'; + assert.strictEqual(clazz[clazz.kTestStaticAccessorInternal], 'static internal getset'); + } + }; + + const testStaticMethod = (clazz) => { + assert.strictEqual(clazz.testStaticMethod('method'), 'method static'); + assert.strictEqual(clazz[clazz.kTestStaticMethodInternal]('method'), 'method static internal'); + }; + + const testStaticEnumerables = (clazz) => { + // Object.keys + assert.deepEqual(Object.keys(clazz), [ + 'testStaticValue', + 'testStaticGetter', + 'testStaticGetSet', + 'testStaticMethod' + ]); + + // for..in + { + const keys = []; + for (let key in clazz) { + keys.push(key); + } + + assert.deepEqual(keys, [ + 'testStaticValue', + 'testStaticGetter', + 'testStaticGetSet', + 'testStaticMethod' + ]); + } + }; + + const testObj = (obj, clazz) => { + testValue(obj, clazz); + testAccessor(obj, clazz); + testMethod(obj, clazz); + + testEnumerables(obj, clazz); + + testConventions(obj, clazz); } - testObj(new Test()); - testObj(new Test(1)); - testObj(new Test(1, 2, 3, 4, 5, 6)); - testObj(new Test(1, 2, 3, 4, 5, 6, 7)); + const testClass = (clazz) => { + testStaticValue(clazz); + testStaticAccessor(clazz); + testStaticMethod(clazz); + + testStaticEnumerables(clazz); + }; + + // `Test` is needed for accessing exposed symbols + testObj(new Test(), Test); + testClass(Test); } + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); \ No newline at end of file From b6e2d92c09d33bb4c7568b69f5bc41e6ef928d50 Mon Sep 17 00:00:00 2001 From: Jinho Date: Thu, 6 Sep 2018 09:43:21 +0900 Subject: [PATCH 039/696] src: enable DataView feature by default This patch contains the following things: - Add a document for `DataView` feature - Remove NAPI_DATA_VIEW_FEATURE Refs: https://github.com/nodejs/node-addon-api/issues/196 PR-URL: https://github.com/nodejs/node-addon-api/pull/331 Refs: https://github.com/nodejs/node-addon-api/issues/196 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- README.md | 1 + doc/dataview.md | 244 +++++++++++++++++++++++++++++++++++++++++++++++ napi-inl.h | 4 - napi.h | 4 - test/binding.gyp | 4 +- 5 files changed, 247 insertions(+), 10 deletions(-) create mode 100644 doc/dataview.md diff --git a/README.md b/README.md index bdb8b9389..9a27d6c4d 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ still a work in progress as its not yet complete). - [ArrayBuffer](doc/array_buffer.md) - [TypedArray](doc/typed_array.md) - [TypedArrayOf](doc/typed_array_of.md) + - [DataView](doc/dataview.md) - [Memory Management](doc/memory_management.md) - [Async Operations](doc/async_operations.md) - [AsyncWorker](doc/async_worker.md) diff --git a/doc/dataview.md b/doc/dataview.md new file mode 100644 index 000000000..7f4af81db --- /dev/null +++ b/doc/dataview.md @@ -0,0 +1,244 @@ +# DataView + +The `Napi::DataView` class corresponds to the +[JavaScript `DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) +class. + +## Methods + +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::ArrayBuffer`. + +```cpp +static Napi::DataView New(napi_env env, Napi::ArrayBuffer arrayBuffer); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] arrayBuffer` : `Napi::ArrayBuffer` underlying the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::ArrayBuffer`. + +```cpp +static Napi::DataView New(napi_env env, Napi::ArrayBuffer arrayBuffer, size_t byteOffset); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] arrayBuffer` : `Napi::ArrayBuffer` underlying the `Napi::DataView`. +- `[in] byteOffset` : The byte offset within the `Napi::ArrayBuffer` from which to start projecting the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::ArrayBuffer`. + +```cpp +static Napi::DataView New(napi_env env, Napi::ArrayBuffer arrayBuffer, size_t byteOffset, size_t byteLength); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] arrayBuffer` : `Napi::ArrayBuffer` underlying the `Napi::DataView`. +- `[in] byteOffset` : The byte offset within the `Napi::ArrayBuffer` from which to start projecting the `Napi::DataView`. +- `[in] byteLength` : Number of elements in the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + +### Constructor + +Initializes an empty instance of the `Napi::DataView` class. + +```cpp +DataView(); +``` + +### Constructor + +Initializes a wrapper instance of an existing `Napi::DataView` instance. + +```cpp +DataView(napi_env env, napi_value value); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] value`: The `Napi::DataView` reference to wrap. + +### ArrayBuffer + +```cpp +Napi::ArrayBuffer ArrayBuffer() const; +``` + +Returns the backing array buffer. + +### ByteOffset + +```cpp +size_t ByteOffset() const; +``` + +Returns the offset into the `Napi::DataView` where the array starts, in bytes. + +### ByteLength + +```cpp +size_t ByteLength() const; +``` + +Returns the length of the array, in bytes. + +### GetFloat32 + +```cpp +float GetFloat32(size_t byteOffset) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. + +Returns a signed 32-bit float (float) at the specified byte offset from the start of the `DataView`. + +### GetFloat64 + +```cpp +double GetFloat64(size_t byteOffset) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. + +Returns a signed 64-bit float (double) at the specified byte offset from the start of the `Napi::DataView`. + +### GetInt8 + +```cpp +int8_t GetInt8(size_t byteOffset) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. + +Returns a signed 8-bit integer (byte) at the specified byte offset from the start of the `Napi::DataView`. + +### GetInt16 + +```cpp +int16_t GetInt16(size_t byteOffset) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. + +Returns a signed 16-bit integer (short) at the specified byte offset from the start of the `Napi::DataView`. + +### GetInt32 + +```cpp +int32_t GetInt32(size_t byteOffset) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. + +Returns a signed 32-bit integer (long) at the specified byte offset from the start of the `Napi::DataView`. + +### GetUint8 + +```cpp +uint8_t GetUint8(size_t byteOffset) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. + +Returns a unsigned 8-bit integer (unsigned byte) at the specified byte offset from the start of the `Napi::DataView`. + +### GetUint16 + +```cpp +uint16_t GetUint16(size_t byteOffset) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. + +Returns a unsigned 16-bit integer (unsigned short) at the specified byte offset from the start of the `Napi::DataView`. + +### GetUint32 + +```cpp +uint32_t GetUint32(size_t byteOffset) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. + +Returns a unsigned 32-bit integer (unsigned long) at the specified byte offset from the start of the `Napi::DataView`. + +### SetFloat32 + +```cpp +void SetFloat32(size_t byteOffset, float value) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. +- `[in] value`: The value to set. + +### SetFloat64 + +```cpp +void SetFloat64(size_t byteOffset, double value) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. +- `[in] value`: The value to set. + +### SetInt8 + +```cpp +void SetInt8(size_t byteOffset, int8_t value) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. +- `[in] value`: The value to set. + +### SetInt16 + +```cpp +void SetInt16(size_t byteOffset, int16_t value) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. +- `[in] value`: The value to set. + +### SetInt32 + +```cpp +void SetInt32(size_t byteOffset, int32_t value) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. +- `[in] value`: The value to set. + +### SetUint8 + +```cpp +void SetUint8(size_t byteOffset, uint8_t value) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. +- `[in] value`: The value to set. + +### SetUint16 + +```cpp +void SetUint16(size_t byteOffset, uint16_t value) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. +- `[in] value`: The value to set. + +### SetUint32 + +```cpp +void SetUint32(size_t byteOffset, uint32_t value) const; +``` + +- `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. +- `[in] value`: The value to set. diff --git a/napi-inl.h b/napi-inl.h index 59eacf9f8..c96319843 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -364,7 +364,6 @@ inline bool Value::IsPromise() const { return result; } -#if NAPI_DATA_VIEW_FEATURE inline bool Value::IsDataView() const { if (_value == nullptr) { return false; @@ -375,7 +374,6 @@ inline bool Value::IsDataView() const { NAPI_THROW_IF_FAILED(_env, status, false); return result; } -#endif inline bool Value::IsBuffer() const { if (_value == nullptr) { @@ -1255,7 +1253,6 @@ inline void ArrayBuffer::EnsureInfo() const { } } -#if NAPI_DATA_VIEW_FEATURE //////////////////////////////////////////////////////////////////////////////// // DataView class //////////////////////////////////////////////////////////////////////////////// @@ -1426,7 +1423,6 @@ inline void DataView::WriteData(size_t byteOffset, T value) const { *reinterpret_cast(static_cast(_data) + byteOffset) = value; } -#endif //////////////////////////////////////////////////////////////////////////////// // TypedArray class diff --git a/napi.h b/napi.h index a8eafd312..7b0f17f11 100644 --- a/napi.h +++ b/napi.h @@ -189,9 +189,7 @@ namespace Napi { bool IsObject() const; ///< Tests if a value is a JavaScript object. bool IsFunction() const; ///< Tests if a value is a JavaScript function. bool IsPromise() const; ///< Tests if a value is a JavaScript promise. -#if NAPI_DATA_VIEW_FEATURE bool IsDataView() const; ///< Tests if a value is a JavaScript data view. -#endif bool IsBuffer() const; ///< Tests if a value is a Node buffer. bool IsExternal() const; ///< Tests if a value is a pointer to external data. @@ -836,7 +834,6 @@ namespace Napi { T* data); }; -#if NAPI_DATA_VIEW_FEATURE /// The DataView provides a low-level interface for reading/writing multiple /// number types in an ArrayBuffer irrespective of the platform's endianness. class DataView : public Object { @@ -888,7 +885,6 @@ namespace Napi { void* _data; size_t _length; }; -#endif class Function : public Object { public: diff --git a/test/binding.gyp b/test/binding.gyp index 3945a8085..ed9c6899b 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -37,7 +37,7 @@ 'targets': [ { 'target_name': 'binding', - 'defines': [ 'NAPI_CPP_EXCEPTIONS', 'NAPI_DATA_VIEW_FEATURE' ], + 'defines': [ 'NAPI_CPP_EXCEPTIONS' ], 'cflags!': [ '-fno-exceptions' ], 'cflags_cc!': [ '-fno-exceptions' ], 'msvs_settings': { @@ -54,7 +54,7 @@ }, { 'target_name': 'binding_noexcept', - 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS', 'NAPI_DATA_VIEW_FEATURE' ], + 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], 'cflags': [ '-fno-exceptions' ], 'cflags_cc': [ '-fno-exceptions' ], 'msvs_settings': { From 73fed84ceb51dea3993179885009e86c9630eb12 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Wed, 19 Sep 2018 18:54:28 -0400 Subject: [PATCH 040/696] test: add ability to control experimental tests Add the ability to specify a NAPI_VERSION which limits the tests executed to those supported by that version. As an example tests can be built/run as: npm test --NAPI_VERSION=3 PR-URL: https://github.com/nodejs/node-addon-api/pull/350 Fixes: https://github.com/nodejs/node-addon-api/issues/349 Reviewed-By: Gus Caplan Reviewed-By: Sakthipriyan Vairamani Reviewed-By: Jinho Bang --- napi-inl.h | 8 ++++-- napi.h | 20 +++++++++---- test/bigint.cc | 5 ++++ test/binding.cc | 9 ++++++ test/binding.gyp | 6 ++++ test/index.js | 13 +++++++++ test/typedarray-bigint.js | 59 +++++++++++++++++++++++++++++++++++++++ test/typedarray.cc | 16 +++++++++++ test/typedarray.js | 47 ------------------------------- 9 files changed, 129 insertions(+), 54 deletions(-) create mode 100644 test/typedarray-bigint.js diff --git a/napi-inl.h b/napi-inl.h index c96319843..5846cfb7b 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -298,7 +298,9 @@ inline bool Value::IsNumber() const { return Type() == napi_number; } -#ifdef NAPI_EXPERIMENTAL +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) inline bool Value::IsBigInt() const { return Type() == napi_bigint; } @@ -520,7 +522,9 @@ inline double Number::DoubleValue() const { return result; } -#ifdef NAPI_EXPERIMENTAL +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) //////////////////////////////////////////////////////////////////////////////// // BigInt Class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 7b0f17f11..8085617f2 100644 --- a/napi.h +++ b/napi.h @@ -52,7 +52,9 @@ namespace Napi { class Value; class Boolean; class Number; -#ifdef NAPI_EXPERIMENTAL +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) class BigInt; #endif // NAPI_EXPERIMENTAL class String; @@ -75,7 +77,9 @@ namespace Napi { typedef TypedArrayOf Uint32Array; ///< Typed-array of unsigned 32-bit integers typedef TypedArrayOf Float32Array; ///< Typed-array of 32-bit floating-point values typedef TypedArrayOf Float64Array; ///< Typed-array of 64-bit floating-point values -#ifdef NAPI_EXPERIMENTAL +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) typedef TypedArrayOf BigInt64Array; ///< Typed array of signed 64-bit integers typedef TypedArrayOf BigUint64Array; ///< Typed array of unsigned 64-bit integers #endif // NAPI_EXPERIMENTAL @@ -178,7 +182,9 @@ namespace Napi { bool IsNull() const; ///< Tests if a value is a null JavaScript value. bool IsBoolean() const; ///< Tests if a value is a JavaScript boolean. bool IsNumber() const; ///< Tests if a value is a JavaScript number. -#ifdef NAPI_EXPERIMENTAL +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint. #endif // NAPI_EXPERIMENTAL bool IsString() const; ///< Tests if a value is a JavaScript string. @@ -250,7 +256,9 @@ namespace Napi { double DoubleValue() const; ///< Converts a Number value to a 64-bit floating-point value. }; -#ifdef NAPI_EXPERIMENTAL +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) /// A JavaScript bigint value. class BigInt : public Value { public: @@ -754,7 +762,9 @@ namespace Napi { : std::is_same::value ? napi_uint32_array : std::is_same::value ? napi_float32_array : std::is_same::value ? napi_float64_array -#ifdef NAPI_EXPERIMENTAL +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) : std::is_same::value ? napi_bigint64_array : std::is_same::value ? napi_biguint64_array #endif // NAPI_EXPERIMENTAL diff --git a/test/bigint.cc b/test/bigint.cc index 48e44ac18..5d1e36367 100644 --- a/test/bigint.cc +++ b/test/bigint.cc @@ -3,6 +3,9 @@ using namespace Napi; +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) namespace { Value IsLossless(const CallbackInfo& info) { @@ -74,3 +77,5 @@ Object InitBigInt(Env env) { return exports; } + +#endif diff --git a/test/binding.cc b/test/binding.cc index 071b75172..c2bd101f3 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -1,3 +1,4 @@ +#define NAPI_EXPERIMENTAL #include "napi.h" using namespace Napi; @@ -7,7 +8,11 @@ Object InitAsyncWorker(Env env); Object InitBasicTypesBoolean(Env env); Object InitBasicTypesNumber(Env env); Object InitBasicTypesValue(Env env); +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) Object InitBigInt(Env env); +#endif Object InitBuffer(Env env); Object InitDataView(Env env); Object InitDataViewReadWrite(Env env); @@ -30,7 +35,11 @@ Object Init(Env env, Object exports) { exports.Set("basic_types_boolean", InitBasicTypesBoolean(env)); exports.Set("basic_types_number", InitBasicTypesNumber(env)); exports.Set("basic_types_value", InitBasicTypesValue(env)); +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) exports.Set("bigint", InitBigInt(env)); +#endif exports.Set("buffer", InitBuffer(env)); exports.Set("dataview", InitDataView(env)); exports.Set("dataview_read_write", InitDataView(env)); diff --git a/test/binding.gyp b/test/binding.gyp index ed9c6899b..7bff35e8a 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -1,4 +1,7 @@ { + 'variables': { + 'NAPI_VERSION%': "" + }, 'target_defaults': { 'sources': [ 'arraybuffer.cc', @@ -29,6 +32,9 @@ 'objectreference.cc', 'version_management.cc' ], + 'conditions': [ + ['NAPI_VERSION!=""', { 'defines': ['NAPI_VERSION=<@(NAPI_VERSION)'] } ] + ], 'include_dirs': [" { + try { + const length = 4; + const t = binding.typedarray.createTypedArray(type, length); + assert.ok(t instanceof Constructor); + assert.strictEqual(binding.typedarray.getTypedArrayType(t), type); + assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); + + t[3] = 11n; + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11n); + binding.typedarray.setTypedArrayElement(t, 3, 22n); + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 22n); + assert.strictEqual(t[3], 22n); + + const b = binding.typedarray.getTypedArrayBuffer(t); + assert.ok(b instanceof ArrayBuffer); + } catch (e) { + console.log(type, Constructor); + throw e; + } + + try { + const length = 4; + const offset = 8; + const b = new ArrayBuffer(offset + 64 * 4); + + const t = binding.typedarray.createTypedArray(type, length, b, offset); + assert.ok(t instanceof Constructor); + assert.strictEqual(binding.typedarray.getTypedArrayType(t), type); + assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); + + t[3] = 11n; + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11n); + binding.typedarray.setTypedArrayElement(t, 3, 22n); + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 22n); + assert.strictEqual(t[3], 22n); + + assert.strictEqual(binding.typedarray.getTypedArrayBuffer(t), b); + } catch (e) { + console.log(type, Constructor); + throw e; + } + }); + + assert.throws(() => { + binding.typedarray.createInvalidTypedArray(); + }, /Invalid (pointer passed as )?argument/); +} diff --git a/test/typedarray.cc b/test/typedarray.cc index d231f69f6..9b3a1996f 100644 --- a/test/typedarray.cc +++ b/test/typedarray.cc @@ -65,6 +65,9 @@ Value CreateTypedArray(const CallbackInfo& info) { NAPI_TYPEDARRAY_NEW(Float64Array, info.Env(), length, napi_float64_array) : NAPI_TYPEDARRAY_NEW_BUFFER(Float64Array, info.Env(), length, buffer, bufferOffset, napi_float64_array); +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) } else if (arrayType == "bigint64") { return buffer.IsUndefined() ? NAPI_TYPEDARRAY_NEW(BigInt64Array, info.Env(), length, napi_bigint64_array) : @@ -75,6 +78,7 @@ Value CreateTypedArray(const CallbackInfo& info) { NAPI_TYPEDARRAY_NEW(BigUint64Array, info.Env(), length, napi_biguint64_array) : NAPI_TYPEDARRAY_NEW_BUFFER(BigUint64Array, info.Env(), length, buffer, bufferOffset, napi_biguint64_array); +#endif } else { Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); return Value(); @@ -97,8 +101,12 @@ Value GetTypedArrayType(const CallbackInfo& info) { case napi_uint32_array: return String::New(info.Env(), "uint32"); case napi_float32_array: return String::New(info.Env(), "float32"); case napi_float64_array: return String::New(info.Env(), "float64"); +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) case napi_bigint64_array: return String::New(info.Env(), "bigint64"); case napi_biguint64_array: return String::New(info.Env(), "biguint64"); +#endif default: return String::New(info.Env(), "invalid"); } } @@ -135,10 +143,14 @@ Value GetTypedArrayElement(const CallbackInfo& info) { return Number::New(info.Env(), array.As()[index]); case napi_float64_array: return Number::New(info.Env(), array.As()[index]); +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) case napi_bigint64_array: return BigInt::New(info.Env(), array.As()[index]); case napi_biguint64_array: return BigInt::New(info.Env(), array.As()[index]); +#endif default: Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); return Value(); @@ -177,6 +189,9 @@ void SetTypedArrayElement(const CallbackInfo& info) { case napi_float64_array: array.As()[index] = value.DoubleValue(); break; +// currently experimental guard with version of NAPI_VERSION that it is +// released in once it is no longer experimental +#if (NAPI_VERSION > 2147483646) case napi_bigint64_array: { bool lossless; array.As()[index] = value.As().Int64Value(&lossless); @@ -187,6 +202,7 @@ void SetTypedArrayElement(const CallbackInfo& info) { array.As()[index] = value.As().Uint64Value(&lossless); break; } +#endif default: Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); } diff --git a/test/typedarray.js b/test/typedarray.js index 680cf856e..9aa880c16 100644 --- a/test/typedarray.js +++ b/test/typedarray.js @@ -64,53 +64,6 @@ function test(binding) { } }); - [ - ['bigint64', BigInt64Array], - ['biguint64', BigUint64Array], - ].forEach(([type, Constructor]) => { - try { - const length = 4; - const t = binding.typedarray.createTypedArray(type, length); - assert.ok(t instanceof Constructor); - assert.strictEqual(binding.typedarray.getTypedArrayType(t), type); - assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); - - t[3] = 11n; - assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11n); - binding.typedarray.setTypedArrayElement(t, 3, 22n); - assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 22n); - assert.strictEqual(t[3], 22n); - - const b = binding.typedarray.getTypedArrayBuffer(t); - assert.ok(b instanceof ArrayBuffer); - } catch (e) { - console.log(type, Constructor); - throw e; - } - - try { - const length = 4; - const offset = 8; - const b = new ArrayBuffer(offset + 64 * 4); - - const t = binding.typedarray.createTypedArray(type, length, b, offset); - assert.ok(t instanceof Constructor); - assert.strictEqual(binding.typedarray.getTypedArrayType(t), type); - assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); - - t[3] = 11n; - assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11n); - binding.typedarray.setTypedArrayElement(t, 3, 22n); - assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 22n); - assert.strictEqual(t[3], 22n); - - assert.strictEqual(binding.typedarray.getTypedArrayBuffer(t), b); - } catch (e) { - console.log(type, Constructor); - throw e; - } - }); - assert.throws(() => { binding.typedarray.createInvalidTypedArray(); }, /Invalid (pointer passed as )?argument/); From 97c4ab5cf2a66526162113d91420c8c19070172a Mon Sep 17 00:00:00 2001 From: NickNaso Date: Tue, 18 Sep 2018 01:04:29 +0200 Subject: [PATCH 041/696] src: add Call and MakeCallback that accept cargs PR-URL: https://github.com/nodejs/node-addon-api/pull/344 Reviewed-By: Michael Dawson --- doc/function_reference.md | 34 ++++++++++++++++++++++++++++++++++ napi-inl.h | 20 ++++++++++++++++++++ napi.h | 2 ++ 3 files changed, 56 insertions(+) diff --git a/doc/function_reference.md b/doc/function_reference.md index e555f8b04..356fe1d93 100644 --- a/doc/function_reference.md +++ b/doc/function_reference.md @@ -148,6 +148,23 @@ arguments of the referenced function. Returns a `Napi::Value` representing the JavaScript object returned by the referenced function. +### Call + +Calls a referenced JavaScript function from a native add-on. + +```cpp +Napi::Value Napi::FunctionReference::Call(napi_value recv, size_t argc, const napi_value* args) const; +``` + +- `[in] recv`: The `this` object passed to the referenced function when it's called. +- `[in] argc`: The number of arguments passed to the referenced function. +- `[in] args`: Array of JavaScript values as `napi_value` representing the +arguments of the referenced function. + +Returns a `Napi::Value` representing the JavaScript object returned by the referenced +function. + + ### MakeCallback Calls a referenced Javascript function from a native add-on after an asynchronous @@ -180,6 +197,23 @@ arguments of the referenced function. Returns a `Napi::Value` representing the JavaScript object returned by the referenced function. +### MakeCallback + +Calls a referenced JavaScript function from a native add-on after an asynchronous +operation. + +```cpp +Napi::Value Napi::FunctionReference::MakeCallback(napi_value recv, size_t argc, const napi_value* args) const; +``` + +- `[in] recv`: The `this` object passed to the referenced function when it's called. +- `[in] argc`: The number of arguments passed to the referenced function. +- `[in] args`: Array of JavaScript values as `napi_value` representing the +arguments of the referenced function. + +Returns a `Napi::Value` representing the JavaScript object returned by the referenced +function. + ## Operator ```cpp diff --git a/napi-inl.h b/napi-inl.h index 5846cfb7b..a4b1d426b 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2405,6 +2405,16 @@ inline Napi::Value FunctionReference::Call( return scope.Escape(result); } +inline Napi::Value FunctionReference::Call( + napi_value recv, size_t argc, const napi_value* args) const { + EscapableHandleScope scope(_env); + Napi::Value result = Value().Call(recv, argc, args); + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +} + inline Napi::Value FunctionReference::MakeCallback( napi_value recv, const std::initializer_list& args) const { EscapableHandleScope scope(_env); @@ -2425,6 +2435,16 @@ inline Napi::Value FunctionReference::MakeCallback( return scope.Escape(result); } +inline Napi::Value FunctionReference::MakeCallback( + napi_value recv, size_t argc, const napi_value* args) const { + EscapableHandleScope scope(_env); + Napi::Value result = Value().MakeCallback(recv, argc, args); + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +} + inline Object FunctionReference::New(const std::initializer_list& args) const { EscapableHandleScope scope(_env); return scope.Escape(Value().New(args)).As(); diff --git a/napi.h b/napi.h index 8085617f2..2e3753091 100644 --- a/napi.h +++ b/napi.h @@ -1097,9 +1097,11 @@ namespace Napi { Napi::Value Call(const std::vector& args) const; Napi::Value Call(napi_value recv, const std::initializer_list& args) const; Napi::Value Call(napi_value recv, const std::vector& args) const; + Napi::Value Call(napi_value recv, size_t argc, const napi_value* args) const; Napi::Value MakeCallback(napi_value recv, const std::initializer_list& args) const; Napi::Value MakeCallback(napi_value recv, const std::vector& args) const; + Napi::Value MakeCallback(napi_value recv, size_t argc, const napi_value* args) const; Object New(const std::initializer_list& args) const; Object New(const std::vector& args) const; From fc11c944b2e1e828a9baadfc3807a6d9c52535ba Mon Sep 17 00:00:00 2001 From: NickNaso Date: Fri, 7 Sep 2018 21:38:06 +0200 Subject: [PATCH 042/696] doc: major doc cleanup * Cleaning the documentation. * Remove comments about things under development. * Include Napi namespace consistently. PR-URL: https://github.com/nodejs/node-addon-api/pull/335 Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- README.md | 15 ++- doc/array_buffer.md | 74 +++++------ doc/async_operations.md | 2 +- doc/async_worker.md | 169 +++++++++++++------------ doc/basic_types.md | 127 +++++++++---------- doc/boolean.md | 16 +-- doc/buffer.md | 72 +++++------ doc/callbackinfo.md | 47 ++++--- doc/env.md | 24 ++-- doc/error.md | 60 ++++----- doc/error_handling.md | 57 +++++---- doc/escapable_handle_scope.md | 34 +++--- doc/external.md | 38 +++--- doc/function.md | 36 +++--- doc/function_reference.md | 34 +++--- doc/handle_scope.md | 29 +++-- doc/memory_management.md | 4 +- doc/number.md | 17 ++- doc/object.md | 70 +++++------ doc/object_lifetime_management.md | 20 +-- doc/object_reference.md | 28 ++--- doc/promises.md | 42 +++---- doc/property_descriptor.md | 22 ++-- doc/range_error.md | 40 +++--- doc/reference.md | 62 +++++----- doc/setup.md | 2 +- doc/string.md | 30 ++--- doc/symbol.md | 30 ++--- doc/type_error.md | 40 +++--- doc/typed_array.md | 28 ++--- doc/typed_array_of.md | 64 +++++----- doc/value.md | 170 +++++++++++++------------- doc/working_with_javascript_values.md | 14 ++- 33 files changed, 754 insertions(+), 763 deletions(-) diff --git a/README.md b/README.md index 9a27d6c4d..8a22b123d 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -# **node-addon-api module** +# **node-addon-api module** This module contains **header-only C++ wrapper classes** which simplify the use of the C based [N-API](https://nodejs.org/dist/latest/docs/api/n-api.html) provided by Node.js when using C++. It provides a C++ object model and exception handling semantics with low overhead. -N-API is an ABI stable C interface provided by Node.js for building native +N-API is an ABI stable C interface provided by Node.js for building native addons. It is independent from the underlying JavaScript runtime (e.g. V8 or ChakraCore) -and is maintained as part of Node.js itself. It is intended to insulate -native addons from changes in the underlying JavaScript engine and allow -modules compiled for one version to run on later versions of Node.js without +and is maintained as part of Node.js itself. It is intended to insulate +native addons from changes in the underlying JavaScript engine and allow +modules compiled for one version to run on later versions of Node.js without recompilation. The `node-addon-api` module, which is not part of Node.js, preserves the benefits of the N-API as it consists only of inline code that depends only on the stable API -provided by N-API. As such, modules built against one version of Node.js +provided by N-API. As such, modules built against one version of Node.js using node-addon-api should run without having to be rebuilt with newer versions of Node.js. @@ -63,8 +63,7 @@ to ideas specified in the **ECMA262 Language Specification**. ### **API Documentation** -The following is the documentation for node-addon-api (NOTE: -still a work in progress as its not yet complete). +The following is the documentation for node-addon-api. - [Basic Types](doc/basic_types.md) - [Array](doc/basic_types.md#array) diff --git a/doc/array_buffer.md b/doc/array_buffer.md index 754983686..e7217d73a 100644 --- a/doc/array_buffer.md +++ b/doc/array_buffer.md @@ -1,6 +1,6 @@ # ArrayBuffer -The `ArrayBuffer` class corresponds to the +The `Napi::ArrayBuffer` class corresponds to the [JavaScript `ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) class. @@ -8,114 +8,114 @@ class. ### New -Allocates a new `ArrayBuffer` instance with a given length. +Allocates a new `Napi::ArrayBuffer` instance with a given length. ```cpp -static ArrayBuffer New(napi_env env, size_t byteLength); +static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, size_t byteLength); ``` -- `[in] env`: The environment in which to create the `ArrayBuffer` instance. +- `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. - `[in] byteLength`: The length to be allocated, in bytes. -Returns a new `ArrayBuffer` instance. +Returns a new `Napi::ArrayBuffer` instance. ### New -Wraps the provided external data into a new `ArrayBuffer` instance. +Wraps the provided external data into a new `Napi::ArrayBuffer` instance. -The `ArrayBuffer` instance does not assume ownership for the data and expects it -to be valid for the lifetime of the instance. Since the `ArrayBuffer` is subject -to garbage collection this overload is only suitable for data which is static -and never needs to be freed. +The `Napi::ArrayBuffer` instance does not assume ownership for the data and +expects it to be valid for the lifetime of the instance. Since the +`Napi::ArrayBuffer` is subject to garbage collection this overload is only +suitable for data which is static and never needs to be freed. ```cpp -static ArrayBuffer New(napi_env env, void* externalData, size_t byteLength); +static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength); ``` -- `[in] env`: The environment in which to create the `ArrayBuffer` instance. +- `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. - `[in] externalData`: The pointer to the external data to wrap. - `[in] byteLength`: The length of the `externalData`, in bytes. -Returns a new `ArrayBuffer` instance. +Returns a new `Napi::ArrayBuffer` instance. ### New -Wraps the provided external data into a new `ArrayBuffer` instance. +Wraps the provided external data into a new `Napi::ArrayBuffer` instance. -The `ArrayBuffer` instance does not assume ownership for the data and expects it -to be valid for the lifetime of the instance. The data can only be freed once -the `finalizeCallback` is invoked to indicate that the `ArrayBuffer` has been -released. +The `Napi::ArrayBuffer` instance does not assume ownership for the data and +expects it to be valid for the lifetime of the instance. The data can only be +freed once the `finalizeCallback` is invoked to indicate that the +`Napi::ArrayBuffer` has been released. ```cpp template -static ArrayBuffer New(napi_env env, +static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength, Finalizer finalizeCallback); ``` -- `[in] env`: The environment in which to create the `ArrayBuffer` instance. +- `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. - `[in] externalData`: The pointer to the external data to wrap. - `[in] byteLength`: The length of the `externalData`, in bytes. -- `[in] finalizeCallback`: A function to be called when the `ArrayBuffer` is +- `[in] finalizeCallback`: A function to be called when the `Napi::ArrayBuffer` is destroyed. It must implement `operator()`, accept a `void*` (which is the `externalData` pointer), and return `void`. -Returns a new `ArrayBuffer` instance. +Returns a new `Napi::ArrayBuffer` instance. ### New -Wraps the provided external data into a new `ArrayBuffer` instance. +Wraps the provided external data into a new `Napi::ArrayBuffer` instance. -The `ArrayBuffer` instance does not assume ownership for the data and expects it +The `Napi::ArrayBuffer` instance does not assume ownership for the data and expects it to be valid for the lifetime of the instance. The data can only be freed once -the `finalizeCallback` is invoked to indicate that the `ArrayBuffer` has been +the `finalizeCallback` is invoked to indicate that the `Napi::ArrayBuffer` has been released. ```cpp template -static ArrayBuffer New(napi_env env, +static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength, Finalizer finalizeCallback, Hint* finalizeHint); ``` -- `[in] env`: The environment in which to create the `ArrayBuffer` instance. +- `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. - `[in] externalData`: The pointer to the external data to wrap. - `[in] byteLength`: The length of the `externalData`, in bytes. -- `[in] finalizeCallback`: The function to be called when the `ArrayBuffer` is +- `[in] finalizeCallback`: The function to be called when the `Napi::ArrayBuffer` is destroyed. It must implement `operator()`, accept a `void*` (which is the `externalData` pointer) and `Hint*`, and return `void`. - `[in] finalizeHint`: The hint to be passed as the second parameter of the finalize callback. -Returns a new `ArrayBuffer` instance. +Returns a new `Napi::ArrayBuffer` instance. ### Constructor -Initializes an empty instance of the `ArrayBuffer` class. +Initializes an empty instance of the `Napi::ArrayBuffer` class. ```cpp -ArrayBuffer(); +Napi::ArrayBuffer::ArrayBuffer(); ``` ### Constructor -Initializes a wrapper instance of an existing `ArrayBuffer` object. +Initializes a wrapper instance of an existing `Napi::ArrayBuffer` object. ```cpp -ArrayBuffer(napi_env env, napi_value value); +Napi::ArrayBuffer::ArrayBuffer(napi_env env, napi_value value); ``` -- `[in] env`: The environment in which to create the `ArrayBuffer` instance. -- `[in] value`: The `ArrayBuffer` reference to wrap. +- `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. +- `[in] value`: The `Napi::ArrayBuffer` reference to wrap. ### ByteLength ```cpp -size_t ByteLength() const; +size_t Napi::ArrayBuffer::ByteLength() const; ``` Returns the length of the wrapped data, in bytes. @@ -123,7 +123,7 @@ Returns the length of the wrapped data, in bytes. ### Data ```cpp -T* Data() const; +T* Napi::ArrayBuffer::Data() const; ``` Returns a pointer the wrapped data. diff --git a/doc/async_operations.md b/doc/async_operations.md index b8dec37cf..ee445dd3f 100644 --- a/doc/async_operations.md +++ b/doc/async_operations.md @@ -17,7 +17,7 @@ Node Addon API provides an interface to support functions that cover the most common asynchronous use cases. There is an abstract classes to implement asynchronous operations: -- **[AsyncWorker](async_worker.md)** +- **[`Napi::AsyncWorker`](async_worker.md)** These class helps manage asynchronous operations through an abstraction of the concept of moving data between the **event loop** and **worker threads**. diff --git a/doc/async_worker.md b/doc/async_worker.md index 0d78c1b62..a71b29d9f 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -1,17 +1,19 @@ # AsyncWorker -`AsyncWorker` is an abstract class that you can subclass to remove many of the -tedious tasks of moving data between the event loop and worker threads. This +`Napi::AsyncWorker` is an abstract class that you can subclass to remove many of +the tedious tasks of moving data between the event loop and worker threads. This class internally handles all the details of creating and executing an asynchronous operation. -Once created, execution is requested by calling `Queue`. When a thread is -available for execution the `Execute` method will be invoked. Once `Execute` -complets either `OnOK` or `OnError` will be invoked. Once the `OnOK` or -`OnError` methods are complete the AsyncWorker instance is destructed. +Once created, execution is requested by calling `Napi::AsyncWorker::Queue`. When +a thread is available for execution the `Napi::AsyncWorker::Execute` method will +be invoked. Once `Napi::AsyncWorker::Execute` completes either +`Napi::AsyncWorker::OnOK` or `Napi::AsyncWorker::OnError` will be invoked. Once +the `Napi::AsyncWorker::OnOK` or `Napi::AsyncWorker::OnError` methods are +complete the `Napi::AsyncWorker` instance is destructed. -For the most basic use, only the `Execute` method must be implemented in a -subclass. +For the most basic use, only the `Napi::AsyncWorker::Execute` method must be +implemented in a subclass. ## Methods @@ -20,7 +22,7 @@ subclass. Requests the environment in which the async worker has been initially created. ```cpp -Env Env() const; +Napi::Env Napi::AsyncWorker::Env() const; ``` Returns the environment in which the async worker has been created. @@ -30,7 +32,7 @@ Returns the environment in which the async worker has been created. Requests that the work be queued for execution. ```cpp -void Queue(); +void Napi::AsyncWorker::Queue(); ``` ### Cancel @@ -40,13 +42,13 @@ executing, it cannot be cancelled. If cancelled successfully neither `OnOK` nor `OnError` will be called. ```cpp -void Cancel(); +void Napi::AsyncWorker::Cancel(); ``` ### Receiver ```cpp -ObjectReference& Receiver(); +Napi::ObjectReference& Napi::AsyncWorker::Receiver(); ``` Returns the persistent object reference of the receiver object set when the async @@ -55,22 +57,24 @@ worker was created. ### Callback ```cpp -FunctionReference& Callback(); +Napi::FunctionReference& Napi::AsyncWorker::Callback(); ``` Returns the persistent function reference of the callback set when the async worker was created. The returned function reference will receive the results of -the computation that happened in the `Execute` method, unless the default -implementation of `OnOK` or `OnError` is overridden. +the computation that happened in the `Napi::AsyncWorker::Execute` method, unless +the default implementation of `Napi::AsyncWorker::OnOK` or +`Napi::AsyncWorker::OnError` is overridden. ### SetError Sets the error message for the error that happened during the execution. Setting -an error message will cause the `OnError` method to be invoked instead of `OnOK` -once the `Execute` method completes. +an error message will cause the `Napi::AsyncWorker::OnError` method to be +invoked instead of `Napi::AsyncWorker::OnOKOnOK` once the +`Napi::AsyncWorker::Execute` method completes. ```cpp -void SetError(const std::string& error); +void Napi::AsyncWorker::SetError(const std::string& error); ``` - `[in] error`: The reference to the string that represent the message of the error. @@ -81,13 +85,13 @@ This method is used to execute some tasks out of the **event loop** on a libuv worker thread. Subclasses must implement this method and the method is run on a thread other than that running the main event loop. As the method is not running on the main event loop, it must avoid calling any methods from node-addon-api -or running any code that might invoke JavaScript. Instead once this method is +or running any code that might invoke JavaScript. Instead, once this method is complete any interaction through node-addon-api with JavaScript should be implemented -in the `OnOK` method which runs on the main thread and is invoked when the `Execute` -method completes. +in the `Napi::AsyncWorker::OnOK` method which runs on the main thread and is +invoked when the `Napi::AsyncWorker::Execute` method completes. ```cpp -virtual void Execute() = 0; +virtual void Napi::AsyncWorker::Execute() = 0; ``` ### OnOK @@ -97,41 +101,41 @@ The default implementation runs the Callback provided when the AsyncWorker class was created. ```cpp -virtual void OnOK(); +virtual void Napi::AsyncWorker::OnOK(); ``` ### OnError -This method is invoked afer Execute() completes if an error occurs -while `Execute` is running and C++ exceptions are enabled or if an -error was set through a call to `SetError`. The default implementation -calls the callback provided when the AsyncWorker class was created, passing -in the error as the first parameter. +This method is invoked afer `Napi::AsyncWorker::Execute` completes if an error +occurs while `Napi::AsyncWorker::Execute` is running and C++ exceptions are +enabled or if an error was set through a call to `Napi::AsyncWorker::SetError`. +The default implementation calls the callback provided when the `Napi::AsyncWorker` +class was created, passing in the error as the first parameter. ```cpp -virtual void OnError(const Error& e); +virtual void Napi::AsyncWorker::OnError(const Napi::Error& e); ``` ### Constructor -Creates a new `AsyncWorker`. +Creates a new `Napi::AsyncWorker`. ```cpp -explicit AsyncWorker(const Function& callback); +explicit Napi::AsyncWorker(const Napi::Function& callback); ``` - `[in] callback`: The function which will be called when an asynchronous operations ends. The given function is called from the main event loop thread. -Returns an AsyncWork instance which can later be queued for execution by calling +Returns a`Napi::AsyncWork` instance which can later be queued for execution by calling `Queue`. ### Constructor -Creates a new `AsyncWorker`. +Creates a new `Napi::AsyncWorker`. ```cpp -explicit AsyncWorker(const Function& callback, const char* resource_name); +explicit Napi::AsyncWorker(const Napi::Function& callback, const char* resource_name); ``` - `[in] callback`: The function which will be called when an asynchronous @@ -140,16 +144,15 @@ operations ends. The given function is called from the main event loop thread. identifier for the kind of resource that is being provided for diagnostic information exposed by the async_hooks API. -Returns an AsyncWork instance which can later be queued for execution by calling -`Queue`. - +Returns a `Napi::AsyncWork` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. ### Constructor -Creates a new `AsyncWorker`. +Creates a new `Napi::AsyncWorker`. ```cpp -explicit AsyncWorker(const Function& callback, const char* resource_name, const Object& resource); +explicit Napi::AsyncWorker(const Napi::Function& callback, const char* resource_name, const Napi::Object& resource); ``` - `[in] callback`: The function which will be called when an asynchronous @@ -160,31 +163,30 @@ information exposed by the async_hooks API. - `[in] resource`: Object associated with the asynchronous operation that will be passed to possible async_hooks. -Returns an AsyncWork instance which can later be queued for execution by calling -`Queue`. +Returns a `Napi::AsyncWork` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. ### Constructor -Creates a new `AsyncWorker`. +Creates a new `Napi::AsyncWorker`. ```cpp -explicit AsyncWorker(const Object& receiver, const Function& callback); +explicit Napi::AsyncWorker(const Napi::Object& receiver, const Napi::Function& callback); ``` - `[in] receiver`: The `this` object passed to the called function. - `[in] callback`: The function which will be called when an asynchronous operations ends. The given function is called from the main event loop thread. -Returns an AsyncWork instance which can later be queued for execution by calling -`Queue`. - +Returns a `Napi::AsyncWork` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. ### Constructor -Creates a new `AsyncWorker`. +Creates a new `Napi::AsyncWorker`. ```cpp -explicit AsyncWorker(const Object& receiver, const Function& callback,const char* resource_name); +explicit Napi::AsyncWorker(const Napi::Object& receiver, const Napi::Function& callback,const char* resource_name); ``` - `[in] receiver`: The `this` object passed to the called function. @@ -194,16 +196,15 @@ operations ends. The given function is called from the main event loop thread. identifier for the kind of resource that is being provided for diagnostic information exposed by the async_hooks API. -Returns an AsyncWork instance which can later be queued for execution by calling -`Queue`. - +Returns a `Napi::AsyncWork` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. ### Constructor -Creates a new `AsyncWorker`. +Creates a new `Napi::AsyncWorker`. ```cpp -explicit AsyncWorker(const Object& receiver, const Function& callback, const char* resource_name, const Object& resource); +explicit Napi::AsyncWorker(const Napi::Object& receiver, const Napi::Function& callback, const char* resource_name, const Napi::Object& resource); ``` - `[in] receiver`: The `this` object passed to the called function. @@ -215,42 +216,44 @@ information exposed by the async_hooks API. - `[in] resource`: Object associated with the asynchronous operation that will be passed to possible async_hooks. -Returns an AsyncWork instance which can later be queued for execution by calling -`Queue`. +Returns a `Napi::AsyncWork` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. ### Destructor Deletes the created work object that is used to execute logic asynchronously. ```cpp -virtual ~AsyncWorker(); +virtual Napi::AsyncWorker::~AsyncWorker(); ``` ## Operator ```cpp -operator napi_async_work() const; +Napi::AsyncWorker::operator napi_async_work() const; ``` -Returns the N-API napi_async_work wrapped by the AsyncWorker object. This can be -used to mix usage of the C N-API and node-addon-api. +Returns the N-API napi_async_work wrapped by the `Napi::AsyncWorker` object. This +can be used to mix usage of the C N-API and node-addon-api. ## Example -The first step to use the `AsyncWorker` class is to create a new class that inherit -from it and implement the `Execute` abstract method. Typically input to your -worker will be saved within class' fields generally passed in through its -constructor. +The first step to use the `Napi::AsyncWorker` class is to create a new class that +inherits from it and implement the `Napi::AsyncWorker::Execute` abstract method. +Typically input to your worker will be saved within class' fields generally +passed in through its constructor. -When the `Execute` method completes without errors the `OnOK` function callback -will be invoked. In this function the results of the computation will be -reassembled and returned back to the initial JavaScript context. +When the `Napi::AsyncWorker::Execute` method completes without errors the +`Napi::AsyncWorker::OnOK` function callback will be invoked. In this function the +results of the computation will be reassembled and returned back to the initial +JavaScript context. -`AsyncWorker` ensures that all the code in the `Execute` function runs in the -background out of the **event loop** thread and at the end the `OnOK` or `OnError` -function will be called and are executed as part of the event loop. +`Napi::AsyncWorker` ensures that all the code in the `Napi::AsyncWorker::Execute` +function runs in the background out of the **event loop** thread and at the end +the `Napi::AsyncWorker::OnOK` or `Napi::AsyncWorker::OnError` function will be +called and are executed as part of the event loop. -The code below show a basic example of `AsyncWorker` the implementation: +The code below show a basic example of `Napi::AsyncWorker` the implementation: ```cpp #include @@ -283,13 +286,21 @@ class EchoWorker : public AsyncWorker { ``` The `EchoWorker`'s contructor calls the base class' constructor to pass in the -callback that the `AsyncWorker` base class will store persistently. When the work -on the `Execute` method is done the `OnOk` method is called and the results return -back to JavaScript invoking the stored callback with its associated environment. +callback that the `Napi::AsyncWorker` base class will store persistently. When +the work on the `Napi::AsyncWorker::Execute` method is done the +`Napi::AsyncWorker::OnOk` method is called and the results return back to +JavaScript invoking the stored callback with its associated environment. -The following code shows an example on how to create and and use an `AsyncWorker` +The following code shows an example on how to create and and use an `Napi::AsyncWorker` ```cpp +#include + +// Include EchoWorker class +// .. + +use namespace Napi; + Value Echo(const CallbackInfo& info) { // You need to check the input data here Function cb = info[1].As(); @@ -299,8 +310,8 @@ Value Echo(const CallbackInfo& info) { return info.Env().Undefined(); ``` -Using the implementation of an `AsyncWorker` is straight forward. You need only create -a new instance and pass to its constructor the callback you want to execute when -your asynchronous task ends and other data you need for your computation. Once created the -only other action you have to do is to call the `Queue` method that will that will -queue the created worker for execution. +Using the implementation of a `Napi::AsyncWorker` is straight forward. You only +need to create a new instance and pass to its constructor the callback you want to +execute when your asynchronous task ends and other data you need for your +computation. Once created the only other action you have to do is to call the +`Napi::AsyncWorker::Queue` method that will queue the created worker for execution. diff --git a/doc/basic_types.md b/doc/basic_types.md index 43b041dc8..7e65ead1b 100644 --- a/doc/basic_types.md +++ b/doc/basic_types.md @@ -6,7 +6,7 @@ interoperate with their C++ counterparts. ## Value -Value is the base class of Node Addon API's fundamental object type hierarchy. +`Napi::Value` is the base class of Node Addon API's fundamental object type hierarchy. It represents a JavaScript value of an unknown type. It is a thin wrapper around the N-API datatype `napi_value`. Methods on this class can be used to check the JavaScript type of the underlying N-API `napi_value` and also to convert to @@ -15,21 +15,21 @@ C++ types. ### Constructor ```cpp -Value(); +Napi::Value::Value(); ``` -Used to create a Node Addon API `Value` that represents an **empty** value. +Used to create a Node Addon API `Napi::Value` that represents an **empty** value. ```cpp -Value(napi_env env, napi_value value); +Napi::Value::Value(napi_env env, napi_value value); ``` -- `[in] env` - The `napi_env` environment in which to construct the Value +- `[in] env` - The `napi_env` environment in which to construct the `Napi::Value` object. -- `[in] value` - The underlying JavaScript value that the `Value` instance +- `[in] value` - The underlying JavaScript value that the `Napi::Value` instance represents. -Returns a Node.js Addon API `Value` that represents the `napi_value` passed +Returns a Node.js Addon API `Napi::Value` that represents the `napi_value` passed in. ### Operators @@ -37,7 +37,7 @@ in. #### operator napi_value ```cpp -operator napi_value() const; +Napi::Value::operator napi_value() const; ``` Returns the underlying N-API `napi_value`. If the instance is _empty_, this @@ -46,7 +46,7 @@ returns `nullptr`. #### operator == ```cpp -bool operator ==(const Value& other) const; +bool Napi::Value::operator ==(const Value& other) const; ``` Returns `true` if this value strictly equals another value, or `false` otherwise. @@ -54,7 +54,7 @@ Returns `true` if this value strictly equals another value, or `false` otherwise #### operator != ```cpp -bool operator !=(const Value& other) const; +bool Napi::Value::operator !=(const Value& other) const; ``` Returns `false` if this value strictly equals another value, or `true` otherwise. @@ -64,10 +64,10 @@ Returns `false` if this value strictly equals another value, or `true` otherwise #### From ```cpp template -static Value From(napi_env env, const T& value); +static Napi::Value Napi::Value::From(napi_env env, const T& value); ``` -- `[in] env` - The `napi_env` environment in which to construct the Value object. +- `[in] env` - The `napi_env` environment in which to construct the `Napi::Value` object. - `[in] value` - The C++ type to represent in JavaScript. Returns a `Napi::Value` representing the input C++ type in JavaScript. @@ -86,7 +86,7 @@ Here, `value` may be any of: #### As ```cpp -template T As() const; +template T Napi::Value::As() const; ``` Returns the `Napi::Value` cast to a desired C++ type. @@ -99,7 +99,7 @@ the actual value type will throw `Napi::Error`. #### StrictEquals ```cpp -bool StrictEquals(const Value& other) const; +bool Napi::Value::StrictEquals(const Value& other) const; ``` - `[in] other` - The value to compare against. @@ -108,7 +108,7 @@ Returns true if the other `Napi::Value` is strictly equal to this one. #### Env ```cpp -Napi::Env Env() const; +Napi::Env Napi::Value::Env() const; ``` Returns the environment that the value is associated with. See @@ -116,7 +116,7 @@ Returns the environment that the value is associated with. See #### IsEmpty ```cpp -bool IsEmpty() const; +bool Napi::Value::IsEmpty() const; ``` Returns `true` if the value is uninitialized. @@ -125,21 +125,21 @@ An empty value is invalid, and most attempts to perform an operation on an empty value will result in an exception. An empty value is distinct from JavaScript `null` or `undefined`, which are valid values. -When C++ exceptions are disabled at compile time, a method with a `Value` +When C++ exceptions are disabled at compile time, a method with a `Napi::Value` return type may return an empty value to indicate a pending exception. If C++ exceptions are not being used, callers should check the result of `Env::IsExceptionPending` before attempting to use the value. #### Type ```cpp -napi_valuetype Type() const; +napi_valuetype Napi::Value::Type() const; ``` Returns the underlying N-API `napi_valuetype` of the value. #### IsUndefined ```cpp -bool IsUndefined() const; +bool Napi::Value::IsUndefined() const; ``` Returns `true` if the underlying value is a JavaScript `undefined` or `false` @@ -147,7 +147,7 @@ otherwise. #### IsNull ```cpp -bool IsNull() const; +bool Napi::Value::IsNull() const; ``` Returns `true` if the underlying value is a JavaScript `null` or `false` @@ -155,103 +155,103 @@ otherwise. #### IsBoolean ```cpp -bool IsBoolean() const; +bool Napi::Value::IsBoolean() const; ``` Returns `true` if the underlying value is a JavaScript `true` or JavaScript -`false`, or `false` if the value is not a Boolean value in JavaScript. +`false`, or `false` if the value is not a `Napi::Boolean` value in JavaScript. #### IsNumber ```cpp -bool IsNumber() const; +bool Napi::Value::IsNumber() const; ``` -Returns `true` if the underlying value is a JavaScript `Number` or `false` +Returns `true` if the underlying value is a JavaScript `Napi::Number` or `false` otherwise. #### IsString ```cpp -bool IsString() const; +bool Napi::Value::IsString() const; ``` -Returns `true` if the underlying value is a JavaScript `String` or `false` +Returns `true` if the underlying value is a JavaScript `Napi::String` or `false` otherwise. #### IsSymbol ```cpp -bool IsSymbol() const; +bool Napi::Value::IsSymbol() const; ``` -Returns `true` if the underlying value is a JavaScript `Symbol` or `false` +Returns `true` if the underlying value is a JavaScript `Napi::Symbol` or `false` otherwise. #### IsArray ```cpp -bool IsArray() const; +bool Napi::Value::IsArray() const; ``` -Returns `true` if the underlying value is a JavaScript `Array` or `false` +Returns `true` if the underlying value is a JavaScript `Napi::Array` or `false` otherwise. #### IsArrayBuffer ```cpp -bool IsArrayBuffer() const; +bool Napi::Value::IsArrayBuffer() const; ``` -Returns `true` if the underlying value is a JavaScript `ArrayBuffer` or `false` +Returns `true` if the underlying value is a JavaScript `Napi::ArrayBuffer` or `false` otherwise. #### IsTypedArray ```cpp -bool IsTypedArray() const; +bool Napi::Value::IsTypedArray() const; ``` -Returns `true` if the underlying value is a JavaScript `TypedArray` or `false` +Returns `true` if the underlying value is a JavaScript `Napi::TypedArray` or `false` otherwise. #### IsObject ```cpp -bool IsObject() const; +bool Napi::Value::IsObject() const; ``` -Returns `true` if the underlying value is a JavaScript `Object` or `false` +Returns `true` if the underlying value is a JavaScript `Napi::Object` or `false` otherwise. #### IsFunction ```cpp -bool IsFunction() const; +bool Napi::Value::IsFunction() const; ``` -Returns `true` if the underlying value is a JavaScript `Function` or `false` +Returns `true` if the underlying value is a JavaScript `Napi::Function` or `false` otherwise. #### IsPromise ```cpp -bool IsPromise() const; +bool Napi::Value::IsPromise() const; ``` -Returns `true` if the underlying value is a JavaScript `Promise` or `false` +Returns `true` if the underlying value is a JavaScript `Napi::Promise` or `false` otherwise. #### IsDataView ```cpp -bool IsDataView() const; +bool Napi::Value::IsDataView() const; ``` -Returns `true` if the underlying value is a JavaScript `DataView` or `false` +Returns `true` if the underlying value is a JavaScript `Napi::DataView` or `false` otherwise. #### IsBuffer ```cpp -bool IsBuffer() const; +bool Napi::Value::IsBuffer() const; ``` -Returns `true` if the underlying value is a Node.js `Buffer` or `false` +Returns `true` if the underlying value is a Node.js `Napi::Buffer` or `false` otherwise. #### IsExternal ```cpp -bool IsExternal() const; +bool Napi::Value::IsExternal() const; ``` Returns `true` if the underlying value is a N-API external object or `false` @@ -259,7 +259,7 @@ otherwise. #### ToBoolean ```cpp -Boolean ToBoolean() const; +Napi::Boolean Napi::Value::ToBoolean() const; ``` Returns a `Napi::Boolean` representing the `Napi::Value`. @@ -269,10 +269,9 @@ exception if the coercion fails. If C++ exceptions are not being used, callers should check the result of `Env::IsExceptionPending` before attempting to use the returned value. - #### ToNumber ```cpp -Number ToNumber() const; +Napi::Number Napi::Value::ToNumber() const; ``` Returns a `Napi::Number` representing the `Napi::Value`. @@ -284,10 +283,9 @@ exception if the coercion fails. If C++ exceptions are not being used, callers should check the result of `Env::IsExceptionPending` before attempting to use the returned value. - #### ToString ```cpp -String ToString() const; +Napi::String Napi::Value::ToString() const; ``` Returns a `Napi::String` representing the `Napi::Value`. @@ -298,10 +296,9 @@ JavaScript exception if the coercion fails. If C++ exceptions are not being used, callers should check the result of `Env::IsExceptionPending` before attempting to use the returned value. - #### ToObject ```cpp -Object ToObject() const; +Napi::Object Napi::Value::ToObject() const; ``` Returns a `Napi::Object` representing the `Napi::Value`. @@ -311,29 +308,28 @@ exception if the coercion fails. If C++ exceptions are not being used, callers should check the result of `Env::IsExceptionPending` before attempting to use the returned value. - ## Name Names are JavaScript values that can be used as a property name. There are two -specialized types of names supported in Node.js Addon API- [`String`](string.md) -and [`Symbol`](symbol.md). +specialized types of names supported in Node.js Addon API [`Napi::String`](string.md) +and [`Napi::Symbol`](symbol.md). ### Methods #### Constructor ```cpp -Name(); +Napi::Name::Name(); ``` -Returns an empty `Name`. +Returns an empty `Napi::Name`. ```cpp -Name(napi_env env, napi_value value); +Napi::Name::Name(napi_env env, napi_value value); ``` - `[in] env` - The environment in which to create the array. - `[in] value` - The primitive to wrap. -Returns a Name created from the JavaScript primitive. +Returns a `Napi::Name` created from the JavaScript primitive. Note: The value is not coerced to a string. @@ -345,7 +341,7 @@ around `napi_value` representing a JavaScript Array. ### Constructor ```cpp -Array(); +Napi::Array::Array(); ``` Returns an empty array. @@ -354,9 +350,8 @@ If an error occurs, a `Napi::Error` will be thrown. If C++ exceptions are not being used, callers should check the result of `Env::IsExceptionPending` before attempting to use the returned value. - ```cpp -Array(napi_env env, napi_value value); +Napi::Array::Array(napi_env env, napi_value value); ``` - `[in] env` - The environment in which to create the array. - `[in] value` - The primitive to wrap. @@ -371,7 +366,7 @@ attempting to use the returned value. #### New ```cpp -static Array New(napi_env env); +static Napi::Array Napi::Array::New(napi_env env); ``` - `[in] env` - The environment in which to create the array. @@ -384,7 +379,7 @@ attempting to use the returned value. #### New ```cpp -static Array New(napi_env env, size_t length); +static Napi::Array Napi::Array::New(napi_env env, size_t length); ``` - `[in] env` - The environment in which to create the array. - `[in] length` - The length of the array. @@ -395,9 +390,9 @@ If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not being used, callers should check the result of `Env::IsExceptionPending` before attempting to use the returned value. -#### New +#### Length ```cpp -uint32_t Length() const; +uint32_t Napi::Array::Length() const; ``` Returns the length of the array. diff --git a/doc/boolean.md b/doc/boolean.md index 070f21e1f..2886b1d0c 100644 --- a/doc/boolean.md +++ b/doc/boolean.md @@ -1,9 +1,5 @@ # Boolean -You are reading a draft of the next documentation and it's in continuous 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/) - # Methods ### Constructor @@ -15,19 +11,19 @@ Napi::Boolean::New(Napi::Env env, bool value); - `[in] value`: The Javascript boolean value ```cpp -Napi::Boolean(); +Napi::Boolean::Boolean(); ``` returns a new empty Javascript Boolean value type. ### operator bool -Converts a Boolean value to a boolean primitive. +Converts a `Napi::Boolean` value to a boolean primitive. ```cpp -operator bool() const; +Napi::Boolean::operator bool() const; ``` ### Value -Converts a Boolean value to a boolean primitive. +Converts a `Napi::Boolean` value to a boolean primitive. ```cpp -bool Value() const; -``` \ No newline at end of file +bool Napi::Boolean::Value() const; +``` diff --git a/doc/buffer.md b/doc/buffer.md index e37f7d980..8f76b200d 100644 --- a/doc/buffer.md +++ b/doc/buffer.md @@ -1,132 +1,132 @@ # Buffer -The `Buffer` class creates a projection of raw data that can be consumed by +The `Napi::Buffer` class creates a projection of raw data that can be consumed by script. ## Methods ### New -Allocates a new `Buffer` object with a given length. +Allocates a new `Napi::Buffer` object with a given length. ```cpp -static Buffer New(napi_env env, size_t length); +static Napi::Buffer Napi::Buffer::New(napi_env env, size_t length); ``` -- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] length`: The number of `T` elements to allocate. -Returns a new `Buffer` object. +Returns a new `Napi::Buffer` object. ### New -Wraps the provided external data into a new `Buffer` object. +Wraps the provided external data into a new `Napi::Buffer` object. -The `Buffer` object does not assume ownership for the data and expects it to be -valid for the lifetime of the object. Since the `Buffer` is subject to garbage +The `Napi::Buffer` object does not assume ownership for the data and expects it to be +valid for the lifetime of the object. Since the `Napi::Buffer` is subject to garbage collection this overload is only suitable for data which is static and never needs to be freed. ```cpp -static Buffer New(napi_env env, T* data, size_t length); +static Napi::Buffer Napi::Buffer::New(napi_env env, T* data, size_t length); ``` -- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] data`: The pointer to the external data to expose. - `[in] length`: The number of `T` elements in the external data. -Returns a new `Buffer` object. +Returns a new `Napi::Buffer` object. ### New -Wraps the provided external data into a new `Buffer` object. +Wraps the provided external data into a new `Napi::Buffer` object. -The `Buffer` object does not assume ownership for the data and expects it +The `Napi::Buffer` object does not assume ownership for the data and expects it to be valid for the lifetime of the object. The data can only be freed once the -`finalizeCallback` is invoked to indicate that the `Buffer` has been released. +`finalizeCallback` is invoked to indicate that the `Napi::Buffer` has been released. ```cpp template -static Buffer New(napi_env env, +static Napi::Buffer Napi::Buffer::New(napi_env env, T* data, size_t length, Finalizer finalizeCallback); ``` -- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] data`: The pointer to the external data to expose. - `[in] length`: The number of `T` elements in the external data. -- `[in] finalizeCallback`: The function to be called when the `Buffer` is +- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is destroyed. It must implement `operator()`, accept a `T*` (which is the external data pointer), and return `void`. -Returns a new `Buffer` object. +Returns a new `Napi::Buffer` object. ### New -Wraps the provided external data into a new `Buffer` object. +Wraps the provided external data into a new `Napi::Buffer` object. -The `Buffer` object does not assume ownership for the data and expects it to be +The `Napi::Buffer` object does not assume ownership for the data and expects it to be valid for the lifetime of the object. The data can only be freed once the -`finalizeCallback` is invoked to indicate that the `Buffer` has been released. +`finalizeCallback` is invoked to indicate that the `Napi::Buffer` has been released. ```cpp template -static Buffer New(napi_env env, +static Napi::Buffer Napi::Buffer::New(napi_env env, T* data, size_t length, Finalizer finalizeCallback, Hint* finalizeHint); ``` -- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] data`: The pointer to the external data to expose. - `[in] length`: The number of `T` elements in the external data. -- `[in] finalizeCallback`: The function to be called when the `Buffer` is +- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is destroyed. It must implement `operator()`, accept a `T*` (which is the external data pointer) and `Hint*`, and return `void`. - `[in] finalizeHint`: The hint to be passed as the second parameter of the finalize callback. -Returns a new `Buffer` object. +Returns a new `Napi::Buffer` object. ### Copy -Allocates a new `Buffer` object and copies the provided external data into it. +Allocates a new `Napi::Buffer` object and copies the provided external data into it. ```cpp -static Buffer Copy(napi_env env, const T* data, size_t length); +static Napi::Buffer Napi::Buffer::Copy(napi_env env, const T* data, size_t length); ``` -- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] data`: The pointer to the external data to copy. - `[in] length`: The number of `T` elements in the external data. -Returns a new `Buffer` object containing a copy of the data. +Returns a new `Napi::Buffer` object containing a copy of the data. ### Constructor -Initializes an empty instance of the `Buffer` class. +Initializes an empty instance of the `Napi::Buffer` class. ```cpp -Buffer(); +Napi::Buffer::Buffer(); ``` ### Constructor -Initializes the `Buffer` object using an existing Uint8Array. +Initializes the `Napi::Buffer` object using an existing Uint8Array. ```cpp -Buffer(napi_env env, napi_value value); +Napi::Buffer::Buffer(napi_env env, napi_value value); ``` -- `[in] env`: The environment in which to create the `Buffer` object. +- `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] value`: The Uint8Array reference to wrap. ### Data ```cpp -T* Data() const; +T* Napi::Buffer::Data() const; ``` Returns a pointer the external data. @@ -134,7 +134,7 @@ Returns a pointer the external data. ### Length ```cpp -size_t Length() const; +size_t Napi::Buffer::Length() const; ``` Returns the number of `T` elements in the external data. diff --git a/doc/callbackinfo.md b/doc/callbackinfo.md index add1ea00e..0bf4e1ad1 100644 --- a/doc/callbackinfo.md +++ b/doc/callbackinfo.md @@ -1,30 +1,28 @@ -**WORK IN PROGRESS, NOT YET COMPLETE** - # CallbackInfo The object representing the components of the JavaScript request being made. -The CallbackInfo object is usually created and passed by the Node.js runtime or node-addon-api infrastructure. +The `Napi::CallbackInfo` object is usually created and passed by the Node.js runtime or node-addon-api infrastructure. -The CallbackInfo object contains the arguments passed by the caller. The number of arguments is returned by the `Length` method. Each individual argument can be accessed using the `operator[]` method. +The `Napi::CallbackInfo` object contains the arguments passed by the caller. The number of arguments is returned by the `Length` method. Each individual argument can be accessed using the `operator[]` method. -The `SetData` and `Data` methods are used to set and retrieve the data pointer contained in the CallbackInfo object. +The `SetData` and `Data` methods are used to set and retrieve the data pointer contained in the `Napi::CallbackInfo` object. ## Methods ### Constructor ```cpp -CallbackInfo(napi_env env, napi_callback_info info); +Napi::CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info); ``` -- `[in] env`: The `napi_env` environment in which to construct the `CallbackInfo` object. -- `[in] info`: The `napi_callback_info` data structure from which to construct the `CallbackInfo` object. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::CallbackInfo` object. +- `[in] info`: The `napi_callback_info` data structure from which to construct the `Napi::CallbackInfo` object. ### Env ```cpp -Napi::Env Env() const; +Napi::Env Napi::CallbackInfo::Env() const; ``` Returns the `Env` object in which the request is being made. @@ -32,42 +30,41 @@ Returns the `Env` object in which the request is being made. ### NewTarget ```cpp -Value NewTarget() const; +Napi::Value Napi::CallbackInfo::NewTarget() const; ``` -Returns the `new.target` value of the constructor call. If the function that was invoked (and for which the CallbackInfo was passed) is not a constructor call, a call to `IsEmpty()` on the returned value returns true. +Returns the `new.target` value of the constructor call. If the function that was invoked (and for which the `Napi::NCallbackInfo` was passed) is not a constructor call, a call to `IsEmpty()` on the returned value returns true. ### IsConstructCall ```cpp -bool IsConstructCall() const; +bool Napi::CallbackInfo::IsConstructCall() const; ``` -Returns a `bool` indicating if the function that was invoked (and for which the CallbackInfo was passed) is a constructor call. - +Returns a `bool` indicating if the function that was invoked (and for which the `Napi::CallbackInfo` was passed) is a constructor call. ### Length ```cpp -size_t Length() const; +size_t Napi::CallbackInfo::Length() const; ``` -Returns the number of arguments passed in the CallbackInfo object. +Returns the number of arguments passed in the `Napi::CallbackInfo` object. ### operator [] ```cpp -const Value operator [](size_t index) const; +const Napi::Value operator [](size_t index) const; ``` - `[in] index`: The zero-based index of the requested argument. -Returns a `Value` object containing the requested argument. +Returns a `Napi::Value` object containing the requested argument. ### This ```cpp -Value This() const; +Napi::Value Napi::CallbackInfo::This() const; ``` Returns the JavaScript `this` value for the call @@ -75,7 +72,7 @@ Returns the JavaScript `this` value for the call ### Data ```cpp -void* Data() const; +void* Napi::CallbackInfo::Data() const; ``` Returns the data pointer for the callback. @@ -83,18 +80,18 @@ Returns the data pointer for the callback. ### SetData ```cpp -void SetData(void* data); +void Napi::CallbackInfo::SetData(void* data); ``` -- `[in] data`: The new data pointer to associate with this CallbackInfo object. +- `[in] data`: The new data pointer to associate with this `Napi::CallbackInfo` object. Returns `void`. ### Not documented here ```cpp -~CallbackInfo(); +Napi::CallbackInfo::~CallbackInfo(); // Disallow copying to prevent multiple free of _dynamicArgs -CallbackInfo(CallbackInfo const &) = delete; -void operator=(CallbackInfo const &) = delete; +Napi::CallbackInfo::CallbackInfo(CallbackInfo const &) = delete; +void Napi::CallbackInfo::operator=(CallbackInfo const &) = delete; ``` diff --git a/doc/env.md b/doc/env.md index 344b8be24..9bde741ca 100644 --- a/doc/env.md +++ b/doc/env.md @@ -1,5 +1,3 @@ -**WORK IN PROGRESS, NOT YET COMPLETE** - # Env The opaque data structure containing the environment in which the request is being run. @@ -11,10 +9,10 @@ The Env object is usually created and passed by the Node.js runtime or node-addo ### Constructor ```cpp -Env(napi_env env); +Napi::Env::Env(napi_env env); ``` -- `[in] env`: The `napi_env` environment from which to construct the `Env` object. +- `[in] env`: The `napi_env` environment from which to construct the `Napi::Env` object. ### napi_env @@ -27,31 +25,31 @@ Returns the `napi_env` opaque data structure representing the environment. ### Global ```cpp -Object Global() const; +Napi::Object Napi::Env::Global() const; ``` -Returns the `Object` representing the environment's JavaScript Global Object. +Returns the `Napi::Object` representing the environment's JavaScript Global Object. ### Undefined ```cpp -Value Undefined() const; +Napi::Value Napi::Env::Undefined() const; ``` -Returns the `Value` representing the environment's JavaScript Undefined Object. +Returns the `Napi::Value` representing the environment's JavaScript Undefined Object. ### Null ```cpp -Value Null() const; +Napi::Value Napi::Env::Null() const; ``` -Returns the `Value` representing the environment's JavaScript Null Object. +Returns the `Napi::Value` representing the environment's JavaScript Null Object. ### IsExceptionPending ```cpp -bool IsExceptionPending() const; +bool Napi::Env::IsExceptionPending() const; ``` Returns a `bool` indicating if an exception is pending in the environment. @@ -59,7 +57,7 @@ Returns a `bool` indicating if an exception is pending in the environment. ### GetAndClearPendingException ```cpp -Error GetAndClearPendingException(); +Napi::Error Napi::Env::GetAndClearPendingException(); ``` -Returns an `Error` object representing the environment's pending exception, if any. +Returns an `Napi::Error` object representing the environment's pending exception, if any. diff --git a/doc/error.md b/doc/error.md index dc5e7ea07..1526bddf0 100644 --- a/doc/error.md +++ b/doc/error.md @@ -1,14 +1,14 @@ # Error -The **Error** class is a representation of the JavaScript Error object that is thrown +The `Napi::Error` class is a representation of the JavaScript `Error` object that is thrown when runtime errors occur. The Error object can also be used as a base object for user-defined exceptions. -The **Error** class is a persistent reference to a JavaScript error object thus -inherits its behavior from the `ObjectReference` class (for more info see: [ObjectReference](object_reference.md)). +The `Napi::Error` class is a persistent reference to a JavaScript error object thus +inherits its behavior from the `Napi::ObjectReference` class (for more info see: [`Napi::ObjectReference`](object_reference.md)). If C++ exceptions are enabled (for more info see: [Setup](setup.md)), then the -**Error** class extends `std::exception` and enables integrated +`Napi::Error` class extends `std::exception` and enables integrated error-handling for C++ exceptions and JavaScript exceptions. For more details about error handling refer to the section titled [Error handling](error_handling.md). @@ -17,41 +17,41 @@ For more details about error handling refer to the section titled [Error handlin ### New -Creates empty instance of an `Error` object for the specified environment. +Creates empty instance of an `Napi::Error` object for the specified environment. ```cpp -Error::New(Napi:Env env); +Napi::Error::New(Napi::Env env); ``` -- `[in] Env`: The environment in which to construct the Error object. +- `[in] env`: The environment in which to construct the `Napi::Error` object. -Returns an instance of `Error` object. +Returns an instance of `Napi::Error` object. ### New -Creates instance of an `Error` object. +Creates instance of an `Napi::Error` object. ```cpp -Error::New(Napi:Env env, const char* message); +Napi::Error::New(Napi::Env env, const char* message); ``` -- `[in] Env`: The environment in which to construct the Error object. -- `[in] message`: Null-terminated string to be used as the message for the Error. +- `[in] env`: The environment in which to construct the `Napi::Error` object. +- `[in] message`: Null-terminated string to be used as the message for the `Napi::Error`. -Returns instance of an `Error` object. +Returns instance of an `Napi::Error` object. ### New -Creates instance of an `Error` object +Creates instance of an `Napi::Error` object ```cpp -Error::New(Napi:Env env, const std::string& message); +Napi::Error::New(Napi::Env env, const std::string& message); ``` -- `[in] Env`: The environment in which to construct the `Error` object. -- `[in] message`: Reference string to be used as the message for the `Error`. +- `[in] env`: The environment in which to construct the `Napi::Error` object. +- `[in] message`: Reference string to be used as the message for the `Napi::Error`. -Returns instance of an `Error` object. +Returns instance of an `Napi::Error` object. ### Fatal @@ -59,38 +59,38 @@ In case of an unrecoverable error in a native module, a fatal error can be throw to immediately terminate the process. ```cpp -static NAPI_NO_RETURN void Fatal(const char* location, const char* message); +static NAPI_NO_RETURN void Napi::Error::Fatal(const char* location, const char* message); ``` The function call does not return, the process will be terminated. ### Constructor -Creates empty instance of an `Error`. +Creates empty instance of an `Napi::Error`. ```cpp -Error(); +Napi::Error::Error(); ``` -Returns an instance of `Error` object. +Returns an instance of `Napi::Error` object. ### Constructor -Initializes an `Error` instance from an existing JavaScript error object. +Initializes an `Napi::Error` instance from an existing JavaScript error object. ```cpp -Error(napi_env env, napi_value value); +Napi::Error::Error(napi_env env, napi_value value); ``` -- `[in] Env`: The environment in which to construct the Error object. -- `[in] value`: The `Error` reference to wrap. +- `[in] env`: The environment in which to construct the error object. +- `[in] value`: The `Napi::Error` reference to wrap. -Returns instance of an `Error` object. +Returns instance of an `Napi::Error` object. ### Message ```cpp -std::string& Message() const NAPI_NOEXCEPT; +std::string& Napi::Error::Message() const NAPI_NOEXCEPT; ``` Returns the reference to the string that represent the message of the error. @@ -100,7 +100,7 @@ Returns the reference to the string that represent the message of the error. Throw the error as JavaScript exception. ```cpp -void ThrowAsJavaScriptException() const; +void Napi::Error::ThrowAsJavaScriptException() const; ``` Throws the error as a JavaScript exception. @@ -108,7 +108,7 @@ Throws the error as a JavaScript exception. ### what ```cpp -const char* what() const NAPI_NOEXCEPT override; +const char* Napi::Error::what() const NAPI_NOEXCEPT override; ``` Returns a pointer to a null-terminated string that is used to identify the diff --git a/doc/error_handling.md b/doc/error_handling.md index d56281575..d5df55450 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -6,12 +6,12 @@ have to handle and dispatch it correctly. **node-addon-api** uses return values JavaScript exceptions for error handling. You can choose return values or exception handling based on the mechanism that works best for your add-on. -The **Error** is a persistent reference (for more info see: [Object reference](object_reference.md)) +The `Napi::Error` is a persistent reference (for more info see: [`Napi::ObjectReference`](object_reference.md)) to a JavaScript error object. Use of this class depends on whether C++ exceptions are enabled at compile time. If C++ exceptions are enabled (for more info see: [Setup](setup.md)), then the -**Error** class extends `std::exception` and enables integrated +`Napi::Error` class extends `std::exception` and enables integrated error-handling for C++ exceptions and JavaScript exceptions. The following sections explain the approach for each case: @@ -34,14 +34,14 @@ returning from a native method. If a node-addon-api call fails without executing any JavaScript code (for example due to an invalid argument), then node-addon-api automatically converts and throws -the error as a C++ exception of type **Error**. +the error as a C++ exception of type `Napi::Error`. If a JavaScript function called by C++ code via node-addon-api throws a JavaScript exception, then node-addon-api automatically converts and throws it as a C++ -exception of type **Error** on return from the JavaScript code to the native +exception of type `Napi:Error` on return from the JavaScript code to the native method. -If a C++ exception of type **Error** escapes from a N-API C++ callback, then +If a C++ exception of type `Napi::Error` escapes from a N-API C++ callback, then the N-API wrapper automatically converts and throws it as a JavaScript exception. On return from a native method, node-addon-api will automatically convert a pending C++ @@ -57,35 +57,35 @@ returning from a native method. ```cpp Env env = ... -throw Error::New(env, "Example exception"); +throw Napi::Error::New(env, "Example exception"); // other C++ statements // ... ``` The statements following the throw statement will not be executed. The exception -will bubble up as a C++ exception of type **Error**, until it is either caught +will bubble up as a C++ exception of type `Napi::Error`, until it is either caught while still in C++, or else automatically propagated as a JavaScript exception when returning to JavaScript. ### Propagating a N-API C++ exception ```cpp -Function jsFunctionThatThrows = someObj.As(); -Value result = jsFunctionThatThrows({ arg1, arg2 }); +Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); // other C++ statements // ... ``` The C++ statements following the call to the JavaScript function will not be -executed. The exception will bubble up as a C++ exception of type **Error**, +executed. The exception will bubble up as a C++ exception of type `Napi::Error`, until it is either caught while still in C++, or else automatically propagated as a JavaScript exception when returning to JavaScript. ### Handling a N-API C++ exception ```cpp -Function jsFunctionThatThrows = someObj.As(); -Value result; +Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Value result; try { result = jsFunctionThatThrows({ arg1, arg2 }); } catch (const Error& e) { @@ -101,22 +101,22 @@ exception. ## Handling Errors Without C++ Exceptions If C++ exceptions are disabled (for more info see: [Setup](setup.md)), then the -**Error** class does not extend `std::exception`. This means that any calls to +`Napi::Error` class does not extend `std::exception`. This means that any calls to node-addon-api function do not throw a C++ exceptions. Instead, it raises -_pending_ JavaScript exceptions and returns an _empty_ **Value**. +_pending_ JavaScript exceptions and returns an _empty_ `Napi::Value`. The calling code should check `env.IsExceptionPending()` before attempting to use a -returned value, and may use methods on the **Env** class +returned value, and may use methods on the `Napi::Env` class to check for, get, and clear a pending JavaScript exception (for more info see: [Env](env.md)). If the pending exception is not cleared, it will be thrown when the native code -returns to JavaScript. +returns to JavaScript. ## Examples with C++ exceptions disabled ### Throwing a JS exception ```cpp -Env env = ... -Error::New(env, "Example exception").ThrowAsJavaScriptException(); +Napi::Env env = ... +Napi::Error::New(env, "Example exception").ThrowAsJavaScriptException(); return; ``` @@ -126,28 +126,27 @@ immediately from the native callback, after performing any necessary cleanup. ### Propagating a N-API JS exception ```cpp -Env env = ... -Function jsFunctionThatThrows = someObj.As(); -Value result = jsFunctionThatThrows({ arg1, arg2 }); +Napi::Env env = ... +Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); if (env.IsExceptionPending()) { Error e = env.GetAndClearPendingException(); return e.Value(); } ``` -If env.IsExceptionPending() is returns true a -JavaScript exception is pending. To let the exception propagate, the code should -generally return immediately from the native callback, after performing any -necessary cleanup. +If env.IsExceptionPending() returns true a JavaScript exception is pending. To +let the exception propagate, the code should generally return immediately from +the native callback, after performing any necessary cleanup. ### Handling a N-API JS exception ```cpp -Env env = ... -Function jsFunctionThatThrows = someObj.As(); -Value result = jsFunctionThatThrows({ arg1, arg2 }); +Napi::Env env = ... +Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); if (env.IsExceptionPending()) { - Error e = env.GetAndClearPendingException(); + Napi::Error e = env.GetAndClearPendingException(); cerr << "Caught JavaScript exception: " + e.Message(); } ``` diff --git a/doc/escapable_handle_scope.md b/doc/escapable_handle_scope.md index 4ebd107f4..978aab354 100644 --- a/doc/escapable_handle_scope.md +++ b/doc/escapable_handle_scope.md @@ -1,17 +1,17 @@ # EscapableHandleScope -The EscapableHandleScope class is used to manage the lifetime of object handles +The `Napi::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 +The `Napi::EscapableHandleScope` is a special type of `Napi::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]. +[Object lifetime management](object_lifetime_management.md). ## Methods @@ -20,63 +20,63 @@ For more details refer to the section titled Creates a new escapable handle scope. ```cpp -EscapableHandleScope EscapableHandleScope::New(Napi:Env env); +Napi::EscapableHandleScope Napi::EscapableHandleScope::New(Napi:Env env); ``` -- `[in] Env`: The environment in which to construct the EscapableHandleScope object. +- `[in] Env`: The environment in which to construct the `Napi::EscapableHandleScope` object. -Returns a new EscapableHandleScope +Returns a new `Napi::EscapableHandleScope` ### Constructor Creates a new escapable handle scope. ```cpp -EscapableHandleScope EscapableHandleScope::New(napi_env env, napi_handle_scope scope); +Napi::EscapableHandleScope Napi::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 +Returns a new `Napi::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 +operator Napi::EscapableHandleScope::napi_escapable_handle_scope() const ``` -Returns the N-API napi_escapable_handle_scope wrapped by the EscapableHandleScope object. +Returns the N-API napi_escapable_handle_scope wrapped by the `Napi::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(); +Napi::EscapableHandleScope::~EscapableHandleScope(); ``` -Deletes the EscapableHandleScope instance and allows any objects/handles created +Deletes the `Napi::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); +napi::Value Napi::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 +Returns `Napi::Value` which can be used in the outer scope. This method can +be called at most once on a given `Napi::EscapableHandleScope`. If it is called more than once an exception will be thrown. ### Env ```cpp -Napi::Env Env() const; +Napi::Env Napi::EscapableHandleScope::Env() const; ``` -Returns the Napi:Env associated with the EscapableHandleScope. +Returns the `Napi::Env` associated with the `Napi::EscapableHandleScope`. diff --git a/doc/external.md b/doc/external.md index 3bcda8fda..4022b61dd 100644 --- a/doc/external.md +++ b/doc/external.md @@ -1,10 +1,8 @@ -**WORK IN PROGRESS, NOT YET COMPLETE** - # External (template) -The External template class implements the ability to create a Value object with arbitrary C++ data. It is the user's responsibility to manage the memory for the arbitrary C++ data. +The `Napi::External` template class implements the ability to create a `Napi::Value` object with arbitrary C++ data. It is the user's responsibility to manage the memory for the arbitrary C++ data. -External objects can be created with an optional Finalizer function and optional Hint value. The Finalizer function, if specified, is called when your External object is released by Node's garbage collector. It gives your code the opportunity to free any dynamically created data. If you specify a Hint value, it is passed to your Finalizer function. +`Napi::External` objects can be created with an optional Finalizer function and optional Hint value. The Finalizer function, if specified, is called when your `Napi::External` object is released by Node's garbage collector. It gives your code the opportunity to free any dynamically created data. If you specify a Hint value, it is passed to your Finalizer function. ## Methods @@ -12,50 +10,50 @@ External objects can be created with an optional Finalizer function and optional ```cpp template -static External New(napi_env env, T* data); +static Napi::External Napi::External::New(napi_env env, T* data); ``` -- `[in] env`: The `napi_env` environment in which to construct the External object. -- `[in] data`: The arbitrary C++ data to be held by the External object. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::External` object. +- `[in] data`: The arbitrary C++ data to be held by the `Napi::External` object. -Returns the created `External` object. +Returns the created `Napi::External` object. ### New ```cpp template -static External New(napi_env env, +static Napi::External Napi::External::New(napi_env env, T* data, Finalizer finalizeCallback); ``` -- `[in] env`: The `napi_env` environment in which to construct the External object. -- `[in] data`: The arbitrary C++ data to be held by the External object. -- `[in] finalizeCallback`: A function called when the External object is released by the garbage collector accepting a T* and returning void. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::External` object. +- `[in] data`: The arbitrary C++ data to be held by the `Napi::External` object. +- `[in] finalizeCallback`: A function called when the `Napi::External` object is released by the garbage collector accepting a T* and returning void. -Returns the created `External` object. +Returns the created `Napi::External` object. ### New ```cpp template -static External New(napi_env env, +static Napi::External Napi::External::New(napi_env env, T* data, Finalizer finalizeCallback, Hint* finalizeHint); ``` -- `[in] env`: The `napi_env` environment in which to construct the External object. -- `[in] data`: The arbitrary C++ data to be held by the External object. -- `[in] finalizeCallback`: A function called when the External object is released by the garbage collector accepting T* and Hint* parameters and returning void. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::External` object. +- `[in] data`: The arbitrary C++ data to be held by the `Napi::External` object. +- `[in] finalizeCallback`: A function called when the `Napi::External` object is released by the garbage collector accepting T* and Hint* parameters and returning void. - `[in] finalizeHint`: A hint value passed to the `finalizeCallback` function. -Returns the created `External` object. +Returns the created `Napi::External` object. ### Data ```cpp -T* Data() const; +T* Napi::External::Data() const; ``` -Returns a pointer to the arbitrary C++ data held by the External object. +Returns a pointer to the arbitrary C++ data held by the `Napi::External` object. diff --git a/doc/function.md b/doc/function.md index ca85ef05a..3e8351ba3 100644 --- a/doc/function.md +++ b/doc/function.md @@ -5,7 +5,7 @@ native code that can later be called from JavaScript. The created function is no automatically visible from JavaScript. Instead it needs to be part of the add-on's module exports or be returned by one of the module's exported functions. -In addition the `Function` class also provides methods that can be used to call +In addition the `Napi::Function` class also provides methods that can be used to call functions that were created in JavaScript and passed to the native add-on. The `Napi::Function` class inherits its behavior from the `Napi::Object` class (for more info @@ -54,7 +54,7 @@ on the stack (for example when running a native method called from JavaScript). Creates a new empty instance of `Napi::Function`. ```cpp -Function(); +Napi::Function::Function(); ``` ### Constructor @@ -62,7 +62,7 @@ Function(); Creates a new instance of the `Napi::Function` object. ```cpp -Function(napi_env env, napi_value value); +Napi::Function::Function(napi_env env, napi_value value); ``` - `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. @@ -76,7 +76,7 @@ Creates an instance of a `Napi::Function` object. ```cpp template -static Function New(napi_env env, Callable cb, const char* utf8name = nullptr, void* data = nullptr); +static Napi::Function Napi::Function::New(napi_env env, Callable cb, const char* utf8name = nullptr, void* data = nullptr); ``` - `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. @@ -91,7 +91,7 @@ Returns an instance of a `Napi::Function` object. ```cpp template -static Function New(napi_env env, Callable cb, const std::string& utf8name, void* data = nullptr); +static Napi::Function Napi::Function::New(napi_env env, Callable cb, const std::string& utf8name, void* data = nullptr); ``` - `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. @@ -108,7 +108,7 @@ Creates a new JavaScript value from one that represents the constructor for the object. ```cpp -Napi::Object New(const std::initializer_list& args) const; +Napi::Object Napi::Function::New(const std::initializer_list& args) const; ``` - `[in] args`: Initializer list of JavaScript values as `napi_value` representing @@ -122,7 +122,7 @@ Creates a new JavaScript value from one that represents the constructor for the object. ```cpp -Napi::Object New(const std::vector& args) const; +Napi::Object Napi::Function::New(const std::vector& args) const; ``` - `[in] args`: Vector of JavaScript values as `napi_value` representing the @@ -136,7 +136,7 @@ Creates a new JavaScript value from one that represents the constructor for the object. ```cpp -Napi::Object New(size_t argc, const napi_value* args) const; +Napi::Object Napi::Function::New(size_t argc, const napi_value* args) const; ``` - `[in] argc`: The number of the arguments passed to the contructor function. @@ -150,7 +150,7 @@ Returns a new JavaScript object. Calls a Javascript function from a native add-on. ```cpp -Napi::Value Call(const std::initializer_list& args) const; +Napi::Value Napi::Function::Call(const std::initializer_list& args) const; ``` - `[in] args`: Initializer list of JavaScript values as `napi_value` representing @@ -163,7 +163,7 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi Calls a JavaScript function from a native add-on. ```cpp -Napi::Value Call(const std::vector& args) const; +Napi::Value Napi::Function::Call(const std::vector& args) const; ``` - `[in] args`: Vector of JavaScript values as `napi_value` representing the @@ -176,7 +176,7 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi Calls a Javascript function from a native add-on. ```cpp -Napi::Value Call(size_t argc, const napi_value* args) const; +Napi::Value Napi::Function::Call(size_t argc, const napi_value* args) const; ``` - `[in] argc`: The number of the arguments passed to the function. @@ -190,7 +190,7 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi Calls a Javascript function from a native add-on. ```cpp -Napi::Value Call(napi_value recv, const std::initializer_list& args) const; +Napi::Value Napi::Function::Call(napi_value recv, const std::initializer_list& args) const; ``` - `[in] recv`: The `this` object passed to the called function. @@ -204,7 +204,7 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi Calls a Javascript function from a native add-on. ```cpp -Napi::Value Call(napi_value recv, const std::vector& args) const; +Napi::Value Napi::Function::Call(napi_value recv, const std::vector& args) const; ``` - `[in] recv`: The `this` object passed to the called function. @@ -218,7 +218,7 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi Calls a Javascript function from a native add-on. ```cpp -Napi::Value Call(napi_value recv, size_t argc, const napi_value* args) const; +Napi::Value Napi::Function::Call(napi_value recv, size_t argc, const napi_value* args) const; ``` - `[in] recv`: The `this` object passed to the called function. @@ -233,7 +233,7 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi Calls a Javascript function from a native add-on after an asynchronous operation. ```cpp -Napi::Value MakeCallback(napi_value recv, const std::initializer_list& args) const; +Napi::Value Napi::Function::MakeCallback(napi_value recv, const std::initializer_list& args) const; ``` - `[in] recv`: The `this` object passed to the called function. @@ -247,7 +247,7 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi Calls a Javascript function from a native add-on after an asynchronous operation. ```cpp -Napi::Value MakeCallback(napi_value recv, const std::vector& args) const; +Napi::Value Napi::Function::MakeCallback(napi_value recv, const std::vector& args) const; ``` - `[in] recv`: The `this` object passed to the called function. @@ -261,7 +261,7 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi Calls a Javascript function from a native add-on after an asynchronous operation. ```cpp -Napi::Value MakeCallback(napi_value recv, size_t argc, const napi_value* args) const; +Napi::Value Napi::Function::MakeCallback(napi_value recv, size_t argc, const napi_value* args) const; ``` - `[in] recv`: The `this` object passed to the called function. @@ -274,7 +274,7 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi ## Operator ```cpp -Napi::Value operator ()(const std::initializer_list& args) const; +Napi::Value Napi::Function::operator ()(const std::initializer_list& args) const; ``` - `[in] args`: Initializer list of JavaScript values as `napi_value`. diff --git a/doc/function_reference.md b/doc/function_reference.md index 356fe1d93..a18a9b898 100644 --- a/doc/function_reference.md +++ b/doc/function_reference.md @@ -23,7 +23,7 @@ Creates a "weak" reference to the value, in that the initial reference count is set to 0. ```cpp -static FunctionReference Weak(const Function& value); +static Napi::FunctionReference Napi::FunctionReference::Weak(const Napi::Function& value); ``` - `[in] value`: The value which is to be referenced. @@ -36,7 +36,7 @@ Creates a "persistent" reference to the value, in that the initial reference count is set to 1. ```cpp -static FunctionReference Persistent(const Function& value); +static Napi::FunctionReference Napi::FunctionReference::Persistent(const Napi::Function& value); ``` - `[in] value`: The value which is to be referenced. @@ -48,7 +48,7 @@ Returns the newly created reference. Creates a new empty instance of `Napi::FunctionReference`. ```cpp -FunctionReference(); +Napi::FunctionReference::FunctionReference(); ``` ### Constructor @@ -56,7 +56,7 @@ FunctionReference(); Creates a new instance of the `Napi::FunctionReference`. ```cpp -FunctionReference(napi_env env, napi_ref ref); +Napi::FunctionReference::FunctionReference(napi_env env, napi_ref ref); ``` - `[in] env`: The environment in which to construct the `Napi::FunctionReference` object. @@ -69,7 +69,7 @@ Returns a newly created `Napi::FunctionReference` object. Constructs a new instance by calling the constructor held by this reference. ```cpp -Napi::Object New(const std::initializer_list& args) const; +Napi::Object Napi::FunctionReference::New(const std::initializer_list& args) const; ``` - `[in] args`: Initializer list of JavaScript values as `napi_value` representing @@ -82,7 +82,7 @@ Returns a new JavaScript object. Constructs a new instance by calling the constructor held by this reference. ```cpp -Napi::Object New(const std::vector& args) const; +Napi::Object Napi::FunctionReference::New(const std::vector& args) const; ``` - `[in] args`: Vector of JavaScript values as `napi_value` representing the @@ -95,7 +95,7 @@ Returns a new JavaScript object. Calls a referenced Javascript function from a native add-on. ```cpp -Napi::Value Call(const std::initializer_list& args) const; +Napi::Value Napi::FunctionReference::Call(const std::initializer_list& args) const; ``` - `[in] args`: Initializer list of JavaScript values as `napi_value` representing @@ -106,10 +106,10 @@ function. ### Call -Calls a referenced Javascript function from a native add-on. +Calls a referenced JavaScript function from a native add-on. ```cpp -Napi::Value Call(const std::vector& args) const; +Napi::Value Napi::FunctionReference::Call(const std::vector& args) const; ``` - `[in] args`: Vector of JavaScript values as `napi_value` representing the @@ -120,10 +120,10 @@ function. ### Call -Calls a referenced Javascript function from a native add-on. +Calls a referenced JavaScript function from a native add-on. ```cpp -Napi::Value Call(napi_value recv, const std::initializer_list& args) const; +Napi::Value Napi::FunctionReference::Call(napi_value recv, const std::initializer_list& args) const; ``` - `[in] recv`: The `this` object passed to the referenced function when it's called. @@ -135,10 +135,10 @@ function. ### Call -Calls a referenced Javascript function from a native add-on. +Calls a referenced JavaScript function from a native add-on. ```cpp -Napi::Value Call(napi_value recv, const std::vector& args) const; +Napi::Value Napi::FunctionReference::Call(napi_value recv, const std::vector& args) const; ``` - `[in] recv`: The `this` object passed to the referenced function when it's called. @@ -167,11 +167,11 @@ function. ### MakeCallback -Calls a referenced Javascript function from a native add-on after an asynchronous +Calls a referenced JavaScript function from a native add-on after an asynchronous operation. ```cpp -Napi::Value MakeCallback(napi_value recv, const std::initializer_list& args) const; +Napi::Value Napi::FunctionReference::MakeCallback(napi_value recv, const std::initializer_list& args) const; ``` - `[in] recv`: The `this` object passed to the referenced function when it's called. @@ -183,11 +183,11 @@ function. ### MakeCallback -Calls a referenced Javascript function from a native add-on after an asynchronous +Calls a referenced JavaScript function from a native add-on after an asynchronous operation. ```cpp -Napi::Value MakeCallback(napi_value recv, const std::vector& args) const; +Napi::Value Napi::FunctionReference::MakeCallback(napi_value recv, const std::vector& args) const; ``` - `[in] recv`: The `this` object passed to the referenced function when it's called. diff --git a/doc/handle_scope.md b/doc/handle_scope.md index 81a63fa28..9b34fcf2f 100644 --- a/doc/handle_scope.md +++ b/doc/handle_scope.md @@ -6,7 +6,7 @@ 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]. +the section titled [Object lifetime management](object_lifetime_management.md). ## Methods @@ -15,52 +15,51 @@ the section titled (Object lifetime management)[object_lifetime_management]. Creates a new handle scope on the stack. ```cpp -HandleScope(Napi:Env env); +Napi::HandleScope::HandleScope(Napi::Env env); ``` -- `[in] env`: The environment in which to construct the HandleScope object. - -Returns a new HandleScope +- `[in] env`: The environment in which to construct the `Napi::HandleScope` object. +Returns a new `Napi::HandleScope` ### Constructor Creates a new handle scope on the stack. ```cpp -HandleScope(Napi::Env env, Napi::HandleScope scope); +Napi::HandleScope::HandleScope(Napi::Env env, Napi::HandleScope scope); ``` -- `[in] env`: Napi::Env in which the scope passed in was created. -- `[in] scope`: pre-existing Napi::HandleScope. +- `[in] env`: `Napi::Env` in which the scope passed in was created. +- `[in] scope`: pre-existing `Napi::HandleScope`. -Returns a new HandleScope instance which wraps the napi_handle_scope +Returns a new `Napi::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 napi_handle_scope() const +operator Napi::HandleScope::napi_handle_scope() const ``` -Returns the N-API napi_handle_scope wrapped by the EscapableHandleScope object. +Returns the N-API napi_handle_scope wrapped by the `Napi::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(); +Napi::HandleScope::~HandleScope(); ``` -Deletes the HandleScope instance and allows any objects/handles created +Deletes the `Napi::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; +Napi::Env Napi::HandleScope::Env() const; ``` -Returns the Napi:Env associated with the HandleScope. +Returns the `Napi::Env` associated with the `Napi::HandleScope`. diff --git a/doc/memory_management.md b/doc/memory_management.md index 2cabd57ca..afa622550 100644 --- a/doc/memory_management.md +++ b/doc/memory_management.md @@ -1,6 +1,6 @@ # MemoryManagement -The `MemoryManagement` class contains functions that give the JavaScript engine +The `Napi::MemoryManagement` class contains functions that give the JavaScript engine an indication of the amount of externally allocated memory that is kept alive by JavaScript objects. @@ -17,7 +17,7 @@ more often than it would otherwise in an attempt to garbage collect the JavaScri objects that keep the externally allocated memory alive. ```cpp -static int64_t MemoryManagement::AdjustExternalMemory(Env env, int64_t change_in_bytes); +static int64_t Napi::MemoryManagement::AdjustExternalMemory(Napi::Env env, int64_t change_in_bytes); ``` - `[in] env`: The environment in which the API is invoked under. diff --git a/doc/number.md b/doc/number.md index a08b0abc4..d1bee4810 100644 --- a/doc/number.md +++ b/doc/number.md @@ -3,7 +3,6 @@ A Javascript number value. ## Methods - ### Constructor ```cpp @@ -13,21 +12,21 @@ Napi::Number::New(Napi::Env env, double value); - `[in] value`: The value the Javascript Number will contain ```cpp -Napi::Number(); +Napi::Number::Number(); ``` returns a new empty Javascript Number You can easily cast a Javascript number to one of: - - int32_t - - uint32_t - - int64_t - - float - - double + - `int32_t` + - `uint32_t` + - `int64_t` + - `float` + - `double` The following shows an example of casting a number to an uint32_t value. ```cpp -uint32_t operatorVal = Number::New(Env(), 10.0); // Number to unsigned 32 bit integer +uint32_t operatorVal = Napi::Number::New(Env(), 10.0); // Number to unsigned 32 bit integer // or -auto instanceVal = info[0].As().Uint32Value(); +auto instanceVal = info[0].As().Uint32Value(); ``` diff --git a/doc/object.md b/doc/object.md index 38895a10b..3e32fa4e9 100644 --- a/doc/object.md +++ b/doc/object.md @@ -1,12 +1,12 @@ # Object -The Object class corresponds to a JavaScript object. It is extended by the following node-addon-api classes that you may use when working with more specific types: +The `Napi::Object` class corresponds to a JavaScript object. It is extended by the following node-addon-api classes that you may use when working with more specific types: -- [Value](value.md) and extends [Array](array.md) -- [ArrayBuffer](array_buffer.md) -- [Buffer](buffer.md) -- [Function](function.md) -- [TypedArray](typed_array.md). +- [`Napi::Value`](value.md) and extends [`Napi::Array`](array.md) +- [`Napi::ArrayBuffer`](array_buffer.md) +- [`Napi::Buffer`](buffer.md) +- [`Napi::Function`](function.md) +- [`Napi::TypedArray`](typed_array.md). This class provides a number of convenience methods, most of which are used to set or get properties on a JavaScript object. For example, Set() and Get(). @@ -62,19 +62,19 @@ Napi::Object::Object(napi_env env, napi_value value); - const char16_t* (encoded using UTF-16-LE, null-terminated) - std::string (encoded using UTF-8) - std::u16string - - napi::Value + - Napi::Value - napi_value -Creates a non-empty Object instance. +Creates a non-empty `Napi::Object` instance. ### New() ```cpp -Object Napi::Object::New(napi_env env); +Napi::Object Napi::Object::New(napi_env env); ``` -- `[in] env`: The `napi_env` environment in which to construct the Value object. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Value` object. -Creates a new Object value. +Creates a new `Napi::Object` value. ### Set() @@ -88,14 +88,14 @@ Add a property with the specified key with the specified value to the object. The key can be any of the following types: - `napi_value` -- [Value](value.md) +- [`Napi::Value`](value.md) - `const char*` - `const std::string&` - `uint32_t` While the value must be any of the following types: - `napi_value` -- [Value](value.md) +- [`Napi::Value`](value.md) - `const char*` - `std::string&` - `bool` @@ -104,15 +104,15 @@ While the value must be any of the following types: ### Get() ```cpp -Value Napi::Object::Get(____ key); +Napi::Value Napi::Object::Get(____ key); ``` - `[in] key`: The name of the property to return the value for. -Returns the [Value](value.md) associated with the key property. Returns NULL if no such key exists. +Returns the [`Napi::Value`](value.md) associated with the key property. Returns NULL if no such key exists. The `key` can be any of the following types: - `napi_value` -- [Value](value.md) +- [`Napi::Value`](value.md) - `const char *` - `const std::string &` - `uint32_t` @@ -131,18 +131,18 @@ Returns a `bool` that is *true* if the object has a property named `key` and *fa ```cpp bool Napi::Object::InstanceOf (const Function& constructor) const ``` -- `[in] constructor`: The constructor [Function](function.md) of the value that is being compared with the object. +- `[in] constructor`: The constructor [`Napi::Function`](function.md) of the value that is being compared with the object. -Returns a `bool` that is true if the Object is an instance created by the `constructor` and false otherwise. +Returns a `bool` that is true if the `Napi::Object` is an instance created by the `constructor` and false otherwise. Note: This is equivalent to the JavaScript instanceof operator. ### DefineProperty() ```cpp -void Napi::Object::DefineProperty (const PropertyDescriptor& property); +void Napi::Object::DefineProperty (const Napi::PropertyDescriptor& property); ``` -- `[in] property`: A [PropertyDescriptor](propertydescriptor.md). +- `[in] property`: A [`Napi::PropertyDescriptor`](propertydescriptor.md). Define a property on the object. @@ -151,52 +151,52 @@ Define a property on the object. ```cpp void Napi::Object::DefineProperties (____ properties) ``` -- `[in] properties`: A list of [PropertyDescriptor](propertydescriptor.md). Can be one of the following types: - - const std::initializer_list& - - const std::vector& +- `[in] properties`: A list of [`Napi::PropertyDescriptor`](propertydescriptor.md). Can be one of the following types: + - const std::initializer_list& + - const std::vector& Defines properties on the object. ### Operator[]() ```cpp -PropertyLValue Napi::Object::operator[] (const char* utf8name); +Napi::PropertyLValue Napi::Object::operator[] (const char* utf8name); ``` - `[in] utf8name`: UTF-8 encoded null-terminated property name. -Returns a [PropertyLValue](propertylvalue.md) as the named property or sets the named property. +Returns a [`Napi::PropertyLValue`](propertylvalue.md) as the named property or sets the named property. ```cpp -PropertyLValue Napi::Object::operator[] (const std::string& utf8name); +Napi::PropertyLValue Napi::Object::operator[] (const std::string& utf8name); ``` - `[in] utf8name`: UTF-8 encoded property name. -Returns a [PropertyLValue](propertylvalue.md) as the named property or sets the named property. +Returns a [`Napi::PropertyLValue`](propertylvalue.md) as the named property or sets the named property. ```cpp -PropertyLValue Napi::Object::operator[] (uint32_t index); +Napi::PropertyLValue Napi::Object::operator[] (uint32_t index); ``` - `[in] index`: Element index. -Returns a [PropertyLValue](propertylvalue.md) or sets an indexed property or array element. +Returns a [`Napi::PropertyLValue`](propertylvalue.md) or sets an indexed property or array element. ```cpp -Value Napi::Object::operator[] (const char* utf8name) const; +Napi::Value Napi::Object::operator[] (const char* utf8name) const; ``` - `[in] utf8name`: UTF-8 encoded null-terminated property name. -Returns the named property as a [Value](value.md). +Returns the named property as a [`Napi::Value`](value.md). ```cpp -Value Napi::Object::operator[] (const std::string& utf8name) const; +Napi::Value Napi::Object::operator[] (const std::string& utf8name) const; ``` - `[in] utf8name`: UTF-8 encoded property name. -Returns the named property as a [Value](value.md). +Returns the named property as a [`Napi::Value`](value.md). ```cpp -Value Napi::Object::operator[] (uint32_t index) const; +Napi::Value Napi::Object::operator[] (uint32_t index) const; ``` - `[in] index`: Element index. -Returns an indexed property or array element as a [Value](value.md). +Returns an indexed property or array element as a [`Napi::Value`](value.md). diff --git a/doc/object_lifetime_management.md b/doc/object_lifetime_management.md index 5c7a66c28..4ab19ecd1 100644 --- a/doc/object_lifetime_management.md +++ b/doc/object_lifetime_management.md @@ -34,7 +34,7 @@ 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()); + Napi::Value newValue = Napi::String::New(info.Env(), name.c_str()); // do something with newValue }; ``` @@ -47,8 +47,8 @@ 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 +associated with the scope are invalidated. The `Napi::HandleScope` +and `Napi::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 @@ -56,28 +56,28 @@ 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 +`Napi::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 +Taking the earlier example, creating a `Napi::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()); + Napi::HandleScope scope(info.Env()); std::string name = std::string("inner-scope") + std::to_string(i); - Value newValue = String::New(info.Env(), name.c_str()); + Napi::Value newValue = Napi::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 +provides the `Napi::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. +scope to that of the outer scope. The `Escape` method can only be called +once for a given `Napi::EscapableHandleScope`. diff --git a/doc/object_reference.md b/doc/object_reference.md index da826f109..4c20f16f8 100644 --- a/doc/object_reference.md +++ b/doc/object_reference.md @@ -1,8 +1,8 @@ # Object Reference -ObjectReference is a subclass of [Reference](reference.md), and is equivalent to an instance of `Reference`. This means that an ObjectReference holds an [Object](object.md), and a count of the number of references to that Object. When the count is greater than 0, an ObjectReference is not eligible for garbage collection. This ensures that the Object being held as a value of the ObjectReference will remain accessible, even if the original Object no longer is. However, ObjectReference is unique from a Reference since properties can be set and get to the Object itself that can be accessed through the ObjectReference. +`Napi::ObjectReference` is a subclass of [`Napi::Reference`](reference.md), and is equivalent to an instance of `Napi::Reference`. This means that a `Napi::ObjectReference` holds a [`Napi::Object`](object.md), and a count of the number of references to that Object. When the count is greater than 0, an ObjectReference is not eligible for garbage collection. This ensures that the Object being held as a value of the ObjectReference will remain accessible, even if the original Object no longer is. However, ObjectReference is unique from a Reference since properties can be set and get to the Object itself that can be accessed through the ObjectReference. -For more general information on references, please consult [Reference](referenc.md). +For more general information on references, please consult [`Napi::Reference`](referenc.md). ## Example ```cpp @@ -30,17 +30,17 @@ void Init(Env env) { ### Initialization ```cpp -static ObjectReference New(const Object& value, uint32_t initialRefcount = 0); +static Napi::ObjectReference Napi::ObjectReference::New(const Napi::Object& value, uint32_t initialRefcount = 0); ``` -* `[in] value`: The Object which is to be referenced. +* `[in] value`: The `Napi::Object` which is to be referenced. * `[in] initialRefcount`: The initial reference count. Returns the newly created reference. ```cpp -static ObjectReference Weak(const Object& value); +static Napi::ObjectReference Napi::ObjectReference::Weak(const Napi::Object& value); ``` Creates a "weak" reference to the value, in that the initial count of number of references is set to 0. @@ -50,7 +50,7 @@ Creates a "weak" reference to the value, in that the initial count of number of Returns the newly created reference. ```cpp -static ObjectReference Persistent(const Object& value); +static Napi::ObjectReference Napi::ObjectReference::Persistent(const Napi::Object& value); ``` Creates a "persistent" reference to the value, in that the initial count of number of references is set to 1. @@ -62,26 +62,26 @@ Returns the newly created reference. ### Empty Constructor ```cpp -ObjectReference(); +Napi::ObjectReference::ObjectReference(); ``` -Returns a new _empty_ ObjectReference instance. +Returns a new _empty_ `Napi::ObjectReference` instance. ### Constructor ```cpp -ObjectReference(napi_env env, napi_value value); +Napi::ObjectReference::ObjectReference(napi_env env, napi_value value); ``` -* `[in] env`: The `napi_env` environment in which to construct the ObjectReference object. +* `[in] env`: The `napi_env` environment in which to construct the `Napi::ObjectReference` object. -* `[in] value`: The N-API primitive value to be held by the ObjectReference. +* `[in] value`: The N-API primitive value to be held by the `Napi::ObjectReference`. Returns the newly created reference. ### Set ```cpp -void Set(___ key, ___ value); +void Napi::ObjectReference::Set(___ key, ___ value); ``` * `[in] key`: The name for the property being assigned. @@ -103,12 +103,12 @@ The `value` can be any of the following types: ### Get ```cpp -Value Get(___ key); +Napi::Value Napi::ObjectReference::Get(___ key); ``` * `[in] key`: The name of the property to return the value for. -Returns the [Value](value.md) associated with the key property. Returns NULL if no such key exists. +Returns the [`Napi::Value`](value.md) associated with the key property. Returns NULL if no such key exists. The `key` can be any of the following types: - `const char*` diff --git a/doc/promises.md b/doc/promises.md index e67483bbb..62529006b 100644 --- a/doc/promises.md +++ b/doc/promises.md @@ -1,23 +1,19 @@ -You are reading a draft of the next documentation and it's in continuous 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/) - # Promise -The Promise class, along with its Promise::Deferred class, implement the ability to create, resolve, and reject Promise objects. +The `Napi::Promise` class, along with its `Napi::Promise::Deferred` class, implement the ability to create, resolve, and reject Promise objects. -The basic approach is to create a Promise::Deferred object and return to your caller the value returned by the Promise::Deferred::Promise method. For example: +The basic approach is to create a `Napi::Promise::Deferred` object and return to your caller the value returned by the `Napi::Promise::Deferred::Promise` method. For example: ```cpp -Value YourFunction(const CallbackInfo& info) { +Napi::Value YourFunction(const Napi::CallbackInfo& info) { // your code goes here... - Promise::Deferred deferred = Promise::Deferred::New(info.Env()); + Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env()); // deferred needs to survive this call... return deferred.Promise(); } ``` -Later, when the asynchronous process completes, call either the `Resolve` or `Reject` method on the Promise::Deferred object created earlier: +Later, when the asynchronous process completes, call either the `Resolve` or `Reject` method on the `Napi::Promise::Deferred` object created earlier: ```cpp deferred.Resolve(String::New(info.Env(), "OK")); @@ -28,51 +24,51 @@ Later, when the asynchronous process completes, call either the `Resolve` or `Re ### Factory Method ```cpp -static Promise::Deferred Promise::Deferred::New(napi_env env); +static Napi::Promise::Deferred Napi::Promise::Deferred::New(napi_env env); ``` -* `[in] env`: The `napi_env` environment in which to create the Deferred object. +* `[in] env`: The `napi_env` environment in which to create the `Napi::Promise::Deferred` object. ### Constructor ```cpp -Promise::Deferred(napi_env env); +Napi::Promise::Deferred(napi_env env); ``` -* `[in] env`: The `napi_env` environment in which to construct the Deferred object. +* `[in] env`: The `napi_env` environment in which to construct the `Napi::Promise::Deferred` object. ### Env ```cpp -Napi::Env Env() const; +Napi::Env Napi::Promise::Deferred::Env() const; ``` -Returns the Env environment this Promise::Deferred object is associated with. +Returns the Env environment this `Napi::Promise::Deferred` object is associated with. ### Promise ```cpp -Promise Promise::Deferred::Promise() const; +Napi::Promise Napi::Promise::Deferred::Promise() const; ``` -Returns the Promise object held by the Promise::Deferred object. +Returns the `Napi::Promise` object held by the `Napi::Promise::Deferred` object. ### Resolve ```cpp -void Promise::Deferred::Resolve(napi_value value) const; +void Napi::Promise::Deferred::Resolve(napi_value value) const; ``` -Resolves the Promise object held by the Promise::Deferred object. +Resolves the `Napi::Promise` object held by the `Napi::Promise::Deferred` object. -* `[in] value`: The N-API primitive value with which to resolve the Promise. +* `[in] value`: The N-API primitive value with which to resolve the `Napi::Promise`. ### Reject ```cpp -void Promise::Deferred::Reject(napi_value value) const; +void Napi::Promise::Deferred::Reject(napi_value value) const; ``` -Rejects the Promise object held by the Promise::Deferred object. +Rejects the Promise object held by the `Napi::Promise::Deferred` object. -* `[in] value`: The N-API primitive value with which to reject the Promise. +* `[in] value`: The N-API primitive value with which to reject the `Napi::Promise`. diff --git a/doc/property_descriptor.md b/doc/property_descriptor.md index 02c022fcb..2c0fa6fac 100644 --- a/doc/property_descriptor.md +++ b/doc/property_descriptor.md @@ -1,6 +1,6 @@ # Property Descriptor -An [Object](object.md) can be assigned properites via its [DefineProperty](object.md#defineproperty) and [DefineProperties](object.md#defineproperties) function, which take PropertyDescrptor(s) as their parameters. The PropertyDescriptor can contain either values or functions, which are then assigned to the Object. Note that a single instance of a PropertyDescriptor class can only contain either one value, or at most two functions. PropertyDescriptors can only be created through the class methods [Accessor](#accessor), [Function](#function), or [Value](#value), each of which return a new static instance of a PropertyDescriptor. +A [`Napi::Object`](object.md) can be assigned properites via its [`DefineProperty`](object.md#defineproperty) and [`DefineProperties`](object.md#defineproperties) functions, which take PropertyDescrptor(s) as their parameters. The `Napi::PropertyDescriptor` can contain either values or functions, which are then assigned to the `Napi::Object`. Note that a single instance of a `Napi::PropertyDescriptor` class can only contain either one value, or at most two functions. PropertyDescriptors can only be created through the class methods [`Accessor`](#accessor), [`Function`](#function), or [`Value`](#value), each of which return a new static instance of a `Napi::PropertyDescriptor`. ## Example @@ -52,7 +52,7 @@ Napi::PropertyDescriptor::PropertyDescriptor (napi_property_descriptor desc); ### Accessor ```cpp -static PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, Getter getter, napi_property_attributes attributes = napi_default, void *data = nullptr); @@ -69,10 +69,10 @@ The name of the property can be any of the following types: - `const char*` - `const std::string &` - `napi_value value` -- `Name` +- `Napi::Name` ```cpp -static PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, Getter getter, Setter setter, napi_property_attributes attributes = napi_default, @@ -85,18 +85,18 @@ static PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, * `[in] attributes`: Potential attributes for the getter function. * `[in] data`: A pointer to data of any type, default is a null pointer. -Returns a PropertyDescriptor that contains a Getter and Setter function. +Returns a `Napi::PropertyDescriptor` that contains a `Getter` and `Setter` function. The name of the property can be any of the following types: - `const char*` - `const std::string &` - `napi_value value` -- `Name` +- `Napi::Name` ### Function ```cpp -static PropertyDescriptor Napi::PropertyDescriptor::Function (___ name, +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Function (___ name, Callable cb, napi_property_attributes attributes = napi_default, void *data = nullptr); @@ -107,18 +107,18 @@ static PropertyDescriptor Napi::PropertyDescriptor::Function (___ name, * `[in] attributes`: Potential attributes for the getter function. * `[in] data`: A pointer to data of any type, default is a null pointer. -Returns a PropertyDescriptor that contains a callable Function. +Returns a `Napi::PropertyDescriptor` that contains a callable `Napi::Function`. The name of the property can be any of the following types: - `const char*` - `const std::string &` - `napi_value value` -- `Name` +- `Napi::Name` ### Value ```cpp -static PropertyDescriptor Napi::PropertyDescriptor::Value (___ name, +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Value (___ name, napi_value value, napi_property_attributes attributes = napi_default); ``` @@ -127,7 +127,7 @@ The name of the property can be any of the following types: - `const char*` - `const std::string &` - `napi_value value` -- `Name` +- `Napi::Name` ## Related Information diff --git a/doc/range_error.md b/doc/range_error.md index e0bd14d30..e134a4098 100644 --- a/doc/range_error.md +++ b/doc/range_error.md @@ -1,11 +1,11 @@ # RangeError -The **RangeError** class is a representation of the JavaScript RangeError that is +The `Napi::RangeError` class is a representation of the JavaScript `RangeError` that is thrown when trying to pass a value as an argument to a function that does not allow a range that includes the value. -The **RangeError** class inherits its behaviors from the **Error** class (for -more info see: [Error](error.md)). +The `Napi::RangeError` class inherits its behaviors from the `Napi::Error` class (for +more info see: [`Napi::Error`](error.md)). For more details about error handling refer to the section titled [Error handling](error_handling.md). @@ -13,47 +13,47 @@ For more details about error handling refer to the section titled [Error handlin ### New -Creates a new instance of a `RangeError` object. +Creates a new instance of a `Napi::RangeError` object. ```cpp -RangeError::New(Napi:Env env, const char* message); +Napi::RangeError::New(Napi::Env env, const char* message); ``` -- `[in] Env`: The environment in which to construct the `RangeError` object. -- `[in] message`: Null-terminated string to be used as the message for the `RangeError`. +- `[in] Env`: The environment in which to construct the `Napi::RangeError` object. +- `[in] message`: Null-terminated string to be used as the message for the `Napi::RangeError`. -Returns an instance of a `RangeError` object. +Returns an instance of a `Napi::RangeError` object. ### New -Creates a new instance of a `RangeError` object. +Creates a new instance of a `Napi::RangeError` object. ```cpp -RangeError::New(Napi:Env env, const std::string& message); +Napi::RangeError::New(Napi::Env env, const std::string& message); ``` -- `[in] Env`: The environment in which to construct the `RangeError` object. -- `[in] message`: Reference string to be used as the message for the `RangeError`. +- `[in] Env`: The environment in which to construct the `Napi::RangeError` object. +- `[in] message`: Reference string to be used as the message for the `Napi::RangeError`. -Returns an instance of a `RangeError` object. +Returns an instance of a `Napi::RangeError` object. ### Constructor -Creates a new empty instance of a `RangeError`. +Creates a new empty instance of a `Napi::RangeError`. ```cpp -RangeError(); +Napi::RangeError::RangeError(); ``` ### Constructor -Initializes a `RangeError` instance from an existing Javascript error object. +Initializes a `Napi::RangeError` instance from an existing Javascript error object. ```cpp -RangeError(napi_env env, napi_value value); +Napi::RangeError::RangeError(napi_env env, napi_value value); ``` -- `[in] Env`: The environment in which to construct the `RangeError` object. -- `[in] value`: The `Error` reference to wrap. +- `[in] Env`: The environment in which to construct the `Napi::RangeError` object. +- `[in] value`: The `Napi::Error` reference to wrap. -Returns an instance of a `RangeError` object. \ No newline at end of file +Returns an instance of a `Napi::RangeError` object. diff --git a/doc/reference.md b/doc/reference.md index c25f98d8e..108c009bb 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -1,27 +1,23 @@ -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/) - # Reference (template) -Holds a counted reference to a [Value](value.md) object; initially a weak reference unless otherwise specified, may be changed to/from a strong reference by adjusting the refcount. +Holds a counted reference to a [`Napi::Value`](value.md) object; initially a weak reference unless otherwise specified, may be changed to/from a strong reference by adjusting the refcount. -The referenced Value is not immediately destroyed when the reference count is zero; it is merely then eligible for garbage-collection if there are no other references to the Value. +The referenced `Napi::Value` is not immediately destroyed when the reference count is zero; it is merely then eligible for garbage-collection if there are no other references to the `Napi::Value`. -Reference objects allocated in static space, such as a global static instance, must call the `SuppressDestruct` method to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. +`Napi::Reference` objects allocated in static space, such as a global static instance, must call the `SuppressDestruct` method to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. -The following classes inherit, either directly or indirectly, from Reference: +The following classes inherit, either directly or indirectly, from `Napi::Reference`: -* [ObjectWrap](object_wrap.md) -* [ObjectReference](object_reference.md) -* [FunctionReference](function_reference.md) +* [`Napi::ObjectWrap`](object_wrap.md) +* [`Napi::ObjectReference`](object_reference.md) +* [`Napi::FunctionReference`](function_reference.md) ## Methods ### Factory Method ```cpp -static Reference New(const T& value, uint32_t initialRefcount = 0); +static Napi::Reference Napi::Reference::New(const T& value, uint32_t initialRefcount = 0); ``` * `[in] value`: The value which is to be referenced. @@ -31,85 +27,85 @@ static Reference New(const T& value, uint32_t initialRefcount = 0); ### Empty Constructor ```cpp -Reference(); +Napi::Reference::Reference(); ``` -Creates a new _empty_ Reference instance. +Creates a new _empty_ `Napi::Reference` instance. ### Constructor ```cpp -Reference(napi_env env, napi_value value); +Napi::Reference::Reference(napi_env env, napi_value value); ``` -* `[in] env`: The `napi_env` environment in which to construct the Reference object. +* `[in] env`: The `napi_env` environment in which to construct the `Napi::Reference` object. -* `[in] value`: The N-API primitive value to be held by the Reference. +* `[in] value`: The N-API primitive value to be held by the `Napi::Reference`. ### Env ```cpp -Napi::Env Env() const; +Napi::Env Napi::Reference::Env() const; ``` -Returns the `Env` value in which the Reference was instantiated. +Returns the `Napi::Env` value in which the `Napi::Reference` was instantiated. ### IsEmpty ```cpp -bool IsEmpty() const; +bool Napi::Reference::IsEmpty() const; ``` -Determines whether the value held by the Reference is empty. +Determines whether the value held by the `Napi::Reference` is empty. ### Value ```cpp -T Value() const; +T Napi::Reference::Value() const; ``` -Returns the value held by the Reference. +Returns the value held by the `Napi::Reference`. ### Ref ```cpp -uint32_t Ref(); +uint32_t Napi::Reference::Ref(); ``` -Increments the reference count for the Reference and returns the resulting reference count. Throws an error if the increment fails. +Increments the reference count for the `Napi::Reference` and returns the resulting reference count. Throws an error if the increment fails. ### Unref ```cpp -uint32_t Unref(); +uint32_t Napi::Reference::Unref(); ``` -Decrements the reference count for the Reference and returns the resulting reference count. Throws an error if the decrement fails. +Decrements the reference count for the `Napi::Reference` and returns the resulting reference count. Throws an error if the decrement fails. ### Reset (Empty) ```cpp -void Reset(); +void Napi::Reference::Reset(); ``` -Sets the value held by the Reference to be empty. +Sets the value held by the `Napi::Reference` to be empty. ### Reset ```cpp -void Reset(const T& value, uint32_t refcount = 0); +void Napi::Reference::Reset(const T& value, uint32_t refcount = 0); ``` * `[in] value`: The value which is to be referenced. * `[in] initialRefcount`: The initial reference count. -Sets the value held by the Reference. +Sets the value held by the `Napi::Reference`. ### SuppressDestruct ```cpp -void SuppressDestruct(); +void Napi::Reference::SuppressDestruct(); ``` -Call this method on a Reference that is declared as static data to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. +Call this method on a `Napi::Reference` that is declared as static data to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. diff --git a/doc/setup.md b/doc/setup.md index 176815a96..542729a69 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -19,7 +19,7 @@ To use **N-API** in a native module: ```json "dependencies": { - "node-addon-api": "1.2.0", + "node-addon-api": "*", } ``` diff --git a/doc/string.md b/doc/string.md index faa308a2d..21ce8f8cb 100644 --- a/doc/string.md +++ b/doc/string.md @@ -3,17 +3,17 @@ ## Constructor ```cpp -String(); +Napi::String::String(); ``` -Returns a new **empty** String instance. +Returns a new **empty** `Napi::String` instance. If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not being used, callers should check the result of `Env::IsExceptionPending` before attempting to use the returned value. ```cpp -String(napi_env env, napi_value value); ///< Wraps a N-API value primitive. +Napi::String::String(napi_env env, napi_value value); ///< Wraps a N-API value primitive. ``` - `[in] env` - The environment in which to create the string. - `[in] value` - The primitive to wrap. @@ -29,14 +29,14 @@ attempting to use the returned value. ### operator std::string ```cpp -operator std::string() const; +Napi::String::operator std::string() const; ``` Returns a UTF-8 encoded C++ string. ### operator std::u16string ```cpp -operator std::u16string() const; +Napi::String::operator std::u16string() const; ``` Returns a UTF-16 encoded C++ string. @@ -45,21 +45,21 @@ Returns a UTF-16 encoded C++ string. ### New ```cpp -String::New(); +Napi::String::New(); ``` -Returns a new empty String +Returns a new empty `Napi::String`. ### New ```cpp -String::New(napi_env env, const std::string& value); -String::New(napi_env env, const std::u16string& value); -String::New(napi_env env, const char* value); -String::New(napi_env env, const char16_t* value); +Napi::String::New(napi_env env, const std::string& value); +Napi::String::New(napi_env env, const std::u16::string& value); +Napi::String::New(napi_env env, const char* value); +Napi::String::New(napi_env env, const char16_t* value); ``` -- `[in] env`: The `napi_env` environment in which to construct the Value object. -- `[in] value`: The C++ primitive from which to instantiate the Value. `value` may be any of: +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Value` object. +- `[in] value`: The C++ primitive from which to instantiate the `Napi::Value`. `value` may be any of: - `std::string&` - represents an ANSI string. - `std::u16string&` - represents a UTF16-LE string. - `const char*` - represents a UTF8 string. @@ -73,14 +73,14 @@ attempting to use the returned value. ### Utf8Value ```cpp -std::string Utf8Value() const; +std::string Napi::String::Utf8Value() const; ``` Returns a UTF-8 encoded C++ string. ### Utf16Value ```cpp -std::u16string Utf16Value() const; +std::u16string Napi::String::Utf16Value() const; ``` Returns a UTF-16 encoded C++ string. diff --git a/doc/symbol.md b/doc/symbol.md index fe7ee9c03..13abe3e20 100644 --- a/doc/symbol.md +++ b/doc/symbol.md @@ -4,24 +4,24 @@ ### Constructor -Instantiates a new `Symbol` value +Instantiates a new `Napi::Symbol` value. ```cpp -Symbol(); +Napi::Symbol::Symbol(); ``` -Returns a new empty Symbol. +Returns a new empty `Napi::Symbol`. ### New ```cpp -Symbol::New(napi_env env, const std::string& description); -Symbol::New(napi_env env, const char* description); -Symbol::New(napi_env env, String description); -Symbol::New(napi_env env, napi_value description); +Napi::Symbol::New(napi_env env, const std::string& description); +Napi::Symbol::New(napi_env env, const char* description); +Napi::Symbol::New(napi_env env, Napi::String description); +Napi::Symbol::New(napi_env env, napi_value description); ``` -- `[in] env`: The `napi_env` environment in which to construct the Symbol object. -- `[in] value`: The C++ primitive which represents the description hint for the Symbol. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Symbol` object. +- `[in] value`: The C++ primitive which represents the description hint for the `Napi::Symbol`. `description` may be any of: - `std::string&` - ANSI string description. - `const char*` - represents a UTF8 string description. @@ -29,16 +29,16 @@ Symbol::New(napi_env env, napi_value description); - `napi_value` - N-API `napi_value` description. If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not -being used, callers should check the result of `Env::IsExceptionPending` before +being used, callers should check the result of `Napi::Env::IsExceptionPending` before attempting to use the returned value. ### Utf8Value ```cpp -static Symbol WellKnown(napi_env env, const std::string& name); +static Napi::Symbol Napi::Symbol::WellKnown(napi_env env, const std::string& name); ``` -- `[in] env`: The `napi_env` environment in which to construct the Symbol object. -- `[in] name`: The C++ string representing the `Symbol` to retrieve. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Symbol` object. +- `[in] name`: The C++ string representing the `Napi::Symbol` to retrieve. -Returns a `Napi::Symbol` representing a well-known Symbol from the -Symbol registry. +Returns a `Napi::Symbol` representing a well-known `Symbol` from the +`Symbol` registry. diff --git a/doc/type_error.md b/doc/type_error.md index 7cfb9d1bf..24bbf8eda 100644 --- a/doc/type_error.md +++ b/doc/type_error.md @@ -1,11 +1,11 @@ # TypeError -The **TypeError** class is a representation of the JavaScript `TypeError` that is +The `Napi::TypeError` class is a representation of the JavaScript `TypeError` that is thrown when an operand or argument passed to a function is incompatible with the type expected by the operator or function. -The **TypeError** class inherits its behaviors from the **Error** class (for more info -see: [Error](error.md)). +The `Napi::TypeError` class inherits its behaviors from the `Napi::Error` class (for more info +see: [`Napi::Error`](error.md)). For more details about error handling refer to the section titled [Error handling](error_handling.md). @@ -13,47 +13,47 @@ For more details about error handling refer to the section titled [Error handlin ### New -Creates a new instance of the `TypeError` object. +Creates a new instance of the `Napi::TypeError` object. ```cpp -TypeError::New(Napi:Env env, const char* message); +Napi::TypeError::New(Napi:Env env, const char* message); ``` -- `[in] Env`: The environment in which to construct the `TypeError` object. -- `[in] message`: Null-terminated string to be used as the message for the `TypeError`. +- `[in] Env`: The environment in which to construct the `Napi::TypeError` object. +- `[in] message`: Null-terminated string to be used as the message for the `Napi::TypeError`. -Returns an instance of a `TypeError` object. +Returns an instance of a `Napi::TypeError` object. ### New -Creates a new instance of a `TypeError` object. +Creates a new instance of a `Napi::TypeError` object. ```cpp -TypeError::New(Napi:Env env, const std::string& message); +Napi::TypeError::New(Napi:Env env, const std::string& message); ``` -- `[in] Env`: The environment in which to construct the `TypeError` object. -- `[in] message`: Reference string to be used as the message for the `TypeError`. +- `[in] Env`: The environment in which to construct the `Napi::TypeError` object. +- `[in] message`: Reference string to be used as the message for the `Napi::TypeError`. -Returns an instance of a `TypeError` object. +Returns an instance of a `Napi::TypeError` object. ### Constructor -Creates a new empty instance of a `TypeError`. +Creates a new empty instance of a `Napi::TypeError`. ```cpp -TypeError(); +Napi::TypeError::TypeError(); ``` ### Constructor -Initializes a ```TypeError``` instance from an existing JavaScript error object. +Initializes a `Napi::TypeError` instance from an existing JavaScript error object. ```cpp -TypeError(napi_env env, napi_value value); +Napi::TypeError::TypeError(napi_env env, napi_value value); ``` -- `[in] Env`: The environment in which to construct the `TypeError` object. -- `[in] value`: The `Error` reference to wrap. +- `[in] Env`: The environment in which to construct the `Napi::TypeError` object. +- `[in] value`: The `Napi::Error` reference to wrap. -Returns an instance of a `TypeError` object. \ No newline at end of file +Returns an instance of a `Napi::TypeError` object. diff --git a/doc/typed_array.md b/doc/typed_array.md index f96d36499..ced67d8e4 100644 --- a/doc/typed_array.md +++ b/doc/typed_array.md @@ -1,6 +1,6 @@ # TypedArray -The `TypedArray` class corresponds to the +The `Napi::TypedArray` class corresponds to the [JavaScript `TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) class. @@ -8,27 +8,27 @@ class. ### Constructor -Initializes an empty instance of the `TypedArray` class. +Initializes an empty instance of the `Napi::TypedArray` class. ```cpp -TypedArray(); +Napi::TypedArray::TypedArray(); ``` ### Constructor -Initializes a wrapper instance of an existing `TypedArray` instance. +Initializes a wrapper instance of an existing `Napi::TypedArray` instance. ```cpp -TypedArray(napi_env env, napi_value value); +Napi::TypedArray::TypedArray(napi_env env, napi_value value); ``` -- `[in] env`: The environment in which to create the `TypedArray` instance. -- `[in] value`: The `TypedArray` reference to wrap. +- `[in] env`: The environment in which to create the `Napi::TypedArray` instance. +- `[in] value`: The `Napi::TypedArray` reference to wrap. ### TypedArrayType ```cpp -napi_typedarray_type TypedArrayType() const; +napi_typedarray_type Napi::TypedArray::TypedArrayType() const; ``` Returns the type of this instance. @@ -36,7 +36,7 @@ Returns the type of this instance. ### ArrayBuffer ```cpp -Napi::ArrayBuffer ArrayBuffer() const; +Napi::ArrayBuffer Napi::TypedArray::ArrayBuffer() const; ``` Returns the backing array buffer. @@ -44,7 +44,7 @@ Returns the backing array buffer. ### ElementSize ```cpp -uint8_t ElementSize() const; +uint8_t Napi::TypedArray::ElementSize() const; ``` Returns the size of one element, in bytes. @@ -52,7 +52,7 @@ Returns the size of one element, in bytes. ### ElementLength ```cpp -size_t ElementLength() const; +size_t Napi::TypedArray::ElementLength() const; ``` Returns the number of elements. @@ -60,15 +60,15 @@ Returns the number of elements. ### ByteOffset ```cpp -size_t ByteOffset() const; +size_t Napi::TypedArray::ByteOffset() const; ``` -Returns the offset into the `ArrayBuffer` where the array starts, in bytes. +Returns the offset into the `Napi::ArrayBuffer` where the array starts, in bytes. ### ByteLength ```cpp -size_t ByteLength() const; +size_t Napi::TypedArray::ByteLength() const; ``` Returns the length of the array, in bytes. diff --git a/doc/typed_array_of.md b/doc/typed_array_of.md index 868e5ec44..fc30218c1 100644 --- a/doc/typed_array_of.md +++ b/doc/typed_array_of.md @@ -1,6 +1,6 @@ # TypedArrayOf -The `TypedArrayOf` class corresponds to the various +The `Napi::TypedArrayOf` class corresponds to the various [JavaScript `TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) classes. @@ -9,14 +9,14 @@ classes. The common JavaScript `TypedArray` types are pre-defined for each of use: ```cpp -typedef TypedArrayOf Int8Array; -typedef TypedArrayOf Uint8Array; -typedef TypedArrayOf Int16Array; -typedef TypedArrayOf Uint16Array; -typedef TypedArrayOf Int32Array; -typedef TypedArrayOf Uint32Array; -typedef TypedArrayOf Float32Array; -typedef TypedArrayOf Float64Array; +typedef Napi::TypedArrayOf Int8Array; +typedef Napi::TypedArrayOf Uint8Array; +typedef Napi::TypedArrayOf Int16Array; +typedef Napi::TypedArrayOf Uint16Array; +typedef Napi::TypedArrayOf Int32Array; +typedef Napi::TypedArrayOf Uint32Array; +typedef Napi::TypedArrayOf Float32Array; +typedef Napi::TypedArrayOf Float64Array; ``` The one exception is the `Uint8ClampedArray` which requires explicit @@ -33,71 +33,71 @@ behavior is only applied in JavaScript. ### New -Allocates a new `TypedArray` instance with a given length. The underlying -`ArrayBuffer` is allocated automatically to the desired number of elements. +Allocates a new `Napi::TypedArray` instance with a given length. The underlying +`Napi::ArrayBuffer` is allocated automatically to the desired number of elements. The array type parameter can normally be omitted (because it is inferred from the template parameter T), except when creating a "clamped" array. ```cpp -static TypedArrayOf New(napi_env env, +static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env, size_t elementLength, napi_typedarray_type type); ``` -- `[in] env`: The environment in which to create the `TypedArrayOf` instance. +- `[in] env`: The environment in which to create the `Napi::TypedArrayOf` instance. - `[in] elementLength`: The length to be allocated, in elements. - `[in] type`: The type of array to allocate (optional). -Returns a new `TypedArrayOf` instance. +Returns a new `Napi::TypedArrayOf` instance. ### New -Wraps the provided `ArrayBuffer` into a new `TypedArray` instance. +Wraps the provided `Napi::ArrayBuffer` into a new `Napi::TypedArray` instance. The array `type` parameter can normally be omitted (because it is inferred from the template parameter `T`), except when creating a "clamped" array. ```cpp -static TypedArrayOf New(napi_env env, +static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env, size_t elementLength, Napi::ArrayBuffer arrayBuffer, size_t bufferOffset, napi_typedarray_type type); ``` -- `[in] env`: The environment in which to create the `TypedArrayOf` instance. +- `[in] env`: The environment in which to create the `Napi::TypedArrayOf` instance. - `[in] elementLength`: The length to array, in elements. -- `[in] arrayBuffer`: The backing `ArrayBuffer` instance. -- `[in] bufferOffset`: The offset into the `ArrayBuffer` where the array starts, +- `[in] arrayBuffer`: The backing `Napi::ArrayBuffer` instance. +- `[in] bufferOffset`: The offset into the `Napi::ArrayBuffer` where the array starts, in bytes. - `[in] type`: The type of array to allocate (optional). -Returns a new `TypedArrayOf` instance. +Returns a new `Napi::TypedArrayOf` instance. ### Constructor -Initializes an empty instance of the `TypedArrayOf` class. +Initializes an empty instance of the `Napi::TypedArrayOf` class. ```cpp -TypedArrayOf(); +Napi::TypedArrayOf::TypedArrayOf(); ``` ### Constructor -Initializes a wrapper instance of an existing `TypedArrayOf` object. +Initializes a wrapper instance of an existing `Napi::TypedArrayOf` object. ```cpp -TypedArrayOf(napi_env env, napi_value value); +Napi::TypedArrayOf::TypedArrayOf(napi_env env, napi_value value); ``` -- `[in] env`: The environment in which to create the `TypedArrayOf` object. -- `[in] value`: The `TypedArrayOf` reference to wrap. +- `[in] env`: The environment in which to create the `Napi::TypedArrayOf` object. +- `[in] value`: The `Napi::TypedArrayOf` reference to wrap. ### operator [] ```cpp -T& operator [](size_t index); +T& Napi::TypedArrayOf::operator [](size_t index); ``` - `[in] index: The element index into the array. @@ -107,7 +107,7 @@ Returns the element found at the given index. ### operator [] ```cpp -const T& operator [](size_t index) const; +const T& Napi::TypedArrayOf::operator [](size_t index) const; ``` - `[in] index: The element index into the array. @@ -117,17 +117,17 @@ Returns the element found at the given index. ### Data ```cpp -T* Data() const; +T* Napi::TypedArrayOf::Data() const; ``` -Returns a pointer into the backing `ArrayBuffer` which is offset to point to the +Returns a pointer into the backing `Napi::ArrayBuffer` which is offset to point to the start of the array. ### Data ```cpp -const T* Data() const +const T* Napi::TypedArrayOf::Data() const ``` -Returns a pointer into the backing `ArrayBuffer` which is offset to point to the +Returns a pointer into the backing `Napi::ArrayBuffer` which is offset to point to the start of the array. diff --git a/doc/value.md b/doc/value.md index 5e79d352e..e9f9f8a7f 100644 --- a/doc/value.md +++ b/doc/value.md @@ -1,67 +1,65 @@ -**WORK IN PROGRESS, NOT YET COMPLETE** - # Value -Value is the C++ manifestation of a JavaScript value. +`Napi::Value` is the C++ manifestation of a JavaScript value. Value is a the base class upon which other JavaScript values such as Number, Boolean, String, and Object are based. -The following classes inherit, either directly or indirectly, from Value: - -- [Array](array.md) -- [ArrayBuffer](array_buffer.md) -- [Boolean](boolean.md) -- [Buffer](buffer.md) -- [External](external.md) -- [Function](function.md) -- [Name](name.md) -- [Number](number.md) -- [Object](object.md) -- [String](string.md) -- [Symbol](symbol.md) -- [TypedArray](typed_array.md) -- [TypedArrayOf](typed_array_of.md) +The following classes inherit, either directly or indirectly, from `Napi::Value`: + +- [`Napi::Array`](array.md) +- [`Napi::ArrayBuffer`](array_buffer.md) +- [`Napi::Boolean`](boolean.md) +- [`Napi::Buffer`](buffer.md) +- [`Napi::External`](external.md) +- [`Napi::Function`](function.md) +- [`Napi::Name`](name.md) +- [`Napi::Number`](number.md) +- [`Napi::Object`](object.md) +- [`Napi::String`](string.md) +- [`Napi::Symbol`](symbol.md) +- [`Napi::TypedArray`](typed_array.md) +- [`Napi::TypedArrayOf`](typed_array_of.md) ## Methods ### Empty Constructor ```cpp -Value(); +Napi::Value::Value(); ``` -Creates a new *empty* Value instance. +Creates a new *empty* `Napi::Value` instance. ### Constructor ```cpp -Value(napi_env env, napi_value value); +Napi::Value::Value(napi_env env, napi_value value); ``` -- `[in] env`: The `napi_env` environment in which to construct the Value object. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Value` object. -- `[in] value`: The C++ primitive from which to instantiate the Value. `value` may be any of: - - bool +- `[in] value`: The C++ primitive from which to instantiate the `Napi::Value`. `value` may be any of: + - `bool` - Any integer type - Any floating point type - - const char* (encoded using UTF-8, null-terminated) - - const char16_t* (encoded using UTF-16-LE, null-terminated) - - std::string (encoded using UTF-8) - - std::u16string - - napi::Value - - napi_value + - `const char*` (encoded using UTF-8, null-terminated) + - `const char16_t*` (encoded using UTF-16-LE, null-terminated) + - `std::string` (encoded using UTF-8) + - `std::u16string` + - `Napi::Value` + - `napi_value` ### From ```cpp -template static Value From(napi_env env, const T& value); +template static Napi::Value Napi::Value::From(napi_env env, const T& value); ``` -- `[in] env`: The `napi_env` environment in which to create the Value object. +- `[in] env`: The `napi_env` environment in which to create the `Napi::Value` object. -- `[in] value`: The N-API primitive value from which to create the Value object. +- `[in] value`: The N-API primitive value from which to create the `Napi::Value` object. -Returns a Value object from an N-API primitive value. +Returns a `Napi::Value` object from an N-API primitive value. ### operator napi_value @@ -71,165 +69,167 @@ operator napi_value() const; Returns this Value's N-API value primitive. -Returns `nullptr` if this Value is *empty*. +Returns `nullptr` if this `Napi::Value` is *empty*. ### operator == ```cpp -bool operator ==(const Value& other) const; + +bool Napi::Value::operator ==(const Napi::Value& other) const; ``` -- `[in] other`: The Value object to be compared. +- `[in] other`: The `Napi::Value` object to be compared. -Returns a `bool` indicating if this Value strictly equals another Value. +Returns a `bool` indicating if this `Napi::Value` strictly equals another `Napi::Value`. ### operator != ```cpp -bool operator !=(const Value& other) const; +bool Napi::Value::operator !=(const Napi::Value& other) const; ``` -- `[in] other`: The Value object to be compared. +- `[in] other`: The `Napi::Value` object to be compared. -Returns a `bool` indicating if this Value does not strictly equal another Value. +Returns a `bool` indicating if this `Napi::Value` does not strictly equal another `Napi::Value`. ### StrictEquals ```cpp -bool StrictEquals(const Value& other) const; +bool Napi::Value::StrictEquals(const Napi::Value& other) const; ``` -- `[in] other`: The Value object to be compared. +- `[in] other`: The `Napi::Value` object to be compared. -Returns a `bool` indicating if this Value strictly equals another Value. +Returns a `bool` indicating if this `Napi::Value` strictly equals another `Napi::Value`. ### Env ```cpp -Napi::Env Env() const; +Napi::Env Napi::Value::Env() const; ``` -Returns the `Env` environment this value is associated with. +Returns the `Napi::Env` environment this value is associated with. ### IsEmpty ```cpp -bool IsEmpty() const; +bool Napi::Value::IsEmpty() const; ``` -Returns a `bool` indicating if this Value is *empty* (uninitialized). +Returns a `bool` indicating if this `Napi::Value` is *empty* (uninitialized). -An empty Value is invalid, and most attempts to perform an operation on an empty Value will result in an exception. Note an empty Value is distinct from JavaScript `null` or `undefined`, which are valid values. +An empty `Napi::Value` is invalid, and most attempts to perform an operation on an empty Value will result in an exception. +Note an empty `Napi::Value` is distinct from JavaScript `null` or `undefined`, which are valid values. -When C++ exceptions are disabled at compile time, a method with a `Value` return type may return an empty Value to indicate a pending exception. So when not using C++ exceptions, callers should check whether this Value is empty before attempting to use it. +When C++ exceptions are disabled at compile time, a method with a `Napi::Value` return type may return an empty Value to indicate a pending exception. So when not using C++ exceptions, callers should check whether this `Napi::Value` is empty before attempting to use it. ### Type ```cpp -napi_valuetype Type() const; +napi_valuetype Napi::Value::Type() const; ``` -Returns the `napi_valuetype` type of the Value. +Returns the `napi_valuetype` type of the `Napi::Value`. ### IsUndefined ```cpp -bool IsUndefined() const; +bool Napi::Value::IsUndefined() const; ``` -Returns a `bool` indicating if this Value is an undefined JavaScript value. +Returns a `bool` indicating if this `Napi::Value` is an undefined JavaScript value. ### IsNull ```cpp -bool IsNull() const; +bool Napi::Value::IsNull() const; ``` -Returns a `bool` indicating if this Value is a null JavaScript value. +Returns a `bool` indicating if this `Napi::Value` is a null JavaScript value. ### IsBoolean ```cpp -bool IsBoolean() const; +bool Napi::Value::IsBoolean() const; ``` -Returns a `bool` indicating if this Value is a JavaScript boolean. +Returns a `bool` indicating if this `Napi::Value` is a JavaScript boolean. ### IsNumber ```cpp -bool IsNumber() const; +bool Napi::Value::IsNumber() const; ``` -Returns a `bool` indicating if this Value is a JavaScript number. +Returns a `bool` indicating if this `Napi::Value` is a JavaScript number. ### IsString ```cpp -bool IsString() const; +bool Napi::Value::IsString() const; ``` -Returns a `bool` indicating if this Value is a JavaScript string. +Returns a `bool` indicating if this `Napi::Value` is a JavaScript string. ### IsSymbol ```cpp -bool IsSymbol() const; +bool Napi::Value::IsSymbol() const; ``` -Returns a `bool` indicating if this Value is a JavaScript symbol. +Returns a `bool` indicating if this `Napi::Value` is a JavaScript symbol. ### IsArray ```cpp -bool IsArray() const; +bool Napi::Value::IsArray() const; ``` -Returns a `bool` indicating if this Value is a JavaScript array. +Returns a `bool` indicating if this `Napi::Value` is a JavaScript array. ### IsArrayBuffer ```cpp -bool IsArrayBuffer() const; +bool Napi::Value::IsArrayBuffer() const; ``` -Returns a `bool` indicating if this Value is a JavaScript array buffer. +Returns a `bool` indicating if this `Napi::Value` is a JavaScript array buffer. ### IsTypedArray ```cpp -bool IsTypedArray() const; +bool Napi::Value::IsTypedArray() const; ``` -Returns a `bool` indicating if this Value is a JavaScript typed array. +Returns a `bool` indicating if this `Napi::Value` is a JavaScript typed array. ### IsObject ```cpp -bool IsObject() const; +bool Napi::Value::IsObject() const; ``` -Returns a `bool` indicating if this Value is JavaScript object. +Returns a `bool` indicating if this `Napi::Value` is JavaScript object. ### IsFunction ```cpp -bool IsFunction() const; +bool Napi::Value::IsFunction() const; ``` -Returns a `bool` indicating if this Value is a JavaScript function. +Returns a `bool` indicating if this `Napi::Value` is a JavaScript function. ### IsBuffer ```cpp -bool IsBuffer() const; +bool Napi::Value::IsBuffer() const; ``` -Returns a `bool` indicating if this Value is a Node buffer. +Returns a `bool` indicating if this `Napi::Value` is a Node buffer. ### As ```cpp -template T As() const; +template T Napi::Value::As() const; ``` Casts to another type of `Napi::Value`, when the actual type is known or assumed. @@ -239,31 +239,31 @@ This conversion does not coerce the type. Calling any methods inappropriate for ### ToBoolean ```cpp -Boolean ToBoolean() const; +Napi::Boolean Napi::Value::ToBoolean() const; ``` -Returns the Value coerced to a JavaScript boolean. +Returns the `Napi::Value` coerced to a JavaScript boolean. ### ToNumber ```cpp -Number ToNumber() const; +Napi::Number Napi::Value::ToNumber() const; ``` -Returns the Value coerced to a JavaScript number. +Returns the `Napi::Value` coerced to a JavaScript number. ### ToString ```cpp -String ToString() const; +Napi::String Napi::Value::ToString() const; ``` -Returns the Value coerced to a JavaScript string. +Returns the `Napi::Value` coerced to a JavaScript string. ### ToObject ```cpp -Object ToObject() const; +Napi::Object Napi::Value::ToObject() const; ``` -Returns the Value coerced to a JavaScript object. +Returns the `Napi::Value` coerced to a JavaScript object. diff --git a/doc/working_with_javascript_values.md b/doc/working_with_javascript_values.md index 0c5cbe2ee..fc208f10f 100644 --- a/doc/working_with_javascript_values.md +++ b/doc/working_with_javascript_values.md @@ -1,5 +1,13 @@ # Working with JavaScript Values -You are reading a draft of the next documentation and it's in continuous 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/) +`node-addon-api` provides a set of classes that allow to create and manage +JavaScript object: + +- [Function](doc/function.md) + - [FunctionReference](doc/function_reference.md) +- [ObjectWrap](doc/object_wrap.md) + - [ClassPropertyDescriptor](doc/class_property_descriptor.md) +- [Buffer](doc/buffer.md) +- [ArrayBuffer](doc/array_buffer.md) +- [TypedArray](doc/typed_array.md) + - [TypedArrayOf](doc/typed_array_of.md) From fd3c37b0f289a4c5756eb60f39533d00f88a7253 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Tue, 18 Sep 2018 11:02:04 -0400 Subject: [PATCH 043/696] tools: add tool to check for N-API modules Adds tools/check-napi.js which uses `nm -a` on UNIX and `dumpbin /imports` on Windows to check whether a given `.node` file is an N-API module or not. Intentionally ignores files named `nothing.node` because they are node-addon-api build artefacts. Sets the target type for `nothing` (which gets built when a built-in N-API is found to be present) to `'static_library'` so as to avoid the creation of `nothing.node` files which incorrectly end up showing up in the output of `check-napi.js` as non-N-API modules. PR-URL: https://github.com/nodejs/node-addon-api/pull/346 Reviewed-By: Sakthipriyan Vairamani Reviewed-By: Michael Dawson --- README.md | 1 + doc/checker-tool.md | 32 ++++++++++++++ tools/check-napi.js | 100 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 doc/checker-tool.md create mode 100644 tools/check-napi.js diff --git a/README.md b/README.md index 8a22b123d..90c8bb06c 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ to ideas specified in the **ECMA262 Language Specification**. - [node-gyp](doc/node-gyp.md) - [cmake-js](doc/cmake-js.md) - [Conversion tool](doc/conversion-tool.md) + - [Checker tool](doc/checker-tool.md) - [Generator](doc/generator.md) diff --git a/doc/checker-tool.md b/doc/checker-tool.md new file mode 100644 index 000000000..499d3ab9d --- /dev/null +++ b/doc/checker-tool.md @@ -0,0 +1,32 @@ +# Checker Tool + +**node-addon-api** provides a [checker tool][] that will inspect a given +directory tree, identifying all Node.js native addons therein, and further +indicating for each addon whether it is an N-API addon. + +## To use the checker tool: + + 1. Install the application with `npm install`. + + 2. If the application does not depend on **node-addon-api**, copy the + checker tool into the application's directory. + + 3. If the application does not depend on **node-addon-api**, run the checker + tool from the application's directory: + + ```sh + node ./check-napi.js + ``` + + Otherwise, the checker tool can be run from the application's + `node_modules/` subdirectory: + + ```sh + node ./node_modules/node-addon-api/tools/check-napi.js + ``` + +The tool accepts the root directory from which to start checking for Node.js +native addons as a single optional command line parameter. If ommitted it will +start checking from the current directory (`.`). + +[checker tool]: ../tools/check-napi.js diff --git a/tools/check-napi.js b/tools/check-napi.js new file mode 100644 index 000000000..48fdfc077 --- /dev/null +++ b/tools/check-napi.js @@ -0,0 +1,100 @@ +'use strict'; +// Descend into a directory structure and, for each file matching *.node, output +// based on the imports found in the file whether it's an N-API module or not. + +const fs = require('fs'); +const path = require('path'); +const child_process = require('child_process'); + +// Read the output of the command, break it into lines, and use the reducer to +// decide whether the file is an N-API module or not. +function checkFile(file, command, argv, reducer) { + const child = child_process.spawn(command, argv, { + stdio: ['inherit', 'pipe', 'inherit'] + }); + let leftover = ''; + let isNapi = undefined; + child.stdout.on('data', (chunk) => { + if (isNapi === undefined) { + chunk = (leftover + chunk.toString()).split(/[\r\n]+/); + leftover = chunk.pop(); + isNapi = chunk.reduce(reducer, isNapi); + if (isNapi !== undefined) { + child.kill(); + } + } + }); + child.on('close', (code, signal) => { + if ((code === null && signal !== null) || (code !== 0)) { + console.log( + command + ' exited with code: ' + code + ' and signal: ' + signal); + } else { + // Green if it's a N-API module, red otherwise. + console.log( + '\x1b[' + (isNapi ? '42' : '41') + 'm' + + (isNapi ? ' N-API' : 'Not N-API') + + '\x1b[0m: ' + file); + } + }); +} + +// Use nm -a to list symbols. +function checkFileUNIX(file) { + checkFile(file, 'nm', ['-a', file], (soFar, line) => { + if (soFar === undefined) { + line = line.match(/([0-9a-f]*)? ([a-zA-Z]) (.*$)/); + if (line[2] === 'U') { + if (/^napi/.test(line[3])) { + soFar = true; + } + } + } + return soFar; + }); +} + +// Use dumpbin /imports to list symbols. +function checkFileWin32(file) { + checkFile(file, 'dumpbin', ['/imports', file], (soFar, line) => { + if (soFar === undefined) { + line = line.match(/([0-9a-f]*)? +([a-zA-Z0-9]) (.*$)/); + if (line && /^napi/.test(line[line.length - 1])) { + soFar = true; + } + } + return soFar; + }); +} + +// Descend into a directory structure and pass each file ending in '.node' to +// one of the above checks, depending on the OS. +function recurse(top) { + fs.readdir(top, (error, items) => { + if (error) { + throw ("error reading directory " + top + ": " + error); + } + items.forEach((item) => { + item = path.join(top, item); + fs.stat(item, ((item) => (error, stats) => { + if (error) { + throw ("error about " + item + ": " + error); + } + if (stats.isDirectory()) { + recurse(item); + } else if (/[.]node$/.test(item) && + // Explicitly ignore files called 'nothing.node' because they are + // artefacts of node-addon-api having identified a version of + // Node.js that ships with a correct implementation of N-API. + path.basename(item) !== 'nothing.node') { + process.platform === 'win32' ? + checkFileWin32(item) : + checkFileUNIX(item); + } + })(item)); + }); + }); +} + +// Start with the directory given on the command line or the current directory +// if nothing was given. +recurse(process.argv.length > 3 ? process.argv[2] : '.'); From 211ed38d0dfa251827531455476ca835593d0c61 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Tue, 18 Sep 2018 17:41:34 -0400 Subject: [PATCH 044/696] src: make 'nothing' target a static library This avoids the creation of `nothing.node` files in other projects. These files will otherwise be identified as non-N-API modules, even though they are empty. PR-URL: https://github.com/nodejs/node-addon-api/pull/348 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- src/node_api.gyp | 4 +++- src/nothing.c | 0 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 src/nothing.c diff --git a/src/node_api.gyp b/src/node_api.gyp index fa34d8308..3de7da141 100644 --- a/src/node_api.gyp +++ b/src/node_api.gyp @@ -1,7 +1,9 @@ { 'targets': [ { - 'target_name': 'nothing' + 'target_name': 'nothing', + 'type': 'static_library', + 'sources': [ 'nothing.c' ] }, { 'target_name': 'node-api', diff --git a/src/nothing.c b/src/nothing.c new file mode 100644 index 000000000..e69de29bb From 51ffe453f8b18206f50620904221dc734bf3b478 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Fri, 21 Sep 2018 21:27:34 +0200 Subject: [PATCH 045/696] doc: doc cleanup - Some fix on bigint and dataview. - Add section dedicated to prebuild tools. PR-URL: https://github.com/nodejs/node-addon-api/pull/353 Reviewed-By: Michael Dawson --- README.md | 2 ++ doc/bigint.md | 22 ++++++------ doc/dataview.md | 50 +++++++++++++-------------- doc/prebuild_tools.md | 16 +++++++++ doc/working_with_javascript_values.md | 17 ++++----- 5 files changed, 62 insertions(+), 45 deletions(-) create mode 100644 doc/prebuild_tools.md diff --git a/README.md b/README.md index 90c8bb06c..febfbe669 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ to ideas specified in the **ECMA262 Language Specification**. - [Conversion tool](doc/conversion-tool.md) - [Checker tool](doc/checker-tool.md) - [Generator](doc/generator.md) + - [Prebuild tools](doc/prebuild_tools.md) @@ -72,6 +73,7 @@ The following is the documentation for node-addon-api. - [String](doc/string.md) - [Name](doc/basic_types.md#name) - [Number](doc/number.md) + - [BigInt](doc/bigint.md) - [Boolean](doc/boolean.md) - [Env](doc/env.md) - [Value](doc/value.md) diff --git a/doc/bigint.md b/doc/bigint.md index 6d3b0afc0..cc981040b 100644 --- a/doc/bigint.md +++ b/doc/bigint.md @@ -7,24 +7,23 @@ A JavaScript BigInt value. ### New ```cpp -static BigInt New(Napi::Env env, int64_t value); -static BigInt New(Napi::Env env, uint64_t value); +static Napi::BigInt Napi::BigInt::New(Napi::Env env, int64_t value); ``` - - `[in] env`: The environment in which to construct the `BigInt` object. + - `[in] env`: The environment in which to construct the `Napi::BigInt` object. - `[in] value`: The value the JavaScript `BigInt` will contain These APIs convert the C `int64_t` and `uint64_t` types to the JavaScript `BigInt` type. ```cpp -static BigInt New(Napi::Env env, +static Napi::BigInt Napi::BigInt::New(Napi::Env env, int sign_bit, size_t word_count, const uint64_t* words); ``` - - `[in] env`: The environment in which to construct the `BigInt` object. + - `[in] env`: The environment in which to construct the `Napi::BigInt` object. - `[in] sign_bit`: Determines if the resulting `BigInt` will be positive or negative. - `[in] word_count`: The length of the words array. - `[in] words`: An array of `uint64_t` little-endian 64-bit words. @@ -43,16 +42,15 @@ Returns a new JavaScript `BigInt`. Napi::BigInt(); ``` -Returns a new empty JavaScript `BigInt`. +Returns a new empty JavaScript `Napi::BigInt`. ### Int64Value ```cpp -int64_t Int64Value(bool* lossless) const; +int64_t Napi::BitInt::Int64Value(bool* lossless) const; ``` - - `[out] lossless`: Indicates whether the `BigInt` value was converted - losslessly. + - `[out] lossless`: Indicates whether the `BigInt` value was converted losslessly. Returns the C `int64_t` primitive equivalent of the given JavaScript `BigInt`. If needed it will truncate the value, setting lossless to false. @@ -60,7 +58,7 @@ Returns the C `int64_t` primitive equivalent of the given JavaScript ### Uint64Value ```cpp -uint64_t Uint64Value(bool* lossless) const; +uint64_t Napi::BigInt::Uint64Value(bool* lossless) const; ``` - `[out] lossless`: Indicates whether the `BigInt` value was converted @@ -72,7 +70,7 @@ Returns the C `uint64_t` primitive equivalent of the given JavaScript ### WordCount ```cpp -size_t WordCount() const; +size_t Napi::BigInt::WordCount() const; ``` Returns the number of words needed to store this `BigInt` value. @@ -80,7 +78,7 @@ Returns the number of words needed to store this `BigInt` value. ### ToWords ```cpp -void ToWords(size_t* word_count, int* sign_bit, uint64_t* words); +void Napi::BigInt::ToWords(size_t* word_count, int* sign_bit, uint64_t* words); ``` - `[out] sign_bit`: Integer representing if the JavaScript `BigInt` is positive diff --git a/doc/dataview.md b/doc/dataview.md index 7f4af81db..64b865b1c 100644 --- a/doc/dataview.md +++ b/doc/dataview.md @@ -11,7 +11,7 @@ class. Allocates a new `Napi::DataView` instance with a given `Napi::ArrayBuffer`. ```cpp -static Napi::DataView New(napi_env env, Napi::ArrayBuffer arrayBuffer); +static Napi::DataView Napi::DataView::New(napi_env env, Napi::ArrayBuffer arrayBuffer); ``` - `[in] env`: The environment in which to create the `Napi::DataView` instance. @@ -24,7 +24,7 @@ Returns a new `Napi::DataView` instance. Allocates a new `Napi::DataView` instance with a given `Napi::ArrayBuffer`. ```cpp -static Napi::DataView New(napi_env env, Napi::ArrayBuffer arrayBuffer, size_t byteOffset); +static Napi::DataView Napi::DataView::New(napi_env env, Napi::ArrayBuffer arrayBuffer, size_t byteOffset); ``` - `[in] env`: The environment in which to create the `Napi::DataView` instance. @@ -38,7 +38,7 @@ Returns a new `Napi::DataView` instance. Allocates a new `Napi::DataView` instance with a given `Napi::ArrayBuffer`. ```cpp -static Napi::DataView New(napi_env env, Napi::ArrayBuffer arrayBuffer, size_t byteOffset, size_t byteLength); +static Napi::DataView Napi::DataView::New(napi_env env, Napi::ArrayBuffer arrayBuffer, size_t byteOffset, size_t byteLength); ``` - `[in] env`: The environment in which to create the `Napi::DataView` instance. @@ -53,7 +53,7 @@ Returns a new `Napi::DataView` instance. Initializes an empty instance of the `Napi::DataView` class. ```cpp -DataView(); +Napi::DataView(); ``` ### Constructor @@ -61,7 +61,7 @@ DataView(); Initializes a wrapper instance of an existing `Napi::DataView` instance. ```cpp -DataView(napi_env env, napi_value value); +Napi::DataView(napi_env env, napi_value value); ``` - `[in] env`: The environment in which to create the `Napi::DataView` instance. @@ -70,7 +70,7 @@ DataView(napi_env env, napi_value value); ### ArrayBuffer ```cpp -Napi::ArrayBuffer ArrayBuffer() const; +Napi::ArrayBuffer Napi::DataView::ArrayBuffer() const; ``` Returns the backing array buffer. @@ -78,7 +78,7 @@ Returns the backing array buffer. ### ByteOffset ```cpp -size_t ByteOffset() const; +size_t Napi::DataView::ByteOffset() const; ``` Returns the offset into the `Napi::DataView` where the array starts, in bytes. @@ -86,7 +86,7 @@ Returns the offset into the `Napi::DataView` where the array starts, in bytes. ### ByteLength ```cpp -size_t ByteLength() const; +size_t Napi::DataView::ByteLength() const; ``` Returns the length of the array, in bytes. @@ -94,17 +94,17 @@ Returns the length of the array, in bytes. ### GetFloat32 ```cpp -float GetFloat32(size_t byteOffset) const; +float Napi::DataView::GetFloat32(size_t byteOffset) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. -Returns a signed 32-bit float (float) at the specified byte offset from the start of the `DataView`. +Returns a signed 32-bit float (float) at the specified byte offset from the start of the `Napi::DataView`. ### GetFloat64 ```cpp -double GetFloat64(size_t byteOffset) const; +double Napi::DataView::GetFloat64(size_t byteOffset) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -114,7 +114,7 @@ Returns a signed 64-bit float (double) at the specified byte offset from the sta ### GetInt8 ```cpp -int8_t GetInt8(size_t byteOffset) const; +int8_t Napi::DataView::GetInt8(size_t byteOffset) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -124,7 +124,7 @@ Returns a signed 8-bit integer (byte) at the specified byte offset from the star ### GetInt16 ```cpp -int16_t GetInt16(size_t byteOffset) const; +int16_t Napi::DataView::GetInt16(size_t byteOffset) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -134,7 +134,7 @@ Returns a signed 16-bit integer (short) at the specified byte offset from the st ### GetInt32 ```cpp -int32_t GetInt32(size_t byteOffset) const; +int32_t Napi::DataView::GetInt32(size_t byteOffset) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -144,7 +144,7 @@ Returns a signed 32-bit integer (long) at the specified byte offset from the sta ### GetUint8 ```cpp -uint8_t GetUint8(size_t byteOffset) const; +uint8_t Napi::DataView::GetUint8(size_t byteOffset) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -154,7 +154,7 @@ Returns a unsigned 8-bit integer (unsigned byte) at the specified byte offset fr ### GetUint16 ```cpp -uint16_t GetUint16(size_t byteOffset) const; +uint16_t Napi::DataView::GetUint16(size_t byteOffset) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -164,7 +164,7 @@ Returns a unsigned 16-bit integer (unsigned short) at the specified byte offset ### GetUint32 ```cpp -uint32_t GetUint32(size_t byteOffset) const; +uint32_t Napi::DataView::GetUint32(size_t byteOffset) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -174,7 +174,7 @@ Returns a unsigned 32-bit integer (unsigned long) at the specified byte offset f ### SetFloat32 ```cpp -void SetFloat32(size_t byteOffset, float value) const; +void Napi::DataView::SetFloat32(size_t byteOffset, float value) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -183,7 +183,7 @@ void SetFloat32(size_t byteOffset, float value) const; ### SetFloat64 ```cpp -void SetFloat64(size_t byteOffset, double value) const; +void Napi::DataView::SetFloat64(size_t byteOffset, double value) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -192,7 +192,7 @@ void SetFloat64(size_t byteOffset, double value) const; ### SetInt8 ```cpp -void SetInt8(size_t byteOffset, int8_t value) const; +void Napi::DataView::SetInt8(size_t byteOffset, int8_t value) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -201,7 +201,7 @@ void SetInt8(size_t byteOffset, int8_t value) const; ### SetInt16 ```cpp -void SetInt16(size_t byteOffset, int16_t value) const; +void Napi::DataView::SetInt16(size_t byteOffset, int16_t value) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -210,7 +210,7 @@ void SetInt16(size_t byteOffset, int16_t value) const; ### SetInt32 ```cpp -void SetInt32(size_t byteOffset, int32_t value) const; +void Napi::DataView::SetInt32(size_t byteOffset, int32_t value) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -219,7 +219,7 @@ void SetInt32(size_t byteOffset, int32_t value) const; ### SetUint8 ```cpp -void SetUint8(size_t byteOffset, uint8_t value) const; +void Napi::DataView::SetUint8(size_t byteOffset, uint8_t value) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -228,7 +228,7 @@ void SetUint8(size_t byteOffset, uint8_t value) const; ### SetUint16 ```cpp -void SetUint16(size_t byteOffset, uint16_t value) const; +void Napi::DataView::SetUint16(size_t byteOffset, uint16_t value) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. @@ -237,7 +237,7 @@ void SetUint16(size_t byteOffset, uint16_t value) const; ### SetUint32 ```cpp -void SetUint32(size_t byteOffset, uint32_t value) const; +void Napi::DataView::SetUint32(size_t byteOffset, uint32_t value) const; ``` - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. diff --git a/doc/prebuild_tools.md b/doc/prebuild_tools.md new file mode 100644 index 000000000..4025a58d1 --- /dev/null +++ b/doc/prebuild_tools.md @@ -0,0 +1,16 @@ +# Prebuild tools + +The distribution of a native add-on is just as important as its implementation. +In order to install a native add-on it's important to have all the necessary +dependencies installed and well configured (see the [setup](doc/setum.md) section). +The end-user will need to compile the add-on when they will do an `npm install` +and in some cases this could create problems. To avoid the compilation process it's +possible to ditribute the native add-on in pre-built form for different platform +and architectures. The prebuild tools help to create and distrubute the pre-built +form of a native add-on. + +The following list report two of the tools that are compatible with **N-API**: + +- **[node-pre-gyp](https://www.npmjs.com/package/node-pre-gyp)** +- **[prebuild](https://www.npmjs.com/package/prebuild)** +- **[prebuildify](https://www.npmjs.com/package/prebuildify)** diff --git a/doc/working_with_javascript_values.md b/doc/working_with_javascript_values.md index fc208f10f..00dad25ff 100644 --- a/doc/working_with_javascript_values.md +++ b/doc/working_with_javascript_values.md @@ -3,11 +3,12 @@ `node-addon-api` provides a set of classes that allow to create and manage JavaScript object: -- [Function](doc/function.md) - - [FunctionReference](doc/function_reference.md) -- [ObjectWrap](doc/object_wrap.md) - - [ClassPropertyDescriptor](doc/class_property_descriptor.md) -- [Buffer](doc/buffer.md) -- [ArrayBuffer](doc/array_buffer.md) -- [TypedArray](doc/typed_array.md) - - [TypedArrayOf](doc/typed_array_of.md) +- [Function](function.md) + - [FunctionReference](function_reference.md) +- [ObjectWrap](object_wrap.md) + - [ClassPropertyDescriptor](class_property_descriptor.md) +- [Buffer](buffer.md) +- [ArrayBuffer](array_buffer.md) +- [TypedArray](typed_array.md) + - [TypedArrayOf](typed_array_of.md) +- [DataView](dataview.md) From 779560f3973d587a4c0d60b46bfaf09c879957dc Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 25 Sep 2018 11:24:18 +0900 Subject: [PATCH 046/696] test: add operator overloading tests in Number PR-URL: https://github.com/nodejs/node-addon-api/pull/355 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- test/basic_types/number.cc | 41 ++++++++++++++++++++++++++++++++++++++ test/basic_types/number.js | 33 ++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/test/basic_types/number.cc b/test/basic_types/number.cc index 288460365..436d51918 100644 --- a/test/basic_types/number.cc +++ b/test/basic_types/number.cc @@ -41,6 +41,40 @@ Value MaxDouble(const CallbackInfo& info) { return Number::New(info.Env(), DBL_MAX); } +Value OperatorInt32(const CallbackInfo& info) { + Number number = info[0].As(); + return Boolean::New(info.Env(), number.Int32Value() == static_cast(number)); +} + +Value OperatorUint32(const CallbackInfo& info) { + Number number = info[0].As(); + return Boolean::New(info.Env(), number.Uint32Value() == static_cast(number)); +} + +Value OperatorInt64(const CallbackInfo& info) { + Number number = info[0].As(); + return Boolean::New(info.Env(), number.Int64Value() == static_cast(number)); +} + +Value OperatorFloat(const CallbackInfo& info) { + Number number = info[0].As(); + return Boolean::New(info.Env(), number.FloatValue() == static_cast(number)); +} + +Value OperatorDouble(const CallbackInfo& info) { + Number number = info[0].As(); + return Boolean::New(info.Env(), number.DoubleValue() == static_cast(number)); +} + +Value CreateEmptyNumber(const CallbackInfo& info) { + Number* number = new Number(); + return Boolean::New(info.Env(), number->IsEmpty()); +} + +Value CreateNumberFromExistingValue(const CallbackInfo& info) { + return info[0].As(); +} + Object InitBasicTypesNumber(Env env) { Object exports = Object::New(env); @@ -53,6 +87,13 @@ Object InitBasicTypesNumber(Env env) { exports["maxFloat"] = Function::New(env, MaxFloat); exports["minDouble"] = Function::New(env, MinDouble); exports["maxDouble"] = Function::New(env, MaxDouble); + exports["operatorInt32"] = Function::New(env, OperatorInt32); + exports["operatorUint32"] = Function::New(env, OperatorUint32); + exports["operatorInt64"] = Function::New(env, OperatorInt64); + exports["operatorFloat"] = Function::New(env, OperatorFloat); + exports["operatorDouble"] = Function::New(env, OperatorDouble); + exports["createEmptyNumber"] = Function::New(env, CreateEmptyNumber); + exports["createNumberFromExistingValue"] = Function::New(env, CreateNumberFromExistingValue); return exports; } diff --git a/test/basic_types/number.js b/test/basic_types/number.js index df3d9820f..a7c66bbc3 100644 --- a/test/basic_types/number.js +++ b/test/basic_types/number.js @@ -80,4 +80,37 @@ function test(binding) { assert.strictEqual(0, binding.basic_types_number.toDouble(MIN_DOUBLE * MIN_DOUBLE)); assert.strictEqual(Infinity, binding.basic_types_number.toDouble(MAX_DOUBLE * MAX_DOUBLE)); } + + // Test for operator overloading + { + assert.strictEqual(binding.basic_types_number.operatorInt32(MIN_INT32), true); + assert.strictEqual(binding.basic_types_number.operatorInt32(MAX_INT32), true); + assert.strictEqual(binding.basic_types_number.operatorUint32(MIN_UINT32), true); + assert.strictEqual(binding.basic_types_number.operatorUint32(MAX_UINT32), true); + assert.strictEqual(binding.basic_types_number.operatorInt64(MIN_INT64), true); + assert.strictEqual(binding.basic_types_number.operatorInt64(MAX_INT64), true); + assert.strictEqual(binding.basic_types_number.operatorFloat(MIN_FLOAT), true); + assert.strictEqual(binding.basic_types_number.operatorFloat(MAX_FLOAT), true); + assert.strictEqual(binding.basic_types_number.operatorFloat(MAX_DOUBLE), true); + assert.strictEqual(binding.basic_types_number.operatorDouble(MIN_DOUBLE), true); + assert.strictEqual(binding.basic_types_number.operatorDouble(MAX_DOUBLE), true); + } + + // Construction test + { +    assert.strictEqual(binding.basic_types_number.createEmptyNumber(), true); +    randomRangeTestForInteger(MIN_INT32, MAX_INT32, binding.basic_types_number.createNumberFromExistingValue); +    assert.strictEqual(MIN_INT32, binding.basic_types_number.createNumberFromExistingValue(MIN_INT32)); +    assert.strictEqual(MAX_INT32, binding.basic_types_number.createNumberFromExistingValue(MAX_INT32)); +    randomRangeTestForInteger(MIN_UINT32, MAX_UINT32, binding.basic_types_number.createNumberFromExistingValue); +    assert.strictEqual(MIN_UINT32, binding.basic_types_number.createNumberFromExistingValue(MIN_UINT32)); +    assert.strictEqual(MAX_UINT32, binding.basic_types_number.createNumberFromExistingValue(MAX_UINT32)); +    randomRangeTestForInteger(MIN_INT64, MAX_INT64, binding.basic_types_number.createNumberFromExistingValue); +    assert.strictEqual(MIN_INT64, binding.basic_types_number.createNumberFromExistingValue(MIN_INT64)); +    assert.strictEqual(MAX_INT64, binding.basic_types_number.createNumberFromExistingValue(MAX_INT64)); +    assert.strictEqual(MIN_FLOAT, binding.basic_types_number.createNumberFromExistingValue(MIN_FLOAT)); +    assert.strictEqual(MAX_FLOAT, binding.basic_types_number.createNumberFromExistingValue(MAX_FLOAT)); +    assert.strictEqual(MIN_DOUBLE, binding.basic_types_number.createNumberFromExistingValue(MIN_DOUBLE)); +    assert.strictEqual(MAX_DOUBLE, binding.basic_types_number.createNumberFromExistingValue(MAX_DOUBLE)); + } } From 78374f72d29d7fec28d6b869f8ba0502317db517 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Tue, 25 Sep 2018 18:02:59 +0200 Subject: [PATCH 047/696] doc: number documentation PR-URL: https://github.com/nodejs/node-addon-api/pull/356 Reviewed-By: Michael Dawson --- doc/number.md | 147 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 139 insertions(+), 8 deletions(-) diff --git a/doc/number.md b/doc/number.md index d1bee4810..8226909a8 100644 --- a/doc/number.md +++ b/doc/number.md @@ -1,29 +1,160 @@ # Number -A Javascript number value. +`Napi::Number` class is a representation of the JavaScript `Number` object. The +`Napi::Number` class inherits its behavior from `Napi::Value` class +(for more info see [`Napi::Value`](value.md)) + ## Methods ### Constructor +Creates a new _empty_ instance of a `Napi::Number` object. + +```cpp +Napi::Number(); +``` + +Returns a new _empty_ `Napi::Number` object. + +### Contructor + +Creates a new instance of a `Napi::Number` object. + +```cpp +Napi::Number(napi_env env, napi_value value); +``` + + - `[in] env`: The `napi_env` environment in which to construct the `Napi::Nuber` object. + - `[in] value`: The `napi_value` which is a handle for a JavaScript `Number`. + + Returns a non-empty `Napi::Number` object. + + ### New + + Creates a new instance of a `Napi::Number` object. + +```cpp +Napi::Number Napi::Number::New(napi_env env, double value); +``` + - `[in] env`: The `napi_env` environment in which to construct the `Napi::Nuber` object. + - `[in] value`: The `napi_value` which is a handle for a JavaScript `Number`. + +Creates a new instance of a `Napi::Number` object. + +### Int32Value + +Converts a `Napi::Number` value to a `uint32_t` primitive type. + +```cpp +Napi::Number::Int32Value() const; +``` + +Returns the `int32_t` primitive type of the corresponding `Napi::Number` object. + +### Uint32Value + +Converts a `Napi::Number` value to a `uint32_t` primitive type. + +```cpp +Napi::Number::Uint32Value() const; +``` + +Returns the `uint32_t` primitive type of the corresponding `Napi::Number` object. + +### Int64Value + +Converts a `Napi::Number` value to a `int64_t` primitive type. + +```cpp +Napi::Number::Int64Value() const; +``` + +Returns the `int64_t` primitive type of the corresponding `Napi::Number` object. + +### FloatValue + +Converts a `Napi::Number` value to a `float` primitive type. + ```cpp -Napi::Number::New(Napi::Env env, double value); +Napi::Number::FloatValue() const; ``` - - `[in] env`: The `napi_env` Environment - - `[in] value`: The value the Javascript Number will contain + +Returns the `float` primitive type of the corresponding `Napi::Number` object. + +### DoubleValue + +Converts a `Napi::Number` value to a `double` primitive type. ```cpp -Napi::Number::Number(); +Napi::Number::DoubleValue() const; ``` -returns a new empty Javascript Number -You can easily cast a Javascript number to one of: +Returns the `double` primitive type of the corresponding `Napi::Number` object. + +## Operators + +The `Napi::Number` class contains a set of operators to easily cast JavaScript +`Number` object to one of the following primitive types: + - `int32_t` - `uint32_t` - `int64_t` - `float` - `double` -The following shows an example of casting a number to an uint32_t value. +### operator int32_t + +Converts a `Napi::Number` value to a `int32_t` primitive. + +```cpp +Napi::Number::operator int32_t() const; +``` + +Returns the `int32_t` primitive type of the corresponding `Napi::Number` object. + +### operator uint32_t + +Converts a `Napi::Number` value to a `uint32_t` primitive type. + +```cpp +Napi::Number::operator uint32_t() const; +``` + +Returns the `uint32_t` primitive type of the corresponding `Napi::Number` object. + +### operator int64_t + +Converts a `Napi::Number` value to a `int64_t` primitive type. + +```cpp +Napi::Number::operator int64_t() const; +``` + +Returns the `int64_t` primitive type of the corresponding `Napi::Number` object. + +### operator float + +Converts a `Napi::Number` value to a `float` primitive type. + +```cpp +Napi::Number::operator float() const; +``` + +Returns the `float` primitive type of the corresponding `Napi::Number` object. + +### operator double + +Converts a `Napi::Number` value to a `double` primitive type. + +```cpp +Napi::Number::operator double() const; +``` + +Returns the `double` primitive type of the corresponding `Napi::Number` object. + +### Example + +The following shows an example of casting a number to an `uint32_t` value. ```cpp uint32_t operatorVal = Napi::Number::New(Env(), 10.0); // Number to unsigned 32 bit integer From 4f76262a1083fe856412bf2b3a4f0dba8b0ff965 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Mon, 24 Sep 2018 23:25:17 +0200 Subject: [PATCH 048/696] doc: some fix on `Napi::Boolean` documentation PR-URL: https://github.com/nodejs/node-addon-api/pull/354 Reviewed-By: Michael Dawson --- doc/boolean.md | 53 ++++++++++++++++++++++++++++++------- test/basic_types/boolean.cc | 19 ++++++++++++- test/basic_types/boolean.js | 16 +++++++++++ 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/doc/boolean.md b/doc/boolean.md index 2886b1d0c..01b6a4c0a 100644 --- a/doc/boolean.md +++ b/doc/boolean.md @@ -1,29 +1,64 @@ # Boolean -# Methods +`Napi::Boolean` class is a representation of the JavaScript `Boolean` object. The +`Napi::Boolean` class inherits its behavior from the `Napi::Value` class +(for more info see: [`Napi::Value`](value.md)). + +## Methods ### Constructor +Creates a new empty instance of an `Napi::Boolean` object. + ```cpp -Napi::Boolean::New(Napi::Env env, bool value); +Napi::Boolean::Boolean(); ``` - - `[in] env`: The `napi_env` Environment - - `[in] value`: The Javascript boolean value + +Returns a new _empty_ `Napi::Boolean` object. + +### Contructor + +Creates a new instance of the `Napi::Boolean` object. ```cpp -Napi::Boolean::Boolean(); +Napi::Boolean(napi_env env, napi_value value); ``` -returns a new empty Javascript Boolean value type. -### operator bool -Converts a `Napi::Boolean` value to a boolean primitive. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Boolean` object. +- `[in] value`: The `napi_value` which is a handle for a JavaScript `Boolean`. + +Returns a non-empty `Napi::Boolean` object. + +### New + +Initializes a new instance of the `Napi::Boolean` object. + ```cpp -Napi::Boolean::operator bool() const; +Napi::Boolean Napi::Boolean::New(napi_env env, bool value); ``` +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Boolean` object. +- `[in] value`: The primitive boolean value (`true` or `false`). + +Returns a new instance of the `Napi::Boolean` object. ### Value + Converts a `Napi::Boolean` value to a boolean primitive. ```cpp bool Napi::Boolean::Value() const; ``` + +Returns the boolean primitive type of the corresponding `Napi::Boolean` object. + +## Operators + +### operator bool + +Converts a `Napi::Boolean` value to a boolean primitive. + +```cpp +Napi::Boolean::operator bool() const; +``` + +Returns the boolean primitive type of the corresponding `Napi::Boolean` object. diff --git a/test/basic_types/boolean.cc b/test/basic_types/boolean.cc index c874845eb..900438f62 100644 --- a/test/basic_types/boolean.cc +++ b/test/basic_types/boolean.cc @@ -6,10 +6,27 @@ Value CreateBoolean(const CallbackInfo& info) { return Boolean::New(info.Env(), info[0].As().Value()); } +Value CreateEmptyBoolean(const CallbackInfo& info) { + Boolean* boolean = new Boolean(); + return Boolean::New(info.Env(), boolean->IsEmpty()); +} + +Value CreateBooleanFromExistingValue(const CallbackInfo& info) { + Boolean* boolean = new Boolean(info.Env(), info[0].As()); + return Boolean::New(info.Env(), boolean->Value()); +} + +Value CreateBooleanFromPrimitive(const CallbackInfo& info) { + bool boolean = info[0].As(); + return Boolean::New(info.Env(), boolean); +} + Object InitBasicTypesBoolean(Env env) { Object exports = Object::New(env); exports["createBoolean"] = Function::New(env, CreateBoolean); - + exports["createEmptyBoolean"] = Function::New(env, CreateEmptyBoolean); + exports["createBooleanFromExistingValue"] = Function::New(env, CreateBooleanFromExistingValue); + exports["createBooleanFromPrimitive"] = Function::New(env, CreateBooleanFromPrimitive); return exports; } diff --git a/test/basic_types/boolean.js b/test/basic_types/boolean.js index 3a9c88da8..1c27664f1 100644 --- a/test/basic_types/boolean.js +++ b/test/basic_types/boolean.js @@ -11,4 +11,20 @@ function test(binding) { const bool2 = binding.basic_types_boolean.createBoolean(false); assert.strictEqual(bool2, false); + + const emptyBoolean = binding.basic_types_boolean.createEmptyBoolean(); + assert.strictEqual(emptyBoolean, true); + + const bool3 = binding.basic_types_boolean.createBooleanFromExistingValue(true); + assert.strictEqual(bool3, true); + + const bool4 = binding.basic_types_boolean.createBooleanFromExistingValue(false); + assert.strictEqual(bool4, false); + + const bool5 = binding.basic_types_boolean.createBooleanFromPrimitive(true); + assert.strictEqual(bool5, true); + + const bool6 = binding.basic_types_boolean.createBooleanFromPrimitive(false); + assert.strictEqual(bool6, false); + } From dfcb93945f96d6e2a01fce0d862af446b70d4f73 Mon Sep 17 00:00:00 2001 From: Jinho Bang Date: Mon, 30 Apr 2018 00:41:15 +0900 Subject: [PATCH 049/696] src: implement AsyncContext class This class provides a wrapper for the following custom asynchronous operation APIs. - napi_async_init() - napi_async_destroy() PR-URL: https://github.com/nodejs/node-addon-api/pull/252 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- README.md | 1 + doc/async_context.md | 76 ++++++++++++++++++++++++++++++++++ doc/async_operations.md | 6 +++ doc/function.md | 18 ++++++-- doc/function_reference.md | 18 ++++++-- napi-inl.h | 86 +++++++++++++++++++++++++++++++++------ napi.h | 44 +++++++++++++++++--- test/asynccontext.cc | 21 ++++++++++ test/asynccontext.js | 73 +++++++++++++++++++++++++++++++++ test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 1 + 12 files changed, 323 insertions(+), 24 deletions(-) create mode 100644 doc/async_context.md create mode 100644 test/asynccontext.cc create mode 100644 test/asynccontext.js diff --git a/README.md b/README.md index febfbe669..4f5be7122 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ The following is the documentation for node-addon-api. - [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) - [Version management](doc/version_management.md) diff --git a/doc/async_context.md b/doc/async_context.md new file mode 100644 index 000000000..48de9c496 --- /dev/null +++ b/doc/async_context.md @@ -0,0 +1,76 @@ +# AsyncContext + +The [Napi::AsyncWorker](async_worker.md) class may not be appropriate for every +scenario. When using any other async mechanism, introducing a new class +`Napi::AsyncContext` is necessary to ensure an async operation is properly +tracked by the runtime. The `Napi::AsyncContext` class can be passed to +[Napi::Function::MakeCallback()](function.md) method to properly restore the +correct async execution context. + +## Methods + +### Constructor + +Creates a new `Napi::AsyncContext`. + +```cpp +explicit Napi::AsyncContext::AsyncContext(napi_env env, const char* resource_name); +``` + +- `[in] env`: The environment in which to create the `Napi::AsyncContext`. +- `[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. + +### Constructor + +Creates a new `Napi::AsyncContext`. + +```cpp +explicit Napi::AsyncContext::AsyncContext(napi_env env, const char* resource_name, const Napi::Object& resource); +``` + +- `[in] env`: The environment in which to create the `Napi::AsyncContext`. +- `[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`. + +### Destructor + +The `Napi::AsyncContext` to be destroyed. + +```cpp +virtual Napi::AsyncContext::~AsyncContext(); +``` + +## Operator + +```cpp +Napi::AsyncContext::operator napi_async_context() const; +``` + +Returns the N-API `napi_async_context` wrapped by the `Napi::AsyncContext` +object. This can be used to mix usage of the C N-API and node-addon-api. + +## Example + +```cpp +#include "napi.h" + +void MakeCallbackWithAsyncContext(const Napi::CallbackInfo& info) { + Napi::Function callback = info[0].As(); + Napi::Object resource = info[1].As(); + + // Creat a new async context instance. + Napi::AsyncContext context(info.Env(), "async_context_test", resource); + + // Invoke the callback with the async context instance. + callback.MakeCallback(Napi::Object::New(info.Env()), + std::initializer_list{}, context); + + // The async context instance is automatically destroyed here because it's + // block-scope like `Napi::HandleScope`. +} +``` diff --git a/doc/async_operations.md b/doc/async_operations.md index ee445dd3f..be4f401fc 100644 --- a/doc/async_operations.md +++ b/doc/async_operations.md @@ -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)** diff --git a/doc/function.md b/doc/function.md index 3e8351ba3..efc7ed495 100644 --- a/doc/function.md +++ b/doc/function.md @@ -233,12 +233,16 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi Calls a Javascript function from a native add-on after an asynchronous operation. ```cpp -Napi::Value Napi::Function::MakeCallback(napi_value recv, const std::initializer_list& args) const; +Napi::Value Napi::Function::MakeCallback(napi_value recv, const std::initializer_list& args, napi_async_context context = nullptr) const; ``` - `[in] recv`: The `this` object passed to the called function. - `[in] args`: Initializer list of JavaScript values as `napi_value` representing the arguments of the function. +- `[in] context`: Context for the async operation that is invoking the callback. +This should normally be a value previously obtained from [Napi::AsyncContext](async_context.md). +However `nullptr` is also allowed, which indicates the current async context +(if any) is to be used for the callback. Returns a `Napi::Value` representing the JavaScript value returned by the function. @@ -247,12 +251,16 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi Calls a Javascript function from a native add-on after an asynchronous operation. ```cpp -Napi::Value Napi::Function::MakeCallback(napi_value recv, const std::vector& args) const; +Napi::Value Napi::Function::MakeCallback(napi_value recv, const std::vector& args, napi_async_context context = nullptr) const; ``` - `[in] recv`: The `this` object passed to the called function. - `[in] args`: List of JavaScript values as `napi_value` representing the arguments of the function. +- `[in] context`: Context for the async operation that is invoking the callback. +This should normally be a value previously obtained from [Napi::AsyncContext](async_context.md). +However `nullptr` is also allowed, which indicates the current async context +(if any) is to be used for the callback. Returns a `Napi::Value` representing the JavaScript value returned by the function. @@ -261,13 +269,17 @@ Returns a `Napi::Value` representing the JavaScript value returned by the functi Calls a Javascript function from a native add-on after an asynchronous operation. ```cpp -Napi::Value Napi::Function::MakeCallback(napi_value recv, size_t argc, const napi_value* args) const; +Napi::Value Napi::Function::MakeCallback(napi_value recv, size_t argc, const napi_value* args, napi_async_context context = nullptr) const; ``` - `[in] recv`: The `this` object passed to the called function. - `[in] argc`: The number of the arguments passed to the function. - `[in] args`: Array of JavaScript values as `napi_value` representing the arguments of the function. +- `[in] context`: Context for the async operation that is invoking the callback. +This should normally be a value previously obtained from [Napi::AsyncContext](async_context.md). +However `nullptr` is also allowed, which indicates the current async context +(if any) is to be used for the callback. Returns a `Napi::Value` representing the JavaScript value returned by the function. diff --git a/doc/function_reference.md b/doc/function_reference.md index a18a9b898..a7988acb2 100644 --- a/doc/function_reference.md +++ b/doc/function_reference.md @@ -171,12 +171,16 @@ Calls a referenced JavaScript function from a native add-on after an asynchronou operation. ```cpp -Napi::Value Napi::FunctionReference::MakeCallback(napi_value recv, const std::initializer_list& args) const; +Napi::Value Napi::FunctionReference::MakeCallback(napi_value recv, const std::initializer_list& args, napi_async_context = nullptr) const; ``` - `[in] recv`: The `this` object passed to the referenced function when it's called. - `[in] args`: Initializer list of JavaScript values as `napi_value` representing the arguments of the referenced function. +- `[in] context`: Context for the async operation that is invoking the callback. +This should normally be a value previously obtained from [Napi::AsyncContext](async_context.md). +However `nullptr` is also allowed, which indicates the current async context +(if any) is to be used for the callback. Returns a `Napi::Value` representing the JavaScript object returned by the referenced function. @@ -187,12 +191,16 @@ Calls a referenced JavaScript function from a native add-on after an asynchronou operation. ```cpp -Napi::Value Napi::FunctionReference::MakeCallback(napi_value recv, const std::vector& args) const; +Napi::Value Napi::FunctionReference::MakeCallback(napi_value recv, const std::vector& args, napi_async_context context = nullptr) const; ``` - `[in] recv`: The `this` object passed to the referenced function when it's called. - `[in] args`: Vector of JavaScript values as `napi_value` representing the arguments of the referenced function. +- `[in] context`: Context for the async operation that is invoking the callback. +This should normally be a value previously obtained from [Napi::AsyncContext](async_context.md). +However `nullptr` is also allowed, which indicates the current async context +(if any) is to be used for the callback. Returns a `Napi::Value` representing the JavaScript object returned by the referenced function. @@ -203,13 +211,17 @@ Calls a referenced JavaScript function from a native add-on after an asynchronou operation. ```cpp -Napi::Value Napi::FunctionReference::MakeCallback(napi_value recv, size_t argc, const napi_value* args) const; +Napi::Value Napi::FunctionReference::MakeCallback(napi_value recv, size_t argc, const napi_value* args, napi_async_context context = nullptr) const; ``` - `[in] recv`: The `this` object passed to the referenced function when it's called. - `[in] argc`: The number of arguments passed to the referenced function. - `[in] args`: Array of JavaScript values as `napi_value` representing the arguments of the referenced function. +- `[in] context`: Context for the async operation that is invoking the callback. +This should normally be a value previously obtained from [Napi::AsyncContext](async_context.md). +However `nullptr` is also allowed, which indicates the current async context +(if any) is to be used for the callback. Returns a `Napi::Value` representing the JavaScript object returned by the referenced function. diff --git a/napi-inl.h b/napi-inl.h index a4b1d426b..aead8b9ce 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1651,20 +1651,27 @@ inline Value Function::Call(napi_value recv, size_t argc, const napi_value* args } inline Value Function::MakeCallback( - napi_value recv, const std::initializer_list& args) const { - return MakeCallback(recv, args.size(), args.begin()); + napi_value recv, + const std::initializer_list& args, + napi_async_context context) const { + return MakeCallback(recv, args.size(), args.begin(), context); } inline Value Function::MakeCallback( - napi_value recv, const std::vector& args) const { - return MakeCallback(recv, args.size(), args.data()); + napi_value recv, + const std::vector& args, + napi_async_context context) const { + return MakeCallback(recv, args.size(), args.data(), context); } inline Value Function::MakeCallback( - napi_value recv, size_t argc, const napi_value* args) const { + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context) const { napi_value result; napi_status status = napi_make_callback( - _env, nullptr, recv, _value, argc, args, &result); + _env, context, recv, _value, argc, args, &result); NAPI_THROW_IF_FAILED(_env, status, Value()); return Value(_env, result); } @@ -2416,9 +2423,11 @@ inline Napi::Value FunctionReference::Call( } inline Napi::Value FunctionReference::MakeCallback( - napi_value recv, const std::initializer_list& args) const { + napi_value recv, + const std::initializer_list& args, + napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, args); + Napi::Value result = Value().MakeCallback(recv, args, context); if (scope.Env().IsExceptionPending()) { return Value(); } @@ -2426,9 +2435,11 @@ inline Napi::Value FunctionReference::MakeCallback( } inline Napi::Value FunctionReference::MakeCallback( - napi_value recv, const std::vector& args) const { + napi_value recv, + const std::vector& args, + napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, args); + Napi::Value result = Value().MakeCallback(recv, args, context); if (scope.Env().IsExceptionPending()) { return Value(); } @@ -2436,9 +2447,12 @@ inline Napi::Value FunctionReference::MakeCallback( } inline Napi::Value FunctionReference::MakeCallback( - napi_value recv, size_t argc, const napi_value* args) const { + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, argc, args); + Napi::Value result = Value().MakeCallback(recv, argc, args, context); if (scope.Env().IsExceptionPending()) { return Value(); } @@ -3274,6 +3288,54 @@ inline Value EscapableHandleScope::Escape(napi_value escapee) { return Value(_env, result); } +//////////////////////////////////////////////////////////////////////////////// +// AsyncContext class +//////////////////////////////////////////////////////////////////////////////// + +inline AsyncContext::AsyncContext(napi_env env, const char* resource_name) + : AsyncContext(env, resource_name, Object::New(env)) { +} + +inline AsyncContext::AsyncContext(napi_env env, + const char* resource_name, + const Object& resource) + : _env(env), + _context(nullptr) { + napi_value resource_id; + napi_status status = napi_create_string_utf8( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + status = napi_async_init(_env, resource, resource_id, &_context); + NAPI_THROW_IF_FAILED_VOID(_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; +} + +inline AsyncContext& AsyncContext::operator =(AsyncContext&& other) { + _env = other._env; + other._env = nullptr; + _context = other._context; + other._context = nullptr; + return *this; +} + +inline AsyncContext::operator napi_async_context() const { + return _context; +} + //////////////////////////////////////////////////////////////////////////////// // AsyncWorker class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 2e3753091..ed86272c3 100644 --- a/napi.h +++ b/napi.h @@ -925,9 +925,16 @@ namespace Napi { Value Call(napi_value recv, const std::vector& args) const; Value Call(napi_value recv, size_t argc, const napi_value* args) const; - Value MakeCallback(napi_value recv, const std::initializer_list& args) const; - Value MakeCallback(napi_value recv, const std::vector& args) const; - Value MakeCallback(napi_value recv, size_t argc, const napi_value* args) const; + Value MakeCallback(napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + Value MakeCallback(napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + Value MakeCallback(napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; Object New(const std::initializer_list& args) const; Object New(const std::vector& args) const; @@ -1099,9 +1106,16 @@ namespace Napi { Napi::Value Call(napi_value recv, const std::vector& args) const; Napi::Value Call(napi_value recv, size_t argc, const napi_value* args) const; - Napi::Value MakeCallback(napi_value recv, const std::initializer_list& args) const; - Napi::Value MakeCallback(napi_value recv, const std::vector& args) const; - Napi::Value MakeCallback(napi_value recv, size_t argc, const napi_value* args) const; + Napi::Value MakeCallback(napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + Napi::Value MakeCallback(napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + Napi::Value MakeCallback(napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; Object New(const std::initializer_list& args) const; Object New(const std::vector& args) const; @@ -1583,6 +1597,24 @@ namespace Napi { napi_escapable_handle_scope _scope; }; + class AsyncContext { + public: + explicit AsyncContext(napi_env env, const char* resource_name); + explicit AsyncContext(napi_env env, const char* resource_name, const Object& resource); + virtual ~AsyncContext(); + + AsyncContext(AsyncContext&& other); + AsyncContext& operator =(AsyncContext&& other); + AsyncContext(const AsyncContext&) = delete; + AsyncContext& operator =(AsyncContext&) = delete; + + operator napi_async_context() const; + + private: + napi_env _env; + napi_async_context _context; + }; + class AsyncWorker { public: virtual ~AsyncWorker(); diff --git a/test/asynccontext.cc b/test/asynccontext.cc new file mode 100644 index 000000000..bb1acbb89 --- /dev/null +++ b/test/asynccontext.cc @@ -0,0 +1,21 @@ +#include "napi.h" + +using namespace Napi; + +namespace { + +static void MakeCallback(const CallbackInfo& info) { + Function callback = info[0].As(); + Object resource = info[1].As(); + AsyncContext context(info.Env(), "async_context_test", resource); + callback.MakeCallback(Object::New(info.Env()), + std::initializer_list{}, context); +} + +} // end anonymous namespace + +Object InitAsyncContext(Env env) { + Object exports = Object::New(env); + exports["makeCallback"] = Function::New(env, MakeCallback); + return exports; +} diff --git a/test/asynccontext.js b/test/asynccontext.js new file mode 100644 index 000000000..e9b4aabc3 --- /dev/null +++ b/test/asynccontext.js @@ -0,0 +1,73 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const common = require('./common'); + +// we only check async hooks on 8.x an higher were +// they are closer to working properly +const nodeVersion = process.versions.node.split('.')[0] +let async_hooks = undefined; +function checkAsyncHooks() { + if (nodeVersion >= 8) { + if (async_hooks == undefined) { + async_hooks = require('async_hooks'); + } + return true; + } + return false; +} + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function installAsyncHooksForTest() { + return new Promise((resolve, reject) => { + let id; + const events = []; + const hook = async_hooks.createHook({ + init(asyncId, type, triggerAsyncId, resource) { + if (id === undefined && type === 'async_context_test') { + id = asyncId; + events.push({ eventName: 'init', type, triggerAsyncId, resource }); + } + }, + before(asyncId) { + if (asyncId === id) { + events.push({ eventName: 'before' }); + } + }, + after(asyncId) { + if (asyncId === id) { + events.push({ eventName: 'after' }); + } + }, + destroy(asyncId) { + if (asyncId === id) { + events.push({ eventName: 'destroy' }); + hook.disable(); + resolve(events); + } + } + }).enable(); + }); +} + +function test(binding) { + binding.asynccontext.makeCallback(common.mustCall(), { foo: 'foo' }); + if (!checkAsyncHooks()) + return; + + const hooks = installAsyncHooksForTest(); + const triggerAsyncId = async_hooks.executionAsyncId(); + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { eventName: 'init', + type: 'async_context_test', + triggerAsyncId: triggerAsyncId, + resource: { foo: 'foo' } }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); +} diff --git a/test/binding.cc b/test/binding.cc index c2bd101f3..ffa3ec7a0 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -4,6 +4,7 @@ using namespace Napi; Object InitArrayBuffer(Env env); +Object InitAsyncContext(Env env); Object InitAsyncWorker(Env env); Object InitBasicTypesBoolean(Env env); Object InitBasicTypesNumber(Env env); @@ -31,6 +32,7 @@ Object InitVersionManagement(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_boolean", InitBasicTypesBoolean(env)); exports.Set("basic_types_number", InitBasicTypesNumber(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 7bff35e8a..2eaccdb8b 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -5,6 +5,7 @@ 'target_defaults': { 'sources': [ 'arraybuffer.cc', + 'asynccontext.cc', 'asyncworker.cc', 'basic_types/boolean.cc', 'basic_types/number.cc', diff --git a/test/index.js b/test/index.js index 25b9066c2..a0dadf877 100644 --- a/test/index.js +++ b/test/index.js @@ -9,6 +9,7 @@ process.config.target_defaults.default_configuration = // explicit declaration as follows. let testModules = [ 'arraybuffer', + 'asynccontext', 'asyncworker', 'basic_types/boolean', 'basic_types/number', From 917bd60baa2f3ff800b8e63baa9301bec7e65b90 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 26 Sep 2018 09:29:22 -0400 Subject: [PATCH 050/696] src: remove TODOs by fixing memory leaks PR-URL: https://github.com/nodejs/node-addon-api/pull/343 Fixes: https://github.com/nodejs/node-addon-api/issues/333 Reviewed-By: Michael Dawson --- README.md | 6 + doc/property_descriptor.md | 114 ++++++++++-- doc/setup.md | 3 + napi-inl.deprecated.h | 192 ++++++++++++++++++++ napi-inl.h | 298 +++++++++++++++++++++---------- napi.h | 74 ++++++++ test/binding.cc | 8 + test/binding.gyp | 15 +- test/index.js | 1 + test/object/object.cc | 35 ++-- test/object/object_deprecated.cc | 66 +++++++ test/object/object_deprecated.js | 48 +++++ test/objectwrap.js | 2 +- test/thunking_manual.cc | 140 +++++++++++++++ test/thunking_manual.js | 18 ++ 15 files changed, 895 insertions(+), 125 deletions(-) create mode 100644 napi-inl.deprecated.h create mode 100644 test/object/object_deprecated.cc create mode 100644 test/object/object_deprecated.js create mode 100644 test/thunking_manual.cc create mode 100644 test/thunking_manual.js diff --git a/README.md b/README.md index 4f5be7122..c27000a95 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,12 @@ npm install npm test ``` +To avoid testing the deprecated portions of the API run +``` +npm install +npm test --disable-deprecated +``` + Take a look and get inspired by our **[test suite](https://github.com/nodejs/node-addon-api/tree/master/test)** diff --git a/doc/property_descriptor.md b/doc/property_descriptor.md index 2c0fa6fac..82a87191f 100644 --- a/doc/property_descriptor.md +++ b/doc/property_descriptor.md @@ -22,19 +22,31 @@ Value TestFunction(const CallbackInfo& info) { } Void Init(Env env) { - // Accessor - PropertyDescriptor pd1 = PropertyDescriptor::Accessor("pd1", TestGetter); - PropertyDescriptor pd2 = PropertyDescriptor::Accessor("pd2", TestGetter, TestSetter); - - // Function - PropertyDescriptor pd3 = PropertyDescriptor::Function("function", TestFunction); - - // Value - Boolean true_bool = Boolean::New(env, true); - PropertyDescriptor pd4 = PropertyDescriptor::Value("boolean value", TestFunction, napi_writable); - - // Assign to an Object + // Create an object. Object obj = Object::New(env); + + // Accessor + PropertyDescriptor pd1 = PropertyDescriptor::Accessor(env, + obj, + "pd1", + TestGetter); + PropertyDescriptor pd2 = PropertyDescriptor::Accessor(env, + obj, + "pd2", + TestGetter, + TestSetter); + // Function + PropertyDescriptor pd3 = PropertyDescriptor::Function(env, + "function", + TestFunction); + // Value + Boolean true_bool = Boolean::New(env, true); + PropertyDescriptor pd4 = + PropertyDescriptor::Value("boolean value", + Napi::Boolean::New(env, true), + napi_writable); + + // Assign properties to the object. obj.DefineProperties({pd1, pd2, pd3, pd4}); } ``` @@ -71,6 +83,32 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` +**This signature is deprecated. It will result in a memory leak if used.** + +```cpp +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor ( + Napi::Env env, + Napi::Object object, + ___ name, + Getter getter, + napi_property_attributes attributes = napi_default, + void *data = nullptr); +``` + +* `[in] env`: The environemnt in which to create this accessor. +* `[in] object`: The object on which the accessor will be defined. +* `[in] name`: The name used for the getter function. +* `[in] getter`: A getter function. +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a `Napi::PropertyDescriptor` that contains a `Getter` accessor. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `Napi::Name` + ```cpp static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, Getter getter, @@ -93,6 +131,34 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` +**This signature is deprecated. It will result in a memory leak if used.** + +```cpp +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor ( + Napi::Env env, + Napi::Object object, + ___ name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void *data = nullptr); +``` + +* `[in] env`: The environemnt in which to create this accessor. +* `[in] object`: The object on which the accessor will be defined. +* `[in] name`: The name of the getter and setter function. +* `[in] getter`: The getter function. +* `[in] setter`: The setter function. +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a `Napi::PropertyDescriptor` that contains a `Getter` and `Setter` function. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `Napi::Name` + ### Function ```cpp @@ -115,6 +181,30 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` +**This signature is deprecated. It will result in a memory leak if used.** + +```cpp +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Function ( + Napi::Env env, + ___ name, + Callable cb, + napi_property_attributes attributes = napi_default, + void *data = nullptr); +``` + +* `[in] env`: The environemnt in which to create this accessor. +* `[in] name`: The name of the Callable function. +* `[in] cb`: The function +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a `Napi::PropertyDescriptor` that contains a callable `Napi::Function`. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `Napi::Name` + ### Value ```cpp diff --git a/doc/setup.md b/doc/setup.md index 542729a69..36e6fc956 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -66,3 +66,6 @@ To use **N-API** in a native module: At build time, the N-API back-compat library code will be used only when the targeted node version *does not* have N-API built-in. + +The preprocessor directive `NODE_ADDON_API_DISABLE_DEPRECATED` can be defined at +compile time before including `napi.h` to skip the definition of deprecated APIs. diff --git a/napi-inl.deprecated.h b/napi-inl.deprecated.h new file mode 100644 index 000000000..d00174357 --- /dev/null +++ b/napi-inl.deprecated.h @@ -0,0 +1,192 @@ +#ifndef SRC_NAPI_INL_DEPRECATED_H_ +#define SRC_NAPI_INL_DEPRECATED_H_ + +//////////////////////////////////////////////////////////////////////////////// +// PropertyDescriptor class +//////////////////////////////////////////////////////////////////////////////// + +template +inline PropertyDescriptor +PropertyDescriptor::Accessor(const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter, nullptr }); + + return PropertyDescriptor({ + utf8name, + nullptr, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter, nullptr }); + + return PropertyDescriptor({ + nullptr, + name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, + Getter getter, + napi_property_attributes attributes, + void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Accessor(nameValue, getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { + typedef details::AccessorCallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter, setter }); + + return PropertyDescriptor({ + utf8name, + nullptr, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { + typedef details::AccessorCallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ getter, setter }); + + return PropertyDescriptor({ + nullptr, + name, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Accessor(nameValue, getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { + typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ cb, nullptr }); + + return PropertyDescriptor({ + utf8name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return Function(utf8name.c_str(), cb, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(napi_value name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { + typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; + typedef details::CallbackData CbData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({ cb, nullptr }); + + return PropertyDescriptor({ + nullptr, + name, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData + }); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function(Name name, + Callable cb, + napi_property_attributes attributes, + void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Function(nameValue, cb, attributes, data); +} + +#endif // !SRC_NAPI_INL_DEPRECATED_H_ diff --git a/napi-inl.h b/napi-inl.h index aead8b9ce..6d8a021c1 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -60,6 +60,41 @@ namespace details { } \ } while (0) +// Attach a data item to an object and delete it when the object gets +// garbage-collected. +// TODO: Replace this code with `napi_add_finalizer()` whenever it becomes +// available on all supported versions of Node.js. +template +static inline napi_status AttachData(napi_env env, + napi_value obj, + FreeType* data) { + napi_value symbol, external; + napi_status status = napi_create_symbol(env, nullptr, &symbol); + if (status == napi_ok) { + status = napi_create_external(env, + data, + [](napi_env /*env*/, void* data, void* /*hint*/) { + delete static_cast(data); + }, + nullptr, + &external); + if (status == napi_ok) { + napi_property_descriptor desc = { + nullptr, + symbol, + nullptr, + nullptr, + nullptr, + external, + napi_default, + nullptr + }; + status = napi_define_properties(env, obj, 1, &desc); + } + } + return status; +} + // For use in JS to C++ callback wrappers to catch any Napi::Error exceptions // and rethrow them as JavaScript exceptions before returning from the callback. template @@ -162,6 +197,10 @@ struct AccessorCallbackData { } // namespace details +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED +# include "napi-inl.deprecated.h" +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED + //////////////////////////////////////////////////////////////////////////////// // Module registration //////////////////////////////////////////////////////////////////////////////// @@ -1587,6 +1626,22 @@ inline const T* TypedArrayOf::Data() const { // Function class //////////////////////////////////////////////////////////////////////////////// +template +static inline napi_status +CreateFunction(napi_env env, + const char* utf8name, + napi_callback cb, + CbData* data, + napi_value* result) { + napi_status status = + napi_create_function(env, utf8name, NAPI_AUTO_LENGTH, cb, data, result); + if (status == napi_ok) { + status = Napi::details::AttachData(env, *result, data); + } + + return status; +} + template inline Function Function::New(napi_env env, Callable cb, @@ -1594,12 +1649,14 @@ inline Function Function::New(napi_env env, void* data) { typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; typedef details::CallbackData CbData; - // TODO: Delete when the function is destroyed auto callbackData = new CbData({ cb, data }); napi_value value; - napi_status status = napi_create_function( - env, utf8name, NAPI_AUTO_LENGTH, CbData::Wrapper, callbackData, &value); + napi_status status = CreateFunction(env, + utf8name, + CbData::Wrapper, + callbackData, + &value); NAPI_THROW_IF_FAILED(env, status, Function()); return Function(env, value); } @@ -2541,14 +2598,18 @@ inline void CallbackInfo::SetData(void* data) { template inline PropertyDescriptor -PropertyDescriptor::Accessor(const char* utf8name, +PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, Getter getter, napi_property_attributes attributes, void* /*data*/) { typedef details::CallbackData CbData; - // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, nullptr }); + napi_status status = AttachData(env, object, callbackData); + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + return PropertyDescriptor({ utf8name, nullptr, @@ -2562,22 +2623,28 @@ PropertyDescriptor::Accessor(const char* utf8name, } template -inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, +inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, Getter getter, napi_property_attributes attributes, void* data) { - return Accessor(utf8name.c_str(), getter, attributes, data); + return Accessor(env, object, utf8name.c_str(), getter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, +inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + Name name, Getter getter, napi_property_attributes attributes, void* /*data*/) { typedef details::CallbackData CbData; - // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, nullptr }); + napi_status status = AttachData(env, object, callbackData); + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + return PropertyDescriptor({ nullptr, name, @@ -2590,25 +2657,20 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, }); } -template -inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, - Getter getter, - napi_property_attributes attributes, - void* data) { - napi_value nameValue = name; - return PropertyDescriptor::Accessor(nameValue, getter, attributes, data); -} - template -inline PropertyDescriptor PropertyDescriptor::Accessor(const char* utf8name, +inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, Getter getter, Setter setter, napi_property_attributes attributes, void* /*data*/) { typedef details::AccessorCallbackData CbData; - // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, setter }); + napi_status status = AttachData(env, object, callbackData); + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + return PropertyDescriptor({ utf8name, nullptr, @@ -2622,24 +2684,30 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(const char* utf8name, } template -inline PropertyDescriptor PropertyDescriptor::Accessor(const std::string& utf8name, +inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, Getter getter, Setter setter, napi_property_attributes attributes, void* data) { - return Accessor(utf8name.c_str(), getter, setter, attributes, data); + return Accessor(env, object, utf8name.c_str(), getter, setter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, +inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + Name name, Getter getter, Setter setter, napi_property_attributes attributes, void* /*data*/) { typedef details::AccessorCallbackData CbData; - // TODO: Delete when the function is destroyed auto callbackData = new CbData({ getter, setter }); + napi_status status = AttachData(env, object, callbackData); + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + return PropertyDescriptor({ nullptr, name, @@ -2652,77 +2720,54 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, }); } -template -inline PropertyDescriptor PropertyDescriptor::Accessor(Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { - napi_value nameValue = name; - return PropertyDescriptor::Accessor(nameValue, getter, setter, attributes, data); -} - template -inline PropertyDescriptor PropertyDescriptor::Function(const char* utf8name, +inline PropertyDescriptor PropertyDescriptor::Function(Napi::Env env, + Napi::Object /*object*/, + const char* utf8name, Callable cb, napi_property_attributes attributes, - void* /*data*/) { - typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; - typedef details::CallbackData CbData; - // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ cb, nullptr }); - + void* data) { return PropertyDescriptor({ utf8name, nullptr, - CbData::Wrapper, nullptr, nullptr, nullptr, + Napi::Function::New(env, cb, utf8name, data), attributes, - callbackData + nullptr }); } template -inline PropertyDescriptor PropertyDescriptor::Function(const std::string& utf8name, +inline PropertyDescriptor PropertyDescriptor::Function(Napi::Env env, + Napi::Object object, + const std::string& utf8name, Callable cb, napi_property_attributes attributes, void* data) { - return Function(utf8name.c_str(), cb, attributes, data); + return Function(env, object, utf8name.c_str(), cb, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Function(napi_value name, +inline PropertyDescriptor PropertyDescriptor::Function(Napi::Env env, + Napi::Object /*object*/, + Name name, Callable cb, napi_property_attributes attributes, - void* /*data*/) { - typedef decltype(cb(CallbackInfo(nullptr, nullptr))) ReturnType; - typedef details::CallbackData CbData; - // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ cb, nullptr }); - + void* data) { return PropertyDescriptor({ nullptr, name, - CbData::Wrapper, nullptr, nullptr, nullptr, + Napi::Function::New(env, cb, nullptr, data), attributes, - callbackData + nullptr }); } -template -inline PropertyDescriptor PropertyDescriptor::Function(Name name, - Callable cb, - napi_property_attributes attributes, - void* data) { - napi_value nameValue = name; - return PropertyDescriptor::Function(nameValue, cb, attributes, data); -} - inline PropertyDescriptor PropertyDescriptor::Value(const char* utf8name, napi_value value, napi_property_attributes attributes) { @@ -2791,20 +2836,106 @@ inline T* ObjectWrap::Unwrap(Object wrapper) { return unwrapped; } +template +inline Function +ObjectWrap::DefineClass(Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* descriptors, + void* data) { + napi_status status; + std::vector props(props_count); + + // We copy the descriptors to a local array because before defining the class + // we must replace static method property descriptors with value property + // descriptors such that the value is a function-valued `napi_value` created + // with `CreateFunction()`. + // + // This replacement could be made for instance methods as well, but V8 aborts + // if we do that, because it expects methods defined on the prototype template + // to have `FunctionTemplate`s. + for (size_t index = 0; index < props_count; index++) { + props[index] = descriptors[index]; + napi_property_descriptor* prop = &props[index]; + if (prop->method == T::StaticMethodCallbackWrapper) { + status = CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } else if (prop->method == T::StaticVoidMethodCallbackWrapper) { + status = CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } + } + + napi_value value; + status = napi_define_class(env, + utf8name, + NAPI_AUTO_LENGTH, + T::ConstructorCallbackWrapper, + data, + props_count, + props.data(), + &value); + NAPI_THROW_IF_FAILED(env, status, Function()); + + // After defining the class we iterate once more over the property descriptors + // and attach the data associated with accessors and instance methods to the + // newly created JavaScript class. + for (size_t idx = 0; idx < props_count; idx++) { + const napi_property_descriptor* prop = &props[idx]; + + if (prop->getter == T::StaticGetterCallbackWrapper || + prop->setter == T::StaticSetterCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } else if (prop->getter == T::InstanceGetterCallbackWrapper || + prop->setter == T::InstanceSetterCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } else if (prop->method != nullptr && !(prop->attributes & napi_static)) { + if (prop->method == T::InstanceVoidMethodCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } else if (prop->method == T::InstanceMethodCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } + } + } + + return Function(env, value); +} + template inline Function ObjectWrap::DefineClass( Napi::Env env, const char* utf8name, const std::initializer_list>& properties, void* data) { - napi_value value; - napi_status status = napi_define_class( - env, utf8name, NAPI_AUTO_LENGTH, - T::ConstructorCallbackWrapper, data, properties.size(), - reinterpret_cast(properties.begin()), &value); - NAPI_THROW_IF_FAILED(env, status, Function()); - - return Function(env, value); + return DefineClass(env, + utf8name, + properties.size(), + reinterpret_cast(properties.begin()), + data); } template @@ -2813,14 +2944,11 @@ inline Function ObjectWrap::DefineClass( const char* utf8name, const std::vector>& properties, void* data) { - napi_value value; - napi_status status = napi_define_class( - env, utf8name, NAPI_AUTO_LENGTH, - T::ConstructorCallbackWrapper, data, properties.size(), - reinterpret_cast(properties.data()), &value); - NAPI_THROW_IF_FAILED(env, status, Function()); - - return Function(env, value); + return DefineClass(env, + utf8name, + properties.size(), + reinterpret_cast(properties.data()), + data); } template @@ -2829,7 +2957,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticVoidMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -2846,7 +2973,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -2863,7 +2989,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticVoidMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -2880,7 +3005,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( StaticMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -2898,7 +3022,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( StaticSetterCallback setter, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticAccessorCallbackData* callbackData = new StaticAccessorCallbackData({ getter, setter, data }); @@ -2918,7 +3041,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( StaticSetterCallback setter, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed StaticAccessorCallbackData* callbackData = new StaticAccessorCallbackData({ getter, setter, data }); @@ -2937,7 +3059,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( InstanceVoidMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceVoidMethodCallbackData* callbackData = new InstanceVoidMethodCallbackData({ method, data}); @@ -2955,7 +3076,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( InstanceMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -2972,7 +3092,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( InstanceVoidMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceVoidMethodCallbackData* callbackData = new InstanceVoidMethodCallbackData({ method, data}); @@ -2990,7 +3109,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( InstanceMethodCallback method, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); @@ -3008,7 +3126,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( InstanceSetterCallback setter, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceAccessorCallbackData* callbackData = new InstanceAccessorCallbackData({ getter, setter, data }); @@ -3028,7 +3145,6 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( InstanceSetterCallback setter, napi_property_attributes attributes, void* data) { - // TODO: Delete when the class is destroyed InstanceAccessorCallbackData* callbackData = new InstanceAccessorCallbackData({ getter, setter, data }); diff --git a/napi.h b/napi.h index ed86272c3..61df5fe19 100644 --- a/napi.h +++ b/napi.h @@ -1312,6 +1312,7 @@ namespace Napi { class PropertyDescriptor { public: +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED template static PropertyDescriptor Accessor(const char* utf8name, Getter getter, @@ -1376,6 +1377,74 @@ namespace Napi { Callable cb, napi_property_attributes attributes = napi_default, void* data = nullptr); +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED + + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor(Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function(Napi::Env env, + Napi::Object object, + const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function(Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function(Napi::Env env, + Napi::Object object, + Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor Value(const char* utf8name, napi_value value, napi_property_attributes attributes = napi_default); @@ -1543,6 +1612,11 @@ namespace Napi { static napi_value InstanceGetterCallbackWrapper(napi_env env, napi_callback_info info); static napi_value InstanceSetterCallbackWrapper(napi_env env, napi_callback_info info); static void FinalizeCallback(napi_env env, void* data, void* hint); + static Function DefineClass(Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* props, + void* data = nullptr); template struct MethodCallbackData { diff --git a/test/binding.cc b/test/binding.cc index ffa3ec7a0..101ac1f85 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -24,11 +24,15 @@ Object InitHandleScope(Env env); Object InitMemoryManagement(Env env); Object InitName(Env env); Object InitObject(Env env); +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED +Object InitObjectDeprecated(Env env); +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED Object InitPromise(Env env); Object InitTypedArray(Env env); Object InitObjectWrap(Env env); Object InitObjectReference(Env env); Object InitVersionManagement(Env env); +Object InitThunkingManual(Env env); Object Init(Env env, Object exports) { exports.Set("arraybuffer", InitArrayBuffer(env)); @@ -53,11 +57,15 @@ Object Init(Env env, Object exports) { exports.Set("handlescope", InitHandleScope(env)); exports.Set("memory_management", InitMemoryManagement(env)); exports.Set("object", InitObject(env)); +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED + exports.Set("object_deprecated", InitObjectDeprecated(env)); +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED exports.Set("promise", InitPromise(env)); exports.Set("typedarray", InitTypedArray(env)); exports.Set("objectwrap", InitObjectWrap(env)); exports.Set("objectreference", InitObjectReference(env)); exports.Set("version_management", InitVersionManagement(env)); + exports.Set("thunking_manual", InitThunkingManual(env)); return exports; } diff --git a/test/binding.gyp b/test/binding.gyp index 2eaccdb8b..417a2bb3b 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -1,6 +1,7 @@ { 'variables': { - 'NAPI_VERSION%': "" + 'NAPI_VERSION%': "", + 'disable_deprecated': "(); String nameType = info[1].As(); + Env env = info.Env(); - Boolean trueValue = Boolean::New(info.Env(), true); + Boolean trueValue = Boolean::New(env, true); if (nameType.Utf8Value() == "literal") { obj.DefineProperties({ - PropertyDescriptor::Accessor("readonlyAccessor", TestGetter), - PropertyDescriptor::Accessor("readwriteAccessor", TestGetter, TestSetter), + PropertyDescriptor::Accessor(env, obj, "readonlyAccessor", TestGetter), + PropertyDescriptor::Accessor(env, obj, "readwriteAccessor", TestGetter, TestSetter), PropertyDescriptor::Value("readonlyValue", trueValue), PropertyDescriptor::Value("readwriteValue", trueValue, napi_writable), PropertyDescriptor::Value("enumerableValue", trueValue, napi_enumerable), PropertyDescriptor::Value("configurableValue", trueValue, napi_configurable), - PropertyDescriptor::Function("function", TestFunction), + PropertyDescriptor::Function(env, obj, "function", TestFunction), }); } else if (nameType.Utf8Value() == "string") { // VS2013 has lifetime issues when passing temporary objects into the constructor of another @@ -82,30 +83,30 @@ void DefineProperties(const CallbackInfo& info) { std::string str7("function"); obj.DefineProperties({ - PropertyDescriptor::Accessor(str1, TestGetter), - PropertyDescriptor::Accessor(str2, TestGetter, TestSetter), + PropertyDescriptor::Accessor(env, obj, str1, TestGetter), + PropertyDescriptor::Accessor(env, obj, str2, TestGetter, TestSetter), PropertyDescriptor::Value(str3, trueValue), PropertyDescriptor::Value(str4, trueValue, napi_writable), PropertyDescriptor::Value(str5, trueValue, napi_enumerable), PropertyDescriptor::Value(str6, trueValue, napi_configurable), - PropertyDescriptor::Function(str7, TestFunction), + PropertyDescriptor::Function(env, obj, str7, TestFunction), }); } else if (nameType.Utf8Value() == "value") { obj.DefineProperties({ - PropertyDescriptor::Accessor( - Napi::String::New(info.Env(), "readonlyAccessor"), TestGetter), - PropertyDescriptor::Accessor( - Napi::String::New(info.Env(), "readwriteAccessor"), TestGetter, TestSetter), + PropertyDescriptor::Accessor(env, obj, + Napi::String::New(env, "readonlyAccessor"), TestGetter), + PropertyDescriptor::Accessor(env, obj, + Napi::String::New(env, "readwriteAccessor"), TestGetter, TestSetter), PropertyDescriptor::Value( - Napi::String::New(info.Env(), "readonlyValue"), trueValue), + Napi::String::New(env, "readonlyValue"), trueValue), PropertyDescriptor::Value( - Napi::String::New(info.Env(), "readwriteValue"), trueValue, napi_writable), + Napi::String::New(env, "readwriteValue"), trueValue, napi_writable), PropertyDescriptor::Value( - Napi::String::New(info.Env(), "enumerableValue"), trueValue, napi_enumerable), + Napi::String::New(env, "enumerableValue"), trueValue, napi_enumerable), PropertyDescriptor::Value( - Napi::String::New(info.Env(), "configurableValue"), trueValue, napi_configurable), - PropertyDescriptor::Function( - Napi::String::New(info.Env(), "function"), TestFunction), + Napi::String::New(env, "configurableValue"), trueValue, napi_configurable), + PropertyDescriptor::Function(env, obj, + Napi::String::New(env, "function"), TestFunction), }); } } diff --git a/test/object/object_deprecated.cc b/test/object/object_deprecated.cc new file mode 100644 index 000000000..2ec16e579 --- /dev/null +++ b/test/object/object_deprecated.cc @@ -0,0 +1,66 @@ +#include "napi.h" + +using namespace Napi; + +static bool testValue = true; + +namespace { + +Value TestGetter(const CallbackInfo& info) { + return Boolean::New(info.Env(), testValue); +} + +void TestSetter(const CallbackInfo& info) { + testValue = info[0].As(); +} + +Value TestFunction(const CallbackInfo& info) { + return Boolean::New(info.Env(), true); +} + +void DefineProperties(const CallbackInfo& info) { + Object obj = info[0].As(); + String nameType = info[1].As(); + Env env = info.Env(); + + if (nameType.Utf8Value() == "literal") { + obj.DefineProperties({ + PropertyDescriptor::Accessor("readonlyAccessor", TestGetter), + PropertyDescriptor::Accessor("readwriteAccessor", TestGetter, TestSetter), + PropertyDescriptor::Function("function", TestFunction), + }); + } else if (nameType.Utf8Value() == "string") { + // VS2013 has lifetime issues when passing temporary objects into the constructor of another + // object. It generates code to destruct the object as soon as the constructor call returns. + // Since this isn't a common case for using std::string objects, I'm refactoring the test to + // work around the issue. + std::string str1("readonlyAccessor"); + std::string str2("readwriteAccessor"); + std::string str7("function"); + + obj.DefineProperties({ + PropertyDescriptor::Accessor(str1, TestGetter), + PropertyDescriptor::Accessor(str2, TestGetter, TestSetter), + PropertyDescriptor::Function(str7, TestFunction), + }); + } else if (nameType.Utf8Value() == "value") { + obj.DefineProperties({ + PropertyDescriptor::Accessor( + Napi::String::New(env, "readonlyAccessor"), TestGetter), + PropertyDescriptor::Accessor( + Napi::String::New(env, "readwriteAccessor"), TestGetter, TestSetter), + PropertyDescriptor::Function( + Napi::String::New(env, "function"), TestFunction), + }); + } +} + +} // end of anonymous namespace + +Object InitObjectDeprecated(Env env) { + Object exports = Object::New(env); + + exports["defineProperties"] = Function::New(env, DefineProperties); + + return exports; +} diff --git a/test/object/object_deprecated.js b/test/object/object_deprecated.js new file mode 100644 index 000000000..153fb11e1 --- /dev/null +++ b/test/object/object_deprecated.js @@ -0,0 +1,48 @@ +'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) { + if (!('object_deprecated' in binding)) { + return; + } + function assertPropertyIs(obj, key, attribute) { + const propDesc = Object.getOwnPropertyDescriptor(obj, key); + assert.ok(propDesc); + assert.ok(propDesc[attribute]); + } + + function assertPropertyIsNot(obj, key, attribute) { + const propDesc = Object.getOwnPropertyDescriptor(obj, key); + assert.ok(propDesc); + assert.ok(!propDesc[attribute]); + } + + function testDefineProperties(nameType) { + const obj = {}; + binding.object.defineProperties(obj, nameType); + + assertPropertyIsNot(obj, 'readonlyAccessor', 'enumerable'); + assertPropertyIsNot(obj, 'readonlyAccessor', 'configurable'); + assert.strictEqual(obj.readonlyAccessor, true); + + assertPropertyIsNot(obj, 'readwriteAccessor', 'enumerable'); + assertPropertyIsNot(obj, 'readwriteAccessor', 'configurable'); + obj.readwriteAccessor = false; + assert.strictEqual(obj.readwriteAccessor, false); + obj.readwriteAccessor = true; + assert.strictEqual(obj.readwriteAccessor, true); + + assertPropertyIsNot(obj, 'function', 'writable'); + assertPropertyIsNot(obj, 'function', 'enumerable'); + assertPropertyIsNot(obj, 'function', 'configurable'); + assert.strictEqual(obj.function(), true); + } + + testDefineProperties('literal'); + testDefineProperties('string'); + testDefineProperties('value'); +} diff --git a/test/objectwrap.js b/test/objectwrap.js index 72805000d..1f888234d 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -207,4 +207,4 @@ const test = (binding) => { } test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); \ No newline at end of file +test(require(`./build/${buildType}/binding_noexcept.node`)); diff --git a/test/thunking_manual.cc b/test/thunking_manual.cc new file mode 100644 index 000000000..d52302ea3 --- /dev/null +++ b/test/thunking_manual.cc @@ -0,0 +1,140 @@ +#include + +// The formulaic comment below should accompany any code that results in an +// internal piece of heap data getting created, because each such piece of heap +// data must be attached to an object by way of a deleter which gets called when +// the object gets garbage-collected. +// +// At the very least, you can add a fprintf(stderr, ...) to the deleter in +// napi-inl.h and then count the number of times the deleter prints by running +// node --expose-gc test/thunking_manual.js and counting the number of prints +// between the two rows of dashes. That number should coincide with the number +// of formulaic comments below. +// +// Note that currently this result can only be achieved with node-chakracore, +// because V8 does not garbage-collect classes. + +static Napi::Value TestMethod(const Napi::CallbackInfo& /*info*/) { + return Napi::Value(); +} + +static Napi::Value TestGetter(const Napi::CallbackInfo& /*info*/) { + return Napi::Value(); +} + +static void TestSetter(const Napi::CallbackInfo& /*info*/) { +} + +class TestClass : public Napi::ObjectWrap { + public: + TestClass(const Napi::CallbackInfo& info): + ObjectWrap(info) { + } + static Napi::Value TestClassStaticMethod(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), 42); + } + + static void TestClassStaticVoidMethod(const Napi::CallbackInfo& /*info*/) { + } + + Napi::Value TestClassInstanceMethod(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), 42); + } + + void TestClassInstanceVoidMethod(const Napi::CallbackInfo& /*info*/) { + } + + Napi::Value TestClassInstanceGetter(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), 42); + } + + void TestClassInstanceSetter(const Napi::CallbackInfo& /*info*/, + const Napi::Value& /*new_value*/) { + } + + static Napi::Function NewClass(Napi::Env env) { + return DefineClass(env, "TestClass", { + // Make sure to check that the deleter gets called. + StaticMethod("staticMethod", TestClassStaticMethod), + // Make sure to check that the deleter gets called. + StaticMethod("staticVoidMethod", TestClassStaticVoidMethod), + // Make sure to check that the deleter gets called. + StaticMethod(Napi::Symbol::New(env, "staticMethod"), + TestClassStaticMethod), + // Make sure to check that the deleter gets called. + StaticMethod(Napi::Symbol::New(env, "staticVoidMethod"), + TestClassStaticVoidMethod), + // Make sure to check that the deleter gets called. + InstanceMethod("instanceMethod", &TestClass::TestClassInstanceMethod), + // Make sure to check that the deleter gets called. + InstanceMethod("instanceVoidMethod", + &TestClass::TestClassInstanceVoidMethod), + // Make sure to check that the deleter gets called. + InstanceMethod(Napi::Symbol::New(env, "instanceMethod"), + &TestClass::TestClassInstanceMethod), + // Make sure to check that the deleter gets called. + InstanceMethod(Napi::Symbol::New(env, "instanceVoidMethod"), + &TestClass::TestClassInstanceVoidMethod), + // Make sure to check that the deleter gets called. + InstanceAccessor("instanceAccessor", + &TestClass::TestClassInstanceGetter, + &TestClass::TestClassInstanceSetter) + }); + } +}; + +static Napi::Value CreateTestObject(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::Object item = Napi::Object::New(env); + + // Make sure to check that the deleter gets called. + item["testMethod"] = Napi::Function::New(env, TestMethod, "testMethod"); + + item.DefineProperties({ + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + "accessor_1", + TestGetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + std::string("accessor_1_std_string"), + TestGetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + Napi::String::New(info.Env(), + "accessor_1_js_string"), + TestGetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + "accessor_2", + TestGetter, + TestSetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + std::string("accessor_2_std_string"), + TestGetter, + TestSetter), + // Make sure to check that the deleter gets called. + Napi::PropertyDescriptor::Accessor(env, + item, + Napi::String::New(env, + "accessor_2_js_string"), + TestGetter, + TestSetter), + Napi::PropertyDescriptor::Value("TestClass", TestClass::NewClass(env)), + }); + + return item; +} + +Napi::Object InitThunkingManual(Napi::Env env) { + Napi::Object exports = Napi::Object::New(env); + exports["createTestObject"] = + Napi::Function::New(env, CreateTestObject, "createTestObject"); + return exports; +} diff --git a/test/thunking_manual.js b/test/thunking_manual.js new file mode 100644 index 000000000..22fb8877d --- /dev/null +++ b/test/thunking_manual.js @@ -0,0 +1,18 @@ +// Flags: --expose-gc +'use strict'; +const buildType = 'Debug'; +const assert = require('assert'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + console.log("Thunking: Performing initial GC"); + global.gc(); + console.log("Thunking: Creating test object"); + let object = binding.thunking_manual.createTestObject(); + object = null; + console.log("Thunking: About to GC\n--------"); + global.gc(); + console.log("--------\nThunking: GC complete"); +} From 223474900f8af7e9f7216d8af25ca27f84297c5f Mon Sep 17 00:00:00 2001 From: Dongjin Na Date: Tue, 2 Oct 2018 09:14:32 +0900 Subject: [PATCH 051/696] doc: update Version management PR-URL: https://github.com/nodejs/node-addon-api/pull/360 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- doc/version_management.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/version_management.md b/doc/version_management.md index e41663661..6d6c7fa8e 100644 --- a/doc/version_management.md +++ b/doc/version_management.md @@ -11,7 +11,7 @@ important to make decisions based on different versions of the system. Retrieves the highest N-API version supported by Node.js runtime. ```cpp -static uint32_t GetNapiVersion(Env env); +static uint32_t Napi::VersionManagement::GetNapiVersion(Env env); ``` - `[in] env`: The environment in which the API is invoked under. @@ -34,7 +34,7 @@ typedef struct { ```` ```cpp -static const napi_node_version* GetNodeVersion(Env env); +static const napi_node_version* Napi::VersionManagement::GetNodeVersion(Env env); ``` - `[in] env`: The environment in which the API is invoked under. From ffebf9ba9a3cfefe602756ffe0fc700a39e078ca Mon Sep 17 00:00:00 2001 From: NickNaso Date: Wed, 3 Oct 2018 01:11:04 +0200 Subject: [PATCH 052/696] Updates for release 1.5.0 --- CHANGELOG.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++-- README.md | 2 +- package.json | 8 +++++- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 216ca2545..031aee778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,71 @@ # node-addon-api Changelog -## 2018-07-19 Version 1.4.0 (Current), @NickNasso +## 2018-10-03 Version 1.5.0 (Current), @NickNasso + +### Notable changes: + +#### Documentation + +- Completed the documentation to cover all the API surface. +- Numerous fixes to make documentation more consistent in all of its parts. + +#### API + +- Add `Napi::AsyncContext` class to handle asynchronous operation. +- Add B`Napi::igInt` class to work with BigInt type. +- Add `Napi::VersionManagement` class to retrieve the versions of Node.js and N-API. +- Fix potential memory leaks. +- DataView feature is enabled by default +- Add descriptor for Symbols +- Add new methods on `Napi::FunctionReference`. +- Add the possibility to retrieve the environment on `Napi::Promise::Deferred` + +#### TOOL + +- Add tool to check if a native add-on is built using N-API + +#### TEST + +- Start to increase the test coverage +- Fix in the test suite to better handle the experimental features that are not +yet backported in the previous Node.js version. + +### Commits + +* [[`2009c019af`](https://github.com/nodejs/node-addon-api/commit/2009c019af)] - Merge pull request #292 from devsnek/feature/bigint (Gus Caplan) +* [[`e44aca985e`](https://github.com/nodejs/node-addon-api/commit/e44aca985e)] - add bigint class (Gus Caplan) +* [[`a3951ab973`](https://github.com/nodejs/node-addon-api/commit/a3951ab973)] - Add documentation for Env(). (Rolf Timmermans) [#318](https://github.com/nodejs/node-addon-api/pull/318) +* [[`a6f7a6ad51`](https://github.com/nodejs/node-addon-api/commit/a6f7a6ad51)] - Add Env() to Promise::Deferred. (Rolf Timmermans) +* [[`0097e96b92`](https://github.com/nodejs/node-addon-api/commit/0097e96b92)] - Fixed broken links for Symbol and String (NickNaso) +* [[`b0ecd38d76`](https://github.com/nodejs/node-addon-api/commit/b0ecd38d76)] - Fix Code of conduct link properly (#323) (Jake Yoon) [#323](https://github.com/nodejs/node-addon-api/pull/323) +* [[`223474900f`](https://github.com/nodejs/node-addon-api/commit/223474900f)] - **doc**: update Version management (Dongjin Na) [#360](https://github.com/nodejs/node-addon-api/pull/360) +* [[`4f76262a10`](https://github.com/nodejs/node-addon-api/commit/4f76262a10)] - **doc**: some fix on `Napi::Boolean` documentation (NickNaso) [#354](https://github.com/nodejs/node-addon-api/pull/354) +* [[`78374f72d2`](https://github.com/nodejs/node-addon-api/commit/78374f72d2)] - **doc**: number documentation (NickNaso) [#356](https://github.com/nodejs/node-addon-api/pull/356) +* [[`51ffe453f8`](https://github.com/nodejs/node-addon-api/commit/51ffe453f8)] - **doc**: doc cleanup (NickNaso) [#353](https://github.com/nodejs/node-addon-api/pull/353) +* [[`fc11c944b2`](https://github.com/nodejs/node-addon-api/commit/fc11c944b2)] - **doc**: major doc cleanup (NickNaso) [#335](https://github.com/nodejs/node-addon-api/pull/335) +* [[`100d0a7cb2`](https://github.com/nodejs/node-addon-api/commit/100d0a7cb2)] - **doc**: first pass on objectwrap documentation (NickNaso) [#321](https://github.com/nodejs/node-addon-api/pull/321) +* [[`c7d54180ff`](https://github.com/nodejs/node-addon-api/commit/c7d54180ff)] - **doc**: the Napi::ObjectWrap example does not compile (Arnaud Botella) [#339](https://github.com/nodejs/node-addon-api/pull/339) +* [[`7cdd78726a`](https://github.com/nodejs/node-addon-api/commit/7cdd78726a)] - **doc**: added cpp highlight for string.md (Jaeseok Yoon) [#329](https://github.com/nodejs/node-addon-api/pull/329) +* [[`8ed29f547c`](https://github.com/nodejs/node-addon-api/commit/8ed29f547c)] - **doc**: add blurb about ABI stability (Gabriel Schulhof) [#326](https://github.com/nodejs/node-addon-api/pull/326) +* [[`757eb1f5a3`](https://github.com/nodejs/node-addon-api/commit/757eb1f5a3)] - **doc**: add function and function reference doc (NickNaso) [#299](https://github.com/nodejs/node-addon-api/pull/299) +* [[`2885c18591`](https://github.com/nodejs/node-addon-api/commit/2885c18591)] - **doc**: Create changelog for release 1.4.0 (Nicola Del Gobbo) +* [[`917bd60baa`](https://github.com/nodejs/node-addon-api/commit/917bd60baa)] - **src**: remove TODOs by fixing memory leaks (Gabriel Schulhof) [#343](https://github.com/nodejs/node-addon-api/pull/343) +* [[`dfcb93945f`](https://github.com/nodejs/node-addon-api/commit/dfcb93945f)] - **src**: implement AsyncContext class (Jinho Bang) [#252](https://github.com/nodejs/node-addon-api/pull/252) +* [[`211ed38d0d`](https://github.com/nodejs/node-addon-api/commit/211ed38d0d)] - **src**: make 'nothing' target a static library (Gabriel Schulhof) [#348](https://github.com/nodejs/node-addon-api/pull/348) +* [[`97c4ab5cf2`](https://github.com/nodejs/node-addon-api/commit/97c4ab5cf2)] - **src**: add Call and MakeCallback that accept cargs (NickNaso) [#344](https://github.com/nodejs/node-addon-api/pull/344) +* [[`b6e2d92c09`](https://github.com/nodejs/node-addon-api/commit/b6e2d92c09)] - **src**: enable DataView feature by default (Jinho) [#331](https://github.com/nodejs/node-addon-api/pull/331) +* [[`0a00e7c97b`](https://github.com/nodejs/node-addon-api/commit/0a00e7c97b)] - **src**: implement missing descriptor defs for symbols (Philipp Renoth) [#280](https://github.com/nodejs/node-addon-api/pull/280) +* [[`38e01b7e3b`](https://github.com/nodejs/node-addon-api/commit/38e01b7e3b)] - **src**: first pass on adding version management apis (NickNaso) [#325](https://github.com/nodejs/node-addon-api/pull/325) +* [[`79ee8381d2`](https://github.com/nodejs/node-addon-api/commit/79ee8381d2)] - **src**: fix compile failure in test (Michael Dawson) [#345](https://github.com/nodejs/node-addon-api/pull/345) +* [[`4d92a6066f`](https://github.com/nodejs/node-addon-api/commit/4d92a6066f)] - **src**: Add ObjectReference test case (Anisha Rohra) [#212](https://github.com/nodejs/node-addon-api/pull/212) +* [[`779560f397`](https://github.com/nodejs/node-addon-api/commit/779560f397)] - **test**: add operator overloading tests in Number (Your Name) [#355](https://github.com/nodejs/node-addon-api/pull/355) +* [[`73fed84ceb`](https://github.com/nodejs/node-addon-api/commit/73fed84ceb)] - **test**: add ability to control experimental tests (Michael Dawson) [#350](https://github.com/nodejs/node-addon-api/pull/350) +* [[`14c69abd46`](https://github.com/nodejs/node-addon-api/commit/14c69abd46)] - **test**: write tests for Boolean class (Jaeseok Yoon) [#328](https://github.com/nodejs/node-addon-api/pull/328) +* [[`2ad47a83b1`](https://github.com/nodejs/node-addon-api/commit/2ad47a83b1)] - **test**: explicitly cast to uint32\_t in test (Gabriel Schulhof) [#341](https://github.com/nodejs/node-addon-api/pull/341) +* [[`622ffaea76`](https://github.com/nodejs/node-addon-api/commit/622ffaea76)] - **test**: Tighten up compiler warnings (Mikhail Cheshkov) [#315](https://github.com/nodejs/node-addon-api/pull/315) +* [[`fd3c37b0f2`](https://github.com/nodejs/node-addon-api/commit/fd3c37b0f2)] - **tools**: add tool to check for N-API modules (Gabriel Schulhof) [#346](https://github.com/nodejs/node-addon-api/pull/346) + +## 2018-07-19 Version 1.4.0, @NickNasso ### Notable changes: @@ -61,7 +126,7 @@ - Fixed initialization of std::string to nullptr #### Tests -- Fix test failures on linuxOne and AIX +- Fix test failures on linuxOne and AIX - Added basic tests for Scopes - Fix MSVC warning C4244 in tests diff --git a/README.md b/README.md index c27000a95..2c0600c43 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 1.4** +## **Current version: 1.5** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index 3ba21b326..7999b20ca 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,18 @@ "Andrew Petersen (https://github.com/kirbysayshi)", "Anisha Rohra (https://github.com/anisha-rohra)", "Anna Henningsen (https://github.com/addaleax)", + "Arnaud Botella (https://github.com/BotellaA)", "Arunesh Chandra (https://github.com/aruneshchandra)", "Ben Berman (https://github.com/rivertam)", "Benjamin Byholm (https://github.com/kkoopa)", "Cory Mickelson (https://github.com/corymickelson)", "David Halls (https://github.com/davedoesdev)", + "Dongjin Na (https://github.com/nadongguri)", "Eric Bickle (https://github.com/ebickle)", "Gabriel Schulhof (https://github.com/gabrielschulhof)", + "Gus Caplan (https://github.com/devsnek)", "Hitesh Kanwathirtha (https://github.com/digitalinfinity)", + "Jake Yoon (https://github.com/yjaeseok)", "Jason Ginchereau (https://github.com/jasongin)", "Jim Schlight (https://github.com/jschlight)", "Jinho Bang (https://github.com/romandev)", @@ -23,8 +27,10 @@ "Matteo Collina (https://github.com/mcollina)", "Michael Dawson (https://github.com/mhdawson)", "Michele Campus (https://github.com/kYroL01)", + "Mikhail Cheshkov (https://github.com/mcheshkov)", "Nicola Del Gobbo (https://github.com/NickNaso)", "Nick Soggin (https://github.com/iSkore)", + "Philipp Renoth (https://github.com/DaAitch)", "Rolf Timmermans (https://github.com/rolftimmermans)", "Sampson Gao (https://github.com/sampsongao)", "Taylor Woll (https://github.com/boingoing)" @@ -50,5 +56,5 @@ "test": "node test", "doc": "doxygen doc/Doxyfile" }, - "version": "1.4.0" + "version": "1.5.0" } From fd65078e3c4a5c05a98bcd88eb155ab4a3866647 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Mon, 8 Oct 2018 22:09:57 -0400 Subject: [PATCH 053/696] README.md: link to new ABI stability guide Re: https://github.com/nodejs/abi-stable-node/issues/332 PR-URL: https://github.com/nodejs/node-addon-api/pull/367 Reviewed-By: Nicola Del Gobbo Reviewed-By: Sakthipriyan Vairamani Reviewed-By: Michael Dawson --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2c0600c43..581c9fcaa 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,12 @@ of Node.js. It is important to remember that *other* Node.js interfaces such as `libuv` (included in a project via `#include `) are not ABI-stable across -Node.js major versions. Thus, and addon must use N-API and/or `node-addon-api` +Node.js major versions. Thus, an addon must use N-API and/or `node-addon-api` exclusively and build against a version of Node.js that includes an implementation of N-API (meaning a version of Node.js newer than 6.14.2) in -order to benefit from ABI stability across Node.js major versions. +order to benefit from ABI stability across Node.js major versions. Node.js +provides an [ABI stability guide][] containing a detailed explanation of ABI +stability in general, and the N-API ABI stability guarantee in particular. As new APIs are added to N-API, node-addon-api must be updated to provide wrappers for those new APIs. For this reason node-addon-api provides @@ -167,3 +169,5 @@ Take a look and get inspired by our **[test suite](https://github.com/nodejs/nod Licensed under [MIT](./LICENSE.md) + +[ABI stability guide]: https://nodejs.org/en/docs/guides/abi-stability/ From 015d95312f5e411537592755377b28004d078db4 Mon Sep 17 00:00:00 2001 From: Gentilhomme Date: Mon, 8 Oct 2018 04:59:54 +0200 Subject: [PATCH 054/696] doc: fix Napi::Reference link One of the links to the Napi::Reference documentation is broken. PR-URL: https://github.com/nodejs/node-addon-api/pull/365 Reviewed-By: Nicola Del Gobbo Reviewed-By: Sakthipriyan Vairamani Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- doc/object_reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/object_reference.md b/doc/object_reference.md index 4c20f16f8..c08f8beed 100644 --- a/doc/object_reference.md +++ b/doc/object_reference.md @@ -2,7 +2,7 @@ `Napi::ObjectReference` is a subclass of [`Napi::Reference`](reference.md), and is equivalent to an instance of `Napi::Reference`. This means that a `Napi::ObjectReference` holds a [`Napi::Object`](object.md), and a count of the number of references to that Object. When the count is greater than 0, an ObjectReference is not eligible for garbage collection. This ensures that the Object being held as a value of the ObjectReference will remain accessible, even if the original Object no longer is. However, ObjectReference is unique from a Reference since properties can be set and get to the Object itself that can be accessed through the ObjectReference. -For more general information on references, please consult [`Napi::Reference`](referenc.md). +For more general information on references, please consult [`Napi::Reference`](reference.md). ## Example ```cpp From 405f3e5b5bacd380bd1093939f5e2726b30d3b1c Mon Sep 17 00:00:00 2001 From: Jinho Bang Date: Tue, 2 Oct 2018 10:10:10 +0900 Subject: [PATCH 055/696] src: implement CallbackScope class This is a wrapper class to support the following N-APIs. - napi_open_callback_scope() - napi_close_callback_scope() Refs: https://nodejs.org/api/n-api.html#n_api_napi_open_callback_scope PR-URL: https://github.com/nodejs/node-addon-api/pull/362 Refs: https://nodejs.org/api/n-api.html#n_api_napi_open_callback_scope Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- doc/async_operations.md | 2 ++ doc/callback_scope.md | 54 +++++++++++++++++++++++++++++++++++++++++ napi-inl.h | 28 +++++++++++++++++++++ napi.h | 16 ++++++++++++ test/binding.cc | 2 ++ test/binding.gyp | 1 + test/callbackscope.cc | 20 +++++++++++++++ test/callbackscope.js | 48 ++++++++++++++++++++++++++++++++++++ test/index.js | 1 + 9 files changed, 172 insertions(+) create mode 100644 doc/callback_scope.md create mode 100644 test/callbackscope.cc create mode 100644 test/callbackscope.js diff --git a/doc/async_operations.md b/doc/async_operations.md index be4f401fc..8506e1639 100644 --- a/doc/async_operations.md +++ b/doc/async_operations.md @@ -27,3 +27,5 @@ other asynchronous mechanism, the following API is necessary to ensure an asynchronous operation is properly tracked by the runtime: - **[AsyncContext](async_context.md)** + +- **[CallbackScope](callback_scope.md)** diff --git a/doc/callback_scope.md b/doc/callback_scope.md new file mode 100644 index 000000000..35f0f8d9b --- /dev/null +++ b/doc/callback_scope.md @@ -0,0 +1,54 @@ +# CallbackScope + +There are cases (for example, resolving promises) where it is necessary to have +the equivalent of the scope associated with a callback in place when making +certain N-API calls. + +## Methods + +### Constructor + +Creates a new callback scope on the stack. + +```cpp +Napi::CallbackScope::CallbackScope(napi_env env, napi_callback_scope scope); +``` + +- `[in] env`: The environment in which to create the `Napi::CallbackScope`. +- `[in] scope`: The pre-existing `napi_callback_scope` or `Napi::CallbackScope`. + +### Constructor + +Creates a new callback scope on the stack. + +```cpp +Napi::CallbackScope::CallbackScope(napi_env env, napi_async_context context); +``` + +- `[in] env`: The environment in which to create the `Napi::CallbackScope`. +- `[in] async_context`: The pre-existing `napi_async_context` or `Napi::AsyncContext`. + +### Destructor + +Deletes the instance of `Napi::CallbackScope` object. + +```cpp +virtual Napi::CallbackScope::~CallbackScope(); +``` + +### Env + +```cpp +Napi::Env Napi::CallbackScope::Env() const; +``` + +Returns the `Napi::Env` associated with the `Napi::CallbackScope`. + +## Operator + +```cpp +Napi::CallbackScope::operator napi_callback_scope() const; +``` + +Returns the N-API `napi_callback_scope` wrapped by the `Napi::CallbackScope` +object. This can be used to mix usage of the C N-API and node-addon-api. diff --git a/napi-inl.h b/napi-inl.h index 6d8a021c1..ff84d882e 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3404,6 +3404,34 @@ inline Value EscapableHandleScope::Escape(napi_value escapee) { return Value(_env, result); } +//////////////////////////////////////////////////////////////////////////////// +// CallbackScope class +//////////////////////////////////////////////////////////////////////////////// + +inline CallbackScope::CallbackScope( + napi_env env, napi_callback_scope scope) : _env(env), _scope(scope) { +} + +inline CallbackScope::CallbackScope(napi_env env, napi_async_context context) + : _env(env), + _async_context(context) { + napi_status status = napi_open_callback_scope( + _env, Object::New(env), context, &_scope); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline CallbackScope::~CallbackScope() { + napi_close_callback_scope(_env, _scope); +} + +inline CallbackScope::operator napi_callback_scope() const { + return _scope; +} + +inline Napi::Env CallbackScope::Env() const { + return Napi::Env(_env); +} + //////////////////////////////////////////////////////////////////////////////// // AsyncContext class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 61df5fe19..d60239f3e 100644 --- a/napi.h +++ b/napi.h @@ -1671,6 +1671,22 @@ namespace Napi { napi_escapable_handle_scope _scope; }; + class CallbackScope { + public: + CallbackScope(napi_env env, napi_callback_scope scope); + CallbackScope(napi_env env, napi_async_context context); + virtual ~CallbackScope(); + + operator napi_callback_scope() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_async_context _async_context; + napi_callback_scope _scope; + }; + class AsyncContext { public: explicit AsyncContext(napi_env env, const char* resource_name); diff --git a/test/binding.cc b/test/binding.cc index 101ac1f85..09b4ff624 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -15,6 +15,7 @@ Object InitBasicTypesValue(Env env); Object InitBigInt(Env env); #endif Object InitBuffer(Env env); +Object InitCallbackScope(Env env); Object InitDataView(Env env); Object InitDataViewReadWrite(Env env); Object InitError(Env env); @@ -47,6 +48,7 @@ Object Init(Env env, Object exports) { exports.Set("bigint", InitBigInt(env)); #endif exports.Set("buffer", InitBuffer(env)); + exports.Set("callbackscope", InitCallbackScope(env)); exports.Set("dataview", InitDataView(env)); exports.Set("dataview_read_write", InitDataView(env)); exports.Set("dataview_read_write", InitDataViewReadWrite(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 417a2bb3b..dc648bd00 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -14,6 +14,7 @@ 'bigint.cc', 'binding.cc', 'buffer.cc', + 'callbackscope.cc', 'dataview/dataview.cc', 'dataview/dataview_read_write.cc', 'error.cc', diff --git a/test/callbackscope.cc b/test/callbackscope.cc new file mode 100644 index 000000000..75ac678f5 --- /dev/null +++ b/test/callbackscope.cc @@ -0,0 +1,20 @@ +#include "napi.h" + +using namespace Napi; + +namespace { + +static void RunInCallbackScope(const CallbackInfo& info) { + Function callback = info[0].As(); + AsyncContext context(info.Env(), "callback_scope_test"); + CallbackScope scope(info.Env(), context); + callback.Call({}); +} + +} // end anonymous namespace + +Object InitCallbackScope(Env env) { + Object exports = Object::New(env); + exports["runInCallbackScope"] = Function::New(env, RunInCallbackScope); + return exports; +} diff --git a/test/callbackscope.js b/test/callbackscope.js new file mode 100644 index 000000000..523bca462 --- /dev/null +++ b/test/callbackscope.js @@ -0,0 +1,48 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const common = require('./common'); + +// we only check async hooks on 8.x an higher were +// they are closer to working properly +const nodeVersion = process.versions.node.split('.')[0] +let async_hooks = undefined; +function checkAsyncHooks() { + if (nodeVersion >= 8) { + if (async_hooks == undefined) { + async_hooks = require('async_hooks'); + } + return true; + } + return false; +} + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + if (!checkAsyncHooks()) + return; + + let id; + let insideHook = false; + async_hooks.createHook({ + init(asyncId, type, triggerAsyncId, resource) { + if (id === undefined && type === 'callback_scope_test') { + id = asyncId; + } + }, + before(asyncId) { + if (asyncId === id) + insideHook = true; + }, + after(asyncId) { + if (asyncId === id) + insideHook = false; + } + }).enable(); + + binding.callbackscope.runInCallbackScope(function() { + assert(insideHook); + }); +} diff --git a/test/index.js b/test/index.js index 2f0673bd5..87237ff7f 100644 --- a/test/index.js +++ b/test/index.js @@ -16,6 +16,7 @@ let testModules = [ 'basic_types/value', 'bigint', 'buffer', + 'callbackscope', 'dataview/dataview', 'dataview/dataview_read_write', 'error', From 729f6dc4ee41b02aa92f161cbd1d3b5dcc2e67c9 Mon Sep 17 00:00:00 2001 From: Dongjin Na Date: Tue, 16 Oct 2018 09:46:48 +0900 Subject: [PATCH 056/696] test: add arraybuffer tests - ArrayBuffer() - ArrayBuffer(napi_env env, napi_value value) PR-URL: https://github.com/nodejs/node-addon-api/pull/369 Reviewed-By: Michael Dawson --- test/arraybuffer.cc | 18 ++++++++++++++++++ test/arraybuffer.js | 8 ++++++++ 2 files changed, 26 insertions(+) diff --git a/test/arraybuffer.cc b/test/arraybuffer.cc index 27bb993fe..e46f8cba3 100644 --- a/test/arraybuffer.cc +++ b/test/arraybuffer.cc @@ -135,6 +135,22 @@ Value GetFinalizeCount(const CallbackInfo& info) { return Number::New(info.Env(), finalizeCount); } +Value CreateBufferWithConstructor(const CallbackInfo& info) { + ArrayBuffer buffer = ArrayBuffer::New(info.Env(), testLength); + if (buffer.ByteLength() != testLength) { + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return Value(); + } + InitData(static_cast(buffer.Data()), testLength); + ArrayBuffer buffer2(info.Env(), buffer); + return buffer2; +} + +Value CheckEmptyBuffer(const CallbackInfo& info) { + ArrayBuffer buffer; + return Boolean::New(info.Env(), buffer.IsEmpty()); +} + } // end anonymous namespace Object InitArrayBuffer(Env env) { @@ -148,6 +164,8 @@ Object InitArrayBuffer(Env env) { Function::New(env, CreateExternalBufferWithFinalizeHint); exports["checkBuffer"] = Function::New(env, CheckBuffer); exports["getFinalizeCount"] = Function::New(env, GetFinalizeCount); + exports["createBufferWithConstructor"] = Function::New(env, CreateBufferWithConstructor); + exports["checkEmptyBuffer"] = Function::New(env, CheckEmptyBuffer); return exports; } diff --git a/test/arraybuffer.js b/test/arraybuffer.js index d284fe84a..43604617f 100644 --- a/test/arraybuffer.js +++ b/test/arraybuffer.js @@ -53,5 +53,13 @@ function test(binding) { global.gc(); assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()); }, + + 'ArrayBuffer with constructor', + () => { + assert.strictEqual(true, binding.arraybuffer.checkEmptyBuffer()); + const test = binding.arraybuffer.createBufferWithConstructor(); + binding.arraybuffer.checkBuffer(test); + assert.ok(test instanceof ArrayBuffer); + }, ]); } From 67b7db0a6fdf03f1c9e2e0c5d5a9bdd4851e92c1 Mon Sep 17 00:00:00 2001 From: Jaeseok Yoon Date: Tue, 2 Oct 2018 11:04:40 +0900 Subject: [PATCH 057/696] test: write tests for Array class add tests for array class. PR-URL: https://github.com/nodejs/node-addon-api/pull/363 Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof Reviewed-By: Sakthipriyan Vairamani --- test/basic_types/array.cc | 41 +++++++++++++++++++++++++++++++++++++++ test/basic_types/array.js | 37 +++++++++++++++++++++++++++++++++++ test/binding.cc | 2 ++ test/binding.gyp | 1 + test/index.js | 1 + 5 files changed, 82 insertions(+) create mode 100644 test/basic_types/array.cc create mode 100644 test/basic_types/array.js diff --git a/test/basic_types/array.cc b/test/basic_types/array.cc new file mode 100644 index 000000000..401d93618 --- /dev/null +++ b/test/basic_types/array.cc @@ -0,0 +1,41 @@ +#define NAPI_EXPERIMENTAL +#include "napi.h" + +using namespace Napi; + +Value CreateArray(const CallbackInfo& info) { + if (info.Length() > 0) { + size_t length = info[0].As().Uint32Value(); + return Array::New(info.Env(), length); + } else { + return Array::New(info.Env()); + } +} + +Value GetLength(const CallbackInfo& info) { + Array array = info[0].As(); + return Number::New(info.Env(), static_cast(array.Length())); +} + +Value GetElement(const CallbackInfo& info) { + Array array = info[0].As(); + size_t index = info[1].As().Uint32Value(); + return array[index]; +} + +void SetElement(const CallbackInfo& info) { + Array array = info[0].As(); + size_t index = info[1].As().Uint32Value(); + array[index] = info[2].As(); +} + +Object InitBasicTypesArray(Env env) { + Object exports = Object::New(env); + + exports["createArray"] = Function::New(env, CreateArray); + exports["getLength"] = Function::New(env, GetLength); + exports["get"] = Function::New(env, GetElement); + exports["set"] = Function::New(env, SetElement); + + return exports; +} diff --git a/test/basic_types/array.js b/test/basic_types/array.js new file mode 100644 index 000000000..925022a2d --- /dev/null +++ b/test/basic_types/array.js @@ -0,0 +1,37 @@ +'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) { + + // create empty array + const array = binding.basic_types_array.createArray(); + assert.strictEqual(binding.basic_types_array.getLength(array), 0); + + // create array with length + const arrayWithLength = binding.basic_types_array.createArray(10); + assert.strictEqual(binding.basic_types_array.getLength(arrayWithLength), 10); + + // set function test + binding.basic_types_array.set(array, 0, 10); + binding.basic_types_array.set(array, 1, "test"); + binding.basic_types_array.set(array, 2, 3.0); + + // check length after set data + assert.strictEqual(binding.basic_types_array.getLength(array), 3); + + // get function test + assert.strictEqual(binding.basic_types_array.get(array, 0), 10); + assert.strictEqual(binding.basic_types_array.get(array, 1), "test"); + assert.strictEqual(binding.basic_types_array.get(array, 2), 3.0); + + // overwrite test + binding.basic_types_array.set(array, 0, 5); + assert.strictEqual(binding.basic_types_array.get(array, 0), 5); + + // out of index test + assert.strictEqual(binding.basic_types_array.get(array, 5), undefined); +} diff --git a/test/binding.cc b/test/binding.cc index 09b4ff624..ffe1ed757 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -6,6 +6,7 @@ using namespace Napi; Object InitArrayBuffer(Env env); Object InitAsyncContext(Env env); Object InitAsyncWorker(Env env); +Object InitBasicTypesArray(Env env); Object InitBasicTypesBoolean(Env env); Object InitBasicTypesNumber(Env env); Object InitBasicTypesValue(Env env); @@ -39,6 +40,7 @@ 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_array", InitBasicTypesArray(env)); exports.Set("basic_types_boolean", InitBasicTypesBoolean(env)); exports.Set("basic_types_number", InitBasicTypesNumber(env)); exports.Set("basic_types_value", InitBasicTypesValue(env)); diff --git a/test/binding.gyp b/test/binding.gyp index dc648bd00..77c388074 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -8,6 +8,7 @@ 'arraybuffer.cc', 'asynccontext.cc', 'asyncworker.cc', + 'basic_types/array.cc', 'basic_types/boolean.cc', 'basic_types/number.cc', 'basic_types/value.cc', diff --git a/test/index.js b/test/index.js index 87237ff7f..8bfd59f61 100644 --- a/test/index.js +++ b/test/index.js @@ -11,6 +11,7 @@ let testModules = [ 'arraybuffer', 'asynccontext', 'asyncworker', + 'basic_types/array', 'basic_types/boolean', 'basic_types/number', 'basic_types/value', From 2342415463818c1477155f165acf8aeaee2c08b8 Mon Sep 17 00:00:00 2001 From: Dongjin Na Date: Tue, 16 Oct 2018 21:50:28 +0900 Subject: [PATCH 058/696] test: create test objects in the stack instead of the heap PR-URL: https://github.com/nodejs/node-addon-api/pull/371 Reviewed-By: Michael Dawson Reviewed-By: Hitesh Kanwathirtha Reviewed-By: Nicola Del Gobbo --- test/basic_types/boolean.cc | 4 ++-- test/basic_types/number.cc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/basic_types/boolean.cc b/test/basic_types/boolean.cc index 900438f62..fd6e3165f 100644 --- a/test/basic_types/boolean.cc +++ b/test/basic_types/boolean.cc @@ -12,8 +12,8 @@ Value CreateEmptyBoolean(const CallbackInfo& info) { } Value CreateBooleanFromExistingValue(const CallbackInfo& info) { - Boolean* boolean = new Boolean(info.Env(), info[0].As()); - return Boolean::New(info.Env(), boolean->Value()); + Boolean boolean(info.Env(), info[0].As()); + return Boolean::New(info.Env(), boolean.Value()); } Value CreateBooleanFromPrimitive(const CallbackInfo& info) { diff --git a/test/basic_types/number.cc b/test/basic_types/number.cc index 436d51918..4ccb844b5 100644 --- a/test/basic_types/number.cc +++ b/test/basic_types/number.cc @@ -67,8 +67,8 @@ Value OperatorDouble(const CallbackInfo& info) { } Value CreateEmptyNumber(const CallbackInfo& info) { - Number* number = new Number(); - return Boolean::New(info.Env(), number->IsEmpty()); + Number number; + return Boolean::New(info.Env(), number.IsEmpty()); } Value CreateNumberFromExistingValue(const CallbackInfo& info) { From fa3a6150b3dd46395e67053cfcba5a4d060c93b7 Mon Sep 17 00:00:00 2001 From: Jinho Bang Date: Tue, 2 Oct 2018 09:20:11 +0900 Subject: [PATCH 059/696] src: use MakeCallback() -> Call() in AsyncWorker Change `AsyncWorker::OnOK()` and `AsyncWorker::OnError()` callbacks to **NOT** use `MakeCallback()`. An ordinary function call (`_callback::Call()`) is now correct. PR-URL: https://github.com/nodejs/node-addon-api/pull/361 Refs: https://nodejs.org/api/n-api.html#n_api_napi_make_callback Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof Reviewed-By: Nicola Del Gobbo --- napi-inl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index ff84d882e..babbebd28 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3588,11 +3588,11 @@ inline FunctionReference& AsyncWorker::Callback() { } inline void AsyncWorker::OnOK() { - _callback.MakeCallback(_receiver.Value(), std::initializer_list{}); + _callback.Call(_receiver.Value(), std::initializer_list{}); } inline void AsyncWorker::OnError(const Error& e) { - _callback.MakeCallback(_receiver.Value(), std::initializer_list{ e.Value() }); + _callback.Call(_receiver.Value(), std::initializer_list{ e.Value() }); } inline void AsyncWorker::SetError(const std::string& error) { From 8ce605c6571a39daefad9f4e37877555e48be1e3 Mon Sep 17 00:00:00 2001 From: Jaeseok Yoon Date: Tue, 2 Oct 2018 09:13:04 +0900 Subject: [PATCH 060/696] build: avoid using package-lock.json ```npm install``` creates a ```package-lock.json``` to lock the versions of dependencies that are installed. They recommend to commit this file and that's what I usually do in other projects but we should not use it for this project. This will allow us to always test the latest version of our dependencies (especially in CI). PR-URL: https://github.com/nodejs/node-addon-api/pull/359 Reviewed-By: Michael Dawson Reviewed-By: Sakthipriyan Vairamani Reviewed-By: Nicola Del Gobbo --- .npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..43c97e719 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false From 322dc0943e52950954bd0559b5967ee321d2556f Mon Sep 17 00:00:00 2001 From: NickNaso Date: Fri, 2 Nov 2018 22:03:12 +0100 Subject: [PATCH 061/696] Updates for release 1.6.0 --- CHANGELOG.md | 36 +++++++++++++++++++++++++++++++++--- README.md | 2 +- package.json | 5 +++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 031aee778..7115a0756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,36 @@ # node-addon-api Changelog -## 2018-10-03 Version 1.5.0 (Current), @NickNasso +## 2018-11-02 Version 1.6.0 (Current), @NickNaso + +### Notable changes: + +#### Documentation + +- Improved documentation about ABI stability. + +#### API + +- Add `Napi::CallbackScope` class that help to have the equivalent of the scope +associated with a callback in place when making certain N-API calls + +#### TEST + +- Added tests for `Napi::Array` class. +- Added tests for `Napi::ArrayBuffer` class. + +### Commmits + +* [[`8ce605c657`](https://github.com/nodejs/node-addon-api/commit/8ce605c657)] - **build**: avoid using package-lock.json (Jaeseok Yoon) [#359](https://github.com/nodejs/node-addon-api/pull/359) +* [[`fa3a6150b3`](https://github.com/nodejs/node-addon-api/commit/fa3a6150b3)] - **src**: use MakeCallback() -\> Call() in AsyncWorker (Jinho Bang) [#361](https://github.com/nodejs/node-addon-api/pull/361) +* [[`2342415463`](https://github.com/nodejs/node-addon-api/commit/2342415463)] - **test**: create test objects in the stack instead of the heap (Dongjin Na) [#371](https://github.com/nodejs/node-addon-api/pull/371) +* [[`67b7db0a6f`](https://github.com/nodejs/node-addon-api/commit/67b7db0a6f)] - **test**: write tests for Array class (Jaeseok Yoon) [#363](https://github.com/nodejs/node-addon-api/pull/363) +* [[`729f6dc4ee`](https://github.com/nodejs/node-addon-api/commit/729f6dc4ee)] - **test**: add arraybuffer tests (Dongjin Na) [#369](https://github.com/nodejs/node-addon-api/pull/369) +* [[`405f3e5b5b`](https://github.com/nodejs/node-addon-api/commit/405f3e5b5b)] - **src**: implement CallbackScope class (Jinho Bang) [#362](https://github.com/nodejs/node-addon-api/pull/362) +* [[`015d95312f`](https://github.com/nodejs/node-addon-api/commit/015d95312f)] - **doc**: fix Napi::Reference link (Gentilhomme) [#365](https://github.com/nodejs/node-addon-api/pull/365) +* [[`fd65078e3c`](https://github.com/nodejs/node-addon-api/commit/fd65078e3c)] - README.md: link to new ABI stability guide (Gabriel Schulhof) [#367](https://github.com/nodejs/node-addon-api/pull/367) +* [[`ffebf9ba9a`](https://github.com/nodejs/node-addon-api/commit/ffebf9ba9a)] - Updates for release 1.5.0 (NickNaso) + +## 2018-10-03 Version 1.5.0 (Current), @NickNaso ### Notable changes: @@ -12,7 +42,7 @@ #### API - Add `Napi::AsyncContext` class to handle asynchronous operation. -- Add B`Napi::igInt` class to work with BigInt type. +- Add `Napi::BigInt` class to work with BigInt type. - Add `Napi::VersionManagement` class to retrieve the versions of Node.js and N-API. - Fix potential memory leaks. - DataView feature is enabled by default @@ -65,7 +95,7 @@ yet backported in the previous Node.js version. * [[`622ffaea76`](https://github.com/nodejs/node-addon-api/commit/622ffaea76)] - **test**: Tighten up compiler warnings (Mikhail Cheshkov) [#315](https://github.com/nodejs/node-addon-api/pull/315) * [[`fd3c37b0f2`](https://github.com/nodejs/node-addon-api/commit/fd3c37b0f2)] - **tools**: add tool to check for N-API modules (Gabriel Schulhof) [#346](https://github.com/nodejs/node-addon-api/pull/346) -## 2018-07-19 Version 1.4.0, @NickNasso +## 2018-07-19 Version 1.4.0, @NickNaso ### Notable changes: diff --git a/README.md b/README.md index 581c9fcaa..b8e6728dd 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 1.5** +## **Current version: 1.6** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index 7999b20ca..038cd83ee 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "Philipp Renoth (https://github.com/DaAitch)", "Rolf Timmermans (https://github.com/rolftimmermans)", "Sampson Gao (https://github.com/sampsongao)", - "Taylor Woll (https://github.com/boingoing)" + "Taylor Woll (https://github.com/boingoing)", + "Thomas Gentilhomme (https://github.com/fraxken)" ], "dependencies": {}, "description": "Node.js API (N-API)", @@ -56,5 +57,5 @@ "test": "node test", "doc": "doxygen doc/Doxyfile" }, - "version": "1.5.0" + "version": "1.6.0" } From 4852238b9d35fed9c5b6a574f85b93b49d196052 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Mon, 12 Nov 2018 20:45:27 +0100 Subject: [PATCH 062/696] Added references to changelog maker tool and other minor fixes to explain better the process. --- doc/creating_a_release.md | 53 +++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/doc/creating_a_release.md b/doc/creating_a_release.md index 2e814981e..1618f03b3 100644 --- a/doc/creating_a_release.md +++ b/doc/creating_a_release.md @@ -1,30 +1,51 @@ # Creating a release -Only collaborators in npm for node-addon-api can create releases. +Only collaborators in npm for **node-addon-api** can create releases. If you want to be able to do releases ask one of the existing -collaborators to add you. If necessary you can ask the build +collaborators to add you. If necessary you can ask the build Working Group who manages the Node.js npm user to add you if there are no other active collaborators. +## Prerequisites + +Before to start creating a new release check if you have installed the following +tools: + +* [Changelog maker](https://www.npmjs.com/package/changelog-maker) + +If not please follow the instruction reported in the tool's documentation to +install it. + +## Publish new release + These are the steps to follow to create a new release: -* Open an issue in the node-addon-api repo documenting - the intent to create a new release. Give people some - time to comment or suggest PRs that should land first. +* Open an issue in the **node-addon-api** repo documenting the intent to create a +new release. Give people some time to comment or suggest PRs that should land first. * Validate all tests pass by running npm test on master. -* Use https://ci.nodejs.org/view/x%20-%20Abi%20stable%20module%20API/job/node-test-node-addon-api/ - to validate tests pass for latest 9, 8, 6, 4 releases - (note there are still some issues on SmartOS and - Windows in the testing). +* Update the version in **package.json** appropriately. -* Update the version in package.json appropriately. +* Update the [README.md](https://github.com/nodejs/node-addon-api/blob/master/README.md) +to show the new version as the latest. -* Update the README.md to show the new version as the latest. +* Generate the changelog for the new version using **changelog maker** tool. From +the route folder of the repo launch the following command: + + ```bash + > changelog-maker + ``` +* Use the output generated by **changelog maker** to pdate the [CHANGELOG.md](https://github.com/nodejs/node-addon-api/blob/master/CHANGELOG.md) +following the style used in publishing the previous release. + +* Add any new contributors to the "contributors" section in the package.json + +* Validate all tests pass by running npm test on master. -* Add any new contributors to the "contributors" section in - the package.json +* Use **[CI](https://ci.nodejs.org/view/x%20-%20Abi%20stable%20module%20API/job/node-test-node-addon-api/)** +to validate tests pass for latest 11, 10, 9, 8, 6, 4 releases (note there are still some issues on SmartOS and +Windows in the testing). * Do a clean checkout of node-add-api. @@ -33,9 +54,9 @@ These are the steps to follow to create a new release: * Create a release in Github (look at existing releases for an example). * Validate that you can run `npm install node-addon-api` successfully - and that the correct version is installed. +and that the correct version is installed. -* Comment on the issue opened in the first step that the - release has been created and close the issue. +* Comment on the issue opened in the first step that the release has been created +and close the issue. * Tweet that the release has been created. From b6dc15b88dc3458bd17987e0ad5c600afdfa1d80 Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Mon, 12 Nov 2018 21:47:36 +0100 Subject: [PATCH 063/696] doc: make links point to node-addon-examples repo PR-URL: https://github.com/nodejs/node-addon-api/pull/389 Reviewed-By: Michael Dawson --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b8e6728dd..79340c075 100644 --- a/README.md +++ b/README.md @@ -113,16 +113,16 @@ The following is the documentation for node-addon-api. ### **Examples** -Are you new to **node-addon-api**? Take a look at our **[examples](https://github.com/nodejs/abi-stable-node-addon-examples)** - -- **[Hello World](https://github.com/nodejs/abi-stable-node-addon-examples/tree/master/1_hello_world/node-addon-api)** -- **[Pass arguments to a function](https://github.com/nodejs/abi-stable-node-addon-examples/tree/master/2_function_arguments/node-addon-api)** -- **[Callbacks](https://github.com/nodejs/abi-stable-node-addon-examples/tree/master/3_callbacks/node-addon-api)** -- **[Object factory](https://github.com/nodejs/abi-stable-node-addon-examples/tree/master/4_object_factory/node-addon-api)** -- **[Function factory](https://github.com/nodejs/abi-stable-node-addon-examples/tree/master/5_function_factory/node-addon-api)** -- **[Wrapping C++ Object](https://github.com/nodejs/abi-stable-node-addon-examples/tree/master/6_object_wrap/node-addon-api)** -- **[Factory of wrapped object](https://github.com/nodejs/abi-stable-node-addon-examples/tree/master/7_factory_wrap/node-addon-api)** -- **[Passing wrapped object around](https://github.com/nodejs/abi-stable-node-addon-examples/tree/master/8_passing_wrapped/node-addon-api)** +Are you new to **node-addon-api**? Take a look at our **[examples](https://github.com/nodejs/node-addon-examples)** + +- **[Hello World](https://github.com/nodejs/node-addon-examples/tree/master/1_hello_world/node-addon-api)** +- **[Pass arguments to a function](https://github.com/nodejs/node-addon-examples/tree/master/2_function_arguments/node-addon-api)** +- **[Callbacks](https://github.com/nodejs/node-addon-examples/tree/master/3_callbacks/node-addon-api)** +- **[Object factory](https://github.com/nodejs/node-addon-examples/tree/master/4_object_factory/node-addon-api)** +- **[Function factory](https://github.com/nodejs/node-addon-examples/tree/master/5_function_factory/node-addon-api)** +- **[Wrapping C++ Object](https://github.com/nodejs/node-addon-examples/tree/master/6_object_wrap/node-addon-api)** +- **[Factory of wrapped object](https://github.com/nodejs/node-addon-examples/tree/master/7_factory_wrap/node-addon-api)** +- **[Passing wrapped object around](https://github.com/nodejs/node-addon-examples/tree/master/8_passing_wrapped/node-addon-api)** From 29a0262ab91228ee2fabf759596a721694420105 Mon Sep 17 00:00:00 2001 From: Dongjin Na Date: Tue, 13 Nov 2018 05:48:32 +0900 Subject: [PATCH 064/696] doc: fix typo PR-URL: https://github.com/nodejs/node-addon-api/pull/385 Reviewed-By: Michael Dawson --- README.md | 2 +- doc/creating_a_release.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 79340c075..5bb5832cf 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ wrappers for those new APIs. For this reason node-addon-api provides methods that allow callers to obtain the underlying N-API handles so direct calls to N-API and the use of the objects/methods provided by node-addon-api can be used together. For example, in order to be able -to use an API for which the node-add-api does not yet provide a wrapper. +to use an API for which the node-addon-api does not yet provide a wrapper. APIs exposed by node-addon-api are generally used to create and manipulate JavaScript values. Concepts and operations generally map diff --git a/doc/creating_a_release.md b/doc/creating_a_release.md index 2e814981e..e6ab610c9 100644 --- a/doc/creating_a_release.md +++ b/doc/creating_a_release.md @@ -26,7 +26,7 @@ These are the steps to follow to create a new release: * Add any new contributors to the "contributors" section in the package.json -* Do a clean checkout of node-add-api. +* Do a clean checkout of node-addon-api. * Login and then run `npm publish`. From f3e01db8fabfa0f3ae81c4158f91fadf6a54c603 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Mon, 12 Nov 2018 22:13:06 +0100 Subject: [PATCH 065/696] Removed 4 and 9 from Node.js version to test on CI --- doc/creating_a_release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/creating_a_release.md b/doc/creating_a_release.md index 1618f03b3..d7918e93d 100644 --- a/doc/creating_a_release.md +++ b/doc/creating_a_release.md @@ -44,7 +44,7 @@ following the style used in publishing the previous release. * Validate all tests pass by running npm test on master. * Use **[CI](https://ci.nodejs.org/view/x%20-%20Abi%20stable%20module%20API/job/node-test-node-addon-api/)** -to validate tests pass for latest 11, 10, 9, 8, 6, 4 releases (note there are still some issues on SmartOS and +to validate tests pass for latest 11, 10, 8, 6 releases (note there are still some issues on SmartOS and Windows in the testing). * Do a clean checkout of node-add-api. From d47399fe253b016c0c7f51b74a211999dec9595a Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Mon, 12 Nov 2018 15:35:37 -0500 Subject: [PATCH 066/696] src: guard CallbackScope with N-API v3 CallbackScope support needs to be guarded with N-API version 3, otherwise olders versions of N-API that did not have CallbackScope support will have compile failures. PR-URL: https://github.com/nodejs/node-addon-api/pull/395 Fixes: https://github.com/nodejs/node-addon-api/issues/387 Reviewed-By: Anna Henningsen Reviewed-By: Nicola Del Gobbo --- napi-inl.h | 3 +++ napi.h | 2 ++ test/binding.cc | 4 ++++ test/callbackscope.cc | 2 ++ test/index.js | 6 ++++++ 5 files changed, 17 insertions(+) diff --git a/napi-inl.h b/napi-inl.h index babbebd28..230372d73 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3404,6 +3404,8 @@ inline Value EscapableHandleScope::Escape(napi_value escapee) { return Value(_env, result); } + +#if (NAPI_VERSION > 2) //////////////////////////////////////////////////////////////////////////////// // CallbackScope class //////////////////////////////////////////////////////////////////////////////// @@ -3431,6 +3433,7 @@ inline CallbackScope::operator napi_callback_scope() const { inline Napi::Env CallbackScope::Env() const { return Napi::Env(_env); } +#endif //////////////////////////////////////////////////////////////////////////////// // AsyncContext class diff --git a/napi.h b/napi.h index d60239f3e..f821a97db 100644 --- a/napi.h +++ b/napi.h @@ -1671,6 +1671,7 @@ namespace Napi { napi_escapable_handle_scope _scope; }; +#if (NAPI_VERSION > 2) class CallbackScope { public: CallbackScope(napi_env env, napi_callback_scope scope); @@ -1686,6 +1687,7 @@ namespace Napi { napi_async_context _async_context; napi_callback_scope _scope; }; +#endif class AsyncContext { public: diff --git a/test/binding.cc b/test/binding.cc index ffe1ed757..0068e7480 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -16,7 +16,9 @@ Object InitBasicTypesValue(Env env); Object InitBigInt(Env env); #endif Object InitBuffer(Env env); +#if (NAPI_VERSION > 2) Object InitCallbackScope(Env env); +#endif Object InitDataView(Env env); Object InitDataViewReadWrite(Env env); Object InitError(Env env); @@ -50,7 +52,9 @@ Object Init(Env env, Object exports) { exports.Set("bigint", InitBigInt(env)); #endif exports.Set("buffer", InitBuffer(env)); +#if (NAPI_VERSION > 2) exports.Set("callbackscope", InitCallbackScope(env)); +#endif exports.Set("dataview", InitDataView(env)); exports.Set("dataview_read_write", InitDataView(env)); exports.Set("dataview_read_write", InitDataViewReadWrite(env)); diff --git a/test/callbackscope.cc b/test/callbackscope.cc index 75ac678f5..70b68fe60 100644 --- a/test/callbackscope.cc +++ b/test/callbackscope.cc @@ -2,6 +2,7 @@ using namespace Napi; +#if (NAPI_VERSION > 2) namespace { static void RunInCallbackScope(const CallbackInfo& info) { @@ -18,3 +19,4 @@ Object InitCallbackScope(Env env) { exports["runInCallbackScope"] = Function::New(env, RunInCallbackScope); return exports; } +#endif diff --git a/test/index.js b/test/index.js index 8bfd59f61..51fce464c 100644 --- a/test/index.js +++ b/test/index.js @@ -53,6 +53,12 @@ if ((process.env.npm_config_NAPI_VERSION !== undefined) && testModules.splice(testModules.indexOf('typedarray-bigint'), 1); } +if ((process.env.npm_config_NAPI_VERSION !== undefined) && + (process.env.npm_config_NAPI_VERSION < 3)) { + testModules.splice(testModules.indexOf('callbackscope'), 1); + testModules.splice(testModules.indexOf('version_management'), 1); +} + if (typeof global.gc === 'function') { console.log('Starting test suite\n'); From e7cd292a74a41c27350fcd85ff582f3bbbe90204 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 7 Nov 2018 04:14:33 -0500 Subject: [PATCH 067/696] src: remove unused CallbackScope member PR-URL: https://github.com/nodejs/node-addon-api/pull/391 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo Reviewed-By: Jinho Bang --- napi-inl.h | 3 +-- napi.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 230372d73..b831f34c9 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3415,8 +3415,7 @@ inline CallbackScope::CallbackScope( } inline CallbackScope::CallbackScope(napi_env env, napi_async_context context) - : _env(env), - _async_context(context) { + : _env(env) { napi_status status = napi_open_callback_scope( _env, Object::New(env), context, &_scope); NAPI_THROW_IF_FAILED_VOID(_env, status); diff --git a/napi.h b/napi.h index f821a97db..16d09943a 100644 --- a/napi.h +++ b/napi.h @@ -1684,7 +1684,6 @@ namespace Napi { private: napi_env _env; - napi_async_context _async_context; napi_callback_scope _scope; }; #endif From 269bf12e5f1908aeaa04de6f2efb26eb286cea0a Mon Sep 17 00:00:00 2001 From: NickNaso Date: Wed, 14 Nov 2018 19:28:33 +0100 Subject: [PATCH 068/696] Updates for release 1.6.1 --- CHANGELOG.md | 21 +++++++++++++++++++++ README.md | 2 +- package.json | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7115a0756..dd081c291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # node-addon-api Changelog +## 2018-11-14 Version 1.6.1 (Current), @NickNaso + +### Notable changes: + +#### Documentation + +- Updated links for examples to point to node-addon-examples repo. +- Fixed typos on some parts of documentation. + +#### API + +- Removed unused member on `Napi::CallbackScope`. +- Enabled or disabled `Napi::CallbackScope` only with N-API v3. + +### Commmits + +* [[`e7cd292a74`](https://github.com/nodejs/node-addon-api/commit/e7cd292a74)] - **src**: remove unused CallbackScope member (Gabriel Schulhof) [#391](https://github.com/nodejs/node-addon-api/pull/391) +* [[`d47399fe25`](https://github.com/nodejs/node-addon-api/commit/d47399fe25)] - **src**: guard CallbackScope with N-API v3 (Michael Dawson) [#395](https://github.com/nodejs/node-addon-api/pull/395) +* [[`29a0262ab9`](https://github.com/nodejs/node-addon-api/commit/29a0262ab9)] - **doc**: fix typo (Dongjin Na) [#385](https://github.com/nodejs/node-addon-api/pull/385) +* [[`b6dc15b88d`](https://github.com/nodejs/node-addon-api/commit/b6dc15b88d)] - **doc**: make links point to node-addon-examples repo (Nicola Del Gobbo) [#389](https://github.com/nodejs/node-addon-api/pull/389) + ## 2018-11-02 Version 1.6.0 (Current), @NickNaso ### Notable changes: diff --git a/README.md b/README.md index 5bb5832cf..94266a4a4 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 1.6** +## **Current version: 1.6.1** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index 038cd83ee..8dcafb1b2 100644 --- a/package.json +++ b/package.json @@ -57,5 +57,5 @@ "test": "node test", "doc": "doxygen doc/Doxyfile" }, - "version": "1.6.0" + "version": "1.6.1" } From 07a0fc4e95599e3cfd0db40ea0997cbf568f88e0 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Wed, 28 Nov 2018 16:38:36 -0500 Subject: [PATCH 069/696] src: fix selection logic for 6.x PR-URL: https://github.com/nodejs/node-addon-api/pull/402 Reviewed-By: Gabriel Schulhof Reviewed-By: NickNaso --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 77dc6d493..74e90e8e2 100644 --- a/index.js +++ b/index.js @@ -17,6 +17,7 @@ var versionArray = process.version var isNodeApiBuiltin = ( versionArray[0] > 8 || (versionArray[0] == 8 && versionArray[1] >= 6) || + (versionArray[0] == 6 && versionArray[1] >= 15) || (versionArray[0] == 6 && versionArray[1] >= 14 && versionArray[2] >= 2)); // The flag is not needed when the Node version is not 8, nor if the API is From d8e9c2245abb7fbc67f5696bcaebd10085a4688e Mon Sep 17 00:00:00 2001 From: NickNaso Date: Thu, 29 Nov 2018 01:03:22 +0100 Subject: [PATCH 070/696] Prepare release of version 1.6.2 --- CHANGELOG.md | 14 +++++++++++++- README.md | 2 +- package.json | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd081c291..780030fa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # node-addon-api Changelog +## 2018-11-29 Version 1.6.2 (Current), @NickNaso + +### Notable changes: + +#### API + +- Fixed selection logic for version 6.x. + +### Commmits + +* [[`07a0fc4e95`](https://github.com/nodejs/node-addon-api/commit/07a0fc4e95)] - **src**: fix selection logic for 6.x (Michael Dawson) [#402](https://github.com/nodejs/node-addon-api/pull/402) + ## 2018-11-14 Version 1.6.1 (Current), @NickNaso ### Notable changes: @@ -12,7 +24,7 @@ #### API - Removed unused member on `Napi::CallbackScope`. -- Enabled or disabled `Napi::CallbackScope` only with N-API v3. +- Enabled `Napi::CallbackScope` only with N-API v3. ### Commmits diff --git a/README.md b/README.md index 94266a4a4..4f6a4431d 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 1.6.1** +## **Current version: 1.6.2** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index 8dcafb1b2..cd313a30c 100644 --- a/package.json +++ b/package.json @@ -57,5 +57,5 @@ "test": "node test", "doc": "doxygen doc/Doxyfile" }, - "version": "1.6.1" + "version": "1.6.2" } From c1ff2936f97f4e103c89fe71d1dba7ab84d29cdb Mon Sep 17 00:00:00 2001 From: Luciano Martorella Date: Tue, 23 Oct 2018 09:43:47 +0200 Subject: [PATCH 071/696] src: fix missing void*data usage in PropertyDescriptors PR-URL: https://github.com/nodejs/node-addon-api/pull/374 Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- napi-inl.deprecated.h | 4 ++-- napi-inl.h | 19 +++++++++++-------- test/object/object.cc | 26 ++++++++++++++++++++++++++ test/object/object.js | 11 +++++++++++ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/napi-inl.deprecated.h b/napi-inl.deprecated.h index d00174357..f19aca76b 100644 --- a/napi-inl.deprecated.h +++ b/napi-inl.deprecated.h @@ -73,7 +73,7 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(const char* utf8name, void* /*data*/) { typedef details::AccessorCallbackData CbData; // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ getter, setter }); + auto callbackData = new CbData({ getter, setter, nullptr }); return PropertyDescriptor({ utf8name, @@ -104,7 +104,7 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(napi_value name, void* /*data*/) { typedef details::AccessorCallbackData CbData; // TODO: Delete when the function is destroyed - auto callbackData = new CbData({ getter, setter }); + auto callbackData = new CbData({ getter, setter, nullptr }); return PropertyDescriptor({ nullptr, diff --git a/napi-inl.h b/napi-inl.h index b831f34c9..261a261ee 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -176,6 +176,7 @@ struct AccessorCallbackData { CallbackInfo callbackInfo(env, info); AccessorCallbackData* callbackData = static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); return callbackData->getterCallback(callbackInfo); }); } @@ -186,6 +187,7 @@ struct AccessorCallbackData { CallbackInfo callbackInfo(env, info); AccessorCallbackData* callbackData = static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); callbackData->setterCallback(callbackInfo); return nullptr; }); @@ -193,6 +195,7 @@ struct AccessorCallbackData { Getter getterCallback; Setter setterCallback; + void* data; }; } // namespace details @@ -2603,9 +2606,9 @@ PropertyDescriptor::Accessor(Napi::Env env, const char* utf8name, Getter getter, napi_property_attributes attributes, - void* /*data*/) { + void* data) { typedef details::CallbackData CbData; - auto callbackData = new CbData({ getter, nullptr }); + auto callbackData = new CbData({ getter, data }); napi_status status = AttachData(env, object, callbackData); NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); @@ -2638,9 +2641,9 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, Name name, Getter getter, napi_property_attributes attributes, - void* /*data*/) { + void* data) { typedef details::CallbackData CbData; - auto callbackData = new CbData({ getter, nullptr }); + auto callbackData = new CbData({ getter, data }); napi_status status = AttachData(env, object, callbackData); NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); @@ -2664,9 +2667,9 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, Getter getter, Setter setter, napi_property_attributes attributes, - void* /*data*/) { + void* data) { typedef details::AccessorCallbackData CbData; - auto callbackData = new CbData({ getter, setter }); + auto callbackData = new CbData({ getter, setter, data }); napi_status status = AttachData(env, object, callbackData); NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); @@ -2701,9 +2704,9 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, Getter getter, Setter setter, napi_property_attributes attributes, - void* /*data*/) { + void* data) { typedef details::AccessorCallbackData CbData; - auto callbackData = new CbData({ getter, setter }); + auto callbackData = new CbData({ getter, setter, data }); napi_status status = AttachData(env, object, callbackData); NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); diff --git a/test/object/object.cc b/test/object/object.cc index 0dfc61818..c94a4215c 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -33,6 +33,10 @@ Value HasPropertyWithCStyleString(const CallbackInfo& info); Value HasPropertyWithCppStyleString(const CallbackInfo& info); static bool testValue = true; +// Used to test void* Data() integrity +struct UserDataHolder { + int32_t value; +}; Value TestGetter(const CallbackInfo& info) { return Boolean::New(info.Env(), testValue); @@ -42,6 +46,16 @@ void TestSetter(const CallbackInfo& info) { testValue = info[0].As(); } +Value TestGetterWithUserData(const CallbackInfo& info) { + const UserDataHolder* holder = reinterpret_cast(info.Data()); + return Number::New(info.Env(), holder->value); +} + +void TestSetterWithUserData(const CallbackInfo& info) { + UserDataHolder* holder = reinterpret_cast(info.Data()); + holder->value = info[0].As().Int32Value(); +} + Value TestFunction(const CallbackInfo& info) { return Boolean::New(info.Env(), true); } @@ -58,11 +72,15 @@ void DefineProperties(const CallbackInfo& info) { Env env = info.Env(); Boolean trueValue = Boolean::New(env, true); + UserDataHolder* holder = new UserDataHolder(); + holder->value = 1234; if (nameType.Utf8Value() == "literal") { obj.DefineProperties({ PropertyDescriptor::Accessor(env, obj, "readonlyAccessor", TestGetter), PropertyDescriptor::Accessor(env, obj, "readwriteAccessor", TestGetter, TestSetter), + PropertyDescriptor::Accessor(env, obj, "readonlyAccessorWithUserData", TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + PropertyDescriptor::Accessor(env, obj, "readwriteAccessorWithUserData", TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), PropertyDescriptor::Value("readonlyValue", trueValue), PropertyDescriptor::Value("readwriteValue", trueValue, napi_writable), PropertyDescriptor::Value("enumerableValue", trueValue, napi_enumerable), @@ -76,6 +94,8 @@ void DefineProperties(const CallbackInfo& info) { // work around the issue. std::string str1("readonlyAccessor"); std::string str2("readwriteAccessor"); + std::string str1a("readonlyAccessorWithUserData"); + std::string str2a("readwriteAccessorWithUserData"); std::string str3("readonlyValue"); std::string str4("readwriteValue"); std::string str5("enumerableValue"); @@ -85,6 +105,8 @@ void DefineProperties(const CallbackInfo& info) { obj.DefineProperties({ PropertyDescriptor::Accessor(env, obj, str1, TestGetter), PropertyDescriptor::Accessor(env, obj, str2, TestGetter, TestSetter), + PropertyDescriptor::Accessor(env, obj, str1a, TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + PropertyDescriptor::Accessor(env, obj, str2a, TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), PropertyDescriptor::Value(str3, trueValue), PropertyDescriptor::Value(str4, trueValue, napi_writable), PropertyDescriptor::Value(str5, trueValue, napi_enumerable), @@ -97,6 +119,10 @@ void DefineProperties(const CallbackInfo& info) { Napi::String::New(env, "readonlyAccessor"), TestGetter), PropertyDescriptor::Accessor(env, obj, Napi::String::New(env, "readwriteAccessor"), TestGetter, TestSetter), + PropertyDescriptor::Accessor(env, obj, + Napi::String::New(env, "readonlyAccessorWithUserData"), TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + PropertyDescriptor::Accessor(env, obj, + Napi::String::New(env, "readwriteAccessorWithUserData"), TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), PropertyDescriptor::Value( Napi::String::New(env, "readonlyValue"), trueValue), PropertyDescriptor::Value( diff --git a/test/object/object.js b/test/object/object.js index 6b5884519..b4fe57dff 100644 --- a/test/object/object.js +++ b/test/object/object.js @@ -26,6 +26,10 @@ function test(binding) { assertPropertyIsNot(obj, 'readonlyAccessor', 'configurable'); assert.strictEqual(obj.readonlyAccessor, true); + assertPropertyIsNot(obj, 'readonlyAccessorWithUserData', 'enumerable'); + assertPropertyIsNot(obj, 'readonlyAccessorWithUserData', 'configurable'); + assert.strictEqual(obj.readonlyAccessorWithUserData, 1234, nameType); + assertPropertyIsNot(obj, 'readwriteAccessor', 'enumerable'); assertPropertyIsNot(obj, 'readwriteAccessor', 'configurable'); obj.readwriteAccessor = false; @@ -33,6 +37,13 @@ function test(binding) { obj.readwriteAccessor = true; assert.strictEqual(obj.readwriteAccessor, true); + assertPropertyIsNot(obj, 'readwriteAccessorWithUserData', 'enumerable'); + assertPropertyIsNot(obj, 'readwriteAccessorWithUserData', 'configurable'); + obj.readwriteAccessorWithUserData = 2; + assert.strictEqual(obj.readwriteAccessorWithUserData, 2); + obj.readwriteAccessorWithUserData = -14; + assert.strictEqual(obj.readwriteAccessorWithUserData, -14); + assertPropertyIsNot(obj, 'readonlyValue', 'writable'); assertPropertyIsNot(obj, 'readonlyValue', 'enumerable'); assertPropertyIsNot(obj, 'readonlyValue', 'configurable'); From 0b4027575216c3ceb5839d4699556b9a27814f88 Mon Sep 17 00:00:00 2001 From: Philipp Renoth Date: Fri, 28 Dec 2018 12:25:15 +0100 Subject: [PATCH 072/696] src: fix noexcept control flow issues - adapt `NAPI_THROW`, create `NAPI_THROW_VOID`: either throw or return, never continue - fix `Error::ThrowAsJavaScriptException`: if `napi_throw` fails, either explicitly throw or create fatal error Fixes: https://github.com/nodejs/node-addon-api/issues/419 PR-URL: https://github.com/nodejs/node-addon-api/pull/420 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- napi-inl.h | 59 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 261a261ee..aeb14ff3e 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -19,11 +19,15 @@ namespace details { #ifdef NAPI_CPP_EXCEPTIONS -#define NAPI_THROW(e) throw e - // When C++ exceptions are enabled, Errors are thrown directly. There is no need -// to return anything after the throw statement. The variadic parameter is an +// to return anything after the throw statements. The variadic parameter is an // optional return value that is ignored. +// We need _VOID versions of the macros to avoid warnings resulting from +// leaving the NAPI_THROW_* `...` argument empty. + +#define NAPI_THROW(e, ...) throw e +#define NAPI_THROW_VOID(e) throw e + #define NAPI_THROW_IF_FAILED(env, status, ...) \ if ((status) != napi_ok) throw Error::New(env); @@ -32,19 +36,30 @@ namespace details { #else // NAPI_CPP_EXCEPTIONS -#define NAPI_THROW(e) (e).ThrowAsJavaScriptException(); - // When C++ exceptions are disabled, Errors are thrown as JavaScript exceptions, // which are pending until the callback returns to JS. The variadic parameter // is an optional return value; usually it is an empty result. +// We need _VOID versions of the macros to avoid warnings resulting from +// leaving the NAPI_THROW_* `...` argument empty. + +#define NAPI_THROW(e, ...) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ + } while (0) + +#define NAPI_THROW_VOID(e) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return; \ + } while (0) + #define NAPI_THROW_IF_FAILED(env, status, ...) \ if ((status) != napi_ok) { \ Error::New(env).ThrowAsJavaScriptException(); \ return __VA_ARGS__; \ } -// We need a _VOID version of this macro to avoid warnings resulting from -// leaving the NAPI_THROW_IF_FAILED `...` argument empty. #define NAPI_THROW_IF_FAILED_VOID(env, status) \ if ((status) != napi_ok) { \ Error::New(env).ThrowAsJavaScriptException(); \ @@ -1312,8 +1327,8 @@ inline DataView DataView::New(napi_env env, size_t byteOffset) { if (byteOffset > arrayBuffer.ByteLength()) { NAPI_THROW(RangeError::New(env, - "Start offset is outside the bounds of the buffer")); - return DataView(); + "Start offset is outside the bounds of the buffer"), + DataView()); } return New(env, arrayBuffer, byteOffset, arrayBuffer.ByteLength() - byteOffset); @@ -1324,8 +1339,8 @@ inline DataView DataView::New(napi_env env, size_t byteOffset, size_t byteLength) { if (byteOffset + byteLength > arrayBuffer.ByteLength()) { - NAPI_THROW(RangeError::New(env, "Invalid DataView length")); - return DataView(); + NAPI_THROW(RangeError::New(env, "Invalid DataView length"), + DataView()); } napi_value value; napi_status status = napi_create_dataview( @@ -1451,8 +1466,7 @@ inline T DataView::ReadData(size_t byteOffset) const { if (byteOffset + sizeof(T) > _length || byteOffset + sizeof(T) < byteOffset) { // overflow NAPI_THROW(RangeError::New(_env, - "Offset is outside the bounds of the DataView")); - return 0; + "Offset is outside the bounds of the DataView"), 0); } return *reinterpret_cast(static_cast(_data) + byteOffset); @@ -1462,9 +1476,8 @@ template inline void DataView::WriteData(size_t byteOffset, T value) const { if (byteOffset + sizeof(T) > _length || byteOffset + sizeof(T) < byteOffset) { // overflow - NAPI_THROW(RangeError::New(_env, + NAPI_THROW_VOID(RangeError::New(_env, "Offset is outside the bounds of the DataView")); - return; } *reinterpret_cast(static_cast(_data) + byteOffset) = value; @@ -1600,7 +1613,7 @@ inline TypedArrayOf::TypedArrayOf(napi_env env, : TypedArray(env, value, type, length), _data(data) { if (!(type == TypedArrayTypeForPrimitiveType() || (type == napi_uint8_clamped_array && std::is_same::value))) { - NAPI_THROW(TypeError::New(env, "Array type must match the template parameter. " + NAPI_THROW_VOID(TypeError::New(env, "Array type must match the template parameter. " "(Uint8 arrays may optionally have the \"clamped\" array type.)")); } } @@ -2034,8 +2047,20 @@ inline const std::string& Error::Message() const NAPI_NOEXCEPT { inline void Error::ThrowAsJavaScriptException() const { HandleScope scope(_env); if (!IsEmpty()) { + + // We intentionally don't use `NAPI_THROW_*` macros here to ensure + // that there is no possible recursion as `ThrowAsJavaScriptException` + // is part of `NAPI_THROW_*` macro definition for noexcept. + napi_status status = napi_throw(_env, Value()); - NAPI_THROW_IF_FAILED_VOID(_env, status); + +#ifdef NAPI_CPP_EXCEPTIONS + if (status != napi_ok) { + throw Error::New(_env); + } +#else // NAPI_CPP_EXCEPTIONS + NAPI_FATAL_IF_FAILED(status, "Error::ThrowAsJavaScriptException", "napi_throw"); +#endif // NAPI_CPP_EXCEPTIONS } } From 91eaa6f4cb970894ccbf7d9a0efb515d451191f2 Mon Sep 17 00:00:00 2001 From: Philipp Renoth Date: Fri, 28 Dec 2018 01:08:53 +0100 Subject: [PATCH 073/696] src: fix callbackData leaks on error napi status PR-URL: https://github.com/nodejs/node-addon-api/pull/417 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- napi-inl.h | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index aeb14ff3e..40959d8e1 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1673,7 +1673,11 @@ inline Function Function::New(napi_env env, CbData::Wrapper, callbackData, &value); - NAPI_THROW_IF_FAILED(env, status, Function()); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, Function()); + } + return Function(env, value); } @@ -2636,7 +2640,10 @@ PropertyDescriptor::Accessor(Napi::Env env, auto callbackData = new CbData({ getter, data }); napi_status status = AttachData(env, object, callbackData); - NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } return PropertyDescriptor({ utf8name, @@ -2671,7 +2678,10 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, auto callbackData = new CbData({ getter, data }); napi_status status = AttachData(env, object, callbackData); - NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } return PropertyDescriptor({ nullptr, @@ -2697,7 +2707,10 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, auto callbackData = new CbData({ getter, setter, data }); napi_status status = AttachData(env, object, callbackData); - NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } return PropertyDescriptor({ utf8name, @@ -2734,7 +2747,10 @@ inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, auto callbackData = new CbData({ getter, setter, data }); napi_status status = AttachData(env, object, callbackData); - NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } return PropertyDescriptor({ nullptr, From 020ac4a628debf7ce44429fd22c5bdff7c88a50a Mon Sep 17 00:00:00 2001 From: Philipp Renoth Date: Wed, 26 Dec 2018 16:54:20 +0100 Subject: [PATCH 074/696] src: make `Object::GetPropertyNames()` const Fixes: https://github.com/nodejs/node-addon-api/issues/380 PR-URL: https://github.com/nodejs/node-addon-api/pull/415 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- napi-inl.h | 2 +- napi.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 40959d8e1..4f532c77b 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1078,7 +1078,7 @@ inline bool Object::Delete(uint32_t index) { return result; } -inline Array Object::GetPropertyNames() { +inline Array Object::GetPropertyNames() const { napi_value result; napi_status status = napi_get_property_names(_env, _value, &result); NAPI_THROW_IF_FAILED(_env, status, Array()); diff --git a/napi.h b/napi.h index 16d09943a..22fbd2de9 100644 --- a/napi.h +++ b/napi.h @@ -601,7 +601,7 @@ namespace Napi { uint32_t index ///< Property / element index ); - Array GetPropertyNames(); ///< Get all property names + Array GetPropertyNames() const; ///< Get all property names /// Defines a property on the object. void DefineProperty( From fa49d6841616c12d2602a5454599fc6e8783d0cc Mon Sep 17 00:00:00 2001 From: Philipp Renoth Date: Wed, 26 Dec 2018 15:51:27 +0100 Subject: [PATCH 075/696] doc: fix some `Finalizer` signatures - External::New - ArrayBuffer::New - Buffer::New PR-URL: https://github.com/nodejs/node-addon-api/pull/414 Fixes: https://github.com/nodejs/node-addon-api/issues/383 Refs: https://github.com/nodejs/node-addon-api/pull/384 Reviewed-By: Michael Dawson --- napi.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/napi.h b/napi.h index 22fbd2de9..bff387adb 100644 --- a/napi.h +++ b/napi.h @@ -633,12 +633,12 @@ namespace Napi { public: static External New(napi_env env, T* data); - // Finalizer must implement operator() accepting a T* and returning void. + // Finalizer must implement `void operator()(Env env, T* data)`. template static External New(napi_env env, T* data, Finalizer finalizeCallback); - // Finalizer must implement operator() accepting a T* and Hint* and returning void. + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. template static External New(napi_env env, T* data, @@ -686,8 +686,7 @@ namespace Napi { size_t byteLength, ///< Length of the external buffer to be used by the array, /// in bytes Finalizer finalizeCallback ///< Function to be called when the array buffer is destroyed; - /// must implement `operator()`, accept a `void*` (which is the - /// data buffer pointer), and return `void` + /// must implement `void operator()(Env env, void* externalData)` ); /// Creates a new ArrayBuffer instance, using an external buffer with specified byte length. @@ -698,8 +697,7 @@ namespace Napi { size_t byteLength, ///< Length of the external buffer to be used by the array, /// in bytes Finalizer finalizeCallback, ///< Function to be called when the array buffer is destroyed; - /// must implement `operator()`, accept a `void*` (which is the - /// data buffer pointer) and `Hint*`, and return `void` + /// must implement `void operator()(Env env, void* externalData, Hint* hint)` Hint* finalizeHint ///< Hint (second parameter) to be passed to the finalize callback ); @@ -969,12 +967,12 @@ namespace Napi { static Buffer New(napi_env env, size_t length); static Buffer New(napi_env env, T* data, size_t length); - // Finalizer must implement operator() accepting a T* and returning void. + // Finalizer must implement `void operator()(Env env, T* data)`. template static Buffer New(napi_env env, T* data, size_t length, Finalizer finalizeCallback); - // Finalizer must implement operator() accepting a T* and Hint* and returning void. + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. template static Buffer New(napi_env env, T* data, size_t length, From 48220335b09081d9ae7ecba407fa40b889c85a7d Mon Sep 17 00:00:00 2001 From: Jim Schlight Date: Fri, 1 Feb 2019 11:48:13 -0800 Subject: [PATCH 076/696] Membership review update --- README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4f6a4431d..0b46ff5d7 100644 --- a/README.md +++ b/README.md @@ -152,20 +152,27 @@ Take a look and get inspired by our **[test suite](https://github.com/nodejs/nod -### WG Members / Collaborators -| Name | GitHub link | +## WG Members / Collaborators + +### Active +| Name | GitHub Link | | ------------------- | ----------------------------------------------------- | | Anna Henningsen | [addaleax](https://github.com/addaleax) | | Arunesh Chandra | [aruneshchandra](https://github.com/aruneshchandra) | -| Benjamin Byholm | [kkoopa](https://github.com/kkoopa) | | Gabriel Schulhof | [gabrielschulhof](https://github.com/gabrielschulhof) | | Hitesh Kanwathirtha | [digitalinfinity](https://github.com/digitalinfinity) | -| Jason Ginchereau | [jasongin](https://github.com/jasongin) | | Michael Dawson | [mhdawson](https://github.com/mhdawson) | | Nicola Del Gobbo | [NickNaso](https://github.com/NickNaso) | -| Sampson Gao | [sampsongao](https://github.com/sampsongao) | +| Jim Schlight | [jschlight](https://github.com/jschlight) | | Taylor Woll | [boingoing](https://github.com/boingoing) | +### Emeritus +| Name | GitHub Link | +| ------------------- | ----------------------------------------------------- | +| Benjamin Byholm | [kkoopa](https://github.com/kkoopa) | +| Jason Ginchereau | [jasongin](https://github.com/jasongin) | +| Sampson Gao | [sampsongao](https://github.com/sampsongao) | + Licensed under [MIT](./LICENSE.md) From 4921e74d83b00c96ae55609f88443f893f192240 Mon Sep 17 00:00:00 2001 From: Jim Schlight Date: Mon, 4 Feb 2019 09:42:00 -0800 Subject: [PATCH 077/696] Rearranges names to be alphabetical --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b46ff5d7..a38309f52 100644 --- a/README.md +++ b/README.md @@ -161,9 +161,9 @@ Take a look and get inspired by our **[test suite](https://github.com/nodejs/nod | Arunesh Chandra | [aruneshchandra](https://github.com/aruneshchandra) | | Gabriel Schulhof | [gabrielschulhof](https://github.com/gabrielschulhof) | | Hitesh Kanwathirtha | [digitalinfinity](https://github.com/digitalinfinity) | +| Jim Schlight | [jschlight](https://github.com/jschlight) | | Michael Dawson | [mhdawson](https://github.com/mhdawson) | | Nicola Del Gobbo | [NickNaso](https://github.com/NickNaso) | -| Jim Schlight | [jschlight](https://github.com/jschlight) | | Taylor Woll | [boingoing](https://github.com/boingoing) | ### Emeritus From ad6f569f85a6794f655df34cf03755ca25c7d40a Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Singh Date: Sun, 27 Jan 2019 19:08:05 +0530 Subject: [PATCH 078/696] doc: dix typo Fixes type at line 27 of doc/number.md. PR-URL: https://github.com/nodejs/node-addon-api/pull/435 Reviewed-By: Nicola Del Gobbo Reviewed-By: Michael Dawson --- doc/number.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/number.md b/doc/number.md index 8226909a8..fc062a7fb 100644 --- a/doc/number.md +++ b/doc/number.md @@ -24,7 +24,7 @@ Creates a new instance of a `Napi::Number` object. Napi::Number(napi_env env, napi_value value); ``` - - `[in] env`: The `napi_env` environment in which to construct the `Napi::Nuber` object. + - `[in] env`: The `napi_env` environment in which to construct the `Napi::Number` object. - `[in] value`: The `napi_value` which is a handle for a JavaScript `Number`. Returns a non-empty `Napi::Number` object. From 0bc7987806454d8db6ffec46ad9262f0446df4a2 Mon Sep 17 00:00:00 2001 From: Jake Barnes Date: Tue, 22 Jan 2019 11:31:18 +1100 Subject: [PATCH 079/696] doc: fix references to Weak and Persistent PR-URL: https://github.com/nodejs/node-addon-api/pull/428 Reviewed-By: Nicola Del Gobbo Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- doc/function_reference.md | 4 ++-- doc/object_reference.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/function_reference.md b/doc/function_reference.md index a7988acb2..7b299b9bb 100644 --- a/doc/function_reference.md +++ b/doc/function_reference.md @@ -23,7 +23,7 @@ Creates a "weak" reference to the value, in that the initial reference count is set to 0. ```cpp -static Napi::FunctionReference Napi::FunctionReference::Weak(const Napi::Function& value); +static Napi::FunctionReference Napi::Weak(const Napi::Function& value); ``` - `[in] value`: The value which is to be referenced. @@ -36,7 +36,7 @@ Creates a "persistent" reference to the value, in that the initial reference count is set to 1. ```cpp -static Napi::FunctionReference Napi::FunctionReference::Persistent(const Napi::Function& value); +static Napi::FunctionReference Napi::Persistent(const Napi::Function& value); ``` - `[in] value`: The value which is to be referenced. diff --git a/doc/object_reference.md b/doc/object_reference.md index c08f8beed..6e579e2d7 100644 --- a/doc/object_reference.md +++ b/doc/object_reference.md @@ -40,7 +40,7 @@ static Napi::ObjectReference Napi::ObjectReference::New(const Napi::Object& valu Returns the newly created reference. ```cpp -static Napi::ObjectReference Napi::ObjectReference::Weak(const Napi::Object& value); +static Napi::ObjectReference Napi::Weak(const Napi::Object& value); ``` Creates a "weak" reference to the value, in that the initial count of number of references is set to 0. @@ -50,7 +50,7 @@ Creates a "weak" reference to the value, in that the initial count of number of Returns the newly created reference. ```cpp -static Napi::ObjectReference Napi::ObjectReference::Persistent(const Napi::Object& value); +static Napi::ObjectReference Napi::Persistent(const Napi::Object& value); ``` Creates a "persistent" reference to the value, in that the initial count of number of references is set to 1. From b409a2f9877ceeed8f09b783d9bd5e6f45c1baea Mon Sep 17 00:00:00 2001 From: Sam Roberts Date: Mon, 25 Feb 2019 10:29:52 -0800 Subject: [PATCH 080/696] package: add npm search keywords PR-URL: https://github.com/nodejs/node-addon-api/pull/452 Reviewed-By: Michael Dawson --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index cd313a30c..2adc03edf 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ }, "directories": {}, "homepage": "https://github.com/nodejs/node-addon-api", + "keywords": ["n-api", "napi", "addon", "native", "bindings", "c", "c++", "nan", "node-addon-api"], "license": "MIT", "main": "index.js", "name": "node-addon-api", From fcf173d2a177cca2cc5a722117fe374513d7d1fb Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Thu, 14 Feb 2019 19:14:57 -0800 Subject: [PATCH 081/696] src: expose macros that throw errors Macros `NAPI_THROW`, `NAPI_THROW_IF_FAILED`, and `NAPI_FATAL_IF_FAILED` have so far been used only in the implementation of node-addon-api. Nevertheless, they are useful in cases where direct N-API calls must be interspersed between normal node-addon-api usage. The greatest value they provide is that they convert non-`napi_ok` `napi_status` values into errors that can be thrown, and that they throw the errors respecting whether C++ exceptions are enabled or not. PR-URL: https://github.com/nodejs/node-addon-api/pull/448 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- doc/error_handling.md | 31 ++++++++++++++++++++++ napi-inl.h | 62 ------------------------------------------- napi.h | 58 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 62 deletions(-) diff --git a/doc/error_handling.md b/doc/error_handling.md index d5df55450..9a0ef349e 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -153,3 +153,34 @@ if (env.IsExceptionPending()) { Since the exception was cleared here, it will not be propagated as a JavaScript exception after the native callback returns. + +## Calling N-API directly from a **node-addon-api** addon + +**node-addon-api** provides macros for throwing errors in response to non-OK +`napi_status` results when calling [N-API](https://nodejs.org/docs/latest/api/n-api.html) +functions from within a native addon. These macros are defined differently +depending on whether C++ exceptions are enabled or not, but are available for +use in either case. + +### `NAPI_THROW(e, ...)` + +This macro accepts a `Napi::Error`, throws it, and returns the value given as +the last parameter. If C++ exceptions are enabled (by defining +`NAPI_CPP_EXCEPTIONS` during the build), the return value will be ignored. + +### `NAPI_THROW_IF_FAILED(env, status, ...)` + +This macro accepts a `Napi::Env` and a `napi_status`. It constructs an error +from the `napi_status`, throws it, and returns the value given as the last +parameter. If C++ exceptions are enabled (by defining `NAPI_CPP_EXCEPTIONS` +during the build), the return value will be ignored. + +### `NAPI_THROW_IF_FAILED_VOID(env, status)` + +This macro accepts a `Napi::Env` and a `napi_status`. It constructs an error +from the `napi_status`, throws it, and returns. + +### `NAPI_FATAL_IF_FAILED(status, location, message)` + +This macro accepts a `napi_status`, a C string indicating the location where the +error occurred, and a second C string for the message to display. diff --git a/napi-inl.h b/napi-inl.h index 4f532c77b..d5fbb1bf7 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -17,64 +17,6 @@ namespace Napi { // Helpers to handle functions exposed from C++. namespace details { -#ifdef NAPI_CPP_EXCEPTIONS - -// When C++ exceptions are enabled, Errors are thrown directly. There is no need -// to return anything after the throw statements. The variadic parameter is an -// optional return value that is ignored. -// We need _VOID versions of the macros to avoid warnings resulting from -// leaving the NAPI_THROW_* `...` argument empty. - -#define NAPI_THROW(e, ...) throw e -#define NAPI_THROW_VOID(e) throw e - -#define NAPI_THROW_IF_FAILED(env, status, ...) \ - if ((status) != napi_ok) throw Error::New(env); - -#define NAPI_THROW_IF_FAILED_VOID(env, status) \ - if ((status) != napi_ok) throw Error::New(env); - -#else // NAPI_CPP_EXCEPTIONS - -// When C++ exceptions are disabled, Errors are thrown as JavaScript exceptions, -// which are pending until the callback returns to JS. The variadic parameter -// is an optional return value; usually it is an empty result. -// We need _VOID versions of the macros to avoid warnings resulting from -// leaving the NAPI_THROW_* `...` argument empty. - -#define NAPI_THROW(e, ...) \ - do { \ - (e).ThrowAsJavaScriptException(); \ - return __VA_ARGS__; \ - } while (0) - -#define NAPI_THROW_VOID(e) \ - do { \ - (e).ThrowAsJavaScriptException(); \ - return; \ - } while (0) - -#define NAPI_THROW_IF_FAILED(env, status, ...) \ - if ((status) != napi_ok) { \ - Error::New(env).ThrowAsJavaScriptException(); \ - return __VA_ARGS__; \ - } - -#define NAPI_THROW_IF_FAILED_VOID(env, status) \ - if ((status) != napi_ok) { \ - Error::New(env).ThrowAsJavaScriptException(); \ - return; \ - } - -#endif // NAPI_CPP_EXCEPTIONS - -#define NAPI_FATAL_IF_FAILED(status, location, message) \ - do { \ - if ((status) != napi_ok) { \ - Error::Fatal((location), (message)); \ - } \ - } while (0) - // Attach a data item to an object and delete it when the object gets // garbage-collected. // TODO: Replace this code with `napi_add_finalizer()` whenever it becomes @@ -3705,10 +3647,6 @@ inline const napi_node_version* VersionManagement::GetNodeVersion(Env env) { return result; } -// These macros shouldn't be useful in user code. -#undef NAPI_THROW -#undef NAPI_THROW_IF_FAILED - } // namespace Napi #endif // SRC_NAPI_INL_H_ diff --git a/napi.h b/napi.h index bff387adb..13ac4853f 100644 --- a/napi.h +++ b/napi.h @@ -38,6 +38,64 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 #define NAPI_NOEXCEPT noexcept #endif +#ifdef NAPI_CPP_EXCEPTIONS + +// When C++ exceptions are enabled, Errors are thrown directly. There is no need +// to return anything after the throw statements. The variadic parameter is an +// optional return value that is ignored. +// We need _VOID versions of the macros to avoid warnings resulting from +// leaving the NAPI_THROW_* `...` argument empty. + +#define NAPI_THROW(e, ...) throw e +#define NAPI_THROW_VOID(e) throw e + +#define NAPI_THROW_IF_FAILED(env, status, ...) \ + if ((status) != napi_ok) throw Error::New(env); + +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) throw Error::New(env); + +#else // NAPI_CPP_EXCEPTIONS + +// When C++ exceptions are disabled, Errors are thrown as JavaScript exceptions, +// which are pending until the callback returns to JS. The variadic parameter +// is an optional return value; usually it is an empty result. +// We need _VOID versions of the macros to avoid warnings resulting from +// leaving the NAPI_THROW_* `...` argument empty. + +#define NAPI_THROW(e, ...) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ + } while (0) + +#define NAPI_THROW_VOID(e) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return; \ + } while (0) + +#define NAPI_THROW_IF_FAILED(env, status, ...) \ + if ((status) != napi_ok) { \ + Error::New(env).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ + } + +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) { \ + Error::New(env).ThrowAsJavaScriptException(); \ + return; \ + } + +#endif // NAPI_CPP_EXCEPTIONS + +#define NAPI_FATAL_IF_FAILED(status, location, message) \ + do { \ + if ((status) != napi_ok) { \ + Error::Fatal((location), (message)); \ + } \ + } while (0) + //////////////////////////////////////////////////////////////////////////////// /// N-API C++ Wrapper Classes /// From 7b87e0b9993942d4404c0a2a209ee28423790914 Mon Sep 17 00:00:00 2001 From: Bernardo Heynemann Date: Mon, 28 Jan 2019 12:34:58 -0200 Subject: [PATCH 082/696] doc: update number.md Update docs to be consistent with how thing are described in other sections. PR-URL: https://github.com/nodejs/node-addon-api/pull/436 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- doc/number.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/number.md b/doc/number.md index fc062a7fb..f4fd584a9 100644 --- a/doc/number.md +++ b/doc/number.md @@ -25,7 +25,7 @@ Napi::Number(napi_env env, napi_value value); ``` - `[in] env`: The `napi_env` environment in which to construct the `Napi::Number` object. - - `[in] value`: The `napi_value` which is a handle for a JavaScript `Number`. + - `[in] value`: The JavaScript value holding a number. Returns a non-empty `Napi::Number` object. @@ -36,8 +36,9 @@ Napi::Number(napi_env env, napi_value value); ```cpp Napi::Number Napi::Number::New(napi_env env, double value); ``` - - `[in] env`: The `napi_env` environment in which to construct the `Napi::Nuber` object. - - `[in] value`: The `napi_value` which is a handle for a JavaScript `Number`. + - `[in] env`: The `napi_env` environment in which to construct the `Napi::Number` object. + - `[in] value`: The C++ primitive from which to instantiate the `Napi::Number`. + Creates a new instance of a `Napi::Number` object. From c629553cd7b08ea9e56a55176583219ee19a8a86 Mon Sep 17 00:00:00 2001 From: "Bruce A. MacNaughton" Date: Thu, 10 Jan 2019 12:54:20 -0800 Subject: [PATCH 083/696] doc: minor doc corrections and clarifications - class_property_descriptor - Contructor => Constructor - object - returns *undefined* not NULL if key doesn't exist - object_wrap - Contructor => Constructor - property_descriptor - environemnt => environment PR-URL: https://github.com/nodejs/node-addon-api/pull/426 Reviewed-By: Michael Dawson Reviewed-By: NickNaso --- doc/class_property_descriptor.md | 4 ++-- doc/number.md | 1 - doc/object.md | 2 +- doc/object_wrap.md | 2 +- doc/property_descriptor.md | 2 +- tools/README.md | 4 ++-- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/doc/class_property_descriptor.md b/doc/class_property_descriptor.md index 5df283491..7e81358e4 100644 --- a/doc/class_property_descriptor.md +++ b/doc/class_property_descriptor.md @@ -8,7 +8,7 @@ This prevents using descriptors from a different class when defining a new class ## Methods -### Contructor +### Constructor Creates new instance of `Napi::ClassPropertyDescriptor` descriptor object. @@ -33,4 +33,4 @@ Returns the original N-API `napi_property_descriptor` wrapped inside the `Napi:: operator const napi_property_descriptor&() const { return _desc; } ``` -Returns the original N-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` \ No newline at end of file +Returns the original N-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` diff --git a/doc/number.md b/doc/number.md index f4fd584a9..7cf70ae27 100644 --- a/doc/number.md +++ b/doc/number.md @@ -39,7 +39,6 @@ Napi::Number Napi::Number::New(napi_env env, double value); - `[in] env`: The `napi_env` environment in which to construct the `Napi::Number` object. - `[in] value`: The C++ primitive from which to instantiate the `Napi::Number`. - Creates a new instance of a `Napi::Number` object. ### Int32Value diff --git a/doc/object.md b/doc/object.md index 3e32fa4e9..1935664b5 100644 --- a/doc/object.md +++ b/doc/object.md @@ -108,7 +108,7 @@ Napi::Value Napi::Object::Get(____ key); ``` - `[in] key`: The name of the property to return the value for. -Returns the [`Napi::Value`](value.md) associated with the key property. Returns NULL if no such key exists. +Returns the [`Napi::Value`](value.md) associated with the key property. Returns the value *undefined* if the key does not exist. The `key` can be any of the following types: - `napi_value` diff --git a/doc/object_wrap.md b/doc/object_wrap.md index 93773da69..5bc06c871 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -116,7 +116,7 @@ against the class constructor. ## Methods -### Contructor +### Constructor Creates a new instance of a JavaScript object that wraps native instance. diff --git a/doc/property_descriptor.md b/doc/property_descriptor.md index 82a87191f..324b62f74 100644 --- a/doc/property_descriptor.md +++ b/doc/property_descriptor.md @@ -192,7 +192,7 @@ static Napi::PropertyDescriptor Napi::PropertyDescriptor::Function ( void *data = nullptr); ``` -* `[in] env`: The environemnt in which to create this accessor. +* `[in] env`: The environment in which to create this accessor. * `[in] name`: The name of the Callable function. * `[in] cb`: The function * `[in] attributes`: Potential attributes for the getter function. diff --git a/tools/README.md b/tools/README.md index a711a808c..b71e5d92c 100644 --- a/tools/README.md +++ b/tools/README.md @@ -25,7 +25,7 @@ Here is the list of things that can be fixed easily. ### Major Reconstructions -The implementation of `Napi::ObjectWrap` is significantly different from NAN's. `Napi::ObjectWrap` takes a pointer to the wrapped object and creates a reference to the wrapped object inside ObjectWrap constructor. `Napi::ObjectWrap` also associated wrapped object's instance methods to Javascript module instead of static methods like NAN. +The implementation of `Napi::ObjectWrap` is significantly different from NAN's. `Napi::ObjectWrap` takes a pointer to the wrapped object and creates a reference to the wrapped object inside ObjectWrap constructor. `Napi::ObjectWrap` also associates wrapped object's instance methods to Javascript module instead of static methods like NAN. So if you use Nan::ObjectWrap in your module, you will need to execute the following steps. @@ -39,7 +39,7 @@ and define it as ... } ``` -This way, the `Napi::ObjectWrap` constructor will be invoked after the object has been instanciated and `Napi::ObjectWrap` can use the `this` pointer to create reference to the wrapped object. +This way, the `Napi::ObjectWrap` constructor will be invoked after the object has been instantiated and `Napi::ObjectWrap` can use the `this` pointer to create a reference to the wrapped object. 2. Move your original constructor code into the new constructor. Delete your original constructor. 3. In your class initialization function, associate native methods in the following way. The `&` character before methods is required because they are not static methods but instance methods. From fcfc612728fad0533aaa63f5aaad25bae712eeb3 Mon Sep 17 00:00:00 2001 From: Jinho Bang Date: Thu, 17 Jan 2019 09:02:23 +0900 Subject: [PATCH 084/696] build: new build targets for debug purposes In this project, we can use the following command to test examples. $ npm test It might be very inefficient, especially, if the number of files increases. So, this patch introduces new build targets for debugging purpose as follows: $ npm run-script dev # Build with --debug option $ npm run-script dev:incremental # Incremental dev build This idea comes from @DaAitch. PR-URL: https://github.com/nodejs/node-addon-api/pull/186 Reviewed-By: Gabriel Schulhof Reviewed-By: NickNaso Reviewed-By: Michael Dawson --- README.md | 14 ++++++++++++++ package.json | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/README.md b/README.md index a38309f52..1116caeb6 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,20 @@ npm install npm test --disable-deprecated ``` +### **Debug** + +To run the **node-addon-api** tests with `--debug` option: + +``` +npm run-script dev +``` + +If you want faster build, you might use the following option: + +``` +npm run-script dev:incremental +``` + Take a look and get inspired by our **[test suite](https://github.com/nodejs/node-addon-api/tree/master/test)** diff --git a/package.json b/package.json index 2adc03edf..028b72b92 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,10 @@ "scripts": { "pretest": "node-gyp rebuild -C test", "test": "node test", + "predev": "node-gyp rebuild -C test --debug", + "dev": "node test", + "predev:incremental": "node-gyp configure build -C test --debug", + "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile" }, "version": "1.6.2" From 72b1975cfffbc55a04c64047467d7dd0bfa2934e Mon Sep 17 00:00:00 2001 From: Ryuichi Okumura Date: Mon, 11 Mar 2019 21:16:16 +0900 Subject: [PATCH 085/696] doc: fix links to the Property Descriptor docs PR-URL: https://github.com/nodejs/node-addon-api/pull/458 Reviewed-By: Michael Dawson Reviewed-By: NickNaso --- doc/object.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/object.md b/doc/object.md index 1935664b5..8bee8b653 100644 --- a/doc/object.md +++ b/doc/object.md @@ -142,7 +142,7 @@ Note: This is equivalent to the JavaScript instanceof operator. ```cpp void Napi::Object::DefineProperty (const Napi::PropertyDescriptor& property); ``` -- `[in] property`: A [`Napi::PropertyDescriptor`](propertydescriptor.md). +- `[in] property`: A [`Napi::PropertyDescriptor`](property_descriptor.md). Define a property on the object. @@ -151,7 +151,7 @@ Define a property on the object. ```cpp void Napi::Object::DefineProperties (____ properties) ``` -- `[in] properties`: A list of [`Napi::PropertyDescriptor`](propertydescriptor.md). Can be one of the following types: +- `[in] properties`: A list of [`Napi::PropertyDescriptor`](property_descriptor.md). Can be one of the following types: - const std::initializer_list& - const std::vector& From b0f6b601aaa0a8f488fec2b4f09e0f3f65f4a241 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Fri, 7 Dec 2018 13:20:24 -0800 Subject: [PATCH 086/696] src: add AsyncWorker destruction suppression Add method `SuppressDestruct()` to `AsyncWorker`, which will cause an instance of the class to remain allocated even after the `OnOK` callback fires. Such an instance must be explicitly `delete`-ed from user code. Re: https://github.com/nodejs/node-addon-api/issues/231 Re: https://github.com/nodejs/abi-stable-node/issues/353 PR-URL: https://github.com/nodejs/node-addon-api/pull/407 Reviewed-By: Michael Dawson Reviewed-By: NickNaso --- doc/async_worker.md | 9 +++++ napi-inl.h | 13 ++++++- napi.h | 2 + test/asyncworker-persistent.cc | 67 ++++++++++++++++++++++++++++++++++ test/asyncworker-persistent.js | 27 ++++++++++++++ test/binding.cc | 2 + test/binding.gyp | 7 ++++ test/index.js | 1 + 8 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 test/asyncworker-persistent.cc create mode 100644 test/asyncworker-persistent.js diff --git a/doc/async_worker.md b/doc/async_worker.md index a71b29d9f..43e11d587 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -66,6 +66,15 @@ the computation that happened in the `Napi::AsyncWorker::Execute` method, unless the default implementation of `Napi::AsyncWorker::OnOK` or `Napi::AsyncWorker::OnError` is overridden. +### SuppressDestruct + +```cpp +void Napi::AsyncWorker::SuppressDestruct(); +``` + +Prevents the destruction of the `Napi::AsyncWorker` instance upon completion of +the `Napi::AsyncWorker::OnOK` callback. + ### SetError Sets the error message for the error that happened during the execution. Setting diff --git a/napi-inl.h b/napi-inl.h index d5fbb1bf7..c15ed97a1 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3510,7 +3510,8 @@ inline AsyncWorker::AsyncWorker(const Object& receiver, const Object& resource) : _env(callback.Env()), _receiver(Napi::Persistent(receiver)), - _callback(Napi::Persistent(callback)) { + _callback(Napi::Persistent(callback)), + _suppress_destruct(false) { napi_value resource_id; napi_status status = napi_create_string_latin1( _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); @@ -3536,6 +3537,7 @@ inline AsyncWorker::AsyncWorker(AsyncWorker&& other) { _receiver = std::move(other._receiver); _callback = std::move(other._callback); _error = std::move(other._error); + _suppress_destruct = other._suppress_destruct; } inline AsyncWorker& AsyncWorker::operator =(AsyncWorker&& other) { @@ -3546,6 +3548,7 @@ inline AsyncWorker& AsyncWorker::operator =(AsyncWorker&& other) { _receiver = std::move(other._receiver); _callback = std::move(other._callback); _error = std::move(other._error); + _suppress_destruct = other._suppress_destruct; return *this; } @@ -3575,6 +3578,10 @@ inline FunctionReference& AsyncWorker::Callback() { return _callback; } +inline void AsyncWorker::SuppressDestruct() { + _suppress_destruct = true; +} + inline void AsyncWorker::OnOK() { _callback.Call(_receiver.Value(), std::initializer_list{}); } @@ -3615,7 +3622,9 @@ inline void AsyncWorker::OnWorkComplete( return nullptr; }); } - delete self; + if (!self->_suppress_destruct) { + delete self; + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 13ac4853f..7a83d792e 100644 --- a/napi.h +++ b/napi.h @@ -1778,6 +1778,7 @@ namespace Napi { void Queue(); void Cancel(); + void SuppressDestruct(); ObjectReference& Receiver(); FunctionReference& Callback(); @@ -1816,6 +1817,7 @@ namespace Napi { ObjectReference _receiver; FunctionReference _callback; std::string _error; + bool _suppress_destruct; }; // Memory management. diff --git a/test/asyncworker-persistent.cc b/test/asyncworker-persistent.cc new file mode 100644 index 000000000..97aa0cab8 --- /dev/null +++ b/test/asyncworker-persistent.cc @@ -0,0 +1,67 @@ +#include "napi.h" + +// A variant of TestWorker wherein destruction is suppressed. That is, instances +// are not destroyed during the `OnOK` callback. They must be explicitly +// destroyed. + +using namespace Napi; + +namespace { + +class PersistentTestWorker : public AsyncWorker { +public: + static PersistentTestWorker* current_worker; + static void DoWork(const CallbackInfo& info) { + bool succeed = info[0].As(); + Function cb = info[1].As(); + + PersistentTestWorker* worker = new PersistentTestWorker(cb, "TestResource"); + current_worker = worker; + + worker->SuppressDestruct(); + worker->_succeed = succeed; + worker->Queue(); + } + + static Value GetWorkerGone(const CallbackInfo& info) { + return Boolean::New(info.Env(), current_worker == nullptr); + } + + static void DeleteWorker(const CallbackInfo& info) { + (void) info; + delete current_worker; + } + + ~PersistentTestWorker() { + current_worker = nullptr; + } + +protected: + void Execute() override { + if (!_succeed) { + SetError("test error"); + } + } + +private: + PersistentTestWorker(Function cb, + const char* resource_name) + : AsyncWorker(cb, resource_name) {} + + bool _succeed; +}; + +PersistentTestWorker* PersistentTestWorker::current_worker = nullptr; + +} // end of anonymous namespace + +Object InitPersistentAsyncWorker(Env env) { + Object exports = Object::New(env); + exports["doWork"] = Function::New(env, PersistentTestWorker::DoWork); + exports.DefineProperty( + PropertyDescriptor::Accessor(env, exports, "workerGone", + PersistentTestWorker::GetWorkerGone)); + exports["deleteWorker"] = + Function::New(env, PersistentTestWorker::DeleteWorker); + return exports; +} diff --git a/test/asyncworker-persistent.js b/test/asyncworker-persistent.js new file mode 100644 index 000000000..90806ebf9 --- /dev/null +++ b/test/asyncworker-persistent.js @@ -0,0 +1,27 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const common = require('./common'); +const binding = require(`./build/${buildType}/binding.node`); +const noexceptBinding = require(`./build/${buildType}/binding_noexcept.node`); + +function test(binding, succeed) { + return new Promise((resolve) => + // Can't pass an arrow function to doWork because that results in an + // undefined context inside its body when the function gets called. + binding.doWork(succeed, function(e) { + setImmediate(() => { + // If the work is supposed to fail, make sure there's an error. + assert.strictEqual(succeed || e.message === 'test error', true); + assert.strictEqual(binding.workerGone, false); + binding.deleteWorker(); + assert.strictEqual(binding.workerGone, true); + resolve(); + }); + })); +} + +test(binding.persistentasyncworker, false) + .then(() => test(binding.persistentasyncworker, true)) + .then(() => test(noexceptBinding.persistentasyncworker, false)) + .then(() => test(noexceptBinding.persistentasyncworker, true)); diff --git a/test/binding.cc b/test/binding.cc index 0068e7480..4a5fec15c 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -6,6 +6,7 @@ using namespace Napi; Object InitArrayBuffer(Env env); Object InitAsyncContext(Env env); Object InitAsyncWorker(Env env); +Object InitPersistentAsyncWorker(Env env); Object InitBasicTypesArray(Env env); Object InitBasicTypesBoolean(Env env); Object InitBasicTypesNumber(Env env); @@ -42,6 +43,7 @@ Object Init(Env env, Object exports) { exports.Set("arraybuffer", InitArrayBuffer(env)); exports.Set("asynccontext", InitAsyncContext(env)); exports.Set("asyncworker", InitAsyncWorker(env)); + exports.Set("persistentasyncworker", InitPersistentAsyncWorker(env)); exports.Set("basic_types_array", InitBasicTypesArray(env)); exports.Set("basic_types_boolean", InitBasicTypesBoolean(env)); exports.Set("basic_types_number", InitBasicTypesNumber(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 77c388074..0e2b7d05c 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -8,6 +8,7 @@ 'arraybuffer.cc', 'asynccontext.cc', 'asyncworker.cc', + 'asyncworker-persistent.cc', 'basic_types/array.cc', 'basic_types/boolean.cc', 'basic_types/number.cc', @@ -43,6 +44,12 @@ 'defines': ['NODE_ADDON_API_DISABLE_DEPRECATED'] }, { 'sources': ['object/object_deprecated.cc'] + }], + ['OS=="mac"', { + 'cflags+': ['-fvisibility=hidden'], + 'xcode_settings': { + 'OTHER_CFLAGS': ['-fvisibility=hidden'] + } }] ], 'include_dirs': [" Date: Wed, 20 Mar 2019 12:05:38 +0000 Subject: [PATCH 087/696] doc: correct return type of Int32Value to int32_t It currently reads uint32_t, which would be very strange, and is also untrue (I checked napi.h) ;) PR-URL: https://github.com/nodejs/node-addon-api/pull/459 Reviewed-By: Michael Dawson Reviewed-By: NickNaso --- doc/number.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/number.md b/doc/number.md index 7cf70ae27..d6031013d 100644 --- a/doc/number.md +++ b/doc/number.md @@ -43,7 +43,7 @@ Creates a new instance of a `Napi::Number` object. ### Int32Value -Converts a `Napi::Number` value to a `uint32_t` primitive type. +Converts a `Napi::Number` value to a `int32_t` primitive type. ```cpp Napi::Number::Int32Value() const; From 83b41c2fe46a7693aba291ffa740932a9f142be2 Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Tue, 2 Apr 2019 20:44:59 +0200 Subject: [PATCH 088/696] Document adding -fvisibility=hidden flag for macOS users * -fvisibility=hidden flag for macOS user Added section to remember the macOS user to add the `-fvisibility=hidden` flag. PR-URL: https://github.com/nodejs/node-addon-api/pull/460 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- doc/setup.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/doc/setup.md b/doc/setup.md index 36e6fc956..3135bf1d3 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -55,8 +55,19 @@ To use **N-API** in a native module: ```gyp 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], ``` - - 4. Include `napi.h` in the native module code. + 4. If you would like your native addon to support OSX, please also add the + following settings in the `binding.gyp` file: + + ```gyp + ['OS=="mac"', { + 'cflags+': ['-fvisibility=hidden'], + 'xcode_settings': { + 'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden + } + }] + ``` + + 5. Include `napi.h` in the native module code. To ensure only ABI-stable APIs are used, DO NOT include `node.h`, `nan.h`, or `v8.h`. From c12c42519c108b859c7d9f3a3ebb902f948eabd6 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Wed, 3 Apr 2019 22:30:55 +0200 Subject: [PATCH 089/696] Prepare release 1.6.3 --- CHANGELOG.md | 47 +++++++++++++++++++++++++++++++++++++++++++---- README.md | 2 +- package.json | 9 ++++++++- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 780030fa8..b9f1177c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,45 @@ # node-addon-api Changelog -## 2018-11-29 Version 1.6.2 (Current), @NickNaso +## 2019-04-03 Version 1.6.3, @NickNaso + +### Notable changes: + +#### API + +- Added `SuppressDestruct` method to `Napi::AsyncWorker`. +- Added new build targets for debug. +- Exposed macros that throw errors. +- Fixed memory leaks caused by callback data when a napi error occurs. +- Fixed missing `void *data` usage in `Napi::PropertyDescriptors`. + +#### Documentation + +- Some minor corrections all over the documentation + +### Commmits + +* [[`83b41c2fe4`](https://github.com/nodejs/node-addon-api/commit/83b41c2fe4)] - Document adding -fvisibility=hidden flag for macOS users (Nicola Del Gobbo) [#460](https://github.com/nodejs/node-addon-api/pull/460) +* [[`1ed7ad8769`](https://github.com/nodejs/node-addon-api/commit/1ed7ad8769)] - **doc**: correct return type of Int32Value to int32\_t (Bill Gallafent) [#459](https://github.com/nodejs/node-addon-api/pull/459) +* [[`b0f6b601aa`](https://github.com/nodejs/node-addon-api/commit/b0f6b601aa)] - **src**: add AsyncWorker destruction suppression (Gabriel Schulhof) [#407](https://github.com/nodejs/node-addon-api/pull/407) +* [[`72b1975cff`](https://github.com/nodejs/node-addon-api/commit/72b1975cff)] - **doc**: fix links to the Property Descriptor docs (Ryuichi Okumura) [#458](https://github.com/nodejs/node-addon-api/pull/458) +* [[`fcfc612728`](https://github.com/nodejs/node-addon-api/commit/fcfc612728)] - **build**: new build targets for debug purposes (Jinho Bang) [#186](https://github.com/nodejs/node-addon-api/pull/186) +* [[`c629553cd7`](https://github.com/nodejs/node-addon-api/commit/c629553cd7)] - **doc**: minor doc corrections and clarifications (Bruce A. MacNaughton) [#426](https://github.com/nodejs/node-addon-api/pull/426) +* [[`7b87e0b999`](https://github.com/nodejs/node-addon-api/commit/7b87e0b999)] - **doc**: update number.md (Bernardo Heynemann) [#436](https://github.com/nodejs/node-addon-api/pull/436) +* [[`fcf173d2a1`](https://github.com/nodejs/node-addon-api/commit/fcf173d2a1)] - **src**: expose macros that throw errors (Gabriel Schulhof) [#448](https://github.com/nodejs/node-addon-api/pull/448) +* [[`b409a2f987`](https://github.com/nodejs/node-addon-api/commit/b409a2f987)] - **package**: add npm search keywords (Sam Roberts) [#452](https://github.com/nodejs/node-addon-api/pull/452) +* [[`0bc7987806`](https://github.com/nodejs/node-addon-api/commit/0bc7987806)] - **doc**: fix references to Weak and Persistent (Jake Barnes) [#428](https://github.com/nodejs/node-addon-api/pull/428) +* [[`ad6f569f85`](https://github.com/nodejs/node-addon-api/commit/ad6f569f85)] - **doc**: dix typo (Abhishek Kumar Singh) [#435](https://github.com/nodejs/node-addon-api/pull/435) +* [[`28df833a49`](https://github.com/nodejs/node-addon-api/commit/28df833a49)] - Merge pull request #441 from jschlight/master (Jim Schlight) +* [[`4921e74d83`](https://github.com/nodejs/node-addon-api/commit/4921e74d83)] - Rearranges names to be alphabetical (Jim Schlight) +* [[`48220335b0`](https://github.com/nodejs/node-addon-api/commit/48220335b0)] - Membership review update (Jim Schlight) +* [[`44f0695533`](https://github.com/nodejs/node-addon-api/commit/44f0695533)] - Merge pull request #394 from NickNaso/create\_release (Nicola DelGobbo) +* [[`fa49d68416`](https://github.com/nodejs/node-addon-api/commit/fa49d68416)] - **doc**: fix some `Finalizer` signatures (Philipp Renoth) [#414](https://github.com/nodejs/node-addon-api/pull/414) +* [[`020ac4a628`](https://github.com/nodejs/node-addon-api/commit/020ac4a628)] - **src**: make `Object::GetPropertyNames()` const (Philipp Renoth)[#415](https://github.com/nodejs/node-addon-api/pull/415) +* [[`91eaa6f4cb`](https://github.com/nodejs/node-addon-api/commit/91eaa6f4cb)] - **src**: fix callbackData leaks on error napi status (Philipp Renoth) [#417](https://github.com/nodejs/node-addon-api/pull/417) +* [[`0b40275752`](https://github.com/nodejs/node-addon-api/commit/0b40275752)] - **src**: fix noexcept control flow issues (Philipp Renoth) [#420](https://github.com/nodejs/node-addon-api/pull/420) +* [[`c1ff2936f9`](https://github.com/nodejs/node-addon-api/commit/c1ff2936f9)] - **src**: fix missing void\*data usage in PropertyDescriptors (Luciano Martorella) [#374](https://github.com/nodejs/node-addon-api/pull/374) + +## 2018-11-29 Version 1.6.2, @NickNaso ### Notable changes: @@ -12,7 +51,7 @@ * [[`07a0fc4e95`](https://github.com/nodejs/node-addon-api/commit/07a0fc4e95)] - **src**: fix selection logic for 6.x (Michael Dawson) [#402](https://github.com/nodejs/node-addon-api/pull/402) -## 2018-11-14 Version 1.6.1 (Current), @NickNaso +## 2018-11-14 Version 1.6.1, @NickNaso ### Notable changes: @@ -33,7 +72,7 @@ * [[`29a0262ab9`](https://github.com/nodejs/node-addon-api/commit/29a0262ab9)] - **doc**: fix typo (Dongjin Na) [#385](https://github.com/nodejs/node-addon-api/pull/385) * [[`b6dc15b88d`](https://github.com/nodejs/node-addon-api/commit/b6dc15b88d)] - **doc**: make links point to node-addon-examples repo (Nicola Del Gobbo) [#389](https://github.com/nodejs/node-addon-api/pull/389) -## 2018-11-02 Version 1.6.0 (Current), @NickNaso +## 2018-11-02 Version 1.6.0, @NickNaso ### Notable changes: @@ -63,7 +102,7 @@ associated with a callback in place when making certain N-API calls * [[`fd65078e3c`](https://github.com/nodejs/node-addon-api/commit/fd65078e3c)] - README.md: link to new ABI stability guide (Gabriel Schulhof) [#367](https://github.com/nodejs/node-addon-api/pull/367) * [[`ffebf9ba9a`](https://github.com/nodejs/node-addon-api/commit/ffebf9ba9a)] - Updates for release 1.5.0 (NickNaso) -## 2018-10-03 Version 1.5.0 (Current), @NickNaso +## 2018-10-03 Version 1.5.0, @NickNaso ### Notable changes: diff --git a/README.md b/README.md index 1116caeb6..68911ee13 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 1.6.2** +## **Current version: 1.6.3** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index 028b72b92..55c3ea060 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "url": "https://github.com/nodejs/node-addon-api/issues" }, "contributors": [ + "Abhishek Kumar Singh (https://github.com/abhi11210646)", "Andrew Petersen (https://github.com/kirbysayshi)", "Anisha Rohra (https://github.com/anisha-rohra)", "Anna Henningsen (https://github.com/addaleax)", @@ -10,6 +11,8 @@ "Arunesh Chandra (https://github.com/aruneshchandra)", "Ben Berman (https://github.com/rivertam)", "Benjamin Byholm (https://github.com/kkoopa)", + "Bill Gallafent (https://github.com/gallafent)", + "Bruce A. MacNaughton (https://github.com/bmacnaughton)", "Cory Mickelson (https://github.com/corymickelson)", "David Halls (https://github.com/davedoesdev)", "Dongjin Na (https://github.com/nadongguri)", @@ -17,6 +20,7 @@ "Gabriel Schulhof (https://github.com/gabrielschulhof)", "Gus Caplan (https://github.com/devsnek)", "Hitesh Kanwathirtha (https://github.com/digitalinfinity)", + "Jake Barnes (https://github.com/DuBistKomisch)", "Jake Yoon (https://github.com/yjaeseok)", "Jason Ginchereau (https://github.com/jasongin)", "Jim Schlight (https://github.com/jschlight)", @@ -24,6 +28,7 @@ "joshgarde (https://github.com/joshgarde)", "Konstantin Tarkus (https://github.com/koistya)", "Kyle Farnung (https://github.com/kfarnung)", + "Luciano Martorella (https://github.com/lmartorella)", "Matteo Collina (https://github.com/mcollina)", "Michael Dawson (https://github.com/mhdawson)", "Michele Campus (https://github.com/kYroL01)", @@ -32,7 +37,9 @@ "Nick Soggin (https://github.com/iSkore)", "Philipp Renoth (https://github.com/DaAitch)", "Rolf Timmermans (https://github.com/rolftimmermans)", + "Ryuichi Okumura (https://github.com/okuryu)", "Sampson Gao (https://github.com/sampsongao)", + "Sam Roberts (https://github.com/sam-github)", "Taylor Woll (https://github.com/boingoing)", "Thomas Gentilhomme (https://github.com/fraxken)" ], @@ -62,5 +69,5 @@ "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile" }, - "version": "1.6.2" + "version": "1.6.3" } From 36863f087b1f879184ae64ebfef9c19e07c4ae0d Mon Sep 17 00:00:00 2001 From: "Gabriel \"_|Nix|_\" Schulhof" Date: Thu, 4 Apr 2019 15:11:53 -0700 Subject: [PATCH 090/696] doc: refer to TypedArray and ArrayBuffer from Array Add a blurb to the `Napi::Array` documentation that refers readers to `Napi::TypedArray` and `Napi::ArrayBuffer` for using arrays with large amounts of data. PR-URL: https://github.com/nodejs/node-addon-api/pull/465 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- doc/basic_types.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/basic_types.md b/doc/basic_types.md index 7e65ead1b..b01269d17 100644 --- a/doc/basic_types.md +++ b/doc/basic_types.md @@ -339,6 +339,12 @@ The value is not coerced to a string. Arrays are native representations of JavaScript Arrays. `Napi::Array` is a wrapper around `napi_value` representing a JavaScript Array. +[`Napi::TypedArray`][] and [`Napi::ArrayBuffer`][] correspond to JavaScript data +types such as [`Int32Array`][] and [`ArrayBuffer`][], respectively, that can be +used for transferring large amounts of data from JavaScript to the native side. +An example illustrating the use of a JavaScript-provided `ArrayBuffer` in native +code is available [here](https://github.com/nodejs/node-addon-examples/tree/master/array_buffer_to_native/node-addon-api). + ### Constructor ```cpp Napi::Array::Array(); @@ -402,3 +408,8 @@ This can execute JavaScript code implicitly according to JavaScript semantics. If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not being used, callers should check the result of `Env::IsExceptionPending` before attempting to use the returned value. + +[`Napi::TypedArray`]: ./typed_array.md +[`Napi::ArrayBuffer`]: ./array_buffer.md +[`Int32Array`]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int32Array +[`ArrayBuffer`]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer From a3b4d99c456ddedb4defec7652033dae1184926e Mon Sep 17 00:00:00 2001 From: Hitesh Kanwathirtha Date: Mon, 13 May 2019 10:21:28 -0700 Subject: [PATCH 091/696] doc: Add contribution philosophy doc --- CONTRIBUTING.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 5 ++++ 2 files changed, 71 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..0d9fdf926 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,66 @@ +# **node-addon-api** Contribution Philosophy + +The **node-addon-api** team loves contributions. There are many ways in which you can +contribute to **node-addon-api**: +- Source code fixes +- Additional tests +- Documentation improvements +- Joining the N-API working group and participating in meetings + +## Source changes + +**node-addon-api** is meant to be a thin convenience wrapper around N-API. With this +in mind, contributions of any new APIs that wrap around a core N-API API will +be considered for merge. However, changes that wrap existing **node-addon-api** +APIs are encouraged to instead be provided as an ecosystem module. The +**node-addon-api** team is happy to link to a curated set of modules that build on +top of **node-addon-api** if they have broad usefulness to the community and promote +a recommended idiom or pattern. + +### Rationale + +The N-API team considered a couple different approaches with regards to changes +extending **node-addon-api** +- Larger core module - Incorporate these helpers and patterns into **node-addon-api** +- Extras package - Create a new package (strawman name '**node-addon-api**-extras') +that contain utility classes and methods that help promote good patterns and +idioms while writing native addons with **node-addon-api**. +- Ecosystem - Encourage creation of a module ecosystem around **node-addon-api** +where folks can build on top of it. + +#### Larger Core +This is probably our simplest option in terms of immediate action needed. It +would involve landing any open PRs against **node-addon-api**, and continuing to +encourage folks to make PRs for utility helpers against the same repository. + +The downside of the approach is the following: +- Less coherency for our API set +- More maintenance burden on the N-API WG core team. + +#### Extras Package +This involves us spinning up a new package which contains the utility classes +and methods. This has the benefit of having a separate module where helpers +which make it easier to implement certain patterns and idioms for native addons +easier. + +The downside of this approach is the following: +- Potential for confusion - we'll need to provide clear documentation to help the +community understand where a particular contribution should be directed to (what +belongs in **node-addon-api** vs **node-addon-api-extras**) +- Need to define the level of support/API guarantees +- Unclear if the maintenance burden on the N-API WG is reduced or not + +#### Ecosystem +This doesn't require a ton of up-front work from the N-API WG. Instead of +accepting utility PRs into **node-addon-api** or creating and maintaining a new +module, the WG will encourage the creation of an ecosystem of modules that +build on top of **node-addon-api**, and provide some level of advertising for these +modules (listing them out on the repository/wiki, using them in workshops/tutorials +etc). + +The downside of this approach is the following: +- Potential for lack of visibility - evangelism and education is hard, and module +authors might not find right patterns and instead implement things themselves +- There might be greater friction for the N-API WG in evolving APIs since the +ecosystem would have taken dependencies on the API shape of **node-addon-api** + diff --git a/README.md b/README.md index 68911ee13..bcbcb137d 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,11 @@ Take a look and get inspired by our **[test suite](https://github.com/nodejs/nod +## **Contributing** + +We love contributions from the community to **node-addon-api**. +See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around extending this module. + ### **More resource and info about native Addons** - **[C++ Addons](https://nodejs.org/dist/latest/docs/api/addons.html)** - **[N-API](https://nodejs.org/dist/latest/docs/api/n-api.html)** From 3ad5dfc7d9c4ae4b6a1f2cd9ba0eb15dc885b869 Mon Sep 17 00:00:00 2001 From: Alba Mendez Date: Fri, 17 May 2019 22:25:49 +0200 Subject: [PATCH 092/696] Fix link PR-URL: https://github.com/nodejs/node-addon-api/pull/481 Reviewed-By: Michael Dawson Reviewed-By: NickNaso --- doc/prebuild_tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/prebuild_tools.md b/doc/prebuild_tools.md index 4025a58d1..d7b1d3c96 100644 --- a/doc/prebuild_tools.md +++ b/doc/prebuild_tools.md @@ -2,7 +2,7 @@ The distribution of a native add-on is just as important as its implementation. In order to install a native add-on it's important to have all the necessary -dependencies installed and well configured (see the [setup](doc/setum.md) section). +dependencies installed and well configured (see the [setup](setup.md) section). The end-user will need to compile the add-on when they will do an `npm install` and in some cases this could create problems. To avoid the compilation process it's possible to ditribute the native add-on in pre-built form for different platform From e1cf9a35a1b6edc404c1b465ec94f8a821641059 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Mon, 29 Apr 2019 22:17:56 +0200 Subject: [PATCH 093/696] Use `Value::IsEmpty` to check for empty value PR-URL: https://github.com/nodejs/node-addon-api/pull/478 Reviewed-By: Anna Henningsen Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- napi-inl.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index c15ed97a1..2b91b12b8 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -271,7 +271,7 @@ inline bool Value::IsEmpty() const { } inline napi_valuetype Value::Type() const { - if (_value == nullptr) { + if (IsEmpty()) { return napi_undefined; } @@ -314,7 +314,7 @@ inline bool Value::IsSymbol() const { } inline bool Value::IsArray() const { - if (_value == nullptr) { + if (IsEmpty()) { return false; } @@ -325,7 +325,7 @@ inline bool Value::IsArray() const { } inline bool Value::IsArrayBuffer() const { - if (_value == nullptr) { + if (IsEmpty()) { return false; } @@ -336,7 +336,7 @@ inline bool Value::IsArrayBuffer() const { } inline bool Value::IsTypedArray() const { - if (_value == nullptr) { + if (IsEmpty()) { return false; } @@ -355,7 +355,7 @@ inline bool Value::IsFunction() const { } inline bool Value::IsPromise() const { - if (_value == nullptr) { + if (IsEmpty()) { return false; } @@ -366,7 +366,7 @@ inline bool Value::IsPromise() const { } inline bool Value::IsDataView() const { - if (_value == nullptr) { + if (IsEmpty()) { return false; } @@ -377,7 +377,7 @@ inline bool Value::IsDataView() const { } inline bool Value::IsBuffer() const { - if (_value == nullptr) { + if (IsEmpty()) { return false; } From aaea55eda990c42d181a67084f7a17a3f9e56497 Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Fri, 17 May 2019 22:34:00 +0200 Subject: [PATCH 094/696] Little fix on code example PR-URL: https://github.com/nodejs/node-addon-api/pull/470 Reviewed-By: Michael Dawson Reviewed-By: Hitesh Kanwathirtha --- doc/object.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/object.md b/doc/object.md index 8bee8b653..de20dd899 100644 --- a/doc/object.md +++ b/doc/object.md @@ -23,12 +23,12 @@ Void Init(Env env) { // Assign values to properties obj.Set("hello", "world"); - obj.Set(42, "The Answer to Life, the Universe, and Everything"); + obj.Set(uint32_t(42), "The Answer to Life, the Universe, and Everything"); obj.Set("Douglas Adams", true); // Get properties Value val1 = obj.Get("hello"); - Value val2 = obj.Get(42); + Value val2 = obj.Get(uint32_t(42)); Value val3 = obj.Get("Douglas Adams"); // Test if objects have properties. From f633fbd95d233adc8f030954651a76ec4c8d07f9 Mon Sep 17 00:00:00 2001 From: Tux3 Date: Wed, 5 Jun 2019 21:18:05 +0200 Subject: [PATCH 095/696] string.md: Document existing New(env, value, length) APIs PR-URL: https://github.com/nodejs/node-addon-api/pull/486 Reviewed-By: Michael Dawson Reviewed-By: NickNaso --- doc/string.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/string.md b/doc/string.md index 21ce8f8cb..bf78ac73c 100644 --- a/doc/string.md +++ b/doc/string.md @@ -56,6 +56,8 @@ Napi::String::New(napi_env env, const std::string& value); Napi::String::New(napi_env env, const std::u16::string& value); Napi::String::New(napi_env env, const char* value); Napi::String::New(napi_env env, const char16_t* value); +Napi::String::New(napi_env env, const char* value, size_t length); +Napi::String::New(napi_env env, const char16_t* value, size_t length); ``` - `[in] env`: The `napi_env` environment in which to construct the `Napi::Value` object. @@ -64,6 +66,7 @@ Napi::String::New(napi_env env, const char16_t* value); - `std::u16string&` - represents a UTF16-LE string. - `const char*` - represents a UTF8 string. - `const char16_t*` - represents a UTF16-LE string. +- `[in] length`: The length of the string (not necessarily null-terminated) in code units. Returns a new `Napi::String` that represents the passed in C++ string. From 3b6b9eb88aaef2a971908aa1daf56b00276f94e2 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Tue, 4 Jun 2019 12:21:51 -0700 Subject: [PATCH 096/696] AsyncWorker: introduce Destroy() method `AsyncWorker` contained the assumption that instances of its subclasses were allocated using `new`, because it unconditionally destroyed instances using `delete`. This change replaces the call to `delete` with a call to a protected instance method `Destroy()`, which can be overridden by subclasses. This ensures that users can employ their own allocators when creating `AsyncWorker` subclass instances because they can override the `Destroy()` method to use their deallocator of choice. Re: https://github.com/nodejs/node-addon-api/issues/231#issuecomment-480928142 PR-URL: https://github.com/nodejs/node-addon-api/pull/488 Reviewed-By: Michael Dawson --- doc/async_worker.md | 13 +++++++++++++ napi-inl.h | 6 +++++- napi.h | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/doc/async_worker.md b/doc/async_worker.md index 43e11d587..da12cba70 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -125,6 +125,19 @@ class was created, passing in the error as the first parameter. virtual void Napi::AsyncWorker::OnError(const Napi::Error& e); ``` +### Destroy + +This method is invoked when the instance must be deallocated. If +`SuppressDestruct()` was not called then this method will be called after either +`OnError()` or `OnOK()` complete. The default implementation of this method +causes the instance to delete itself using the `delete` operator. The method is +provided so as to ensure that instances allocated by means other than the `new` +operator can be deallocated upon work completion. + +```cpp +virtual void Napi::AsyncWorker::Destroy(); +``` + ### Constructor Creates a new `Napi::AsyncWorker`. diff --git a/napi-inl.h b/napi-inl.h index 2b91b12b8..9f03f3acc 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3529,6 +3529,10 @@ inline AsyncWorker::~AsyncWorker() { } } +inline void AsyncWorker::Destroy() { + delete this; +} + inline AsyncWorker::AsyncWorker(AsyncWorker&& other) { _env = other._env; other._env = nullptr; @@ -3623,7 +3627,7 @@ inline void AsyncWorker::OnWorkComplete( }); } if (!self->_suppress_destruct) { - delete self; + self->Destroy(); } } diff --git a/napi.h b/napi.h index 7a83d792e..bf565fb91 100644 --- a/napi.h +++ b/napi.h @@ -1803,6 +1803,7 @@ namespace Napi { virtual void Execute() = 0; virtual void OnOK(); virtual void OnError(const Error& e); + virtual void Destroy(); void SetError(const std::string& error); From ab7d8fcc48f5727961a0f09442d6ae4c5f9b5a17 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Thu, 13 Jun 2019 16:05:00 -0400 Subject: [PATCH 097/696] src: fix objectwrap test case Refs: https://github.com/nodejs/node-addon-api/issues/485 The test case was relyingon the ordering of "for in" which is not guarranteed as per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in Update the testcase to check in an way that does not depend on ordering. PR-URL: https://github.com/nodejs/node-addon-api/pull/495 Refs: https://github.com/nodejs/node-addon-api/issues/485 Reviewed-By: Gabriel Schulhof Reviewed-By: NickNaso --- test/objectwrap.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/objectwrap.js b/test/objectwrap.js index 1f888234d..aa6d8d3cb 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -73,12 +73,11 @@ const test = (binding) => { keys.push(key); } - assert.deepEqual(keys, [ - 'testGetSet', - 'testGetter', - 'testValue', - 'testMethod' - ]); + assert(keys.length = 4); + assert(obj.testGetSet); + assert(obj.testGetter); + assert(obj.testValue); + assert(obj.testMethod); } }; From a0cac77c82b3676369a7d19cdd393208a22b14ac Mon Sep 17 00:00:00 2001 From: NickNaso Date: Mon, 10 Jun 2019 15:08:40 +0200 Subject: [PATCH 098/696] Added test for bool operator PR-URL: https://github.com/nodejs/node-addon-api/pull/490 Reviewed-By: Michael Dawson --- test/basic_types/boolean.cc | 6 ++++++ test/basic_types/boolean.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/test/basic_types/boolean.cc b/test/basic_types/boolean.cc index fd6e3165f..4abacece4 100644 --- a/test/basic_types/boolean.cc +++ b/test/basic_types/boolean.cc @@ -21,6 +21,11 @@ Value CreateBooleanFromPrimitive(const CallbackInfo& info) { return Boolean::New(info.Env(), boolean); } +Value OperatorBool(const CallbackInfo& info) { + Boolean boolean(info.Env(), info[0].As()); + return Boolean::New(info.Env(), static_cast(boolean)); +} + Object InitBasicTypesBoolean(Env env) { Object exports = Object::New(env); @@ -28,5 +33,6 @@ Object InitBasicTypesBoolean(Env env) { exports["createEmptyBoolean"] = Function::New(env, CreateEmptyBoolean); exports["createBooleanFromExistingValue"] = Function::New(env, CreateBooleanFromExistingValue); exports["createBooleanFromPrimitive"] = Function::New(env, CreateBooleanFromPrimitive); + exports["operatorBool"] = Function::New(env, OperatorBool); return exports; } diff --git a/test/basic_types/boolean.js b/test/basic_types/boolean.js index 1c27664f1..13817ee52 100644 --- a/test/basic_types/boolean.js +++ b/test/basic_types/boolean.js @@ -27,4 +27,10 @@ function test(binding) { const bool6 = binding.basic_types_boolean.createBooleanFromPrimitive(false); assert.strictEqual(bool6, false); + const bool7 = binding.basic_types_boolean.operatorBool(true); + assert.strictEqual(bool7, true); + + const bool8 = binding.basic_types_boolean.operatorBool(false); + assert.strictEqual(bool8, false); + } From b2b08122ea72ef4bb42e0dc99c0e50b7f4cf2f91 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Fri, 7 Jun 2019 18:06:46 +0200 Subject: [PATCH 099/696] AsyncWorker: make callback optional `AsyncWorker` assumed that after work is complete, a JavaScript callback would need to execute. This change removes the restriction of specifying a `Function` callback, and instead replaces it with an `Env` parameter. Since the purpose of `receiver` was for the `this` context for the callback, it has also been removed from the constructors. Re: https://github.com/nodejs/node-addon-api/issues/231#issuecomment-483356091 PR-URL: https://github.com/nodejs/node-addon-api/pull/489 Reviewed-By: Michael Dawson --- doc/async_worker.md | 60 ++++++++++++++++++++++++++++++---- napi-inl.h | 34 +++++++++++++++++-- napi.h | 7 ++++ test/asyncworker-nocallback.js | 15 +++++++++ test/asyncworker.cc | 33 +++++++++++++++++++ test/index.js | 1 + 6 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 test/asyncworker-nocallback.js diff --git a/doc/async_worker.md b/doc/async_worker.md index da12cba70..0516af8f6 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -105,8 +105,8 @@ virtual void Napi::AsyncWorker::Execute() = 0; ### OnOK -This method is invoked when the computation in the `Excecute` method ends. -The default implementation runs the Callback provided when the AsyncWorker class +This method is invoked when the computation in the `Execute` method ends. +The default implementation runs the Callback optionally provided when the AsyncWorker class was created. ```cpp @@ -149,7 +149,7 @@ explicit Napi::AsyncWorker(const Napi::Function& callback); - `[in] callback`: The function which will be called when an asynchronous operations ends. The given function is called from the main event loop thread. -Returns a`Napi::AsyncWork` instance which can later be queued for execution by calling +Returns a `Napi::AsyncWorker` instance which can later be queued for execution by calling `Queue`. ### Constructor @@ -166,7 +166,7 @@ operations ends. The given function is called from the main event loop thread. identifier for the kind of resource that is being provided for diagnostic information exposed by the async_hooks API. -Returns a `Napi::AsyncWork` instance which can later be queued for execution by +Returns a `Napi::AsyncWorker` instance which can later be queued for execution by calling `Napi::AsyncWork::Queue`. ### Constructor @@ -185,7 +185,7 @@ information exposed by the async_hooks API. - `[in] resource`: Object associated with the asynchronous operation that will be passed to possible async_hooks. -Returns a `Napi::AsyncWork` instance which can later be queued for execution by +Returns a `Napi::AsyncWorker` instance which can later be queued for execution by calling `Napi::AsyncWork::Queue`. ### Constructor @@ -200,7 +200,7 @@ explicit Napi::AsyncWorker(const Napi::Object& receiver, const Napi::Function& c - `[in] callback`: The function which will be called when an asynchronous operations ends. The given function is called from the main event loop thread. -Returns a `Napi::AsyncWork` instance which can later be queued for execution by +Returns a `Napi::AsyncWorker` instance which can later be queued for execution by calling `Napi::AsyncWork::Queue`. ### Constructor @@ -241,6 +241,54 @@ will be passed to possible async_hooks. Returns a `Napi::AsyncWork` instance which can later be queued for execution by calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncWorker`. + +```cpp +explicit Napi::AsyncWorker(Napi::Env env); +``` + +- `[in] env`: The environment in which to create the `Napi::AsyncWorker`. + +Returns an `Napi::AsyncWorker` instance which can later be queued for execution by calling +`Napi::AsyncWorker::Queue`. + +### Constructor + +Creates a new `Napi::AsyncWorker`. + +```cpp +explicit Napi::AsyncWorker(Napi::Env env, const char* resource_name); +``` + +- `[in] env`: The environment in which to create the `Napi::AsyncWorker`. +- `[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. + +Returns a `Napi::AsyncWorker` instance which can later be queued for execution by +calling `Napi::AsyncWorker::Queue`. + +### Constructor + +Creates a new `Napi::AsyncWorker`. + +```cpp +explicit Napi::AsyncWorker(Napi::Env env, const char* resource_name, const Napi::Object& resource); +``` + +- `[in] env`: The environment in which to create the `Napi::AsyncWorker`. +- `[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. + +Returns a `Napi::AsyncWorker` instance which can later be queued for execution by +calling `Napi::AsyncWorker::Queue`. + ### Destructor Deletes the created work object that is used to execute logic asynchronously. diff --git a/napi-inl.h b/napi-inl.h index 9f03f3acc..7f94d4858 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3522,6 +3522,32 @@ inline AsyncWorker::AsyncWorker(const Object& receiver, NAPI_THROW_IF_FAILED_VOID(_env, status); } +inline AsyncWorker::AsyncWorker(Napi::Env env) + : AsyncWorker(env, "generic") { +} + +inline AsyncWorker::AsyncWorker(Napi::Env env, + const char* resource_name) + : AsyncWorker(env, resource_name, Object::New(env)) { +} + +inline AsyncWorker::AsyncWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : _env(env), + _receiver(), + _callback(), + _suppress_destruct(false) { + napi_value resource_id; + napi_status status = napi_create_string_latin1( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + status = napi_create_async_work(_env, resource, resource_id, OnExecute, + OnWorkComplete, this, &_work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + inline AsyncWorker::~AsyncWorker() { if (_work != nullptr) { napi_delete_async_work(_env, _work); @@ -3587,11 +3613,15 @@ inline void AsyncWorker::SuppressDestruct() { } inline void AsyncWorker::OnOK() { - _callback.Call(_receiver.Value(), std::initializer_list{}); + if (!_callback.IsEmpty()) { + _callback.Call(_receiver.Value(), std::initializer_list{}); + } } inline void AsyncWorker::OnError(const Error& e) { - _callback.Call(_receiver.Value(), std::initializer_list{ e.Value() }); + if (!_callback.IsEmpty()) { + _callback.Call(_receiver.Value(), std::initializer_list{ e.Value() }); + } } inline void AsyncWorker::SetError(const std::string& error) { diff --git a/napi.h b/napi.h index bf565fb91..799576112 100644 --- a/napi.h +++ b/napi.h @@ -1800,6 +1800,13 @@ namespace Napi { const char* resource_name, const Object& resource); + explicit AsyncWorker(Napi::Env env); + explicit AsyncWorker(Napi::Env env, + const char* resource_name); + explicit AsyncWorker(Napi::Env env, + const char* resource_name, + const Object& resource); + virtual void Execute() = 0; virtual void OnOK(); virtual void OnError(const Error& e); diff --git a/test/asyncworker-nocallback.js b/test/asyncworker-nocallback.js new file mode 100644 index 000000000..c873c5fbf --- /dev/null +++ b/test/asyncworker-nocallback.js @@ -0,0 +1,15 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const common = require('./common'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + const resolving = binding.asyncworker.doWorkNoCallback(true, {}); + resolving.then(common.mustCall()).catch(common.mustNotCall()); + + const rejecting = binding.asyncworker.doWorkNoCallback(false, {}); + rejecting.then(common.mustNotCall()).catch(common.mustCall()); + return; +} \ No newline at end of file diff --git a/test/asyncworker.cc b/test/asyncworker.cc index 12cdcfabe..bbd7e0b19 100644 --- a/test/asyncworker.cc +++ b/test/asyncworker.cc @@ -29,8 +29,41 @@ class TestWorker : public AsyncWorker { bool _succeed; }; +class TestWorkerNoCallback : public AsyncWorker { +public: + static Value DoWork(const CallbackInfo& info) { + napi_env env = info.Env(); + bool succeed = info[0].As(); + Object resource = info[1].As(); + + TestWorkerNoCallback* worker = new TestWorkerNoCallback(env, "TestResource", resource); + worker->_succeed = succeed; + worker->Queue(); + return worker->_deferred.Promise(); + } + +protected: + void Execute() override { + } + virtual void OnOK() override { + _deferred.Resolve(Env().Undefined()); + + } + virtual void OnError(const Napi::Error& /* e */) override { + _deferred.Reject(Env().Undefined()); + } + +private: + TestWorkerNoCallback(napi_env env, const char* resource_name, const Object& resource) + : AsyncWorker(env, resource_name, resource), _deferred(Napi::Promise::Deferred::New(env)) { + } + Promise::Deferred _deferred; + bool _succeed; +}; + Object InitAsyncWorker(Env env) { Object exports = Object::New(env); exports["doWork"] = Function::New(env, TestWorker::DoWork); + exports["doWorkNoCallback"] = Function::New(env, TestWorkerNoCallback::DoWork); return exports; } diff --git a/test/index.js b/test/index.js index 67e630d7f..e46c7b162 100644 --- a/test/index.js +++ b/test/index.js @@ -11,6 +11,7 @@ let testModules = [ 'arraybuffer', 'asynccontext', 'asyncworker', + 'asyncworker-nocallback', 'asyncworker-persistent', 'basic_types/array', 'basic_types/boolean', From 1fb540eeb52db660fbafd8d064754708c1569b0b Mon Sep 17 00:00:00 2001 From: NickNaso Date: Thu, 13 Jun 2019 12:19:28 +0200 Subject: [PATCH 100/696] Use curly brackets to include node_api.h PR-URL: https://github.com/nodejs/node-addon-api/pull/493 Reviewed-By: Michael Dawson --- napi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napi.h b/napi.h index 799576112..fcf34406d 100644 --- a/napi.h +++ b/napi.h @@ -1,7 +1,7 @@ #ifndef SRC_NAPI_H_ #define SRC_NAPI_H_ -#include "node_api.h" +#include #include #include #include From 0a90df2fcb511c56fdee0d193fa7c8a5b6212e33 Mon Sep 17 00:00:00 2001 From: Jinho Bang Date: Mon, 4 Mar 2019 15:13:08 +0900 Subject: [PATCH 101/696] Implement ThreadSafeFunction class This PR is implementing ThreadSafeFunction class wraps napi_threadsafe_function features. FYI, the test files that included in this PR have come from Node.js repo[1]. They've been rewritten based on C++ and node-addon-api. [1] https://github.com/nodejs/node/tree/e800f9d/test/node-api/test_threadsafe_function PR-URL: https://github.com/nodejs/node-addon-api/pull/442/ Fixes: https://github.com/nodejs/node-addon-api/issues/312/ Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- napi-inl.h | 391 ++++++++++++++++++ napi.h | 201 +++++++++ test/binding.cc | 6 + test/binding.gyp | 1 + test/index.js | 1 + .../threadsafe_function.cc | 179 ++++++++ .../threadsafe_function.js | 170 ++++++++ 7 files changed, 949 insertions(+) create mode 100644 test/threadsafe_function/threadsafe_function.cc create mode 100644 test/threadsafe_function/threadsafe_function.js diff --git a/napi-inl.h b/napi-inl.h index 7f94d4858..0db3c9830 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -125,6 +125,78 @@ struct FinalizeData { Hint* hint; }; +template , + typename FinalizerDataType=void> +struct ThreadSafeFinalize { + static inline + void Wrapper(napi_env env, void* rawFinalizeData, void* /* rawContext */) { + if (rawFinalizeData == nullptr) + return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env)); + if (finalizeData->tsfn) { + *finalizeData->tsfn = nullptr; + } + delete finalizeData; + } + + static inline + void FinalizeWrapperWithData(napi_env env, + void* rawFinalizeData, + void* /* rawContext */) { + if (rawFinalizeData == nullptr) + return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env), finalizeData->data); + if (finalizeData->tsfn) { + *finalizeData->tsfn = nullptr; + } + delete finalizeData; + } + + static inline + void FinalizeWrapperWithContext(napi_env env, + void* rawFinalizeData, + void* rawContext) { + if (rawFinalizeData == nullptr) + return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env), static_cast(rawContext)); + if (finalizeData->tsfn) { + *finalizeData->tsfn = nullptr; + } + delete finalizeData; + } + + static inline + void FinalizeFinalizeWrapperWithDataAndContext(napi_env env, + void* rawFinalizeData, + void* rawContext) { + if (rawFinalizeData == nullptr) + return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env), finalizeData->data, + static_cast(rawContext)); + if (finalizeData->tsfn) { + *finalizeData->tsfn = nullptr; + } + delete finalizeData; + } + + FinalizerDataType* data; + Finalizer callback; + napi_threadsafe_function* tsfn; +}; + template struct AccessorCallbackData { static inline @@ -3661,6 +3733,325 @@ inline void AsyncWorker::OnWorkComplete( } } +//////////////////////////////////////////////////////////////////////////////// +// ThreadSafeFunction class +//////////////////////////////////////////////////////////////////////////////// + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount) { + return New(env, callback, Object(), resourceName, maxQueueSize, + initialThreadCount); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + return New(env, callback, Object(), resourceName, maxQueueSize, + initialThreadCount, context); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback) { + return New(env, callback, Object(), resourceName, maxQueueSize, + initialThreadCount, finalizeCallback); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, callback, Object(), resourceName, maxQueueSize, + initialThreadCount, finalizeCallback, data); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback) { + return New(env, callback, Object(), resourceName, maxQueueSize, + initialThreadCount, context, finalizeCallback); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, callback, Object(), resourceName, maxQueueSize, + initialThreadCount, context, finalizeCallback, data); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount) { + return New(env, callback, resource, resourceName, maxQueueSize, + initialThreadCount, static_cast(nullptr) /* context */); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + return New(env, callback, resource, resourceName, maxQueueSize, + initialThreadCount, context, + [](Env, ContextType*) {} /* empty finalizer */); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback) { + return New(env, callback, resource, resourceName, maxQueueSize, + initialThreadCount, static_cast(nullptr) /* context */, + finalizeCallback, static_cast(nullptr) /* data */, + details::ThreadSafeFinalize::Wrapper); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, callback, resource, resourceName, maxQueueSize, + initialThreadCount, static_cast(nullptr) /* context */, + finalizeCallback, data, + details::ThreadSafeFinalize< + void, Finalizer, FinalizerDataType>::FinalizeWrapperWithData); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback) { + return New(env, callback, resource, resourceName, maxQueueSize, + initialThreadCount, context, finalizeCallback, + static_cast(nullptr) /* data */, + details::ThreadSafeFinalize< + ContextType, Finalizer>::FinalizeWrapperWithContext); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, callback, resource, resourceName, maxQueueSize, + initialThreadCount, context, finalizeCallback, data, + details::ThreadSafeFinalize::FinalizeFinalizeWrapperWithDataAndContext); +} + +inline ThreadSafeFunction::ThreadSafeFunction() + : _tsfn(new napi_threadsafe_function(nullptr)) { +} + +inline ThreadSafeFunction::ThreadSafeFunction( + napi_threadsafe_function tsfn) + : _tsfn(new napi_threadsafe_function(tsfn)) { +} + +inline ThreadSafeFunction::ThreadSafeFunction(ThreadSafeFunction&& other) + : _tsfn(std::move(other._tsfn)) { + other._tsfn.reset(); +} + +inline ThreadSafeFunction& ThreadSafeFunction::operator =( + ThreadSafeFunction&& other) { + if (*_tsfn != nullptr) { + Error::Fatal("ThreadSafeFunction::operator =", + "You cannot assign a new TSFN because existing one is still alive."); + return *this; + } + _tsfn = std::move(other._tsfn); + other._tsfn.reset(); + return *this; +} + +inline napi_status ThreadSafeFunction::BlockingCall() const { + return CallInternal(nullptr, napi_tsfn_blocking); +} + +template +inline napi_status ThreadSafeFunction::BlockingCall( + Callback callback) const { + return CallInternal(new CallbackWrapper(callback), napi_tsfn_blocking); +} + +template +inline napi_status ThreadSafeFunction::BlockingCall( + DataType* data, Callback callback) const { + auto wrapper = [data, callback](Env env, Function jsCallback) { + callback(env, jsCallback, data); + }; + return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_blocking); +} + +inline napi_status ThreadSafeFunction::NonBlockingCall() const { + return CallInternal(nullptr, napi_tsfn_nonblocking); +} + +template +inline napi_status ThreadSafeFunction::NonBlockingCall( + Callback callback) const { + return CallInternal(new CallbackWrapper(callback), napi_tsfn_nonblocking); +} + +template +inline napi_status ThreadSafeFunction::NonBlockingCall( + DataType* data, Callback callback) const { + auto wrapper = [data, callback](Env env, Function jsCallback) { + callback(env, jsCallback, data); + }; + return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking); +} + +inline napi_status ThreadSafeFunction::Acquire() const { + return napi_acquire_threadsafe_function(*_tsfn); +} + +inline napi_status ThreadSafeFunction::Release() { + return napi_release_threadsafe_function(*_tsfn, napi_tsfn_release); +} + +inline napi_status ThreadSafeFunction::Abort() { + return napi_release_threadsafe_function(*_tsfn, napi_tsfn_abort); +} + +inline ThreadSafeFunction::ConvertibleContext +ThreadSafeFunction::GetContext() const { + void* context; + napi_get_threadsafe_function_context(*_tsfn, &context); + return ConvertibleContext({ context }); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper) { + static_assert(details::can_make_string::value + || std::is_convertible::value, + "Resource name should be convertible to the string type"); + + ThreadSafeFunction tsfn; + auto* finalizeData = new details::ThreadSafeFinalize({ data, finalizeCallback, tsfn._tsfn.get() }); + napi_status status = napi_create_threadsafe_function(env, callback, resource, + Value::From(env, resourceName), maxQueueSize, initialThreadCount, + finalizeData, wrapper, context, CallJS, tsfn._tsfn.get()); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction()); + } + + return tsfn; +} + +inline napi_status ThreadSafeFunction::CallInternal( + CallbackWrapper* callbackWrapper, + napi_threadsafe_function_call_mode mode) const { + napi_status status = napi_call_threadsafe_function( + *_tsfn, callbackWrapper, mode); + if (status != napi_ok && callbackWrapper != nullptr) { + delete callbackWrapper; + } + + return status; +} + +// static +inline void ThreadSafeFunction::CallJS(napi_env env, + napi_value jsCallback, + void* /* context */, + void* data) { + if (env == nullptr && jsCallback == nullptr) { + return; + } + + if (data != nullptr) { + auto* callbackWrapper = static_cast(data); + (*callbackWrapper)(env, Function(env, jsCallback)); + delete callbackWrapper; + } else if (jsCallback != nullptr) { + Function(env, jsCallback).Call({}); + } +} + //////////////////////////////////////////////////////////////////////////////// // Memory Management class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index fcf34406d..287f16da7 100644 --- a/napi.h +++ b/napi.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -1828,6 +1829,206 @@ namespace Napi { bool _suppress_destruct; }; + class ThreadSafeFunction { + public: + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data); + + ThreadSafeFunction(); + ThreadSafeFunction(napi_threadsafe_function tsFunctionValue); + + ThreadSafeFunction(ThreadSafeFunction&& other); + ThreadSafeFunction& operator=(ThreadSafeFunction&& other); + + // This API may be called from any thread. + napi_status BlockingCall() const; + + // This API may be called from any thread. + template + napi_status BlockingCall(Callback callback) const; + + // This API may be called from any thread. + template + napi_status BlockingCall(DataType* data, Callback callback) const; + + // This API may be called from any thread. + napi_status NonBlockingCall() const; + + // This API may be called from any thread. + template + napi_status NonBlockingCall(Callback callback) const; + + // This API may be called from any thread. + template + napi_status NonBlockingCall(DataType* data, Callback callback) const; + + // This API may be called from any thread. + napi_status Acquire() const; + + // This API may be called from any thread. + napi_status Release(); + + // This API may be called from any thread. + napi_status Abort(); + + struct ConvertibleContext + { + template + operator T*() { return static_cast(context); } + void* context; + }; + + // This API may be called from any thread. + ConvertibleContext GetContext() const; + + private: + using CallbackWrapper = std::function; + + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper); + + napi_status CallInternal(CallbackWrapper* callbackWrapper, + napi_threadsafe_function_call_mode mode) const; + + static void CallJS(napi_env env, + napi_value jsCallback, + void* context, + void* data); + + std::unique_ptr _tsfn; + }; + // Memory management. class MemoryManagement { public: diff --git a/test/binding.cc b/test/binding.cc index 4a5fec15c..141493e94 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -33,6 +33,9 @@ Object InitObject(Env env); Object InitObjectDeprecated(Env env); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED Object InitPromise(Env env); +#if (NAPI_VERSION > 3) +Object InitThreadSafeFunction(Env env); +#endif Object InitTypedArray(Env env); Object InitObjectWrap(Env env); Object InitObjectReference(Env env); @@ -71,6 +74,9 @@ Object Init(Env env, Object exports) { exports.Set("object_deprecated", InitObjectDeprecated(env)); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED exports.Set("promise", InitPromise(env)); +#if (NAPI_VERSION > 3) + exports.Set("threadsafe_function", InitThreadSafeFunction(env)); +#endif exports.Set("typedarray", InitTypedArray(env)); exports.Set("objectwrap", InitObjectWrap(env)); exports.Set("objectreference", InitObjectReference(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 0e2b7d05c..56d636adc 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -32,6 +32,7 @@ 'object/object.cc', 'object/set_property.cc', 'promise.cc', + 'threadsafe_function/threadsafe_function.cc', 'typedarray.cc', 'objectwrap.cc', 'objectreference.cc', diff --git a/test/index.js b/test/index.js index e46c7b162..9a5409b9a 100644 --- a/test/index.js +++ b/test/index.js @@ -36,6 +36,7 @@ let testModules = [ 'object/object_deprecated', 'object/set_property', 'promise', + 'threadsafe_function/threadsafe_function', 'typedarray', 'typedarray-bigint', 'objectwrap', diff --git a/test/threadsafe_function/threadsafe_function.cc b/test/threadsafe_function/threadsafe_function.cc new file mode 100644 index 000000000..529bfb308 --- /dev/null +++ b/test/threadsafe_function/threadsafe_function.cc @@ -0,0 +1,179 @@ +#include +#include +#include "napi.h" + +using namespace Napi; + +constexpr size_t ARRAY_LENGTH = 10; +constexpr size_t MAX_QUEUE_SIZE = 2; + +static std::thread threads[2]; +static ThreadSafeFunction tsfn; + +struct ThreadSafeFunctionInfo { + enum CallType { + DEFAULT, + BLOCKING, + NON_BLOCKING + } type; + bool abort; + bool startSecondary; + FunctionReference jsFinalizeCallback; + uint32_t maxQueueSize; +} tsfnInfo; + +// Thread data to transmit to JS +static int ints[ARRAY_LENGTH]; + +static void SecondaryThread() { + if (tsfn.Release() != napi_ok) { + Error::Fatal("SecondaryThread", "ThreadSafeFunction.Release() failed"); + } +} + +// Source thread producing the data +static void DataSourceThread() { + ThreadSafeFunctionInfo* info = tsfn.GetContext(); + + if (info->startSecondary) { + if (tsfn.Acquire() != napi_ok) { + Error::Fatal("DataSourceThread", "ThreadSafeFunction.Acquire() failed"); + } + + threads[1] = std::thread(SecondaryThread); + } + + bool queueWasFull = false; + bool queueWasClosing = false; + for (int index = ARRAY_LENGTH - 1; index > -1 && !queueWasClosing; index--) { + napi_status status = napi_generic_failure; + auto callback = [](Env env, Function jsCallback, int* data) { + jsCallback.Call({ Number::New(env, *data) }); + }; + + switch (info->type) { + case ThreadSafeFunctionInfo::DEFAULT: + status = tsfn.BlockingCall(); + break; + case ThreadSafeFunctionInfo::BLOCKING: + status = tsfn.BlockingCall(&ints[index], callback); + break; + case ThreadSafeFunctionInfo::NON_BLOCKING: + status = tsfn.NonBlockingCall(&ints[index], callback); + break; + } + + if (info->maxQueueSize == 0) { + // Let's make this thread really busy for 200 ms to give the main thread a + // chance to abort. + auto start = std::chrono::high_resolution_clock::now(); + constexpr auto MS_200 = std::chrono::milliseconds(200); + for (; std::chrono::high_resolution_clock::now() - start < MS_200;); + } + + switch (status) { + case napi_queue_full: + queueWasFull = true; + index++; + // fall through + + case napi_ok: + continue; + + case napi_closing: + queueWasClosing = true; + break; + + default: + Error::Fatal("DataSourceThread", "ThreadSafeFunction.*Call() failed"); + } + } + + if (info->type == ThreadSafeFunctionInfo::NON_BLOCKING && !queueWasFull) { + Error::Fatal("DataSourceThread", "Queue was never full"); + } + + if (info->abort && !queueWasClosing) { + Error::Fatal("DataSourceThread", "Queue was never closing"); + } + + if (!queueWasClosing && tsfn.Release() != napi_ok) { + Error::Fatal("DataSourceThread", "ThreadSafeFunction.Release() failed"); + } +} + +static Value StopThread(const CallbackInfo& info) { + tsfnInfo.jsFinalizeCallback = Napi::Persistent(info[0].As()); + bool abort = info[1].As(); + if (abort) { + tsfn.Abort(); + } else { + tsfn.Release(); + } + return Value(); +} + +// Join the thread and inform JS that we're done. +static void JoinTheThreads(Env /* env */, + std::thread* theThreads, + ThreadSafeFunctionInfo* info) { + theThreads[0].join(); + if (info->startSecondary) { + theThreads[1].join(); + } + + info->jsFinalizeCallback.Call({}); + info->jsFinalizeCallback.Reset(); +} + +static Value StartThreadInternal(const CallbackInfo& info, + ThreadSafeFunctionInfo::CallType type) { + tsfnInfo.type = type; + tsfnInfo.abort = info[1].As(); + tsfnInfo.startSecondary = info[2].As(); + tsfnInfo.maxQueueSize = info[3].As().Uint32Value(); + + tsfn = ThreadSafeFunction::New(info.Env(), info[0].As(), + "Test", tsfnInfo.maxQueueSize, 2, &tsfnInfo, JoinTheThreads, threads); + + threads[0] = std::thread(DataSourceThread); + + return Value(); +} + +static Value Release(const CallbackInfo& /* info */) { + if (tsfn.Release() != napi_ok) { + Error::Fatal("Release", "ThreadSafeFunction.Release() failed"); + } + return Value(); +} + +static Value StartThread(const CallbackInfo& info) { + return StartThreadInternal(info, ThreadSafeFunctionInfo::BLOCKING); +} + +static Value StartThreadNonblocking(const CallbackInfo& info) { + return StartThreadInternal(info, ThreadSafeFunctionInfo::NON_BLOCKING); +} + +static Value StartThreadNoNative(const CallbackInfo& info) { + return StartThreadInternal(info, ThreadSafeFunctionInfo::DEFAULT); +} + +Object InitThreadSafeFunction(Env env) { + for (size_t index = 0; index < ARRAY_LENGTH; index++) { + ints[index] = index; + } + + Object exports = Object::New(env); + exports["ARRAY_LENGTH"] = Number::New(env, ARRAY_LENGTH); + exports["MAX_QUEUE_SIZE"] = Number::New(env, MAX_QUEUE_SIZE); + exports["startThread"] = Function::New(env, StartThread); + exports["startThreadNoNative"] = Function::New(env, StartThreadNoNative); + exports["startThreadNonblocking"] = + Function::New(env, StartThreadNonblocking); + exports["stopThread"] = Function::New(env, StopThread); + exports["release"] = Function::New(env, Release); + + return exports; +} diff --git a/test/threadsafe_function/threadsafe_function.js b/test/threadsafe_function/threadsafe_function.js new file mode 100644 index 000000000..710c21212 --- /dev/null +++ b/test/threadsafe_function/threadsafe_function.js @@ -0,0 +1,170 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const common = require('../common'); + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + const expectedArray = (function(arrayLength) { + const result = []; + for (let index = 0; index < arrayLength; index++) { + result.push(arrayLength - 1 - index); + } + return result; + })(binding.threadsafe_function.ARRAY_LENGTH); + + function testWithJSMarshaller({ + threadStarter, + quitAfter, + abort, + maxQueueSize, + launchSecondary }) { + return new Promise((resolve) => { + const array = []; + binding.threadsafe_function[threadStarter](function testCallback(value) { + array.push(value); + if (array.length === quitAfter) { + setImmediate(() => { + binding.threadsafe_function.stopThread(common.mustCall(() => { + resolve(array); + }), !!abort); + }); + } + }, !!abort, !!launchSecondary, maxQueueSize); + if (threadStarter === 'startThreadNonblocking') { + // Let's make this thread really busy for a short while to ensure that + // the queue fills and the thread receives a napi_queue_full. + const start = Date.now(); + while (Date.now() - start < 200); + } + }); + } + + new Promise(function testWithoutJSMarshaller(resolve) { + let callCount = 0; + binding.threadsafe_function.startThreadNoNative(function testCallback() { + callCount++; + + // The default call-into-JS implementation passes no arguments. + assert.strictEqual(arguments.length, 0); + if (callCount === binding.threadsafe_function.ARRAY_LENGTH) { + setImmediate(() => { + binding.threadsafe_function.stopThread(common.mustCall(() => { + resolve(); + }), false); + }); + } + }, false /* abort */, false /* launchSecondary */, + binding.threadsafe_function.MAX_QUEUE_SIZE); + }) + + // Start the thread in blocking mode, and assert that all values are passed. + // Quit after it's done. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + quitAfter: binding.threadsafe_function.ARRAY_LENGTH + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in blocking mode with an infinite queue, and assert that + // all values are passed. Quit after it's done. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: 0, + quitAfter: binding.threadsafe_function.ARRAY_LENGTH + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in non-blocking mode, and assert that all values are + // passed. Quit after it's done. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + quitAfter: binding.threadsafe_function.ARRAY_LENGTH + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in blocking mode, and assert that all values are passed. + // Quit early, but let the thread finish. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + quitAfter: 1 + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in blocking mode with an infinite queue, and assert that + // all values are passed. Quit early, but let the thread finish. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: 0, + quitAfter: 1 + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + + // Start the thread in non-blocking mode, and assert that all values are + // passed. Quit early, but let the thread finish. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + quitAfter: 1 + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in blocking mode, and assert that all values are passed. + // Quit early, but let the thread finish. Launch a secondary thread to test + // the reference counter incrementing functionality. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + launchSecondary: true + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in non-blocking mode, and assert that all values are + // passed. Quit early, but let the thread finish. Launch a secondary thread + // to test the reference counter incrementing functionality. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + launchSecondary: true + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in blocking mode, and assert that it could not finish. + // Quit early by aborting. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + abort: true + })) + .then((result) => assert.strictEqual(result.indexOf(0), -1)) + + // Start the thread in blocking mode with an infinite queue, and assert that + // it could not finish. Quit early by aborting. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: 0, + abort: true + })) + .then((result) => assert.strictEqual(result.indexOf(0), -1)) + + // Start the thread in non-blocking mode, and assert that it could not finish. + // Quit early and aborting. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + abort: true + })) + .then((result) => assert.strictEqual(result.indexOf(0), -1)) +} From c32d7dbdcf25c69ccfaf0ab2848200cfc10c2459 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Thu, 27 Jun 2019 09:39:16 -0700 Subject: [PATCH 102/696] macros: create errors fully namespaced Errors in `NAPI_THROW()` and Co. were being thrown as `Error:New(...)` but they should be thrown as `Napi::Error::New(...)` because the former assumes that the user has opted to declare `using namespace Napi;`. PR-URL: https://github.com/nodejs/node-addon-api/pull/506 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gus Caplan Reviewed-By: Michael Dawson --- napi.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/napi.h b/napi.h index 287f16da7..c1946413b 100644 --- a/napi.h +++ b/napi.h @@ -51,10 +51,10 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 #define NAPI_THROW_VOID(e) throw e #define NAPI_THROW_IF_FAILED(env, status, ...) \ - if ((status) != napi_ok) throw Error::New(env); + if ((status) != napi_ok) throw Napi::Error::New(env); #define NAPI_THROW_IF_FAILED_VOID(env, status) \ - if ((status) != napi_ok) throw Error::New(env); + if ((status) != napi_ok) throw Napi::Error::New(env); #else // NAPI_CPP_EXCEPTIONS @@ -78,13 +78,13 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 #define NAPI_THROW_IF_FAILED(env, status, ...) \ if ((status) != napi_ok) { \ - Error::New(env).ThrowAsJavaScriptException(); \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ return __VA_ARGS__; \ } #define NAPI_THROW_IF_FAILED_VOID(env, status) \ if ((status) != napi_ok) { \ - Error::New(env).ThrowAsJavaScriptException(); \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ return; \ } @@ -93,7 +93,7 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 #define NAPI_FATAL_IF_FAILED(status, location, message) \ do { \ if ((status) != napi_ok) { \ - Error::Fatal((location), (message)); \ + Napi::Error::Fatal((location), (message)); \ } \ } while (0) From cab3b1e2a2264a985382531a8523fb46c60fda70 Mon Sep 17 00:00:00 2001 From: unknown <29697678+ross-weir@users.noreply.github.com> Date: Sat, 29 Jun 2019 20:39:53 +1000 Subject: [PATCH 103/696] doc: ClassPropertyDescriptor example PR-URL: https://github.com/nodejs/node-addon-api/pull/507 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- doc/class_property_descriptor.md | 84 +++++++++++++++++++++++++++++++- doc/object_wrap.md | 2 +- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/doc/class_property_descriptor.md b/doc/class_property_descriptor.md index 7e81358e4..ae2d27e27 100644 --- a/doc/class_property_descriptor.md +++ b/doc/class_property_descriptor.md @@ -1,4 +1,4 @@ -# Class propertry and descriptor +# Class property and descriptor Property descriptor for use with `Napi::ObjectWrap::DefineClass()`. This is different from the standalone `Napi::PropertyDescriptor` because it is @@ -6,6 +6,88 @@ specific to each `Napi::ObjectWrap` subclass. This prevents using descriptors from a different class when defining a new class (preventing the callbacks from having incorrect `this` pointers). +## Example + +```cpp +#include + +class Example : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object exports); + Example(const Napi::CallbackInfo &info); + + private: + static Napi::FunctionReference constructor; + double _value; + Napi::Value GetValue(const Napi::CallbackInfo &info); + Napi::Value SetValue(const Napi::CallbackInfo &info); +}; + +Napi::Object Example::Init(Napi::Env env, Napi::Object exports) { + Napi::Function func = DefineClass(env, "Example", { + // Register a class instance accessor with getter and setter functions. + InstanceAccessor("value", &Example::GetValue, &Example::SetValue), + // We can also register a readonly accessor by passing nullptr as the setter. + InstanceAccessor("readOnlyProp", &Example::GetValue, nullptr) + }); + + constructor = Napi::Persistent(func); + constructor.SuppressDestruct(); + exports.Set("Example", func); + + return exports; +} + +Example::Example(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) { + Napi::Env env = info.Env(); + // ... + Napi::Number value = info[0].As(); + this->_value = value.DoubleValue(); +} + +Napi::FunctionReference Example::constructor; + +Napi::Value Example::GetValue(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + return Napi::Number::New(env, this->_value); +} + +Napi::Value Example::SetValue(const Napi::CallbackInfo &info, const Napi::Value &value) { + Napi::Env env = info.Env(); + // ... + Napi::Number arg = value.As(); + this->_value = arg.DoubleValue(); + return this->GetValue(info); +} + +// Initialize native add-on +Napi::Object Init (Napi::Env env, Napi::Object exports) { + Example::Init(env, exports); + return exports; +} + +// Register and initialize native add-on +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) +``` + +The above code can be used from JavaScript as follows: + +```js +'use strict'; + +const { Example } = require('bindings')('addon'); + +const example = new Example(11); +console.log(example.value); +// It prints 11 +example.value = 19; +console.log(example.value); +// It prints 19 +example.readOnlyProp = 500; +console.log(example.readOnlyProp); +// Unchanged. It prints 19 +``` + ## Methods ### Constructor diff --git a/doc/object_wrap.md b/doc/object_wrap.md index 5bc06c871..31be140f0 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -78,7 +78,7 @@ Napi::Object Init (Napi::Env env, Napi::Object exports) { return exports; } -// Regisgter and initialize native add-on +// Register and initialize native add-on NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) ``` From e9fa1eaa866d773c32a3aea99cae0c3533874f00 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Tue, 16 Jul 2019 23:31:27 +0200 Subject: [PATCH 104/696] doc: document ThreadSafeFunction (#494) * doc: document ThreadSafeFunction PR-URL: https://github.com/nodejs/node-addon-api/pull/494 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- doc/threadsafe_function.md | 303 +++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 doc/threadsafe_function.md diff --git a/doc/threadsafe_function.md b/doc/threadsafe_function.md new file mode 100644 index 000000000..e547307b4 --- /dev/null +++ b/doc/threadsafe_function.md @@ -0,0 +1,303 @@ +# ThreadSafeFunction + +JavaScript functions can normally only be called from a native addon's main +thread. If an addon creates additional threads, then node-addon-api functions +that require a `Napi::Env`, `Napi::Value`, or `Napi::Reference` must not be +called from those threads. + +When an addon has additional threads and JavaScript functions need to be invoked +based on the processing completed by those threads, those threads must +communicate with the addon's main thread so that the main thread can invoke the +JavaScript function on their behalf. The thread-safe function APIs provide an +easy way to do this. + +These APIs provide the type `Napi::ThreadSafeFunction` as well as APIs to +create, destroy, and call objects of this type. +`Napi::ThreadSafeFunction::New()` creates a persistent reference that holds a +JavaScript function which can be called from multiple threads. The calls happen +asynchronously. This means that values with which the JavaScript callback is to +be called will be placed in a queue, and, for each value in the queue, a call +will eventually be made to the JavaScript function. + +`Napi::ThreadSafeFunction` objects are destroyed when every thread which uses +the object has called `Release()` or has received a return status of +`napi_closing` in response to a call to `BlockingCall()` or `NonBlockingCall()`. +The queue is emptied before the `Napi::ThreadSafeFunction` is destroyed. It is +important that `Release()` be the last API call made in conjunction with a given +`Napi::ThreadSafeFunction`, because after the call completes, there is no +guarantee that the `Napi::ThreadSafeFunction` is still allocated. For the same +reason it is also important that no more use be made of a thread-safe function +after receiving a return value of `napi_closing` in response to a call to +`BlockingCall()` or `NonBlockingCall()`. Data associated with the +`Napi::ThreadSafeFunction` can be freed in its `Finalizer` callback which was +passed to `ThreadSafeFunction::New()`. + +Once the number of threads making use of a `Napi::ThreadSafeFunction` reaches +zero, no further threads can start making use of it by calling `Acquire()`. In +fact, all subsequent API calls associated with it, except `Release()`, will +return an error value of `napi_closing`. + +## Methods + +### Constructor + +Creates a new empty instance of `Napi::ThreadSafeFunction`. + +```cpp +Napi::Function::ThreadSafeFunction(); +``` + +### Constructor + +Creates a new instance of the `Napi::ThreadSafeFunction` object. + +```cpp +Napi::ThreadSafeFunction::ThreadSafeFunction(napi_threadsafe_function tsfn); +``` + +- `tsfn`: The `napi_threadsafe_function` which is a handle for an existing + thread-safe function. + +Returns a non-empty `Napi::ThreadSafeFunction` instance. + +### New + +Creates a new instance of the `Napi::ThreadSafeFunction` object. The `New` +function has several overloads for the various optional parameters: skip the +optional parameter for that specific overload. + +```cpp +New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data); +``` + +- `env`: The `napi_env` environment in which to construct the + `Napi::ThreadSafeFunction` object. +- `callback`: The `Function` to call from another thread. +- `[optional] resource`: An object associated with the async work that will be + passed to possible async_hooks init hooks. +- `resourceName`: A JavaScript string to provide an identifier for the kind of + resource that is being provided for diagnostic information exposed by the + async_hooks API. +- `maxQueueSize`: Maximum size of the queue. `0` for no limit. +- `initialThreadCount`: The initial number of threads, including the main + thread, which will be making use of this function. +- `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`. +- `[optional] finalizeCallback`: Function to call when the `ThreadSafeFunction` + is being destroyed. This callback will be invoked on the main thread when the + thread-safe function is about to be destroyed. It receives the context and the + finalize data given during construction (if given), and provides an + opportunity for cleaning up after the threads e.g. by calling + `uv_thread_join()`. It is important that, aside from the main loop thread, + there be no threads left using the thread-safe function after the finalize + callback completes. Must implement `void operator()(Env env, DataType* data, + Context* hint)`, skipping `data` or `hint` if they are not provided. + Can be retreived via `GetContext()`. +- `[optional] data`: Data to be passed to `finalizeCallback`. + +Returns a non-empty `Napi::ThreadSafeFunction` instance. + +### Acquire + +Add a thread to this thread-safe function object, indicating that a new thread +will start making use of the thread-safe function. + +```cpp +napi_status Napi::ThreadSafeFunction::Acquire() +``` + +Returns one of: +- `napi_ok`: The thread has successfully acquired the thread-safe function +for its use. +- `napi_closing`: The thread-safe function has been marked as closing via a +previous call to `Abort()`. + +### Release + +Indicate that an existing thread will stop making use of the thread-safe +function. A thread should call this API when it stops making use of this +thread-safe function. Using any thread-safe APIs after having called this API +has undefined results in the current thread, as it may have been destroyed. + +```cpp +napi_status Napi::ThreadSafeFunction::Release() +``` + +Returns one of: +- `napi_ok`: The thread-safe function has been successfully released. +- `napi_invalid_arg`: The thread-safe function's thread-count is zero. +- `napi_generic_failure`: A generic error occurred when attemping to release +the thread-safe function. + +### Abort + +"Abort" the thread-safe function. This will cause all subsequent APIs associated +with the thread-safe function except `Release()` to return `napi_closing` even +before its reference count reaches zero. In particular, `BlockingCall` and +`NonBlockingCall()` will return `napi_closing`, thus informing the threads that +it is no longer possible to make asynchronous calls to the thread-safe function. +This can be used as a criterion for terminating the thread. Upon receiving a +return value of `napi_closing` from a thread-safe function call a thread must +make no further use of the thread-safe function because it is no longer +guaranteed to be allocated. + +```cpp +napi_status Napi::ThreadSafeFunction::Abort() +``` + +Returns one of: +- `napi_ok`: The thread-safe function has been successfully aborted. +- `napi_invalid_arg`: The thread-safe function's thread-count is zero. +- `napi_generic_failure`: A generic error occurred when attemping to abort +the thread-safe function. + +### BlockingCall / NonBlockingCall + +Calls the Javascript function in either a blocking or non-blocking fashion. +- `BlockingCall()`: the API blocks until space becomes available in the queue. + Will never block if the thread-safe function was created with a maximum queue + size of `0`. +- `NonBlockingCall()`: will return `napi_queue_full` if the queue was full, + preventing data from being successfully added to the queue. + +There are several overloaded implementations of `BlockingCall()` and +`NonBlockingCall()` for use with optional parameters: skip the optional +parameter for that specific overload. + +```cpp +napi_status Napi::ThreadSafeFunction::BlockingCall(DataType* data, Callback callback) const + +napi_status Napi::ThreadSafeFunction::NonBlockingCall(DataType* data, Callback callback) const +``` + +- `[optional] data`: Data to pass to `callback`. +- `[optional] callback`: C++ function that is invoked on the main thread. The + callback receives the `ThreadSafeFunction`'s JavaScript callback function to + call as an `Napi::Function` in its parameters and the `DataType*` data pointer + (if provided). Must implement `void operator()(Napi::Env env, Function + jsCallback, DataType* data)`, skipping `data` if not provided. It is not + necessary to call into JavaScript via `MakeCallback()` because N-API runs + `callback` in a context appropriate for callbacks. + +Returns one of: +- `napi_ok`: The call was successfully added to the queue. +- `napi_queue_full`: The queue was full when trying to call in a non-blocking + method. +- `napi_closing`: The thread-safe function is aborted and cannot accept more + calls. +- `napi_invalid_arg`: The thread-safe function is closed. +- `napi_generic_failure`: A generic error occurred when attemping to add to the + queue. + +## Example + +```cpp +#include +#include +#include + +using namespace Napi; + +std::thread nativeThread; +ThreadSafeFunction tsfn; + +Value Start( const CallbackInfo& info ) +{ + Napi::Env env = info.Env(); + + if ( info.Length() < 2 ) + { + throw TypeError::New( env, "Expected two arguments" ); + } + else if ( !info[0].IsFunction() ) + { + throw TypeError::New( env, "Expected first arg to be function" ); + } + else if ( !info[1].IsNumber() ) + { + throw TypeError::New( env, "Expected second arg to be number" ); + } + + int count = info[1].As().Int32Value(); + + // Create a ThreadSafeFunction + tsfn = ThreadSafeFunction::New( + env, + info[0].As(), // JavaScript function called asynchronously + "Resource Name", // Name + 0, // Unlimited queue + 1, // Only one thread will use this initially + []( Napi::Env ) { // Finalizer used to clean threads up + nativeThread.join(); + } ); + + // Create a native thread + nativeThread = std::thread( [count] { + auto callback = []( Napi::Env env, Function jsCallback, int* value ) { + // Transform native data into JS data, passing it to the provided + // `jsCallback` -- the TSFN's JavaScript function. + jsCallback.Call( {Number::New( env, *value )} ); + + // We're finished with the data. + delete value; + }; + + for ( int i = 0; i < count; i++ ) + { + // Create new data + int* value = new int( clock() ); + + // Perform a blocking call + napi_status status = tsfn.BlockingCall( value, callback ); + if ( status != napi_ok ) + { + // Handle error + break; + } + + std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); + } + + // Release the thread-safe function + tsfn.Release(); + } ); + + return Boolean::New(env, true); +} + +Napi::Object Init( Napi::Env env, Object exports ) +{ + exports.Set( "start", Function::New( env, Start ) ); + return exports; +} + +NODE_API_MODULE( clock, Init ) +``` + +The above code can be used from JavaScript as follows: + +```js +const { start } = require('bindings')('clock'); + +start(function () { + console.log("JavaScript callback called with arguments", Array.from(arguments)); +}, 5); +``` + +When executed, the output will show the value of `clock()` five times at one +second intervals: + +``` +JavaScript callback called with arguments [ 84745 ] +JavaScript callback called with arguments [ 103211 ] +JavaScript callback called with arguments [ 104516 ] +JavaScript callback called with arguments [ 105104 ] +JavaScript callback called with arguments [ 105691 ] +``` From ac6000d0fd9c49b87748e6a3a1e2b83479b4b0a2 Mon Sep 17 00:00:00 2001 From: Yohei Kishimoto Date: Wed, 17 Jul 2019 06:38:10 +0900 Subject: [PATCH 105/696] doc: fix minor typo PR-URL: https://github.com/nodejs/node-addon-api/pull/510 Reviewed-By: Gabriel Schulhof Reviewed-By: NickNaso Reviewed-By: Michael Dawson --- doc/async_worker.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/async_worker.md b/doc/async_worker.md index 0516af8f6..0c641c66f 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -79,7 +79,7 @@ the `Napi::AsyncWorker::OnOK` callback. Sets the error message for the error that happened during the execution. Setting an error message will cause the `Napi::AsyncWorker::OnError` method to be -invoked instead of `Napi::AsyncWorker::OnOKOnOK` once the +invoked instead of `Napi::AsyncWorker::OnOK` once the `Napi::AsyncWorker::Execute` method completes. ```cpp @@ -115,7 +115,7 @@ virtual void Napi::AsyncWorker::OnOK(); ### OnError -This method is invoked afer `Napi::AsyncWorker::Execute` completes if an error +This method is invoked after `Napi::AsyncWorker::Execute` completes if an error occurs while `Napi::AsyncWorker::Execute` is running and C++ exceptions are enabled or if an error was set through a call to `Napi::AsyncWorker::SetError`. The default implementation calls the callback provided when the `Napi::AsyncWorker` @@ -208,7 +208,7 @@ calling `Napi::AsyncWork::Queue`. Creates a new `Napi::AsyncWorker`. ```cpp -explicit Napi::AsyncWorker(const Napi::Object& receiver, const Napi::Function& callback,const char* resource_name); +explicit Napi::AsyncWorker(const Napi::Object& receiver, const Napi::Function& callback, const char* resource_name); ``` - `[in] receiver`: The `this` object passed to the called function. @@ -361,7 +361,7 @@ the work on the `Napi::AsyncWorker::Execute` method is done the `Napi::AsyncWorker::OnOk` method is called and the results return back to JavaScript invoking the stored callback with its associated environment. -The following code shows an example on how to create and and use an `Napi::AsyncWorker` +The following code shows an example on how to create and use an `Napi::AsyncWorker` ```cpp #include From d9d991bbc9ef7a3364f83bdf43bce6e956075188 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Thu, 18 Jul 2019 21:34:37 +0200 Subject: [PATCH 106/696] doc: add ThreadSafeFunction to main README (#513) PR-URL: https://github.com/nodejs/node-addon-api/pull/513 Reviewed-By: Gabriel Schulhof Reviewed-By: NickNaso Reviewed-By: Michael Dawson --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bcbcb137d..57273be8a 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ The following is the documentation for node-addon-api. - [Async Operations](doc/async_operations.md) - [AsyncWorker](doc/async_worker.md) - [AsyncContext](doc/async_context.md) + - [Thread-safe Functions](doc/threadsafe_function.md) - [Promises](doc/promises.md) - [Version management](doc/version_management.md) From 717c9ab163dba35dee3fe4bc0e6da769eedd91ae Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Tue, 16 Jul 2019 21:12:14 +0200 Subject: [PATCH 107/696] AsyncWorker: add GetResult() method Adds an overridable `GetResult()` method, providing arguments to the callback invoked in `OnOK()`. Refs: https://github.com/nodejs/node-addon-api/issues/231#issuecomment-511504055 PR-URL: https://github.com/nodejs/node-addon-api/pull/512 Reviewed-By: Michael Dawson Reviewed-By: NickNaso Reviewed-By: Gabriel Schulhof --- doc/async_worker.md | 12 +++++++++++- napi-inl.h | 11 +++++++++-- napi.h | 1 + test/asyncworker.cc | 33 +++++++++++++++++++++++++++++++++ test/asyncworker.js | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 3 deletions(-) diff --git a/doc/async_worker.md b/doc/async_worker.md index 0c641c66f..a13e8a18c 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -107,11 +107,21 @@ virtual void Napi::AsyncWorker::Execute() = 0; This method is invoked when the computation in the `Execute` method ends. The default implementation runs the Callback optionally provided when the AsyncWorker class -was created. +was created. The callback will by default receive no arguments. To provide arguments, +override the `GetResult()` method. ```cpp virtual void Napi::AsyncWorker::OnOK(); ``` +### GetResult + +This method returns the arguments passed to the Callback invoked by the default +`OnOK()` implementation. The default implementation returns an empty vector, +providing no arguments to the Callback. + +```cpp +virtual std::vector Napi::AsyncWorker::GetResult(Napi::Env env); +``` ### OnError diff --git a/napi-inl.h b/napi-inl.h index 0db3c9830..11822d43a 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3686,7 +3686,7 @@ inline void AsyncWorker::SuppressDestruct() { inline void AsyncWorker::OnOK() { if (!_callback.IsEmpty()) { - _callback.Call(_receiver.Value(), std::initializer_list{}); + _callback.Call(_receiver.Value(), GetResult(_callback.Env())); } } @@ -3700,7 +3700,14 @@ inline void AsyncWorker::SetError(const std::string& error) { _error = error; } -inline void AsyncWorker::OnExecute(napi_env /*env*/, void* this_pointer) { +inline std::vector AsyncWorker::GetResult(Napi::Env /*env*/) { + return {}; +} +// The OnExecute method receives an napi_env argument. However, do NOT +// use it within this method, as it does not run on the main thread and must +// not run any method that would cause JavaScript to run. In practice, this +// means that almost any use of napi_env will be incorrect. +inline void AsyncWorker::OnExecute(napi_env /*DO_NOT_USE*/, void* this_pointer) { AsyncWorker* self = static_cast(this_pointer); #ifdef NAPI_CPP_EXCEPTIONS try { diff --git a/napi.h b/napi.h index c1946413b..d772fd50d 100644 --- a/napi.h +++ b/napi.h @@ -1812,6 +1812,7 @@ namespace Napi { virtual void OnOK(); virtual void OnError(const Error& e); virtual void Destroy(); + virtual std::vector GetResult(Napi::Env env); void SetError(const std::string& error); diff --git a/test/asyncworker.cc b/test/asyncworker.cc index bbd7e0b19..324146533 100644 --- a/test/asyncworker.cc +++ b/test/asyncworker.cc @@ -29,6 +29,38 @@ class TestWorker : public AsyncWorker { bool _succeed; }; +class TestWorkerWithResult : public AsyncWorker { +public: + static void DoWork(const CallbackInfo& info) { + bool succeed = info[0].As(); + Object resource = info[1].As(); + Function cb = info[2].As(); + Value data = info[3]; + + TestWorkerWithResult* worker = new TestWorkerWithResult(cb, "TestResource", resource); + worker->Receiver().Set("data", data); + worker->_succeed = succeed; + worker->Queue(); + } + +protected: + void Execute() override { + if (!_succeed) { + SetError("test error"); + } + } + + std::vector GetResult(Napi::Env env) override { + return {Boolean::New(env, _succeed), + String::New(env, _succeed ? "ok" : "error")}; + } + +private: + TestWorkerWithResult(Function cb, const char* resource_name, const Object& resource) + : AsyncWorker(cb, resource_name, resource) {} + bool _succeed; +}; + class TestWorkerNoCallback : public AsyncWorker { public: static Value DoWork(const CallbackInfo& info) { @@ -65,5 +97,6 @@ Object InitAsyncWorker(Env env) { Object exports = Object::New(env); exports["doWork"] = Function::New(env, TestWorker::DoWork); exports["doWorkNoCallback"] = Function::New(env, TestWorkerNoCallback::DoWork); + exports["doWorkWithResult"] = Function::New(env, TestWorkerWithResult::DoWork); return exports; } diff --git a/test/asyncworker.js b/test/asyncworker.js index 676afd537..04415d522 100644 --- a/test/asyncworker.js +++ b/test/asyncworker.js @@ -66,6 +66,14 @@ function test(binding) { assert.strictEqual(typeof this, 'object'); assert.strictEqual(this.data, 'test data'); }, 'test data'); + + binding.asyncworker.doWorkWithResult(true, {}, function (succeed, succeedString) { + assert(arguments.length == 2); + assert(succeed); + assert(succeedString == "ok"); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + }, 'test data'); return; } @@ -91,6 +99,30 @@ function test(binding) { }).catch(common.mustNotCall()); } + { + const hooks = installAsyncHooksForTest(); + const triggerAsyncId = async_hooks.executionAsyncId(); + binding.asyncworker.doWorkWithResult(true, { foo: 'foo' }, function (succeed, succeedString) { + assert(arguments.length == 2); + assert(succeed); + assert(succeedString == "ok"); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + }, 'test data'); + + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { eventName: 'init', + type: 'TestResource', + triggerAsyncId: triggerAsyncId, + resource: { foo: 'foo' } }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + } + { const hooks = installAsyncHooksForTest(); const triggerAsyncId = async_hooks.executionAsyncId(); From 0a1380c896f657bbd619755f4fafa84880db4824 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Tue, 23 Jul 2019 16:31:37 +0200 Subject: [PATCH 108/696] Prepare release 1.7.0 --- CHANGELOG.md | 45 ++++++++++++++++++++++++++++++++++++++++++++- README.md | 4 ++-- package.json | 9 +++++++-- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9f1177c5..ea3266964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,48 @@ # node-addon-api Changelog +## 2019-07-23 Version 1.7.0, @NickNaso + +### Notable changes: + +#### API + +- Added `Napi::ThreadSafeFunction` api. +- Added `Napi::AsyncWorker::GetResult()` method to `Napi::AsyncWorker`. +- Added `Napi::AsyncWorker::Destroy()()` method to `Napi::AsyncWorker`. +- Use full namespace on macros that create the errors. + +#### Documentation + +- Added documentation about contribution philosophy. +- Added documentation for `Napi::ThreadSafeFunction`. +- Some minor corrections all over the documentation. + +#### TEST + +- Added test case for bool operator. +- Fixed test case for `Napi::ObjectWrap`. + +### Commmits + +* [[`717c9ab163`](https://github.com/nodejs/node-addon-api/commit/717c9ab163)] - **AsyncWorker**: add GetResult() method (Kevin Eady) [#512](https://github.com/nodejs/node-addon-api/pull/512) +* [[`d9d991bbc9`](https://github.com/nodejs/node-addon-api/commit/d9d991bbc9)] - **doc**: add ThreadSafeFunction to main README (#513) (Kevin Eady) [#513](https://github.com/nodejs/node-addon-api/pull/513) +* [[`ac6000d0fd`](https://github.com/nodejs/node-addon-api/commit/ac6000d0fd)] - **doc**: fix minor typo (Yohei Kishimoto) [#510](https://github.com/nodejs/node-addon-api/pull/510) +* [[`e9fa1eaa86`](https://github.com/nodejs/node-addon-api/commit/e9fa1eaa86)] - **doc**: document ThreadSafeFunction (#494) (Kevin Eady) [#494](https://github.com/nodejs/node-addon-api/pull/494) +* [[`cab3b1e2a2`](https://github.com/nodejs/node-addon-api/commit/cab3b1e2a2)] - **doc**: ClassPropertyDescriptor example (Ross Weir) [#507](https://github.com/nodejs/node-addon-api/pull/507) +* [[`c32d7dbdcf`](https://github.com/nodejs/node-addon-api/commit/c32d7dbdcf)] - **macros**: create errors fully namespaced (Gabriel Schulhof) [#506](https://github.com/nodejs/node-addon-api/pull/506) +* [[`0a90df2fcb`](https://github.com/nodejs/node-addon-api/commit/0a90df2fcb)] - Implement ThreadSafeFunction class (Jinho Bang) +* [[`1fb540eeb5`](https://github.com/nodejs/node-addon-api/commit/1fb540eeb5)] - Use curly brackets to include node\_api.h (NickNaso) [#493](https://github.com/nodejs/node-addon-api/pull/493) +* [[`b2b08122ea`](https://github.com/nodejs/node-addon-api/commit/b2b08122ea)] - **AsyncWorker**: make callback optional (Kevin Eady) [#489](https://github.com/nodejs/node-addon-api/pull/489) +* [[`a0cac77c82`](https://github.com/nodejs/node-addon-api/commit/a0cac77c82)] - Added test for bool operator (NickNaso) [#490](https://github.com/nodejs/node-addon-api/pull/490) +* [[`ab7d8fcc48`](https://github.com/nodejs/node-addon-api/commit/ab7d8fcc48)] - **src**: fix objectwrap test case (Michael Dawson) [#495](https://github.com/nodejs/node-addon-api/pull/495) +* [[`3b6b9eb88a`](https://github.com/nodejs/node-addon-api/commit/3b6b9eb88a)] - **AsyncWorker**: introduce Destroy() method (Gabriel Schulhof) [#488](https://github.com/nodejs/node-addon-api/pull/488) +* [[`f633fbd95d`](https://github.com/nodejs/node-addon-api/commit/f633fbd95d)] - string.md: Document existing New(env, value, length) APIs (Tux3) [#486](https://github.com/nodejs/node-addon-api/pull/486) +* [[`aaea55eda9`](https://github.com/nodejs/node-addon-api/commit/aaea55eda9)] - Little fix on code example (Nicola Del Gobbo) [#470](https://github.com/nodejs/node-addon-api/pull/470) +* [[`e1cf9a35a1`](https://github.com/nodejs/node-addon-api/commit/e1cf9a35a1)] - Use `Value::IsEmpty` to check for empty value (NickNaso) [#478](https://github.com/nodejs/node-addon-api/pull/478) +* [[`3ad5dfc7d9`](https://github.com/nodejs/node-addon-api/commit/3ad5dfc7d9)] - Fix link (Alba Mendez) [#481](https://github.com/nodejs/node-addon-api/pull/481) +* [[`a3b4d99c45`](https://github.com/nodejs/node-addon-api/commit/a3b4d99c45)] - **doc**: Add contribution philosophy doc (Hitesh Kanwathirtha) +* [[`36863f087b`](https://github.com/nodejs/node-addon-api/commit/36863f087b)] - **doc**: refer to TypedArray and ArrayBuffer from Array (Gabriel "_|Nix|_" Schulhof) [#465](https://github.com/nodejs/node-addon-api/pull/465) + ## 2019-04-03 Version 1.6.3, @NickNaso ### Notable changes: @@ -14,7 +57,7 @@ #### Documentation -- Some minor corrections all over the documentation +- Some minor corrections all over the documentation. ### Commmits diff --git a/README.md b/README.md index 57273be8a..42eb93797 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 1.6.3** +## **Current version: 1.7.0** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) @@ -162,7 +162,7 @@ Take a look and get inspired by our **[test suite](https://github.com/nodejs/nod ## **Contributing** -We love contributions from the community to **node-addon-api**. +We love contributions from the community to **node-addon-api**. See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around extending this module. ### **More resource and info about native Addons** diff --git a/package.json b/package.json index 55c3ea060..db2e572e5 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ }, "contributors": [ "Abhishek Kumar Singh (https://github.com/abhi11210646)", + "Alba Mendez (https://github.com/jmendeth)", "Andrew Petersen (https://github.com/kirbysayshi)", "Anisha Rohra (https://github.com/anisha-rohra)", "Anna Henningsen (https://github.com/addaleax)", @@ -26,6 +27,7 @@ "Jim Schlight (https://github.com/jschlight)", "Jinho Bang (https://github.com/romandev)", "joshgarde (https://github.com/joshgarde)", + "Kevin Eady (https://github.com/KevinEady)", "Konstantin Tarkus (https://github.com/koistya)", "Kyle Farnung (https://github.com/kfarnung)", "Luciano Martorella (https://github.com/lmartorella)", @@ -37,11 +39,14 @@ "Nick Soggin (https://github.com/iSkore)", "Philipp Renoth (https://github.com/DaAitch)", "Rolf Timmermans (https://github.com/rolftimmermans)", + "Ross Weir (https://github.com/ross-weir)", "Ryuichi Okumura (https://github.com/okuryu)", "Sampson Gao (https://github.com/sampsongao)", "Sam Roberts (https://github.com/sam-github)", "Taylor Woll (https://github.com/boingoing)", - "Thomas Gentilhomme (https://github.com/fraxken)" + "Thomas Gentilhomme (https://github.com/fraxken)", + "Tux3 (https://github.com/tux3)", + "Yohei Kishimoto (https://github.com/morokosi)" ], "dependencies": {}, "description": "Node.js API (N-API)", @@ -69,5 +74,5 @@ "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile" }, - "version": "1.6.3" + "version": "1.7.0" } From 37b6c185ad079eb90c5fe5f48c6eea093b1ee4f6 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Tue, 23 Jul 2019 21:52:32 +0200 Subject: [PATCH 109/696] Fix compilation breakage on 1.7.0 --- napi-inl.h | 4 ++++ napi.h | 2 ++ test/index.js | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/napi-inl.h b/napi-inl.h index 11822d43a..3fb4465bb 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -125,6 +125,7 @@ struct FinalizeData { Hint* hint; }; +#if (NAPI_VERSION > 3) template , typename FinalizerDataType=void> @@ -196,6 +197,7 @@ struct ThreadSafeFinalize { Finalizer callback; napi_threadsafe_function* tsfn; }; +#endif template struct AccessorCallbackData { @@ -3740,6 +3742,7 @@ inline void AsyncWorker::OnWorkComplete( } } +#if (NAPI_VERSION > 3) //////////////////////////////////////////////////////////////////////////////// // ThreadSafeFunction class //////////////////////////////////////////////////////////////////////////////// @@ -4058,6 +4061,7 @@ inline void ThreadSafeFunction::CallJS(napi_env env, Function(env, jsCallback).Call({}); } } +#endif //////////////////////////////////////////////////////////////////////////////// // Memory Management class diff --git a/napi.h b/napi.h index d772fd50d..7bbea198d 100644 --- a/napi.h +++ b/napi.h @@ -1830,6 +1830,7 @@ namespace Napi { bool _suppress_destruct; }; + #if (NAPI_VERSION > 3) class ThreadSafeFunction { public: // This API may only be called from the main thread. @@ -2029,6 +2030,7 @@ namespace Napi { std::unique_ptr _tsfn; }; + #endif // Memory management. class MemoryManagement { diff --git a/test/index.js b/test/index.js index 9a5409b9a..e8b26da80 100644 --- a/test/index.js +++ b/test/index.js @@ -62,6 +62,11 @@ if ((process.env.npm_config_NAPI_VERSION !== undefined) && testModules.splice(testModules.indexOf('version_management'), 1); } +if ((process.env.npm_config_NAPI_VERSION !== undefined) && + (process.env.npm_config_NAPI_VERSION < 4)) { + testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); +} + if (typeof global.gc === 'function') { console.log('Starting test suite\n'); From 6720d572532fe74b256a4f9ed345963ca3369181 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Tue, 23 Jul 2019 22:40:04 +0200 Subject: [PATCH 110/696] Create the native threadsafe_function for test only for N-API greater than 3. --- test/threadsafe_function/threadsafe_function.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/threadsafe_function/threadsafe_function.cc b/test/threadsafe_function/threadsafe_function.cc index 529bfb308..e9b16083b 100644 --- a/test/threadsafe_function/threadsafe_function.cc +++ b/test/threadsafe_function/threadsafe_function.cc @@ -2,6 +2,8 @@ #include #include "napi.h" +#if (NAPI_VERSION > 3) + using namespace Napi; constexpr size_t ARRAY_LENGTH = 10; @@ -177,3 +179,5 @@ Object InitThreadSafeFunction(Env env) { return exports; } + +#endif From 5a7f8b2c7daae0ed898c198e13e56332169b3571 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Tue, 23 Jul 2019 23:28:11 +0200 Subject: [PATCH 111/696] Prepare release 1.7.1 --- CHANGELOG.md | 14 ++++++++++++++ README.md | 2 +- package.json | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea3266964..e68334eb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # node-addon-api Changelog +## 2019-07-23 Version 1.7.1, @NickNaso + +### Notable changes: + +#### API + +- Fixed compilation problems that happen on Node.js with N-API version less than 4. + +### Commmits + +* [[`c20bcbd069`](https://github.com/nodejs/node-addon-api/commit/c20bcbd069)] - Merge pull request #518 from NickNaso/master (Nicola Del Gobbo) +* [[`6720d57253`](https://github.com/nodejs/node-addon-api/commit/6720d57253)] - Create the native threadsafe\_function for test only for N-API greater than 3. (NickNaso) +* [[`37b6c185ad`](https://github.com/nodejs/node-addon-api/commit/37b6c185ad)] - Fix compilation breakage on 1.7.0 (NickNaso) + ## 2019-07-23 Version 1.7.0, @NickNaso ### Notable changes: diff --git a/README.md b/README.md index 42eb93797..77e1c7ec5 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 1.7.0** +## **Current version: 1.7.1** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index db2e572e5..ce8cc3954 100644 --- a/package.json +++ b/package.json @@ -74,5 +74,5 @@ "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile" }, - "version": "1.7.0" + "version": "1.7.1" } From c3c8814d2f17ea6da39ea720f5aaee778a822b75 Mon Sep 17 00:00:00 2001 From: Michael Price Date: Thu, 18 Jul 2019 18:16:02 -0600 Subject: [PATCH 112/696] implement virutal ObjectWrap::Finalize Adds instance method `Finalize()` to `ObjectWrap()` which gets called immediately before the native instance is freed. It receives the `Napi::Env`, so classes that override it are able to perform cleanup that involves calls into N-API. Re: https://github.com/nodejs/node-addon-api/pull/515#issuecomment-513977222 Co-authored-by: Gabriel Schulhof Co-authored-by: Michael Dawson > PR-URL: https://github.com/nodejs/node-addon-api/pull/515 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- doc/object_wrap.md | 11 +++++++++++ napi-inl.h | 9 ++++++++- napi.h | 2 ++ test/objectwrap.cc | 17 +++++++++++++++++ test/objectwrap.js | 19 +++++++++++++++++++ 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/doc/object_wrap.md b/doc/object_wrap.md index 31be140f0..082c4eabe 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -190,6 +190,17 @@ property of the `Napi::CallbackInfo`. Returns a `Napi::Function` representing the constructor function for the class. +### Finalize + +Provides an opportunity to run cleanup code that requires access to the `Napi::Env` +before the wrapped native object instance is freed. Override to implement. + +```cpp +virtual void Finalize(Napi::Env env); +``` + +- `[in] env`: `Napi::Env`. + ### StaticMethod Creates property descriptor that represents a static method of a JavaScript class. diff --git a/napi-inl.h b/napi-inl.h index 3fb4465bb..2f743a2d9 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2888,6 +2888,9 @@ inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { *instanceRef = Reference(env, ref); } +template +inline ObjectWrap::~ObjectWrap() {} + template inline T* ObjectWrap::Unwrap(Object wrapper) { T* unwrapped; @@ -3261,6 +3264,9 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceValue( return desc; } +template +inline void ObjectWrap::Finalize(Napi::Env /*env*/) {} + template inline napi_value ObjectWrap::ConstructorCallbackWrapper( napi_env env, @@ -3402,8 +3408,9 @@ inline napi_value ObjectWrap::InstanceSetterCallbackWrapper( } template -inline void ObjectWrap::FinalizeCallback(napi_env /*env*/, void* data, void* /*hint*/) { +inline void ObjectWrap::FinalizeCallback(napi_env env, void* data, void* /*hint*/) { T* instance = reinterpret_cast(data); + instance->Finalize(Napi::Env(env)); delete instance; } diff --git a/napi.h b/napi.h index 7bbea198d..b5d346b89 100644 --- a/napi.h +++ b/napi.h @@ -1570,6 +1570,7 @@ namespace Napi { class ObjectWrap : public Reference { public: ObjectWrap(const CallbackInfo& callbackInfo); + virtual ~ObjectWrap(); static T* Unwrap(Object wrapper); @@ -1657,6 +1658,7 @@ namespace Napi { static PropertyDescriptor InstanceValue(Symbol name, Napi::Value value, napi_property_attributes attributes = napi_default); + virtual void Finalize(Napi::Env env); private: static napi_value ConstructorCallbackWrapper(napi_env env, napi_callback_info info); diff --git a/test/objectwrap.cc b/test/objectwrap.cc index ef1cfa73e..4a89530e5 100644 --- a/test/objectwrap.cc +++ b/test/objectwrap.cc @@ -24,6 +24,11 @@ class Test : public Napi::ObjectWrap { public: Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + + if(info.Length() > 0) { + finalizeCb_ = Napi::Persistent(info[0].As()); + } + } void Setter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { @@ -105,8 +110,20 @@ class Test : public Napi::ObjectWrap { })); } + void Finalize(Napi::Env env) { + + if(finalizeCb_.IsEmpty()) { + return; + } + + finalizeCb_.Call(env.Global(), {Napi::Boolean::New(env, true)}); + finalizeCb_.Unref(); + + } + private: std::string value_; + Napi::FunctionReference finalizeCb_; }; Napi::Object InitObjectWrap(Napi::Env env) { diff --git a/test/objectwrap.js b/test/objectwrap.js index aa6d8d3cb..533c05f75 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -182,6 +182,24 @@ const test = (binding) => { } }; + const testFinalize = (clazz) => { + + let finalizeCalled = false; + const finalizeCb = function(called) { + finalizeCalled = called; + }; + + //Scope Test instance so that it can be gc'd. + (function() { + new Test(finalizeCb); + })(); + + global.gc(); + + assert.strictEqual(finalizeCalled, true); + + }; + const testObj = (obj, clazz) => { testValue(obj, clazz); testAccessor(obj, clazz); @@ -198,6 +216,7 @@ const test = (binding) => { testStaticMethod(clazz); testStaticEnumerables(clazz); + testFinalize(clazz); }; // `Test` is needed for accessing exposed symbols From 0b4f3a5b8c441f5380925e4c61606c17868050fb Mon Sep 17 00:00:00 2001 From: legendecas Date: Thu, 29 Aug 2019 19:34:49 +0800 Subject: [PATCH 113/696] tsfn: fix crash on releasing tsfn Refs: https://github.com/nodejs/node-addon-api/issues/531 PR-URL: https://github.com/nodejs/node-addon-api/pull/532 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- napi-inl.h | 4 +-- napi.h | 7 ++++- test/binding.cc | 2 ++ test/binding.gyp | 1 + test/index.js | 2 ++ .../threadsafe_function_ptr.cc | 26 +++++++++++++++++++ .../threadsafe_function_ptr.js | 10 +++++++ 7 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 test/threadsafe_function/threadsafe_function_ptr.cc create mode 100644 test/threadsafe_function/threadsafe_function_ptr.js diff --git a/napi-inl.h b/napi-inl.h index 2f743a2d9..b7a29a092 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3926,12 +3926,12 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, } inline ThreadSafeFunction::ThreadSafeFunction() - : _tsfn(new napi_threadsafe_function(nullptr)) { + : _tsfn(new napi_threadsafe_function(nullptr), _d) { } inline ThreadSafeFunction::ThreadSafeFunction( napi_threadsafe_function tsfn) - : _tsfn(new napi_threadsafe_function(tsfn)) { + : _tsfn(new napi_threadsafe_function(tsfn), _d) { } inline ThreadSafeFunction::ThreadSafeFunction(ThreadSafeFunction&& other) diff --git a/napi.h b/napi.h index b5d346b89..aa650de41 100644 --- a/napi.h +++ b/napi.h @@ -2029,8 +2029,13 @@ namespace Napi { napi_value jsCallback, void* context, void* data); + struct Deleter { + // napi_threadsafe_function is managed by Node.js, leave it alone. + void operator()(napi_threadsafe_function*) const {}; + }; - std::unique_ptr _tsfn; + std::unique_ptr _tsfn; + Deleter _d; }; #endif diff --git a/test/binding.cc b/test/binding.cc index 141493e94..bf0f7c0f5 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -34,6 +34,7 @@ Object InitObjectDeprecated(Env env); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED Object InitPromise(Env env); #if (NAPI_VERSION > 3) +Object InitThreadSafeFunctionPtr(Env env); Object InitThreadSafeFunction(Env env); #endif Object InitTypedArray(Env env); @@ -75,6 +76,7 @@ Object Init(Env env, Object exports) { #endif // !NODE_ADDON_API_DISABLE_DEPRECATED exports.Set("promise", InitPromise(env)); #if (NAPI_VERSION > 3) + exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env)); exports.Set("threadsafe_function", InitThreadSafeFunction(env)); #endif exports.Set("typedarray", InitTypedArray(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 56d636adc..c8acb1ba3 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -32,6 +32,7 @@ 'object/object.cc', 'object/set_property.cc', 'promise.cc', + 'threadsafe_function/threadsafe_function_ptr.cc', 'threadsafe_function/threadsafe_function.cc', 'typedarray.cc', 'objectwrap.cc', diff --git a/test/index.js b/test/index.js index e8b26da80..7f97168c5 100644 --- a/test/index.js +++ b/test/index.js @@ -36,6 +36,7 @@ let testModules = [ 'object/object_deprecated', 'object/set_property', 'promise', + 'threadsafe_function/threadsafe_function_ptr', 'threadsafe_function/threadsafe_function', 'typedarray', 'typedarray-bigint', @@ -64,6 +65,7 @@ if ((process.env.npm_config_NAPI_VERSION !== undefined) && if ((process.env.npm_config_NAPI_VERSION !== undefined) && (process.env.npm_config_NAPI_VERSION < 4)) { + testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ptr'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); } diff --git a/test/threadsafe_function/threadsafe_function_ptr.cc b/test/threadsafe_function/threadsafe_function_ptr.cc new file mode 100644 index 000000000..00e8559b8 --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_ptr.cc @@ -0,0 +1,26 @@ +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +static Value Test(const CallbackInfo& info) { + Object resource = info[0].As(); + Function cb = info[1].As(); + ThreadSafeFunction tsfn = ThreadSafeFunction::New(info.Env(), cb, resource, "Test", 1, 1); + tsfn.Release(); + return info.Env().Undefined(); +} + +} + +Object InitThreadSafeFunctionPtr(Env env) { + Object exports = Object::New(env); + exports["test"] = Function::New(env, Test); + + return exports; +} + +#endif diff --git a/test/threadsafe_function/threadsafe_function_ptr.js b/test/threadsafe_function/threadsafe_function_ptr.js new file mode 100644 index 000000000..535b5d642 --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_ptr.js @@ -0,0 +1,10 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + binding.threadsafe_function_ptr.test({}, () => {}); +} From 7b1ee96d5232d250e4f764a1ba2720576d357b59 Mon Sep 17 00:00:00 2001 From: Nurbol Alpysbayev Date: Wed, 21 Aug 2019 12:55:11 +0600 Subject: [PATCH 114/696] doc: update prebuild_tools.md PR-URL: https://github.com/nodejs/node-addon-api/pull/527 Reviewed-By: Michael Dawson Reviewed-By: NickNaso --- doc/prebuild_tools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/prebuild_tools.md b/doc/prebuild_tools.md index d7b1d3c96..573af681a 100644 --- a/doc/prebuild_tools.md +++ b/doc/prebuild_tools.md @@ -9,7 +9,7 @@ possible to ditribute the native add-on in pre-built form for different platform and architectures. The prebuild tools help to create and distrubute the pre-built form of a native add-on. -The following list report two of the tools that are compatible with **N-API**: +The following list report known tools that are compatible with **N-API**: - **[node-pre-gyp](https://www.npmjs.com/package/node-pre-gyp)** - **[prebuild](https://www.npmjs.com/package/prebuild)** From 6192e705cdd00f0265d7ca80ce2a3c8c1f0fe344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20K=C3=BCsel?= Date: Fri, 28 Jun 2019 21:28:05 +0200 Subject: [PATCH 115/696] src: add napi_date PR-URL: https://github.com/nodejs/node-addon-api/pull/497 Reviewed-By: NickNaso Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof Reviewed-By: Anna Henningsen --- README.md | 1 + doc/basic_types.md | 8 ++++++ doc/date.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++ doc/value.md | 9 ++++++ napi-inl.h | 44 ++++++++++++++++++++++++++++++ napi.h | 24 ++++++++++++++++ test/binding.cc | 6 ++++ test/binding.gyp | 1 + test/date.cc | 45 ++++++++++++++++++++++++++++++ test/date.js | 20 ++++++++++++++ test/index.js | 6 ++++ 11 files changed, 232 insertions(+) create mode 100644 doc/date.md create mode 100644 test/date.cc create mode 100644 test/date.js diff --git a/README.md b/README.md index 77e1c7ec5..9ab1e9f6c 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ The following is the documentation for node-addon-api. - [String](doc/string.md) - [Name](doc/basic_types.md#name) - [Number](doc/number.md) + - [Date](doc/date.md) - [BigInt](doc/bigint.md) - [Boolean](doc/boolean.md) - [Env](doc/env.md) diff --git a/doc/basic_types.md b/doc/basic_types.md index b01269d17..03ec14b4b 100644 --- a/doc/basic_types.md +++ b/doc/basic_types.md @@ -257,6 +257,14 @@ bool Napi::Value::IsExternal() const; Returns `true` if the underlying value is a N-API external object or `false` otherwise. +#### IsDate +```cpp +bool Napi::Value::IsDate() const; +``` + +Returns `true` if the underlying value is a JavaScript `Date` or `false` +otherwise. + #### ToBoolean ```cpp Napi::Boolean Napi::Value::ToBoolean() const; diff --git a/doc/date.md b/doc/date.md new file mode 100644 index 000000000..959b4b9a6 --- /dev/null +++ b/doc/date.md @@ -0,0 +1,68 @@ +# Date + +`Napi::Date` class is a representation of the JavaScript `Date` object. The +`Napi::Date` class inherits its behavior from `Napi::Value` class +(for more info see [`Napi::Value`](value.md)) + +## Methods + +### Constructor + +Creates a new _empty_ instance of a `Napi::Date` object. + +```cpp +Napi::Date::Date(); +``` + +Creates a new _non-empty_ instance of a `Napi::Date` object. + +```cpp +Napi::Date::Date(napi_env env, napi_value value); +``` + + - `[in] env`: The environment in which to construct the `Napi::Date` object. + - `[in] value`: The `napi_value` which is a handle for a JavaScript `Date`. + +### New + +Creates a new instance of a `Napi::Date` object. + +```cpp +static Napi::Date Napi::Date::New(Napi::Env env, double value); +``` + + - `[in] env`: The environment in which to construct the `Napi::Date` object. + - `[in] value`: The time value the JavaScript `Date` will contain represented + as the number of milliseconds since 1 January 1970 00:00:00 UTC. + +Returns a new instance of `Napi::Date` object. + +### ValueOf + +```cpp +double Napi::Date::ValueOf() const; +``` + +Returns the time value as `double` primitive represented as the number of + milliseconds since 1 January 1970 00:00:00 UTC. + +## Operators + +### operator double + +Converts a `Napi::Date` value to a `double` primitive. + +```cpp +Napi::Date::operator double() const; +``` + +### Example + +The following shows an example of casting a `Napi::Date` value to a `double` + primitive. + +```cpp +double operatorVal = Napi::Date::New(Env(), 0); // Napi::Date to double +// or +auto instanceVal = info[0].As().ValueOf(); +``` diff --git a/doc/value.md b/doc/value.md index e9f9f8a7f..2d25eb74f 100644 --- a/doc/value.md +++ b/doc/value.md @@ -10,6 +10,7 @@ The following classes inherit, either directly or indirectly, from `Napi::Value` - [`Napi::ArrayBuffer`](array_buffer.md) - [`Napi::Boolean`](boolean.md) - [`Napi::Buffer`](buffer.md) +- [`Napi::Date`](date.md) - [`Napi::External`](external.md) - [`Napi::Function`](function.md) - [`Napi::Name`](name.md) @@ -226,6 +227,14 @@ bool Napi::Value::IsBuffer() const; Returns a `bool` indicating if this `Napi::Value` is a Node buffer. +### IsDate + +```cpp +bool Napi::Value::IsDate() const; +``` + +Returns a `bool` indicating if this `Napi::Value` is a JavaScript date. + ### As ```cpp diff --git a/napi-inl.h b/napi-inl.h index b7a29a092..64315aea6 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -379,6 +379,19 @@ inline bool Value::IsBigInt() const { } #endif // NAPI_EXPERIMENTAL +#if (NAPI_VERSION > 4) +inline bool Value::IsDate() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_date(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} +#endif + inline bool Value::IsString() const { return Type() == napi_string; } @@ -660,6 +673,37 @@ inline void BigInt::ToWords(int* sign_bit, size_t* word_count, uint64_t* words) } #endif // NAPI_EXPERIMENTAL +#if (NAPI_VERSION > 4) +//////////////////////////////////////////////////////////////////////////////// +// Date Class +//////////////////////////////////////////////////////////////////////////////// + +inline Date Date::New(napi_env env, double val) { + napi_value value; + napi_status status = napi_create_date(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, Date()); + return Date(env, value); +} + +inline Date::Date() : Value() { +} + +inline Date::Date(napi_env env, napi_value value) : Value(env, value) { +} + +inline Date::operator double() const { + return ValueOf(); +} + +inline double Date::ValueOf() const { + double result; + napi_status status = napi_get_date_value( + _env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} +#endif + //////////////////////////////////////////////////////////////////////////////// // Name class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index aa650de41..921aaa8bc 100644 --- a/napi.h +++ b/napi.h @@ -116,6 +116,9 @@ namespace Napi { #if (NAPI_VERSION > 2147483646) class BigInt; #endif // NAPI_EXPERIMENTAL +#if (NAPI_VERSION > 4) + class Date; +#endif class String; class Object; class Array; @@ -246,6 +249,9 @@ namespace Napi { #if (NAPI_VERSION > 2147483646) bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint. #endif // NAPI_EXPERIMENTAL +#if (NAPI_VERSION > 4) + bool IsDate() const; ///< Tests if a value is a JavaScript date. +#endif bool IsString() const; ///< Tests if a value is a JavaScript string. bool IsSymbol() const; ///< Tests if a value is a JavaScript symbol. bool IsArray() const; ///< Tests if a value is a JavaScript array. @@ -358,6 +364,24 @@ namespace Napi { }; #endif // NAPI_EXPERIMENTAL +#if (NAPI_VERSION > 4) + /// A JavaScript date value. + class Date : public Value { + public: + /// Creates a new Date value from a double primitive. + static Date New( + napi_env env, ///< N-API environment + double value ///< Number value + ); + + Date(); ///< Creates a new _empty_ Date instance. + Date(napi_env env, napi_value value); ///< Wraps a N-API value primitive. + operator double() const; ///< Converts a Date value to double primitive + + double ValueOf() const; ///< Converts a Date value to a double primitive. + }; + #endif + /// A JavaScript string or symbol value (that can be used as a property name). class Name : public Value { public: diff --git a/test/binding.cc b/test/binding.cc index bf0f7c0f5..dca00771a 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -20,6 +20,9 @@ Object InitBuffer(Env env); #if (NAPI_VERSION > 2) Object InitCallbackScope(Env env); #endif +#if (NAPI_VERSION > 4) +Object InitDate(Env env); +#endif Object InitDataView(Env env); Object InitDataViewReadWrite(Env env); Object InitError(Env env); @@ -56,6 +59,9 @@ Object Init(Env env, Object exports) { // released in once it is no longer experimental #if (NAPI_VERSION > 2147483646) exports.Set("bigint", InitBigInt(env)); +#endif +#if (NAPI_VERSION > 4) + exports.Set("date", InitDate(env)); #endif exports.Set("buffer", InitBuffer(env)); #if (NAPI_VERSION > 2) diff --git a/test/binding.gyp b/test/binding.gyp index c8acb1ba3..f8c1cb0a8 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -14,6 +14,7 @@ 'basic_types/number.cc', 'basic_types/value.cc', 'bigint.cc', + 'date.cc', 'binding.cc', 'buffer.cc', 'callbackscope.cc', diff --git a/test/date.cc b/test/date.cc new file mode 100644 index 000000000..6c1d2cbf0 --- /dev/null +++ b/test/date.cc @@ -0,0 +1,45 @@ +#define NAPI_EXPERIMENTAL +#include "napi.h" + +using namespace Napi; + +#if (NAPI_VERSION > 4) +namespace { + +Value CreateDate(const CallbackInfo& info) { + double input = info[0].As().DoubleValue(); + + return Date::New(info.Env(), input); +} + +Value IsDate(const CallbackInfo& info) { + Date input = info[0].As(); + + return Boolean::New(info.Env(), input.IsDate()); +} + +Value ValueOf(const CallbackInfo& info) { + Date input = info[0].As(); + + return Number::New(info.Env(), input.ValueOf()); +} + +Value OperatorValue(const CallbackInfo& info) { + Date input = info[0].As(); + + return Boolean::New(info.Env(), input.ValueOf() == static_cast(input)); +} + +} // anonymous namespace + +Object InitDate(Env env) { + Object exports = Object::New(env); + exports["CreateDate"] = Function::New(env, CreateDate); + exports["IsDate"] = Function::New(env, IsDate); + exports["ValueOf"] = Function::New(env, ValueOf); + exports["OperatorValue"] = Function::New(env, OperatorValue); + + return exports; +} + +#endif diff --git a/test/date.js b/test/date.js new file mode 100644 index 000000000..16e618e5b --- /dev/null +++ b/test/date.js @@ -0,0 +1,20 @@ +'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) { + const { + CreateDate, + IsDate, + ValueOf, + OperatorValue, + } = binding.date; + assert.deepStrictEqual(CreateDate(0), new Date(0)); + assert.strictEqual(IsDate(new Date(0)), true); + assert.strictEqual(ValueOf(new Date(42)), 42); + assert.strictEqual(OperatorValue(new Date(42)), true); +} diff --git a/test/index.js b/test/index.js index 7f97168c5..d03c2e5b7 100644 --- a/test/index.js +++ b/test/index.js @@ -18,6 +18,7 @@ let testModules = [ 'basic_types/number', 'basic_types/value', 'bigint', + 'date', 'buffer', 'callbackscope', 'dataview/dataview', @@ -69,6 +70,11 @@ if ((process.env.npm_config_NAPI_VERSION !== undefined) && testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); } +if ((process.env.npm_config_NAPI_VERSION !== undefined) && + (process.env.npm_config_NAPI_VERSION < 5)) { + testModules.splice(testModules.indexOf('date'), 1); +} + if (typeof global.gc === 'function') { console.log('Starting test suite\n'); From 5d6aeae7b58010b09ad4d7a71b202398417fc7cb Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 20 Sep 2019 23:56:18 +0800 Subject: [PATCH 116/696] build: enable travis for fast PR check * test: remove unnecessary NAPI_EXPERIMENTAL * travis: remove chakracore on travis Reviewed-By: Michael Dawson --- .travis.yml | 15 ++++++++------- test/basic_types/array.cc | 1 - 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7821a7223..dda1e2cf7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,20 +12,15 @@ env: # https://github.com/jasongin/nvs/blob/master/doc/CI.md - NVS_VERSION=1.4.2 matrix: - - NODEJS_VERSION=node/4 - NODEJS_VERSION=node/6 - NODEJS_VERSION=node/8 - - NODEJS_VERSION=node/9 - NODEJS_VERSION=node/10 - - NODEJS_VERSION=chakracore/8 - - NODEJS_VERSION=chakracore/10 + - NODEJS_VERSION=node/12 - NODEJS_VERSION=nightly - - NODEJS_VERSION=chakracore-nightly matrix: fast_finish: true allow_failures: - env: NODEJS_VERSION=nightly - - env: NODEJS_VERSION=chakracore-nightly sudo: false cache: directories: @@ -59,7 +54,13 @@ install: script: # Travis CI sets NVM_NODEJS_ORG_MIRROR, but it makes node-gyp fail to download headers for nightly builds. - unset NVM_NODEJS_ORG_MIRROR + - NODEJS_MAJOR_VERSION=$(node -p "process.versions.node.match(/\d+/)[0]") - - npm test $NPMOPT + - | + if [ ${NODEJS_MAJOR_VERSION} -gt 11 ]; then + npm test + else + npm test $NPMOPT --NAPI_VERSION=$(node -p "process.versions.napi") + fi after_success: - cpp-coveralls --gcov-options '\-lp' --build-root test/build --exclude test diff --git a/test/basic_types/array.cc b/test/basic_types/array.cc index 401d93618..fb0074c40 100644 --- a/test/basic_types/array.cc +++ b/test/basic_types/array.cc @@ -1,4 +1,3 @@ -#define NAPI_EXPERIMENTAL #include "napi.h" using namespace Napi; From cf8b8415df4dff83a09d0fbcfa51e05ea1ec7890 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Thu, 3 Oct 2019 17:54:05 -0400 Subject: [PATCH 117/696] doc: add Kevin to the list of collaborators (#539) Kevin has been active in helping answer questions, submitting PRs and helping to move n-api and node-addon-api forward. Believe it's time to add him formally to the team here as well. PR-URL: https://github.com/nodejs/node-addon-api/pull/539 Reviewed-By: Michael Dawson --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9ab1e9f6c..0ec215271 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around | Hitesh Kanwathirtha | [digitalinfinity](https://github.com/digitalinfinity) | | Jim Schlight | [jschlight](https://github.com/jschlight) | | Michael Dawson | [mhdawson](https://github.com/mhdawson) | +| Kevin Eady | [KevinEady](https://github.com/KevinEady) | Nicola Del Gobbo | [NickNaso](https://github.com/NickNaso) | | Taylor Woll | [boingoing](https://github.com/boingoing) | From dd9fa8a4a80103b8fb5cea4a891c32d04d6ae1d2 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Thu, 3 Oct 2019 17:55:51 -0400 Subject: [PATCH 118/696] doc: move Arunesh and Taylor to Emeritus (#540) Based on activity believe it might be time to move to Emeritus. PR-URL: https://github.com/nodejs/node-addon-api/pull/540 Reviewed-By: NickNaso Reviewed-By: Gabriel Schulhof --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0ec215271..c3451b4f5 100644 --- a/README.md +++ b/README.md @@ -179,21 +179,21 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around | Name | GitHub Link | | ------------------- | ----------------------------------------------------- | | Anna Henningsen | [addaleax](https://github.com/addaleax) | -| Arunesh Chandra | [aruneshchandra](https://github.com/aruneshchandra) | | Gabriel Schulhof | [gabrielschulhof](https://github.com/gabrielschulhof) | | Hitesh Kanwathirtha | [digitalinfinity](https://github.com/digitalinfinity) | | Jim Schlight | [jschlight](https://github.com/jschlight) | | Michael Dawson | [mhdawson](https://github.com/mhdawson) | | Kevin Eady | [KevinEady](https://github.com/KevinEady) | Nicola Del Gobbo | [NickNaso](https://github.com/NickNaso) | -| Taylor Woll | [boingoing](https://github.com/boingoing) | ### Emeritus | Name | GitHub Link | | ------------------- | ----------------------------------------------------- | +| Arunesh Chandra | [aruneshchandra](https://github.com/aruneshchandra) | | Benjamin Byholm | [kkoopa](https://github.com/kkoopa) | | Jason Ginchereau | [jasongin](https://github.com/jasongin) | | Sampson Gao | [sampsongao](https://github.com/sampsongao) | +| Taylor Woll | [boingoing](https://github.com/boingoing) | From 828f223a8707e187322916404bcd320882887742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Fri, 18 Oct 2019 16:27:24 -0300 Subject: [PATCH 119/696] doc: fix spelling in ObjectWrap doc (#563) PR-URL: https://github.com/nodejs/node-addon-api/pull/563 Reviewed-By: NickNaso Reviewed-By: Michael Dawson --- doc/object_wrap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/object_wrap.md b/doc/object_wrap.md index 082c4eabe..1db658a91 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -161,7 +161,7 @@ static Napi::Function Napi::ObjectWrap::DefineClass(Napi::Env env, JavaScript constructor function. * `[in] properties`: Initializer list of class property descriptor describing static and instance properties and methods of the class. -See: [`Class propertry and descriptor`](class_property_descriptor.md). +See: [`Class property and descriptor`](class_property_descriptor.md). * `[in] data`: User-provided data passed to the constructor callback as `data` property of the `Napi::CallbackInfo`. @@ -184,7 +184,7 @@ static Napi::Function Napi::ObjectWrap::DefineClass(Napi::Env env, JavaScript constructor function. * `[in] properties`: Vector of class property descriptor describing static and instance properties and methods of the class. -See: [`Class propertry and descriptor`](class_property_descriptor.md). +See: [`Class property and descriptor`](class_property_descriptor.md). * `[in] data`: User-provided data passed to the constructor callback as `data` property of the `Napi::CallbackInfo`. From 2e1769e1a313eddc41062a30409b4cbfe34a7140 Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 11 Oct 2019 00:08:50 +0800 Subject: [PATCH 120/696] error: remove unnecessary if condition `NAPI_FATAL_IF_FAILED` would not return if the status is not `napi_ok`. PR-URL: https://github.com/nodejs/node-addon-api/pull/562 Reviewed-By: NickNaso Reviewed-By: Gabriel Schulhof --- napi-inl.h | 68 +++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 64315aea6..366cb6cc3 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1994,47 +1994,43 @@ inline Error Error::New(napi_env env) { status = napi_get_last_error_info(env, &info); NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_last_error_info"); - if (status == napi_ok) { - if (info->error_code == napi_pending_exception) { + if (info->error_code == napi_pending_exception) { + status = napi_get_and_clear_last_exception(env, &error); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception"); + } + else { + const char* error_message = info->error_message != nullptr ? + info->error_message : "Error in native callback"; + + bool isExceptionPending; + status = napi_is_exception_pending(env, &isExceptionPending); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending"); + + if (isExceptionPending) { status = napi_get_and_clear_last_exception(env, &error); NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception"); } - else { - const char* error_message = info->error_message != nullptr ? - info->error_message : "Error in native callback"; - - bool isExceptionPending; - status = napi_is_exception_pending(env, &isExceptionPending); - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending"); - if (isExceptionPending) { - status = napi_get_and_clear_last_exception(env, &error); - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception"); - } - - napi_value message; - status = napi_create_string_utf8( - env, - error_message, - std::strlen(error_message), - &message); - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_string_utf8"); - - if (status == napi_ok) { - switch (info->error_code) { - case napi_object_expected: - case napi_string_expected: - case napi_boolean_expected: - case napi_number_expected: - status = napi_create_type_error(env, nullptr, message, &error); - break; - default: - status = napi_create_error(env, nullptr, message, &error); - break; - } - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_error"); - } + napi_value message; + status = napi_create_string_utf8( + env, + error_message, + std::strlen(error_message), + &message); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_string_utf8"); + + switch (info->error_code) { + case napi_object_expected: + case napi_string_expected: + case napi_boolean_expected: + case napi_number_expected: + status = napi_create_type_error(env, nullptr, message, &error); + break; + default: + status = napi_create_error(env, nullptr, message, &error); + break; } + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_error"); } return Error(env, error); From ea9ce1c801dc28da409a98e915fea79af23d2404 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Wed, 9 Oct 2019 20:18:56 +0200 Subject: [PATCH 121/696] tsfn: add wrappers for Ref and Unref Ref: https://github.com/nodejs/node-addon-api/issues/556#issuecomment-539157399 PR-URL: https://github.com/nodejs/node-addon-api/pull/561 Reviewed-By: Michael Dawson --- napi-inl.h | 14 +++++ napi.h | 6 +++ test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 1 + test/napi_child.js | 7 +++ .../threadsafe_function_unref.cc | 41 ++++++++++++++ .../threadsafe_function_unref.js | 53 +++++++++++++++++++ 8 files changed, 125 insertions(+) create mode 100644 test/threadsafe_function/threadsafe_function_unref.cc create mode 100644 test/threadsafe_function/threadsafe_function_unref.js diff --git a/napi-inl.h b/napi-inl.h index 366cb6cc3..271819944 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4029,6 +4029,20 @@ inline napi_status ThreadSafeFunction::NonBlockingCall( return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking); } +inline void ThreadSafeFunction::Ref(napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_ref_threadsafe_function(env, *_tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +inline void ThreadSafeFunction::Unref(napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_unref_threadsafe_function(env, *_tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + inline napi_status ThreadSafeFunction::Acquire() const { return napi_acquire_threadsafe_function(*_tsfn); } diff --git a/napi.h b/napi.h index 921aaa8bc..05b2914f4 100644 --- a/napi.h +++ b/napi.h @@ -2011,6 +2011,12 @@ namespace Napi { template napi_status NonBlockingCall(DataType* data, Callback callback) const; + // This API may only be called from the main thread. + void Ref(napi_env env) const; + + // This API may only be called from the main thread. + void Unref(napi_env env) const; + // This API may be called from any thread. napi_status Acquire() const; diff --git a/test/binding.cc b/test/binding.cc index dca00771a..9286a87af 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -38,6 +38,7 @@ Object InitObjectDeprecated(Env env); Object InitPromise(Env env); #if (NAPI_VERSION > 3) Object InitThreadSafeFunctionPtr(Env env); +Object InitThreadSafeFunctionUnref(Env env); Object InitThreadSafeFunction(Env env); #endif Object InitTypedArray(Env env); @@ -83,6 +84,7 @@ Object Init(Env env, Object exports) { exports.Set("promise", InitPromise(env)); #if (NAPI_VERSION > 3) exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env)); + exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env)); exports.Set("threadsafe_function", InitThreadSafeFunction(env)); #endif exports.Set("typedarray", InitTypedArray(env)); diff --git a/test/binding.gyp b/test/binding.gyp index f8c1cb0a8..710beff52 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -34,6 +34,7 @@ 'object/set_property.cc', 'promise.cc', 'threadsafe_function/threadsafe_function_ptr.cc', + 'threadsafe_function/threadsafe_function_unref.cc', 'threadsafe_function/threadsafe_function.cc', 'typedarray.cc', 'objectwrap.cc', diff --git a/test/index.js b/test/index.js index d03c2e5b7..2c3092e93 100644 --- a/test/index.js +++ b/test/index.js @@ -38,6 +38,7 @@ let testModules = [ 'object/set_property', 'promise', 'threadsafe_function/threadsafe_function_ptr', + 'threadsafe_function/threadsafe_function_unref', 'threadsafe_function/threadsafe_function', 'typedarray', 'typedarray-bigint', diff --git a/test/napi_child.js b/test/napi_child.js index 29a80a115..76f0bc56a 100644 --- a/test/napi_child.js +++ b/test/napi_child.js @@ -5,3 +5,10 @@ exports.spawnSync = function(command, args, options) { } return require('child_process').spawnSync(command, args, options); }; + +exports.spawn = function(command, args, options) { + if (require('../index').needsFlag) { + args.splice(0, 0, '--napi-modules'); + } + return require('child_process').spawn(command, args, options); +}; diff --git a/test/threadsafe_function/threadsafe_function_unref.cc b/test/threadsafe_function/threadsafe_function_unref.cc new file mode 100644 index 000000000..6877e50f6 --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_unref.cc @@ -0,0 +1,41 @@ +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +static Value TestUnref(const CallbackInfo& info) { + Napi::Env env = info.Env(); + Object global = env.Global(); + Object resource = info[0].As(); + Function cb = info[1].As(); + Function setTimeout = global.Get("setTimeout").As(); + ThreadSafeFunction* tsfn = new ThreadSafeFunction; + + *tsfn = ThreadSafeFunction::New(info.Env(), cb, resource, "Test", 1, 1, [tsfn](Napi::Env /* env */) { + delete tsfn; + }); + + tsfn->BlockingCall(); + + setTimeout.Call( global, { + Function::New(env, [tsfn](const CallbackInfo& info) { + tsfn->Unref(info.Env()); + }), + Number::New(env, 100) + }); + + return info.Env().Undefined(); +} + +} + +Object InitThreadSafeFunctionUnref(Env env) { + Object exports = Object::New(env); + exports["testUnref"] = Function::New(env, TestUnref); + return exports; +} + +#endif diff --git a/test/threadsafe_function/threadsafe_function_unref.js b/test/threadsafe_function/threadsafe_function_unref.js new file mode 100644 index 000000000..37ce56b4d --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_unref.js @@ -0,0 +1,53 @@ +'use strict'; + +const assert = require('assert'); +const buildType = process.config.target_defaults.default_configuration; + +const isMainProcess = process.argv[1] != __filename; + +/** + * In order to test that the event loop exits even with an active TSFN, we need + * to spawn a new process for the test. + * - Main process: spawns new node instance, executing this script + * - Child process: creates TSFN. Native module Unref's via setTimeout after some time but does NOT call Release. + * + * Main process should expect child process to exit. + */ + +if (isMainProcess) { + test(`../build/${buildType}/binding.node`); + test(`../build/${buildType}/binding_noexcept.node`); +} else { + test(process.argv[2]); +} + +function test(bindingFile) { + if (isMainProcess) { + // Main process + const child = require('../napi_child').spawn(process.argv[0], [ '--expose-gc', __filename, bindingFile ], { + stdio: 'inherit', + }); + + let timeout = setTimeout( function() { + child.kill(); + timeout = 0; + throw new Error("Expected child to die"); + }, 5000); + + child.on("error", (err) => { + clearTimeout(timeout); + timeout = 0; + throw new Error(err); + }) + + child.on("close", (code) => { + if (timeout) clearTimeout(timeout); + assert(!code, "Expected return value 0"); + }); + + } else { + // Child process + const binding = require(bindingFile); + binding.threadsafe_function_unref.testUnref({}, () => { }); + } +} From 740c79823ea8702c1257e2ff3c0d76c16c70ad1b Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Wed, 16 Oct 2019 20:52:52 +0200 Subject: [PATCH 122/696] src: add Env() to AsyncContext PR-URL: https://github.com/nodejs/node-addon-api/pull/568 Reviewed-By: NickNaso Reviewed-By: Michael Dawson Reviewed-By: Anna Henningsen --- doc/async_context.md | 10 ++++++++++ napi-inl.h | 4 ++++ napi.h | 2 ++ 3 files changed, 16 insertions(+) diff --git a/doc/async_context.md b/doc/async_context.md index 48de9c496..8e1f481c5 100644 --- a/doc/async_context.md +++ b/doc/async_context.md @@ -45,6 +45,16 @@ The `Napi::AsyncContext` to be destroyed. virtual Napi::AsyncContext::~AsyncContext(); ``` +### Env + +Requests the environment in which the async context has been initially created. + +```cpp +Napi::Env Env() const; +``` + +Returns the `Napi::Env` environment in which the async context has been created. + ## Operator ```cpp diff --git a/napi-inl.h b/napi-inl.h index 271819944..2313dd281 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3589,6 +3589,10 @@ inline AsyncContext::operator napi_async_context() const { return _context; } +inline Napi::Env AsyncContext::Env() const { + return Napi::Env(_env); +} + //////////////////////////////////////////////////////////////////////////////// // AsyncWorker class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 05b2914f4..510c1968b 100644 --- a/napi.h +++ b/napi.h @@ -1784,6 +1784,8 @@ namespace Napi { operator napi_async_context() const; + Napi::Env Env() const; + private: napi_env _env; napi_async_context _context; From ce139a05e867e5b46cd8971324263823560a89f9 Mon Sep 17 00:00:00 2001 From: legendecas Date: Wed, 16 Oct 2019 00:31:06 +0800 Subject: [PATCH 123/696] src: make failure of closing scopes fatal Properly handle failures instead of ignoring them. PR-URL: https://github.com/nodejs/node-addon-api/pull/566 Reviewed-By: NickNaso Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- napi-inl.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 2313dd281..ecd1a659a 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3468,7 +3468,10 @@ inline HandleScope::HandleScope(Napi::Env env) : _env(env) { } inline HandleScope::~HandleScope() { - napi_close_handle_scope(_env, _scope); + napi_status status = napi_close_handle_scope(_env, _scope); + NAPI_FATAL_IF_FAILED(status, + "HandleScope::~HandleScope", + "napi_close_handle_scope"); } inline HandleScope::operator napi_handle_scope() const { @@ -3493,7 +3496,10 @@ inline EscapableHandleScope::EscapableHandleScope(Napi::Env env) : _env(env) { } inline EscapableHandleScope::~EscapableHandleScope() { - napi_close_escapable_handle_scope(_env, _scope); + napi_status status = napi_close_escapable_handle_scope(_env, _scope); + NAPI_FATAL_IF_FAILED(status, + "EscapableHandleScope::~EscapableHandleScope", + "napi_close_escapable_handle_scope"); } inline EscapableHandleScope::operator napi_escapable_handle_scope() const { @@ -3529,7 +3535,10 @@ inline CallbackScope::CallbackScope(napi_env env, napi_async_context context) } inline CallbackScope::~CallbackScope() { - napi_close_callback_scope(_env, _scope); + napi_status status = napi_close_callback_scope(_env, _scope); + NAPI_FATAL_IF_FAILED(status, + "CallbackScope::~CallbackScope", + "napi_close_callback_scope"); } inline CallbackScope::operator napi_callback_scope() const { From 34c11cf0a4f238654b011e3fd60e583a222d4498 Mon Sep 17 00:00:00 2001 From: legendecas Date: Tue, 15 Oct 2019 14:17:27 +0800 Subject: [PATCH 124/696] src: disallow copying, double close of scopes PR-URL: https://github.com/nodejs/node-addon-api/pull/566 Reviewed-By: NickNaso Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- napi.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/napi.h b/napi.h index 510c1968b..549c34123 100644 --- a/napi.h +++ b/napi.h @@ -1729,6 +1729,10 @@ namespace Napi { explicit HandleScope(Napi::Env env); ~HandleScope(); + // Disallow copying to prevent double close of napi_handle_scope + HandleScope(HandleScope const &) = delete; + void operator=(HandleScope const &) = delete; + operator napi_handle_scope() const; Napi::Env Env() const; @@ -1744,6 +1748,10 @@ namespace Napi { explicit EscapableHandleScope(Napi::Env env); ~EscapableHandleScope(); + // Disallow copying to prevent double close of napi_escapable_handle_scope + EscapableHandleScope(EscapableHandleScope const &) = delete; + void operator=(EscapableHandleScope const &) = delete; + operator napi_escapable_handle_scope() const; Napi::Env Env() const; @@ -1761,6 +1769,10 @@ namespace Napi { CallbackScope(napi_env env, napi_async_context context); virtual ~CallbackScope(); + // Disallow copying to prevent double close of napi_callback_scope + CallbackScope(CallbackScope const &) = delete; + void operator=(CallbackScope const &) = delete; + operator napi_callback_scope() const; Napi::Env Env() const; From b513d1aa7a06fc0cae6d1483831536ce0524d36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Tue, 1 Oct 2019 10:10:22 -0300 Subject: [PATCH 125/696] doc: fix return type of ArrayBuffer::Data PR-URL: https://github.com/nodejs/node-addon-api/pull/552 Reviewed-By: NickNaso Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- doc/array_buffer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/array_buffer.md b/doc/array_buffer.md index e7217d73a..ca9d45c00 100644 --- a/doc/array_buffer.md +++ b/doc/array_buffer.md @@ -123,7 +123,7 @@ Returns the length of the wrapped data, in bytes. ### Data ```cpp -T* Napi::ArrayBuffer::Data() const; +void* Napi::ArrayBuffer::Data() const; ``` Returns a pointer the wrapped data. From e9a4bcd52ac5e24ec00d8d9cc7a6375a8ec9d587 Mon Sep 17 00:00:00 2001 From: Jim Schlight Date: Sun, 6 Oct 2019 19:55:11 -0700 Subject: [PATCH 126/696] doc: updates Make.js doc to current best practices PR-URL: https://github.com/nodejs/node-addon-api/pull/558 Reviewed-By: NickNaso Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- doc/cmake-js.md | 67 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/doc/cmake-js.md b/doc/cmake-js.md index 60408b25f..08cd3ea8c 100644 --- a/doc/cmake-js.md +++ b/doc/cmake-js.md @@ -1,19 +1,68 @@ # CMake.js -**CMake.js** is a build tool that allow native addon developer to compile their -C++ code into executable form. It works like **[node-gyp](node-gyp.md)** but -instead of Google's **gyp** format it is base on **CMake** build system. +[**CMake.js**](https://github.com/cmake-js/cmake-js) is a build tool that allow native addon developers to compile their +C or C++ code into executable form. It works like **[node-gyp](node-gyp.md)** but +instead of Google's [**gyp**](https://gyp.gsrc.io) tool it is based on the [**CMake**](https://cmake.org) build system. -## **CMake** reference +## Quick Start - - [Installation](https://www.npmjs.com/package/cmake-js#installation) - - [How to use](https://www.npmjs.com/package/cmake-js#usage) +### Install CMake + +CMake.js requires that CMake be installed. Installers for a variety of platforms can be found on the [CMake website](https://cmake.org). + +### Install CMake.js + +For developers, CMake.js is typically installed as a global package: + +```bash +npm install -g cmake-js +cmake-js --help +``` + +> For *users* of your native addon, CMake.js should be configured as a dependency in your `package.json` as described in the [CMake.js documentation](https://github.com/cmake-js/cmake-js). + +### CMakeLists.txt + +Your project will require a `CMakeLists.txt` file. The [CMake.js README file](https://github.com/cmake-js/cmake-js#usage) shows what's necessary. + +### NAPI_VERSION + +When building N-API addons, it's crucial to specify the N-API version your code is designed to work with. With CMake.js, this information is specified in the `CMakeLists.txt` file: + +``` +add_definitions(-DNAPI_VERSION=3) +``` + +Since N-API is ABI-stable, your N-API addon will work, without recompilation, with the N-API version you specify in `NAPI_VERSION` and all subsequent N-API versions. + +In the absence of a need for features available only in a specific N-API version, version 3 is a good choice as it is the version of N-API that was active when N-API left experimental status. + +### NAPI_EXPERIMENTAL + +The following line in the `CMakeLists.txt` file will enable N-API experimental features if your code requires them: + +``` +add_definitions(-DNAPI_EXPERIMENTAL) +``` + +### node-addon-api + +If your N-API native add-on uses the optional [**node-addon-api**](https://github.com/nodejs/node-addon-api#node-addon-api-module) C++ wrapper, the `CMakeLists.txt` file requires additional configuration information as described on the [CMake.js README file](https://github.com/cmake-js/cmake-js#n-api-and-node-addon-api). + +## Example + +A working example of an N-API native addon built using CMake.js can be found on the [node-addon-examples repository](https://github.com/nodejs/node-addon-examples/tree/master/build_with_cmake#building-n-api-addons-using-cmakejs). + +## **CMake** Reference + + - [Installation](https://github.com/cmake-js/cmake-js#installation) + - [How to use](https://github.com/cmake-js/cmake-js#usage) - [Using N-API and node-addon-api](https://github.com/cmake-js/cmake-js#n-api-and-node-addon-api) - - [Tutorials](https://www.npmjs.com/package/cmake-js#tutorials) - - [Use case in the works - ArrayFire.js](https://www.npmjs.com/package/cmake-js#use-case-in-the-works---arrayfirejs) + - [Tutorials](https://github.com/cmake-js/cmake-js#tutorials) + - [Use case in the works - ArrayFire.js](https://github.com/cmake-js/cmake-js#use-case-in-the-works---arrayfirejs) Sometimes finding the right settings is not easy so to accomplish at most complicated task please refer to: - [CMake documentation](https://cmake.org/) -- [CMake.js wiki](https://github.com/cmake-js/cmake-js/wiki) \ No newline at end of file +- [CMake.js wiki](https://github.com/cmake-js/cmake-js/wiki) From bcc1d58fc45896d8d26dfa351b1c7ab860b8323e Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Fri, 27 Sep 2019 14:28:27 -0700 Subject: [PATCH 127/696] implement Object::AddFinalizer This allows one to tie the life cycle of one JavaScript object to another. In effect, this allows for JavaScript-style closures. Fixes: https://github.com/nodejs/node-addon-api/issues/508 PR_URL: https://github.com/nodejs/node-addon-api/pull/551 Reviewed-By: Kevin Eady <8634912+KevinEady@users.noreply.github.com> --- doc/object.md | 34 ++++++++++++++++++++++++++++ napi-inl.h | 49 ++++++++++++++++++++++++++++++++++++---- napi.h | 8 +++++++ package.json | 12 +++++++++- test/binding.gyp | 1 + test/index.js | 1 + test/object/finalizer.cc | 29 ++++++++++++++++++++++++ test/object/finalizer.js | 21 +++++++++++++++++ test/object/object.cc | 7 ++++++ 9 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 test/object/finalizer.cc create mode 100644 test/object/finalizer.js diff --git a/doc/object.md b/doc/object.md index de20dd899..32410544f 100644 --- a/doc/object.md +++ b/doc/object.md @@ -137,6 +137,40 @@ Returns a `bool` that is true if the `Napi::Object` is an instance created by th Note: This is equivalent to the JavaScript instanceof operator. +### AddFinalizer() +```cpp +template +inline void AddFinalizer(Finalizer finalizeCallback, T* data); +``` + +- `[in] finalizeCallback`: The function to call when the object is garbage-collected. +- `[in] data`: The data to associate with the object. + +Associates `data` with the object, calling `finalizeCallback` when the object is garbage-collected. `finalizeCallback` +has the signature +```cpp +void finalizeCallback(Napi::Env env, T* data); +``` +where `data` is the pointer that was passed into the call to `AddFinalizer()`. + +### AddFinalizer() +```cpp +template +inline void AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint); +``` + +- `[in] data`: The data to associate with the object. +- `[in] finalizeCallback`: The function to call when the object is garbage-collected. + +Associates `data` with the object, calling `finalizeCallback` when the object is garbage-collected. An additional hint +may be given. It will also be passed to `finalizeCallback`, which has the signature +```cpp +void finalizeCallback(Napi::Env env, T* data, Hint* hint); +``` +where `data` and `hint` are the pointers that were passed into the call to `AddFinalizer()`. + ### DefineProperty() ```cpp diff --git a/napi-inl.h b/napi-inl.h index ecd1a659a..d23fc83f7 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -24,16 +24,21 @@ namespace details { template static inline napi_status AttachData(napi_env env, napi_value obj, - FreeType* data) { + FreeType* data, + napi_finalize finalizer = nullptr, + void* hint = nullptr) { napi_value symbol, external; napi_status status = napi_create_symbol(env, nullptr, &symbol); if (status == napi_ok) { + if (finalizer == nullptr) { + finalizer = [](napi_env /*env*/, void* data, void* /*hint*/) { + delete static_cast(data); + }; + } status = napi_create_external(env, data, - [](napi_env /*env*/, void* data, void* /*hint*/) { - delete static_cast(data); - }, - nullptr, + finalizer, + hint, &external); if (status == napi_ok) { napi_property_descriptor desc = { @@ -1170,6 +1175,40 @@ inline bool Object::InstanceOf(const Function& constructor) const { return result; } +template +inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) { + details::FinalizeData* finalizeData = + new details::FinalizeData({ finalizeCallback, nullptr }); + napi_status status = + details::AttachData(_env, + *this, + data, + details::FinalizeData::Wrapper, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +template +inline void Object::AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint) { + details::FinalizeData* finalizeData = + new details::FinalizeData({ finalizeCallback, finalizeHint }); + napi_status status = + details::AttachData(_env, + *this, + data, + details::FinalizeData::WrapperWithHint, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + //////////////////////////////////////////////////////////////////////////////// // External class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 549c34123..d70270e54 100644 --- a/napi.h +++ b/napi.h @@ -709,6 +709,14 @@ namespace Napi { bool InstanceOf( const Function& constructor ///< Constructor function ) const; + + template + inline void AddFinalizer(Finalizer finalizeCallback, T* data); + + template + inline void AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint); }; template diff --git a/package.json b/package.json index ce8cc3954..96ec6dd43 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,17 @@ }, "directories": {}, "homepage": "https://github.com/nodejs/node-addon-api", - "keywords": ["n-api", "napi", "addon", "native", "bindings", "c", "c++", "nan", "node-addon-api"], + "keywords": [ + "n-api", + "napi", + "addon", + "native", + "bindings", + "c", + "c++", + "nan", + "node-addon-api" + ], "license": "MIT", "main": "index.js", "name": "node-addon-api", diff --git a/test/binding.gyp b/test/binding.gyp index 710beff52..29878c37e 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -27,6 +27,7 @@ 'memory_management.cc', 'name.cc', 'object/delete_property.cc', + 'object/finalizer.cc', 'object/get_property.cc', 'object/has_own_property.cc', 'object/has_property.cc', diff --git a/test/index.js b/test/index.js index 2c3092e93..42dc6ba51 100644 --- a/test/index.js +++ b/test/index.js @@ -30,6 +30,7 @@ let testModules = [ 'memory_management', 'name', 'object/delete_property', + 'object/finalizer', 'object/get_property', 'object/has_own_property', 'object/has_property', diff --git a/test/object/finalizer.cc b/test/object/finalizer.cc new file mode 100644 index 000000000..3518ae99d --- /dev/null +++ b/test/object/finalizer.cc @@ -0,0 +1,29 @@ +#include "napi.h" + +using namespace Napi; + +static int dummy; + +Value AddFinalizer(const CallbackInfo& info) { + ObjectReference* ref = new ObjectReference; + *ref = Persistent(Object::New(info.Env())); + info[0] + .As() + .AddFinalizer([](Napi::Env /*env*/, ObjectReference* ref) { + ref->Set("finalizerCalled", true); + delete ref; + }, ref); + return ref->Value(); +} + +Value AddFinalizerWithHint(const CallbackInfo& info) { + ObjectReference* ref = new ObjectReference; + *ref = Persistent(Object::New(info.Env())); + info[0] + .As() + .AddFinalizer([](Napi::Env /*env*/, ObjectReference* ref, int* dummy_p) { + ref->Set("finalizerCalledWithCorrectHint", dummy_p == &dummy); + delete ref; + }, ref, &dummy); + return ref->Value(); +} diff --git a/test/object/finalizer.js b/test/object/finalizer.js new file mode 100644 index 000000000..26820a05a --- /dev/null +++ b/test/object/finalizer.js @@ -0,0 +1,21 @@ +'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 createWeakRef(binding, bindingToTest) { + return binding.object[bindingToTest]({}); +} + +function test(binding) { + const obj1 = createWeakRef(binding, 'addFinalizer'); + global.gc(); + assert.deepStrictEqual(obj1, { finalizerCalled: true }); + + const obj2 = createWeakRef(binding, 'addFinalizerWithHint'); + global.gc(); + assert.deepStrictEqual(obj2, { finalizerCalledWithCorrectHint: true }); +} diff --git a/test/object/object.cc b/test/object/object.cc index c94a4215c..32f2c237a 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -32,6 +32,10 @@ Value HasPropertyWithNapiWrapperValue(const CallbackInfo& info); Value HasPropertyWithCStyleString(const CallbackInfo& info); Value HasPropertyWithCppStyleString(const CallbackInfo& info); +// Native wrappers for testing Object::AddFinalizer() +Value AddFinalizer(const CallbackInfo& info); +Value AddFinalizerWithHint(const CallbackInfo& info); + static bool testValue = true; // Used to test void* Data() integrity struct UserDataHolder { @@ -201,5 +205,8 @@ Object InitObject(Env env) { exports["createObjectUsingMagic"] = Function::New(env, CreateObjectUsingMagic); + exports["addFinalizer"] = Function::New(env, AddFinalizer); + exports["addFinalizerWithHint"] = Function::New(env, AddFinalizerWithHint); + return exports; } From bc8fc23627df688de0cb97253679918956a4f526 Mon Sep 17 00:00:00 2001 From: legendecas Date: Wed, 30 Oct 2019 00:16:31 +0800 Subject: [PATCH 128/696] test: do not run TSFN tests on NAPI_VERSION < 4 PR-URL: https://github.com/nodejs/node-addon-api/pull/576 Reviewed-By: NickNaso Reviewed-By: Gabriel Schulhof --- test/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/index.js b/test/index.js index 42dc6ba51..33889d4ae 100644 --- a/test/index.js +++ b/test/index.js @@ -69,6 +69,7 @@ if ((process.env.npm_config_NAPI_VERSION !== undefined) && if ((process.env.npm_config_NAPI_VERSION !== undefined) && (process.env.npm_config_NAPI_VERSION < 4)) { testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ptr'), 1); + testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_unref'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); } From 8d6132f60966dfdbeb1ac20bfcfcc17c5f574d22 Mon Sep 17 00:00:00 2001 From: legendecas Date: Thu, 31 Oct 2019 08:52:36 +0800 Subject: [PATCH 129/696] doc: improve AsyncWorker docs (#571) PR-URL: https://github.com/nodejs/node-addon-api/pull/571 Reviewed-By: NickNaso Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- doc/async_worker.md | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/doc/async_worker.md b/doc/async_worker.md index a13e8a18c..88668af07 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -7,8 +7,8 @@ operation. Once created, execution is requested by calling `Napi::AsyncWorker::Queue`. When a thread is available for execution the `Napi::AsyncWorker::Execute` method will -be invoked. Once `Napi::AsyncWorker::Execute` completes either -`Napi::AsyncWorker::OnOK` or `Napi::AsyncWorker::OnError` will be invoked. Once +be invoked. Once `Napi::AsyncWorker::Execute` completes either +`Napi::AsyncWorker::OnOK` or `Napi::AsyncWorker::OnError` will be invoked. Once the `Napi::AsyncWorker::OnOK` or `Napi::AsyncWorker::OnError` methods are complete the `Napi::AsyncWorker` instance is destructed. @@ -38,7 +38,7 @@ void Napi::AsyncWorker::Queue(); ### Cancel Cancels queued work if it has not yet been started. If it has already started -executing, it cannot be cancelled. If cancelled successfully neither +executing, it cannot be cancelled. If cancelled successfully neither `OnOK` nor `OnError` will be called. ```cpp @@ -90,14 +90,14 @@ void Napi::AsyncWorker::SetError(const std::string& error); ### Execute -This method is used to execute some tasks out of the **event loop** on a libuv +This method is used to execute some tasks outside of the **event loop** on a libuv worker thread. Subclasses must implement this method and the method is run on -a thread other than that running the main event loop. As the method is not +a thread other than that running the main event loop. As the method is not running on the main event loop, it must avoid calling any methods from node-addon-api or running any code that might invoke JavaScript. Instead, once this method is complete any interaction through node-addon-api with JavaScript should be implemented -in the `Napi::AsyncWorker::OnOK` method which runs on the main thread and is -invoked when the `Napi::AsyncWorker::Execute` method completes. +in the `Napi::AsyncWorker::OnOK` method and `Napi::AsyncWorker::OnError` which run +on the main thread and are invoked when the `Napi::AsyncWorker::Execute` method completes. ```cpp virtual void Napi::AsyncWorker::Execute() = 0; @@ -106,18 +106,19 @@ virtual void Napi::AsyncWorker::Execute() = 0; ### OnOK This method is invoked when the computation in the `Execute` method ends. -The default implementation runs the Callback optionally provided when the AsyncWorker class -was created. The callback will by default receive no arguments. To provide arguments, -override the `GetResult()` method. +The default implementation runs the `Callback` optionally provided when the +`AsyncWorker` class was created. The `Callback` will by default receive no +arguments. The arguments to the `Callback` can be provided by overriding the +`GetResult()` method. ```cpp virtual void Napi::AsyncWorker::OnOK(); ``` ### GetResult -This method returns the arguments passed to the Callback invoked by the default +This method returns the arguments passed to the `Callback` invoked by the default `OnOK()` implementation. The default implementation returns an empty vector, -providing no arguments to the Callback. +providing no arguments to the `Callback`. ```cpp virtual std::vector Napi::AsyncWorker::GetResult(Napi::Env env); @@ -128,7 +129,7 @@ virtual std::vector Napi::AsyncWorker::GetResult(Napi::Env env); This method is invoked after `Napi::AsyncWorker::Execute` completes if an error occurs while `Napi::AsyncWorker::Execute` is running and C++ exceptions are enabled or if an error was set through a call to `Napi::AsyncWorker::SetError`. -The default implementation calls the callback provided when the `Napi::AsyncWorker` +The default implementation calls the `Callback` provided when the `Napi::AsyncWorker` class was created, passing in the error as the first parameter. ```cpp @@ -172,7 +173,7 @@ explicit Napi::AsyncWorker(const Napi::Function& callback, const char* resource_ - `[in] callback`: The function which will be called when an asynchronous operations ends. The given function is called from the main event loop thread. -- `[in] resource_name`: Null-terminated strings that represents the +- `[in] resource_name`: Null-terminated string that represents the identifier for the kind of resource that is being provided for diagnostic information exposed by the async_hooks API. @@ -189,7 +190,7 @@ explicit Napi::AsyncWorker(const Napi::Function& callback, const char* resource_ - `[in] callback`: The function which will be called when an asynchronous operations ends. The given function is called from the main event loop thread. -- `[in] resource_name`: Null-terminated strings that represents the +- `[in] resource_name`: Null-terminated string 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 @@ -224,7 +225,7 @@ explicit Napi::AsyncWorker(const Napi::Object& receiver, const Napi::Function& c - `[in] receiver`: The `this` object passed to the called function. - `[in] callback`: The function which will be called when an asynchronous operations ends. The given function is called from the main event loop thread. -- `[in] resource_name`: Null-terminated strings that represents the +- `[in] resource_name`: Null-terminated string that represents the identifier for the kind of resource that is being provided for diagnostic information exposed by the async_hooks API. @@ -242,7 +243,7 @@ explicit Napi::AsyncWorker(const Napi::Object& receiver, const Napi::Function& c - `[in] receiver`: The `this` object passed to the called function. - `[in] callback`: The function which will be called when an asynchronous operations ends. The given function is called from the main event loop thread. -- `[in] resource_name`: Null-terminated strings that represents the +- `[in] resource_name`: Null-terminated string 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 @@ -274,7 +275,7 @@ explicit Napi::AsyncWorker(Napi::Env env, const char* resource_name); ``` - `[in] env`: The environment in which to create the `Napi::AsyncWorker`. -- `[in] resource_name`: Null-terminated strings that represents the +- `[in] resource_name`: Null-terminated string that represents the identifier for the kind of resource that is being provided for diagnostic information exposed by the async_hooks API. @@ -290,7 +291,7 @@ explicit Napi::AsyncWorker(Napi::Env env, const char* resource_name, const Napi: ``` - `[in] env`: The environment in which to create the `Napi::AsyncWorker`. -- `[in] resource_name`: Null-terminated strings that represents the +- `[in] resource_name`: Null-terminated string 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 @@ -333,7 +334,7 @@ function runs in the background out of the **event loop** thread and at the end the `Napi::AsyncWorker::OnOK` or `Napi::AsyncWorker::OnError` function will be called and are executed as part of the event loop. -The code below show a basic example of `Napi::AsyncWorker` the implementation: +The code below shows a basic example of `Napi::AsyncWorker` the implementation: ```cpp #include @@ -371,7 +372,7 @@ the work on the `Napi::AsyncWorker::Execute` method is done the `Napi::AsyncWorker::OnOk` method is called and the results return back to JavaScript invoking the stored callback with its associated environment. -The following code shows an example on how to create and use an `Napi::AsyncWorker` +The following code shows an example of how to create and use an `Napi::AsyncWorker`. ```cpp #include @@ -382,7 +383,7 @@ The following code shows an example on how to create and use an `Napi::AsyncWork use namespace Napi; Value Echo(const CallbackInfo& info) { - // You need to check the input data here + // You need to validate the arguments here. Function cb = info[1].As(); std::string in = info[0].As(); EchoWorker* wk = new EchoWorker(cb, in); From b42e21e3a9491858892bc45ce670363a0ef7c2fc Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 30 Oct 2019 18:27:18 -0700 Subject: [PATCH 130/696] build: move node/6 to travis allowed failures and add node/13 (#573) Reviewed-By: Michael Dawson Reviewed-By: Chengzhong Wu --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index dda1e2cf7..6a9075313 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,11 +16,13 @@ env: - NODEJS_VERSION=node/8 - NODEJS_VERSION=node/10 - NODEJS_VERSION=node/12 + - NODEJS_VERSION=node/13 - NODEJS_VERSION=nightly matrix: fast_finish: true allow_failures: - env: NODEJS_VERSION=nightly + - env: NODEJS_VERSION=node/6 sudo: false cache: directories: From 9e955a802bc1cd0c6d8147f7042e73c3e1559755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Wed, 30 Oct 2019 22:29:09 -0300 Subject: [PATCH 131/696] doc: change node.js to Node.js per guideline (#579) PR-URL: https://github.com/nodejs/node-addon-api/pull/579 Reviewed-By: NickNaso Reviewed-By: Michael Dawson Reviewed-By: Chengzhong Wu --- doc/async_operations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/async_operations.md b/doc/async_operations.md index 8506e1639..064a9c50f 100644 --- a/doc/async_operations.md +++ b/doc/async_operations.md @@ -4,7 +4,7 @@ Node.js native add-ons often need to execute long running tasks and to avoid blocking the **event loop** they have to run them asynchronously from the **event loop**. In the Node.js model of execution the event loop thread represents the thread -where JavaScript code is executing. The node.js guidance is to avoid blocking +where JavaScript code is executing. The Node.js guidance is to avoid blocking other work queued on the event loop thread. Therefore, we need to do this work on another thread. From bdfd14101f1ab00bc83ab7da942657856d7130e6 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Sun, 27 Oct 2019 17:56:15 -0700 Subject: [PATCH 132/696] src: attach data with napi_add_finalizer Use `napi_add_finalizer()` to attach data when building against N-API 5 instead of the symbol + external approach that leaves a trace on the target object. Fixes: https://github.com/nodejs/node-addon-api/issues/557 PR-URL: https://github.com/nodejs/node-addon-api/pull/577 Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- napi-inl.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index d23fc83f7..91837f7f4 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -27,14 +27,16 @@ static inline napi_status AttachData(napi_env env, FreeType* data, napi_finalize finalizer = nullptr, void* hint = nullptr) { + napi_status status; + if (finalizer == nullptr) { + finalizer = [](napi_env /*env*/, void* data, void* /*hint*/) { + delete static_cast(data); + }; + } +#if (NAPI_VERSION < 5) napi_value symbol, external; - napi_status status = napi_create_symbol(env, nullptr, &symbol); + status = napi_create_symbol(env, nullptr, &symbol); if (status == napi_ok) { - if (finalizer == nullptr) { - finalizer = [](napi_env /*env*/, void* data, void* /*hint*/) { - delete static_cast(data); - }; - } status = napi_create_external(env, data, finalizer, @@ -54,6 +56,9 @@ static inline napi_status AttachData(napi_env env, status = napi_define_properties(env, obj, 1, &desc); } } +#else // NAPI_VERSION >= 5 + status = napi_add_finalizer(env, obj, data, finalizer, hint, nullptr); +#endif return status; } From 650562cab9fade291169c6574c26d554465101b7 Mon Sep 17 00:00:00 2001 From: legendecas Date: Tue, 27 Aug 2019 21:11:14 +0800 Subject: [PATCH 133/696] src: implement AsyncProgressWorker PR-URL: https://github.com/nodejs/node-addon-api/pull/529 Fixes: https://github.com/nodejs/node-addon-api/issues/473 Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- doc/async_progress_worker.md | 344 +++++++++++++++++++++++++++++++++++ napi-inl.h | 150 +++++++++++++++ napi.h | 60 ++++++ test/asyncprogressworker.cc | 72 ++++++++ test/asyncprogressworker.js | 42 +++++ test/binding.cc | 6 + test/binding.gyp | 1 + test/index.js | 2 + 8 files changed, 677 insertions(+) create mode 100644 doc/async_progress_worker.md create mode 100644 test/asyncprogressworker.cc create mode 100644 test/asyncprogressworker.js diff --git a/doc/async_progress_worker.md b/doc/async_progress_worker.md new file mode 100644 index 000000000..296b51b7d --- /dev/null +++ b/doc/async_progress_worker.md @@ -0,0 +1,344 @@ +# AsyncProgressWorker + +`Napi::AsyncProgressWorker` is an abstract class which implements `Napi::AsyncWorker` +while extending `Napi::AsyncWorker` internally with `Napi::ThreadSafeFunction` for +moving work progress reports from worker thread(s) to event loop threads. + +Like `Napi::AsyncWorker`, once created, execution is requested by calling +`Napi::AsyncProgressWorker::Queue`. When a thread is available for execution +the `Napi::AsyncProgressWorker::Execute` method will be invoked. During the +execution, `Napi::AsyncProgressWorker::ExecutionProgress::Send` can be used to +indicate execution process, which will eventually invoke `Napi::AsyncProgressWorker::OnProgress` +on the JavaScript thread to safely call into JavaScript. Once `Napi::AsyncProgressWorker::Execute` +completes either `Napi::AsyncProgressWorker::OnOK` or `Napi::AsyncProgressWorker::OnError` +will be invoked. Once the `Napi::AsyncProgressWorker::OnOK` or `Napi::AsyncProgressWorker::OnError` +methods are complete the `Napi::AsyncProgressWorker` instance is destructed. + +For the most basic use, only the `Napi::AsyncProgressWorker::Execute` and +`Napi::AsyncProgressWorker::OnProgress` method must be implemented in a subclass. + +## Methods + +[`Napi::AsyncWorker`][] provides detailed descriptions for most methods. + +### Execute + +This method is used to execute some tasks outside of the **event loop** on a libuv +worker thread. Subclasses must implement this method and the method is run on +a thread other than that running the main event loop. As the method is not +running on the main event loop, it must avoid calling any methods from node-addon-api +or running any code that might invoke JavaScript. Instead, once this method is +complete any interaction through node-addon-api with JavaScript should be implemented +in the `Napi::AsyncProgressWorker::OnOK` method and/or `Napi::AsyncProgressWorker::OnError` +which run on the main thread and are invoked when the `Napi::AsyncProgressWorker::Execute` +method completes. + +```cpp +virtual void Napi::AsyncProgressWorker::Execute(const ExecutionProgress& progress) = 0; +``` + +### OnOK + +This method is invoked when the computation in the `Execute` method ends. +The default implementation runs the `Callback` optionally provided when the +`AsyncProgressWorker` class was created. The `Callback` will by default receive no +arguments. Arguments to the callback can be provided by overriding the `GetResult()` +method. + +```cpp +virtual void Napi::AsyncProgressWorker::OnOK(); +``` + +### OnProgress + +This method is invoked when the computation in the `Napi::AsyncProgressWorker::ExecutionProcess::Send` +method was called during worker thread execution. + +```cpp +virtual void Napi::AsyncProgressWorker::OnProgress(const T* data, size_t count) +``` + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Function& callback); +``` + +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Function& callback, const char* resource_name); +``` + +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[in] resource_name`: Null-terminated string that represents the +identifier for the kind of resource that is being provided for diagnostic +information exposed by the async_hooks API. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Function& callback, const char* resource_name, const Napi::Object& resource); +``` + +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[in] resource_name`: Null-terminated string 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. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Object& receiver, const Napi::Function& callback); +``` + +- `[in] receiver`: The `this` object passed to the called function. +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Object& receiver, const Napi::Function& callback, const char* resource_name); +``` + +- `[in] receiver`: The `this` object passed to the called function. +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[in] resource_name`: Null-terminated string that represents the +identifier for the kind of resource that is being provided for diagnostic +information exposed by the async_hooks API. + +Returns a `Napi::AsyncWork` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Object& receiver, const Napi::Function& callback, const char* resource_name, const Napi::Object& resource); +``` + +- `[in] receiver`: The `this` object to be passed to the called function. +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[in] resource_name`: Null-terminated string 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. + +Returns a `Napi::AsyncWork` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(Napi::Env env); +``` + +- `[in] env`: The environment in which to create the `Napi::AsyncProgressWorker`. + +Returns an `Napi::AsyncProgressWorker` instance which can later be queued for execution by calling +`Napi::AsyncProgressWorker::Queue`. + +Available with `NAPI_VERSION` equal to or greater than 5. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(Napi::Env env, const char* resource_name); +``` + +- `[in] env`: The environment in which to create the `Napi::AsyncProgressWorker`. +- `[in] resource_name`: Null-terminated string that represents the +identifier for the kind of resource that is being provided for diagnostic +information exposed by the async_hooks API. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncProgressWorker::Queue`. + +Available with `NAPI_VERSION` equal to or greater than 5. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(Napi::Env env, const char* resource_name, const Napi::Object& resource); +``` + +- `[in] env`: The environment in which to create the `Napi::AsyncProgressWorker`. +- `[in] resource_name`: Null-terminated string 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. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncProgressWorker::Queue`. + +Available with `NAPI_VERSION` equal to or greater than 5. + +### Destructor + +Deletes the created work object that is used to execute logic asynchronously and +release the internal `Napi::ThreadSafeFunction`, which will be aborted to prevent +unexpected upcoming thread safe calls. + +```cpp +virtual Napi::AsyncProgressWorker::~AsyncProgressWorker(); +``` + +# AsyncProgressWorker::ExecutionProcess + +A bridge class created before the worker thread execution of `Napi::AsyncProgressWorker::Execute`. + +## Methods + +### Send + +`Napi::AsyncProgressWorker::ExecutionProcess::Send` takes two arguments, a pointer +to a generic type of data, and a `size_t` to indicate how many items the pointer is +pointing to. + +The data pointed to will be copied to internal slots of `Napi::AsyncProgressWorker` so +after the call to `Napi::AsyncProgressWorker::ExecutionProcess::Send` the data can +be safely released. + +Note that `Napi::AsyncProgressWorker::ExecutionProcess::Send` merely guarantees +**eventual** invocation of `Napi::AsyncProgressWorker::OnProgress`, which means +multiple send might be coalesced into single invocation of `Napi::AsyncProgressWorker::OnProgress` +with latest data. + +```cpp +void Napi::AsyncProgressWorker::ExecutionProcess::Send(const T* data, size_t count) const; +``` + +## Example + +The first step to use the `Napi::AsyncProgressWorker` class is to create a new class that +inherits from it and implement the `Napi::AsyncProgressWorker::Execute` abstract method. +Typically input to the worker will be saved within the class' fields generally +passed in through its constructor. + +During the worker thread execution, the first argument of `Napi::AsyncProgressWorker::Execute` +can be used to report the progress of the execution. + +When the `Napi::AsyncProgressWorker::Execute` method completes without errors the +`Napi::AsyncProgressWorker::OnOK` function callback will be invoked. In this function the +results of the computation will be reassembled and returned back to the initial +JavaScript context. + +`Napi::AsyncProgressWorker` ensures that all the code in the `Napi::AsyncProgressWorker::Execute` +function runs in the background out of the **event loop** thread and at the end +the `Napi::AsyncProgressWorker::OnOK` or `Napi::AsyncProgressWorker::OnError` function will be +called and are executed as part of the event loop. + +The code below shows a basic example of the `Napi::AsyncProgressWorker` implementation: + +```cpp +#include + +#include +#include + +use namespace Napi; + +class EchoWorker : public AsyncProgressWorker { + public: + EchoWorker(Function& callback, std::string& echo) + : AsyncProgressWorker(callback), echo(echo) {} + + ~EchoWorker() {} + // This code will be executed on the worker thread + void Execute(const ExecutionProgress& progress) { + // Need to simulate cpu heavy task + for (uint32_t i = 0; i < 100; ++i) { + progress.Send(&i, 1) + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } + + void OnOK() { + HandleScope scope(Env()); + Callback().Call({Env().Null(), String::New(Env(), echo)}); + } + + void OnProgress(const uint32_t* data, size_t /* count */) { + HandleScope scope(Env()); + Callback().Call({Env().Null(), Env().Null(), Number::New(Env(), *data)}); + } + + private: + std::string echo; +}; +``` + +The `EchoWorker`'s constructor calls the base class' constructor to pass in the +callback that the `Napi::AsyncProgressWorker` base class will store persistently. When +the work on the `Napi::AsyncProgressWorker::Execute` method is done the +`Napi::AsyncProgressWorker::OnOk` method is called and the results are return back to +JavaScript when the stored callback is invoked with its associated environment. + +The following code shows an example of how to create and use an `Napi::AsyncProgressWorker` + +```cpp +#include + +// Include EchoWorker class +// .. + +use namespace Napi; + +Value Echo(const CallbackInfo& info) { + // We need to validate the arguments here + Function cb = info[1].As(); + std::string in = info[0].As(); + EchoWorker* wk = new EchoWorker(cb, in); + wk->Queue(); + return info.Env().Undefined(); +} +``` + +The implementation of a `Napi::AsyncProgressWorker` can be used by creating a +new instance and passing to its constructor the callback to execute when the +asynchronous task ends and other data needed for the computation. Once created, +the only other action needed is to call the `Napi::AsyncProgressWorker::Queue` +method that will queue the created worker for execution. + +[`Napi::AsyncWorker`]: ./async_worker.md diff --git a/napi-inl.h b/napi-inl.h index 91837f7f4..0abfcbac4 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -9,7 +9,9 @@ // Note: Do not include this file directly! Include "napi.h" instead. +#include #include +#include #include namespace Napi { @@ -4179,6 +4181,154 @@ inline void ThreadSafeFunction::CallJS(napi_env env, Function(env, jsCallback).Call({}); } } + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Worker class +//////////////////////////////////////////////////////////////////////////////// + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback) + : AsyncProgressWorker(callback, "generic") { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, + const char* resource_name) + : AsyncProgressWorker(callback, resource_name, Object::New(callback.Env())) { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorker(Object::New(callback.Env()), + callback, + resource_name, + resource) { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback) + : AsyncProgressWorker(receiver, callback, "generic") { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name) + : AsyncProgressWorker(receiver, + callback, + resource_name, + Object::New(callback.Env())) { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncWorker(receiver, callback, resource_name, resource), + _asyncdata(nullptr), + _asyncsize(0) { + _tsfn = ThreadSafeFunction::New(callback.Env(), callback, resource_name, 1, 1); +} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env) + : AsyncProgressWorker(env, "generic") { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, + const char* resource_name) + : AsyncProgressWorker(env, resource_name, Object::New(env)) { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : AsyncWorker(env, resource_name, resource), + _asyncdata(nullptr), + _asyncsize(0) { + // TODO: Once the changes to make the callback optional for threadsafe + // functions are no longer optional we can remove the dummy Function here. + Function callback; + _tsfn = ThreadSafeFunction::New(env, callback, resource_name, 1, 1); +} +#endif + +template +inline AsyncProgressWorker::~AsyncProgressWorker() { + // Abort pending tsfn call. + // Don't send progress events after we've already completed. + _tsfn.Abort(); + { + std::lock_guard lock(_mutex); + _asyncdata = nullptr; + _asyncsize = 0; + } + _tsfn.Release(); +} + +template +inline void AsyncProgressWorker::Execute() { + ExecutionProgress progress(this); + Execute(progress); +} + +template +inline void AsyncProgressWorker::WorkProgress_(Napi::Env /* env */, Napi::Function /* jsCallback */, void* _data) { + AsyncProgressWorker* self = static_cast(_data); + + T* data; + size_t size; + { + std::lock_guard lock(self->_mutex); + data = self->_asyncdata; + size = self->_asyncsize; + self->_asyncdata = nullptr; + self->_asyncsize = 0; + } + + self->OnProgress(data, size); + delete[] data; +} + +template +inline void AsyncProgressWorker::SendProgress_(const T* data, size_t count) { + T* new_data = new T[count]; + std::copy(data, data + count, new_data); + + T* old_data; + { + std::lock_guard lock(_mutex); + old_data = _asyncdata; + _asyncdata = new_data; + _asyncsize = count; + } + _tsfn.NonBlockingCall(this, WorkProgress_); + + delete[] old_data; +} + +template +inline void AsyncProgressWorker::Signal() const { + _tsfn.NonBlockingCall(this, WorkProgress_); +} + +template +inline void AsyncProgressWorker::ExecutionProgress::Signal() const { + _worker->Signal(); +} + +template +inline void AsyncProgressWorker::ExecutionProgress::Send(const T* data, size_t count) const { + _worker->SendProgress_(data, count); +} + #endif //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index d70270e54..f4a1ecb57 100644 --- a/napi.h +++ b/napi.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -2089,6 +2090,65 @@ namespace Napi { std::unique_ptr _tsfn; Deleter _d; }; + + template + class AsyncProgressWorker : public AsyncWorker { + public: + virtual ~AsyncProgressWorker(); + + class ExecutionProgress { + friend class AsyncProgressWorker; + public: + void Signal() const; + void Send(const T* data, size_t count) const; + private: + explicit ExecutionProgress(AsyncProgressWorker* worker) : _worker(worker) {} + AsyncProgressWorker* const _worker; + }; + + protected: + explicit AsyncProgressWorker(const Function& callback); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + +// Optional callback of Napi::ThreadSafeFunction only available after NAPI_VERSION 4. +// Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressWorker(Napi::Env env); + explicit AsyncProgressWorker(Napi::Env env, + const char* resource_name); + explicit AsyncProgressWorker(Napi::Env env, + const char* resource_name, + const Object& resource); +#endif + + virtual void Execute(const ExecutionProgress& progress) = 0; + virtual void OnProgress(const T* data, size_t count) = 0; + + private: + static void WorkProgress_(Napi::Env env, Napi::Function jsCallback, void* data); + + void Execute() override; + void Signal() const; + void SendProgress_(const T* data, size_t count); + + std::mutex _mutex; + T* _asyncdata; + size_t _asyncsize; + ThreadSafeFunction _tsfn; + }; #endif // Memory management. diff --git a/test/asyncprogressworker.cc b/test/asyncprogressworker.cc new file mode 100644 index 000000000..8ac062fcb --- /dev/null +++ b/test/asyncprogressworker.cc @@ -0,0 +1,72 @@ +#include "napi.h" + +#include +#include +#include +#include + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +struct ProgressData { + size_t progress; +}; + +class TestWorker : public AsyncProgressWorker { +public: + static void DoWork(const CallbackInfo& info) { + int32_t times = info[0].As().Int32Value(); + Function cb = info[1].As(); + Function progress = info[2].As(); + + TestWorker* worker = new TestWorker(cb, progress, "TestResource", Object::New(info.Env())); + worker->_times = times; + worker->Queue(); + } + +protected: + void Execute(const ExecutionProgress& progress) override { + if (_times < 0) { + SetError("test error"); + } + ProgressData data{0}; + std::unique_lock lock(_cvm); + for (int32_t idx = 0; idx < _times; idx++) { + data.progress = idx; + progress.Send(&data, 1); + _cv.wait(lock); + } + } + + void OnProgress(const ProgressData* data, size_t /* count */) override { + Napi::Env env = Env(); + if (!_progress.IsEmpty()) { + Number progress = Number::New(env, data->progress); + _progress.MakeCallback(Receiver().Value(), { progress }); + } + _cv.notify_one(); + } + +private: + TestWorker(Function cb, Function progress, const char* resource_name, const Object& resource) + : AsyncProgressWorker(cb, resource_name, resource) { + _progress.Reset(progress, 1); + } + std::condition_variable _cv; + std::mutex _cvm; + int32_t _times; + FunctionReference _progress; +}; + +} + +Object InitAsyncProgressWorker(Env env) { + Object exports = Object::New(env); + exports["doWork"] = Function::New(env, TestWorker::DoWork); + return exports; +} + +#endif diff --git a/test/asyncprogressworker.js b/test/asyncprogressworker.js new file mode 100644 index 000000000..0aee9b7f8 --- /dev/null +++ b/test/asyncprogressworker.js @@ -0,0 +1,42 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const common = require('./common') +const assert = require('assert'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test({ asyncprogressworker }) { + success(asyncprogressworker); + fail(asyncprogressworker); + return; +} + +function success(binding) { + const expected = [0, 1, 2, 3]; + const actual = []; + binding.doWork(expected.length, + common.mustCall((err) => { + if (err) { + assert.fail(err); + } + }), + common.mustCall((_progress) => { + actual.push(_progress); + if (actual.length === expected.length) { + assert.deepEqual(actual, expected); + } + }, expected.length) + ); +} + +function fail(binding) { + binding.doWork(-1, + common.mustCall((err) => { + assert.throws(() => { throw err }, /test error/) + }), + () => { + assert.fail('unexpected progress report'); + } + ); +} diff --git a/test/binding.cc b/test/binding.cc index 9286a87af..0490a85c3 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -5,6 +5,9 @@ using namespace Napi; Object InitArrayBuffer(Env env); Object InitAsyncContext(Env env); +#if (NAPI_VERSION > 3) +Object InitAsyncProgressWorker(Env env); +#endif Object InitAsyncWorker(Env env); Object InitPersistentAsyncWorker(Env env); Object InitBasicTypesArray(Env env); @@ -50,6 +53,9 @@ Object InitThunkingManual(Env env); Object Init(Env env, Object exports) { exports.Set("arraybuffer", InitArrayBuffer(env)); exports.Set("asynccontext", InitAsyncContext(env)); +#if (NAPI_VERSION > 3) + exports.Set("asyncprogressworker", InitAsyncProgressWorker(env)); +#endif exports.Set("asyncworker", InitAsyncWorker(env)); exports.Set("persistentasyncworker", InitPersistentAsyncWorker(env)); exports.Set("basic_types_array", InitBasicTypesArray(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 29878c37e..769175d8b 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -7,6 +7,7 @@ 'sources': [ 'arraybuffer.cc', 'asynccontext.cc', + 'asyncprogressworker.cc', 'asyncworker.cc', 'asyncworker-persistent.cc', 'basic_types/array.cc', diff --git a/test/index.js b/test/index.js index 33889d4ae..d68594449 100644 --- a/test/index.js +++ b/test/index.js @@ -10,6 +10,7 @@ process.config.target_defaults.default_configuration = let testModules = [ 'arraybuffer', 'asynccontext', + 'asyncprogressworker', 'asyncworker', 'asyncworker-nocallback', 'asyncworker-persistent', @@ -68,6 +69,7 @@ if ((process.env.npm_config_NAPI_VERSION !== undefined) && if ((process.env.npm_config_NAPI_VERSION !== undefined) && (process.env.npm_config_NAPI_VERSION < 4)) { + testModules.splice(testModules.indexOf('asyncprogressworker'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ptr'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_unref'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); From 2e71842f633b4138baddc2982079f4590e991fa6 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Fri, 1 Nov 2019 21:34:58 +0100 Subject: [PATCH 134/696] tsfn: Implement copy constructor * tsfn: Implement copy constructor Refs: https://github.com/nodejs/node-addon-api/issues/524 PR-URL: https://github.com/nodejs/node-addon-api/pull/546 Reviewed-By: Michael Dawson Reviewed-By: Chengzhong Wu --- napi-inl.h | 39 ++-- napi.h | 10 +- test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 2 + .../threadsafe_function_sum.cc | 199 ++++++++++++++++++ .../threadsafe_function_sum.js | 65 ++++++ 7 files changed, 284 insertions(+), 34 deletions(-) create mode 100644 test/threadsafe_function/threadsafe_function_sum.cc create mode 100644 test/threadsafe_function/threadsafe_function_sum.js diff --git a/napi-inl.h b/napi-inl.h index 0abfcbac4..05a7ef97c 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4025,29 +4025,16 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, } inline ThreadSafeFunction::ThreadSafeFunction() - : _tsfn(new napi_threadsafe_function(nullptr), _d) { + : _tsfn() { } inline ThreadSafeFunction::ThreadSafeFunction( napi_threadsafe_function tsfn) - : _tsfn(new napi_threadsafe_function(tsfn), _d) { + : _tsfn(tsfn) { } -inline ThreadSafeFunction::ThreadSafeFunction(ThreadSafeFunction&& other) - : _tsfn(std::move(other._tsfn)) { - other._tsfn.reset(); -} - -inline ThreadSafeFunction& ThreadSafeFunction::operator =( - ThreadSafeFunction&& other) { - if (*_tsfn != nullptr) { - Error::Fatal("ThreadSafeFunction::operator =", - "You cannot assign a new TSFN because existing one is still alive."); - return *this; - } - _tsfn = std::move(other._tsfn); - other._tsfn.reset(); - return *this; +inline ThreadSafeFunction::operator napi_threadsafe_function() const { + return _tsfn; } inline napi_status ThreadSafeFunction::BlockingCall() const { @@ -4090,34 +4077,34 @@ inline napi_status ThreadSafeFunction::NonBlockingCall( inline void ThreadSafeFunction::Ref(napi_env env) const { if (_tsfn != nullptr) { - napi_status status = napi_ref_threadsafe_function(env, *_tsfn); + napi_status status = napi_ref_threadsafe_function(env, _tsfn); NAPI_THROW_IF_FAILED_VOID(env, status); } } inline void ThreadSafeFunction::Unref(napi_env env) const { if (_tsfn != nullptr) { - napi_status status = napi_unref_threadsafe_function(env, *_tsfn); + napi_status status = napi_unref_threadsafe_function(env, _tsfn); NAPI_THROW_IF_FAILED_VOID(env, status); } } inline napi_status ThreadSafeFunction::Acquire() const { - return napi_acquire_threadsafe_function(*_tsfn); + return napi_acquire_threadsafe_function(_tsfn); } inline napi_status ThreadSafeFunction::Release() { - return napi_release_threadsafe_function(*_tsfn, napi_tsfn_release); + return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); } inline napi_status ThreadSafeFunction::Abort() { - return napi_release_threadsafe_function(*_tsfn, napi_tsfn_abort); + return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); } inline ThreadSafeFunction::ConvertibleContext ThreadSafeFunction::GetContext() const { void* context; - napi_get_threadsafe_function_context(*_tsfn, &context); + napi_get_threadsafe_function_context(_tsfn, &context); return ConvertibleContext({ context }); } @@ -4140,10 +4127,10 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, ThreadSafeFunction tsfn; auto* finalizeData = new details::ThreadSafeFinalize({ data, finalizeCallback, tsfn._tsfn.get() }); + FinalizerDataType>({ data, finalizeCallback, &tsfn._tsfn }); napi_status status = napi_create_threadsafe_function(env, callback, resource, Value::From(env, resourceName), maxQueueSize, initialThreadCount, - finalizeData, wrapper, context, CallJS, tsfn._tsfn.get()); + finalizeData, wrapper, context, CallJS, &tsfn._tsfn); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction()); @@ -4156,7 +4143,7 @@ inline napi_status ThreadSafeFunction::CallInternal( CallbackWrapper* callbackWrapper, napi_threadsafe_function_call_mode mode) const { napi_status status = napi_call_threadsafe_function( - *_tsfn, callbackWrapper, mode); + _tsfn, callbackWrapper, mode); if (status != napi_ok && callbackWrapper != nullptr) { delete callbackWrapper; } diff --git a/napi.h b/napi.h index f4a1ecb57..d3a79a1c0 100644 --- a/napi.h +++ b/napi.h @@ -2009,8 +2009,7 @@ namespace Napi { ThreadSafeFunction(); ThreadSafeFunction(napi_threadsafe_function tsFunctionValue); - ThreadSafeFunction(ThreadSafeFunction&& other); - ThreadSafeFunction& operator=(ThreadSafeFunction&& other); + operator napi_threadsafe_function() const; // This API may be called from any thread. napi_status BlockingCall() const; @@ -2082,13 +2081,8 @@ namespace Napi { napi_value jsCallback, void* context, void* data); - struct Deleter { - // napi_threadsafe_function is managed by Node.js, leave it alone. - void operator()(napi_threadsafe_function*) const {}; - }; - std::unique_ptr _tsfn; - Deleter _d; + napi_threadsafe_function _tsfn; }; template diff --git a/test/binding.cc b/test/binding.cc index 0490a85c3..e97df1350 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -41,6 +41,7 @@ Object InitObjectDeprecated(Env env); Object InitPromise(Env env); #if (NAPI_VERSION > 3) Object InitThreadSafeFunctionPtr(Env env); +Object InitThreadSafeFunctionSum(Env env); Object InitThreadSafeFunctionUnref(Env env); Object InitThreadSafeFunction(Env env); #endif @@ -90,6 +91,7 @@ Object Init(Env env, Object exports) { exports.Set("promise", InitPromise(env)); #if (NAPI_VERSION > 3) exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env)); + exports.Set("threadsafe_function_sum", InitThreadSafeFunctionSum(env)); exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env)); exports.Set("threadsafe_function", InitThreadSafeFunction(env)); #endif diff --git a/test/binding.gyp b/test/binding.gyp index 769175d8b..b96febda6 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -36,6 +36,7 @@ 'object/set_property.cc', 'promise.cc', 'threadsafe_function/threadsafe_function_ptr.cc', + 'threadsafe_function/threadsafe_function_sum.cc', 'threadsafe_function/threadsafe_function_unref.cc', 'threadsafe_function/threadsafe_function.cc', 'typedarray.cc', diff --git a/test/index.js b/test/index.js index d68594449..38b2a5235 100644 --- a/test/index.js +++ b/test/index.js @@ -40,6 +40,7 @@ let testModules = [ 'object/set_property', 'promise', 'threadsafe_function/threadsafe_function_ptr', + 'threadsafe_function/threadsafe_function_sum', 'threadsafe_function/threadsafe_function_unref', 'threadsafe_function/threadsafe_function', 'typedarray', @@ -71,6 +72,7 @@ if ((process.env.npm_config_NAPI_VERSION !== undefined) && (process.env.npm_config_NAPI_VERSION < 4)) { testModules.splice(testModules.indexOf('asyncprogressworker'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ptr'), 1); + testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_sum'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_unref'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); } diff --git a/test/threadsafe_function/threadsafe_function_sum.cc b/test/threadsafe_function/threadsafe_function_sum.cc new file mode 100644 index 000000000..bba57dd47 --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_sum.cc @@ -0,0 +1,199 @@ +#include "napi.h" +#include +#include +#include +#include + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +struct TestData { + + TestData(Promise::Deferred&& deferred) : deferred(std::move(deferred)) {}; + + // Native Promise returned to JavaScript + Promise::Deferred deferred; + + // List of threads created for test. This list only ever accessed via main + // thread. + std::vector threads = {}; + + ThreadSafeFunction tsfn = ThreadSafeFunction(); +}; + +void FinalizerCallback(Napi::Env env, TestData* finalizeData){ + for (size_t i = 0; i < finalizeData->threads.size(); ++i) { + finalizeData->threads[i].join(); + } + finalizeData->deferred.Resolve(Boolean::New(env,true)); + delete finalizeData; +} + +/** + * See threadsafe_function_sum.js for descriptions of the tests in this file + */ + +void entryWithTSFN(ThreadSafeFunction tsfn, int threadId) { + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); + tsfn.BlockingCall( [=](Napi::Env env, Function callback) { + callback.Call( { Number::New(env, static_cast(threadId))}); + }); + tsfn.Release(); +} + +static Value TestWithTSFN(const CallbackInfo& info) { + int threadCount = info[0].As().Int32Value(); + Function cb = info[1].As(); + + // We pass the test data to the Finalizer for cleanup. The finalizer is + // responsible for deleting this data as well. + TestData *testData = new TestData(Promise::Deferred::New(info.Env())); + + ThreadSafeFunction tsfn = ThreadSafeFunction::New( + info.Env(), cb, "Test", 0, threadCount, + std::function(FinalizerCallback), testData); + + for (int i = 0; i < threadCount; ++i) { + // A copy of the ThreadSafeFunction will go to the thread entry point + testData->threads.push_back( std::thread(entryWithTSFN, tsfn, i) ); + } + + return testData->deferred.Promise(); +} + +// Task instance created for each new std::thread +class DelayedTSFNTask { +public: + // Each instance has its own tsfn + ThreadSafeFunction tsfn; + + // Thread-safety + std::mutex mtx; + std::condition_variable cv; + + // Entry point for std::thread + void entryDelayedTSFN(int threadId) { + std::unique_lock lk(mtx); + cv.wait(lk); + tsfn.BlockingCall([=](Napi::Env env, Function callback) { + callback.Call({Number::New(env, static_cast(threadId))}); + }); + tsfn.Release(); + }; +}; + +struct TestDataDelayed { + + TestDataDelayed(Promise::Deferred &&deferred) + : deferred(std::move(deferred)){}; + ~TestDataDelayed() { taskInsts.clear(); }; + // Native Promise returned to JavaScript + Promise::Deferred deferred; + + // List of threads created for test. This list only ever accessed via main + // thread. + std::vector threads = {}; + + // List of DelayedTSFNThread instances + std::vector> taskInsts = {}; + + ThreadSafeFunction tsfn = ThreadSafeFunction(); +}; + +void FinalizerCallbackDelayed(Napi::Env env, TestDataDelayed *finalizeData) { + for (size_t i = 0; i < finalizeData->threads.size(); ++i) { + finalizeData->threads[i].join(); + } + finalizeData->deferred.Resolve(Boolean::New(env, true)); + delete finalizeData; +} + +static Value TestDelayedTSFN(const CallbackInfo &info) { + int threadCount = info[0].As().Int32Value(); + Function cb = info[1].As(); + + TestDataDelayed *testData = + new TestDataDelayed(Promise::Deferred::New(info.Env())); + + testData->tsfn = + ThreadSafeFunction::New(info.Env(), cb, "Test", 0, threadCount, + std::function( + FinalizerCallbackDelayed), + testData); + + for (int i = 0; i < threadCount; ++i) { + testData->taskInsts.push_back( + std::unique_ptr(new DelayedTSFNTask())); + testData->threads.push_back(std::thread(&DelayedTSFNTask::entryDelayedTSFN, + testData->taskInsts.back().get(), + i)); + } + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); + + for (auto &task : testData->taskInsts) { + std::lock_guard lk(task->mtx); + task->tsfn = testData->tsfn; + task->cv.notify_all(); + } + + return testData->deferred.Promise(); +} + +void entryAcquire(ThreadSafeFunction tsfn, int threadId) { + tsfn.Acquire(); + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); + tsfn.BlockingCall( [=](Napi::Env env, Function callback) { + callback.Call( { Number::New(env, static_cast(threadId))}); + }); + tsfn.Release(); +} + +static Value CreateThread(const CallbackInfo& info) { + TestData* testData = static_cast(info.Data()); + ThreadSafeFunction tsfn = testData->tsfn; + int threadId = testData->threads.size(); + // A copy of the ThreadSafeFunction will go to the thread entry point + testData->threads.push_back( std::thread(entryAcquire, tsfn, threadId) ); + return Number::New(info.Env(), threadId); +} + +static Value StopThreads(const CallbackInfo& info) { + TestData* testData = static_cast(info.Data()); + ThreadSafeFunction tsfn = testData->tsfn; + tsfn.Release(); + return info.Env().Undefined(); +} + +static Value TestAcquire(const CallbackInfo& info) { + Function cb = info[0].As(); + Napi::Env env = info.Env(); + + // We pass the test data to the Finalizer for cleanup. The finalizer is + // responsible for deleting this data as well. + TestData *testData = new TestData(Promise::Deferred::New(info.Env())); + + testData->tsfn = ThreadSafeFunction::New( + env, cb, "Test", 0, 1, + std::function(FinalizerCallback), testData); + + Object result = Object::New(env); + result["createThread"] = Function::New( env, CreateThread, "createThread", testData); + result["stopThreads"] = Function::New( env, StopThreads, "stopThreads", testData); + result["promise"] = testData->deferred.Promise(); + + return result; +} +} + +Object InitThreadSafeFunctionSum(Env env) { + Object exports = Object::New(env); + exports["testDelayedTSFN"] = Function::New(env, TestDelayedTSFN); + exports["testWithTSFN"] = Function::New(env, TestWithTSFN); + exports["testAcquire"] = Function::New(env, TestAcquire); + return exports; +} + +#endif diff --git a/test/threadsafe_function/threadsafe_function_sum.js b/test/threadsafe_function/threadsafe_function_sum.js new file mode 100644 index 000000000..4323dabeb --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_sum.js @@ -0,0 +1,65 @@ +'use strict'; +const assert = require('assert'); +const buildType = process.config.target_defaults.default_configuration; + +/** + * + * ThreadSafeFunction Tests: Thread Id Sums + * + * Every native C++ function that utilizes the TSFN will call the registered + * callback with the thread id. Passing Array.prototype.push with a bound array + * will push the thread id to the array. Therefore, starting `N` threads, we + * will expect the sum of all elements in the array to be `(N-1) * (N) / 2` (as + * thread IDs are 0-based) + * + * We check different methods of passing a ThreadSafeFunction around multiple + * threads: + * - `testWithTSFN`: The main thread creates the TSFN. Then, it creates + * threads, passing the TSFN at thread construction. The number of threads is + * static (known at TSFN creation). + * - `testDelayedTSFN`: The main thread creates threads, passing a promise to a + * TSFN at construction. Then, it creates the TSFN, and resolves each + * threads' promise. The number of threads is static. + * - `testAcquire`: The native binding returns a function to start a new. A + * call to this function will return `false` once `N` calls have been made. + * Each thread will acquire its own use of the TSFN, call it, and then + * release. + */ + +const THREAD_COUNT = 5; +const EXPECTED_SUM = (THREAD_COUNT - 1) * (THREAD_COUNT) / 2; + +module.exports = Promise.all([ + test(require(`../build/${buildType}/binding.node`)), + test(require(`../build/${buildType}/binding_noexcept.node`)) +]); + +/** @param {number[]} N */ +const sum = (N) => N.reduce((sum, n) => sum + n, 0); + +function test(binding) { + async function check(bindingFunction) { + const calls = []; + const result = await bindingFunction(THREAD_COUNT, Array.prototype.push.bind(calls)); + assert.ok(result); + assert.equal(sum(calls), EXPECTED_SUM); + } + + async function checkAcquire() { + const calls = []; + const { promise, createThread, stopThreads } = binding.threadsafe_function_sum.testAcquire(Array.prototype.push.bind(calls)); + for (let i = 0; i < THREAD_COUNT; i++) { + createThread(); + } + stopThreads(); + const result = await promise; + assert.ok(result); + assert.equal(sum(calls), EXPECTED_SUM); + } + + return Promise.all([ + check(binding.threadsafe_function_sum.testDelayedTSFN), + check(binding.threadsafe_function_sum.testWithTSFN), + checkAcquire() + ]); +} From 295e560f5554c87d0a9dde6b73942ebd85fddb9d Mon Sep 17 00:00:00 2001 From: legendecas Date: Sat, 2 Nov 2019 04:56:53 +0800 Subject: [PATCH 135/696] test: improve guards for experimental features NAPI_EXPERIMENTAL would be expand to NAPI_VERSION=2147483647 if NAPI_VERSION is not defined. It would be not compatible with various Node.js versions if we use NAPI_VERSION > 2147483646 mixed with definition of NAPI_EXPERIMENTAL. This fix introduced NODE_MAJOR_VERSION to allow more precise control over test sets that which set should be enabled on a Node.js release. PR-URL: https://github.com/nodejs/node-addon-api/pull/545/ Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- .travis.yml | 8 +------- napi-inl.h | 14 ++++++++------ napi.h | 35 ++++++++++++++++++++--------------- test/bigint.cc | 8 +++++--- test/binding.cc | 15 ++++++++------- test/binding.gyp | 4 +++- test/date.cc | 1 - test/index.js | 27 +++++++++++++-------------- test/typedarray.cc | 33 +++++++++++++++++++++------------ 9 files changed, 79 insertions(+), 66 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6a9075313..008e9edf9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,13 +56,7 @@ install: script: # Travis CI sets NVM_NODEJS_ORG_MIRROR, but it makes node-gyp fail to download headers for nightly builds. - unset NVM_NODEJS_ORG_MIRROR - - NODEJS_MAJOR_VERSION=$(node -p "process.versions.node.match(/\d+/)[0]") - - | - if [ ${NODEJS_MAJOR_VERSION} -gt 11 ]; then - npm test - else - npm test $NPMOPT --NAPI_VERSION=$(node -p "process.versions.napi") - fi + - npm test after_success: - cpp-coveralls --gcov-options '\-lp' --build-root test/build --exclude test diff --git a/napi-inl.h b/napi-inl.h index 05a7ef97c..16fe3b3f2 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -383,9 +383,10 @@ inline bool Value::IsNumber() const { return Type() == napi_number; } -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. +// Once it is no longer experimental guard with the NAPI_VERSION in which it is +// released instead. +#ifdef NAPI_EXPERIMENTAL inline bool Value::IsBigInt() const { return Type() == napi_bigint; } @@ -620,9 +621,10 @@ inline double Number::DoubleValue() const { return result; } -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. +// Once it is no longer experimental guard with the NAPI_VERSION in which it is +// released instead. +#ifdef NAPI_EXPERIMENTAL //////////////////////////////////////////////////////////////////////////////// // BigInt Class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index d3a79a1c0..2fc9b10f3 100644 --- a/napi.h +++ b/napi.h @@ -112,9 +112,10 @@ namespace Napi { class Value; class Boolean; class Number; -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. +// Once it is no longer experimental guard with the NAPI_VERSION in which it is +// released instead. +#ifdef NAPI_EXPERIMENTAL class BigInt; #endif // NAPI_EXPERIMENTAL #if (NAPI_VERSION > 4) @@ -140,9 +141,10 @@ namespace Napi { typedef TypedArrayOf Uint32Array; ///< Typed-array of unsigned 32-bit integers typedef TypedArrayOf Float32Array; ///< Typed-array of 32-bit floating-point values typedef TypedArrayOf Float64Array; ///< Typed-array of 64-bit floating-point values -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. +// Once it is no longer experimental guard with the NAPI_VERSION in which it is +// released instead. +#ifdef NAPI_EXPERIMENTAL typedef TypedArrayOf BigInt64Array; ///< Typed array of signed 64-bit integers typedef TypedArrayOf BigUint64Array; ///< Typed array of unsigned 64-bit integers #endif // NAPI_EXPERIMENTAL @@ -245,9 +247,10 @@ namespace Napi { bool IsNull() const; ///< Tests if a value is a null JavaScript value. bool IsBoolean() const; ///< Tests if a value is a JavaScript boolean. bool IsNumber() const; ///< Tests if a value is a JavaScript number. -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. +// Once it is no longer experimental guard with the NAPI_VERSION in which it is +// released instead. +#ifdef NAPI_EXPERIMENTAL bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint. #endif // NAPI_EXPERIMENTAL #if (NAPI_VERSION > 4) @@ -322,9 +325,10 @@ namespace Napi { double DoubleValue() const; ///< Converts a Number value to a 64-bit floating-point value. }; -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. +// Once it is no longer experimental guard with the NAPI_VERSION in which it is +// released instead. +#ifdef NAPI_EXPERIMENTAL /// A JavaScript bigint value. class BigInt : public Value { public: @@ -852,9 +856,10 @@ namespace Napi { : std::is_same::value ? napi_uint32_array : std::is_same::value ? napi_float32_array : std::is_same::value ? napi_float64_array -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. +// Once it is no longer experimental guard with the NAPI_VERSION in which it is +// released instead. +#ifdef NAPI_EXPERIMENTAL : std::is_same::value ? napi_bigint64_array : std::is_same::value ? napi_biguint64_array #endif // NAPI_EXPERIMENTAL diff --git a/test/bigint.cc b/test/bigint.cc index 5d1e36367..a62ed3c30 100644 --- a/test/bigint.cc +++ b/test/bigint.cc @@ -1,11 +1,13 @@ +// Currently experimental guard with NODE_MAJOR_VERISION in which it was +// released. Once it is no longer experimental guard with the NAPI_VERSION +// in which it is released instead. +#if (NODE_MAJOR_VERSION >= 10) + #define NAPI_EXPERIMENTAL #include "napi.h" using namespace Napi; -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) namespace { Value IsLossless(const CallbackInfo& info) { diff --git a/test/binding.cc b/test/binding.cc index e97df1350..403134bf6 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -1,4 +1,3 @@ -#define NAPI_EXPERIMENTAL #include "napi.h" using namespace Napi; @@ -14,9 +13,10 @@ Object InitBasicTypesArray(Env env); Object InitBasicTypesBoolean(Env env); Object InitBasicTypesNumber(Env env); Object InitBasicTypesValue(Env env); -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with NODE_MAJOR_VERISION in which it was +// released. Once it is no longer experimental guard with the NAPI_VERSION +// in which it is released instead. +#if (NODE_MAJOR_VERSION >= 10) Object InitBigInt(Env env); #endif Object InitBuffer(Env env); @@ -63,9 +63,10 @@ Object Init(Env env, Object exports) { exports.Set("basic_types_boolean", InitBasicTypesBoolean(env)); exports.Set("basic_types_number", InitBasicTypesNumber(env)); exports.Set("basic_types_value", InitBasicTypesValue(env)); -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with NODE_MAJOR_VERISION in which it was +// released. Once it is no longer experimental guard with the NAPI_VERSION +// in which it is released instead. +#if (NODE_MAJOR_VERSION >= 10) exports.Set("bigint", InitBigInt(env)); #endif #if (NAPI_VERSION > 4) diff --git a/test/binding.gyp b/test/binding.gyp index b96febda6..aa575fba3 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -1,6 +1,7 @@ { 'variables': { - 'NAPI_VERSION%': "", + 'NAPI_VERSION%': "= 10) #define NAPI_EXPERIMENTAL +#endif #include "napi.h" using namespace Napi; @@ -65,9 +70,10 @@ Value CreateTypedArray(const CallbackInfo& info) { NAPI_TYPEDARRAY_NEW(Float64Array, info.Env(), length, napi_float64_array) : NAPI_TYPEDARRAY_NEW_BUFFER(Float64Array, info.Env(), length, buffer, bufferOffset, napi_float64_array); -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with NODE_MAJOR_VERISION in which it was +// released. Once it is no longer experimental guard with the NAPI_VERSION +// in which it is released instead. +#if (NODE_MAJOR_VERSION >= 10) } else if (arrayType == "bigint64") { return buffer.IsUndefined() ? NAPI_TYPEDARRAY_NEW(BigInt64Array, info.Env(), length, napi_bigint64_array) : @@ -101,9 +107,10 @@ Value GetTypedArrayType(const CallbackInfo& info) { case napi_uint32_array: return String::New(info.Env(), "uint32"); case napi_float32_array: return String::New(info.Env(), "float32"); case napi_float64_array: return String::New(info.Env(), "float64"); -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with NODE_MAJOR_VERISION in which it was +// released. Once it is no longer experimental guard with the NAPI_VERSION +// in which it is released instead. +#if (NODE_MAJOR_VERSION >= 10) case napi_bigint64_array: return String::New(info.Env(), "bigint64"); case napi_biguint64_array: return String::New(info.Env(), "biguint64"); #endif @@ -143,9 +150,10 @@ Value GetTypedArrayElement(const CallbackInfo& info) { return Number::New(info.Env(), array.As()[index]); case napi_float64_array: return Number::New(info.Env(), array.As()[index]); -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with NODE_MAJOR_VERISION in which it was +// released. Once it is no longer experimental guard with the NAPI_VERSION +// in which it is released instead. +#if (NODE_MAJOR_VERSION >= 10) case napi_bigint64_array: return BigInt::New(info.Env(), array.As()[index]); case napi_biguint64_array: @@ -189,9 +197,10 @@ void SetTypedArrayElement(const CallbackInfo& info) { case napi_float64_array: array.As()[index] = value.DoubleValue(); break; -// currently experimental guard with version of NAPI_VERSION that it is -// released in once it is no longer experimental -#if (NAPI_VERSION > 2147483646) +// Currently experimental guard with NODE_MAJOR_VERISION in which it was +// released. Once it is no longer experimental guard with the NAPI_VERSION +// in which it is released instead. +#if (NODE_MAJOR_VERSION >= 10) case napi_bigint64_array: { bool lossless; array.As()[index] = value.As().Int64Value(&lossless); From b3609d33b6069eb04b52724b5963fc9cceb47b8d Mon Sep 17 00:00:00 2001 From: Tim Rach Date: Tue, 5 Nov 2019 12:26:08 +0100 Subject: [PATCH 136/696] Fix return type and declaration of setter callback Return type should be void and second function argument of type Napi::Value should be present. See typedef: https://github.com/nodejs/node-addon-api/blob/295e560f5554c87d0a9dde6b73942ebd85fddb9d/napi.h#L1623 --- doc/class_property_descriptor.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/class_property_descriptor.md b/doc/class_property_descriptor.md index ae2d27e27..bb492de7d 100644 --- a/doc/class_property_descriptor.md +++ b/doc/class_property_descriptor.md @@ -20,7 +20,7 @@ class Example : public Napi::ObjectWrap { static Napi::FunctionReference constructor; double _value; Napi::Value GetValue(const Napi::CallbackInfo &info); - Napi::Value SetValue(const Napi::CallbackInfo &info); + void SetValue(const Napi::CallbackInfo &info, const Napi::Value &value); }; Napi::Object Example::Init(Napi::Env env, Napi::Object exports) { @@ -52,12 +52,11 @@ Napi::Value Example::GetValue(const Napi::CallbackInfo &info) { return Napi::Number::New(env, this->_value); } -Napi::Value Example::SetValue(const Napi::CallbackInfo &info, const Napi::Value &value) { +void Example::SetValue(const Napi::CallbackInfo &info, const Napi::Value &value) { Napi::Env env = info.Env(); // ... Napi::Number arg = value.As(); this->_value = arg.DoubleValue(); - return this->GetValue(info); } // Initialize native add-on From 2298dfae581c747a306045dd0c42be34ed79f62e Mon Sep 17 00:00:00 2001 From: NickNaso Date: Tue, 5 Nov 2019 14:35:18 +0100 Subject: [PATCH 137/696] doc: Added AsyncProgressWorker to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c3451b4f5..701913221 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ The following is the documentation for node-addon-api. - [Async Operations](doc/async_operations.md) - [AsyncWorker](doc/async_worker.md) - [AsyncContext](doc/async_context.md) + - [AsyncProgressWorker](doc/async_progress_worker.md) - [Thread-safe Functions](doc/threadsafe_function.md) - [Promises](doc/promises.md) - [Version management](doc/version_management.md) From df75e08c2bae11b84ebbf5a1c6d54df8621b16fd Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 10 Nov 2019 18:21:02 +0100 Subject: [PATCH 138/696] tsfn: support direct calls to underlying napi_tsfn support direct calls to underlying napi_tsfn Fixes: https://github.com/nodejs/node-addon-api/issues/556 PR-URL: https://github.com/nodejs/node-addon-api/pull/58 Reviewed-By: Michael Dawson Reviewed-By: Chengzhong Wu Reviewed-By: Gabriel Schulhof --- doc/threadsafe_function.md | 19 ++- napi-inl.h | 12 ++ test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 2 + .../threadsafe_function_existing_tsfn.cc | 112 ++++++++++++++++++ .../threadsafe_function_existing_tsfn.js | 19 +++ 7 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 test/threadsafe_function/threadsafe_function_existing_tsfn.cc create mode 100644 test/threadsafe_function/threadsafe_function_existing_tsfn.js diff --git a/doc/threadsafe_function.md b/doc/threadsafe_function.md index e547307b4..2bd8b67c9 100644 --- a/doc/threadsafe_function.md +++ b/doc/threadsafe_function.md @@ -58,7 +58,10 @@ Napi::ThreadSafeFunction::ThreadSafeFunction(napi_threadsafe_function tsfn); - `tsfn`: The `napi_threadsafe_function` which is a handle for an existing thread-safe function. -Returns a non-empty `Napi::ThreadSafeFunction` instance. +Returns a non-empty `Napi::ThreadSafeFunction` instance. When using this +constructor, only use the `Blocking(void*)` / `NonBlocking(void*)` overloads; +the `Callback` and templated `data*` overloads should _not_ be used. See below +for additional details. ### New @@ -171,6 +174,9 @@ There are several overloaded implementations of `BlockingCall()` and `NonBlockingCall()` for use with optional parameters: skip the optional parameter for that specific overload. +**These specific function overloads should only be used on a `ThreadSafeFunction` +created via `ThreadSafeFunction::New`.** + ```cpp napi_status Napi::ThreadSafeFunction::BlockingCall(DataType* data, Callback callback) const @@ -186,6 +192,17 @@ napi_status Napi::ThreadSafeFunction::NonBlockingCall(DataType* data, Callback c necessary to call into JavaScript via `MakeCallback()` because N-API runs `callback` in a context appropriate for callbacks. +**These specific function overloads should only be used on a `ThreadSafeFunction` +created via `ThreadSafeFunction(napi_threadsafe_function)`.** + +```cpp +napi_status Napi::ThreadSafeFunction::BlockingCall(void* data) const + +napi_status Napi::ThreadSafeFunction::NonBlockingCall(void* data) const +``` +- `data`: Data to pass to `call_js_cb` specified when creating the thread-safe + function via `napi_create_threadsafe_function`. + Returns one of: - `napi_ok`: The call was successfully added to the queue. - `napi_queue_full`: The queue was full when trying to call in a non-blocking diff --git a/napi-inl.h b/napi-inl.h index 16fe3b3f2..98c578ed1 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4043,6 +4043,12 @@ inline napi_status ThreadSafeFunction::BlockingCall() const { return CallInternal(nullptr, napi_tsfn_blocking); } +template <> +inline napi_status ThreadSafeFunction::BlockingCall( + void* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); +} + template inline napi_status ThreadSafeFunction::BlockingCall( Callback callback) const { @@ -4062,6 +4068,12 @@ inline napi_status ThreadSafeFunction::NonBlockingCall() const { return CallInternal(nullptr, napi_tsfn_nonblocking); } +template <> +inline napi_status ThreadSafeFunction::NonBlockingCall( + void* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); +} + template inline napi_status ThreadSafeFunction::NonBlockingCall( Callback callback) const { diff --git a/test/binding.cc b/test/binding.cc index 403134bf6..0999ec7c4 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -40,6 +40,7 @@ Object InitObjectDeprecated(Env env); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED Object InitPromise(Env env); #if (NAPI_VERSION > 3) +Object InitThreadSafeFunctionExistingTsfn(Env env); Object InitThreadSafeFunctionPtr(Env env); Object InitThreadSafeFunctionSum(Env env); Object InitThreadSafeFunctionUnref(Env env); @@ -91,6 +92,7 @@ Object Init(Env env, Object exports) { #endif // !NODE_ADDON_API_DISABLE_DEPRECATED exports.Set("promise", InitPromise(env)); #if (NAPI_VERSION > 3) + exports.Set("threadsafe_function_existing_tsfn", InitThreadSafeFunctionExistingTsfn(env)); exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env)); exports.Set("threadsafe_function_sum", InitThreadSafeFunctionSum(env)); exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env)); diff --git a/test/binding.gyp b/test/binding.gyp index aa575fba3..a21ef8e95 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -36,6 +36,7 @@ 'object/object.cc', 'object/set_property.cc', 'promise.cc', + 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', 'threadsafe_function/threadsafe_function_sum.cc', 'threadsafe_function/threadsafe_function_unref.cc', diff --git a/test/index.js b/test/index.js index bb42b955a..2269aed8a 100644 --- a/test/index.js +++ b/test/index.js @@ -39,6 +39,7 @@ let testModules = [ 'object/object_deprecated', 'object/set_property', 'promise', + 'threadsafe_function/threadsafe_function_existing_tsfn', 'threadsafe_function/threadsafe_function_ptr', 'threadsafe_function/threadsafe_function_sum', 'threadsafe_function/threadsafe_function_unref', @@ -68,6 +69,7 @@ if (napiVersion < 3) { if (napiVersion < 4) { testModules.splice(testModules.indexOf('asyncprogressworker'), 1); + testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_existing_tsfn'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ptr'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_sum'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_unref'), 1); diff --git a/test/threadsafe_function/threadsafe_function_existing_tsfn.cc b/test/threadsafe_function/threadsafe_function_existing_tsfn.cc new file mode 100644 index 000000000..19971b824 --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_existing_tsfn.cc @@ -0,0 +1,112 @@ +#include "napi.h" +#include + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +struct TestContext { + TestContext(Promise::Deferred &&deferred) + : deferred(std::move(deferred)), callData(nullptr){}; + + napi_threadsafe_function tsfn; + Promise::Deferred deferred; + double *callData; + + ~TestContext() { + if (callData != nullptr) + delete callData; + }; +}; + +void FinalizeCB(napi_env env, void * /*finalizeData */, void *context) { + TestContext *testContext = static_cast(context); + if (testContext->callData != nullptr) { + testContext->deferred.Resolve(Number::New(env, *testContext->callData)); + } else { + testContext->deferred.Resolve(Napi::Env(env).Undefined()); + } + delete testContext; +} + +void CallJSWithData(napi_env env, napi_value /* callback */, void *context, + void *data) { + TestContext *testContext = static_cast(context); + testContext->callData = static_cast(data); + + napi_status status = + napi_release_threadsafe_function(testContext->tsfn, napi_tsfn_release); + + NAPI_THROW_IF_FAILED_VOID(env, status); +} + +void CallJSNoData(napi_env env, napi_value /* callback */, void *context, + void * /*data*/) { + TestContext *testContext = static_cast(context); + testContext->callData = nullptr; + + napi_status status = + napi_release_threadsafe_function(testContext->tsfn, napi_tsfn_release); + + NAPI_THROW_IF_FAILED_VOID(env, status); +} + +static Value TestCall(const CallbackInfo &info) { + Napi::Env env = info.Env(); + bool isBlocking = false; + bool hasData = false; + if (info.Length() > 0) { + Object opts = info[0].As(); + if (opts.Has("blocking")) { + isBlocking = opts.Get("blocking").ToBoolean(); + } + if (opts.Has("data")) { + hasData = opts.Get("data").ToBoolean(); + } + } + + // Allow optional callback passed from JS. Useful for testing. + Function cb = Function::New(env, [](const CallbackInfo & /*info*/) {}); + + TestContext *testContext = new TestContext(Napi::Promise::Deferred(env)); + + napi_status status = napi_create_threadsafe_function( + env, cb, Object::New(env), String::New(env, "Test"), 0, 1, + nullptr, /*finalize data*/ + FinalizeCB, testContext, hasData ? CallJSWithData : CallJSNoData, + &testContext->tsfn); + + NAPI_THROW_IF_FAILED(env, status, Value()); + + ThreadSafeFunction wrapped = ThreadSafeFunction(testContext->tsfn); + + // Test the four napi_threadsafe_function direct-accessing calls + if (isBlocking) { + if (hasData) { + wrapped.BlockingCall(static_cast(new double(std::rand()))); + } else { + wrapped.BlockingCall(static_cast(nullptr)); + } + } else { + if (hasData) { + wrapped.NonBlockingCall(static_cast(new double(std::rand()))); + } else { + wrapped.NonBlockingCall(static_cast(nullptr)); + } + } + + return testContext->deferred.Promise(); +} + +} // namespace + +Object InitThreadSafeFunctionExistingTsfn(Env env) { + Object exports = Object::New(env); + exports["testCall"] = Function::New(env, TestCall); + + return exports; +} + +#endif diff --git a/test/threadsafe_function/threadsafe_function_existing_tsfn.js b/test/threadsafe_function/threadsafe_function_existing_tsfn.js new file mode 100644 index 000000000..8843decd1 --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_existing_tsfn.js @@ -0,0 +1,19 @@ +'use strict'; + +const assert = require('assert'); + +const buildType = process.config.target_defaults.default_configuration; + +module.exports = Promise.all([ + test(require(`../build/${buildType}/binding.node`)), + test(require(`../build/${buildType}/binding_noexcept.node`)) +]); + +async function test(binding) { + const testCall = binding.threadsafe_function_existing_tsfn.testCall; + + assert(typeof await testCall({ blocking: true, data: true }) === "number"); + assert(typeof await testCall({ blocking: true, data: false }) === "undefined"); + assert(typeof await testCall({ blocking: false, data: true }) === "number"); + assert(typeof await testCall({ blocking: false, data: false }) === "undefined"); +} From c881168d4951e833fb8275b233fa181ec0b60b43 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Mon, 18 Nov 2019 15:03:26 -0800 Subject: [PATCH 139/696] tsfn: add error checking on GetContext (#583) PR-URL: https://github.com/nodejs/node-addon-api/pull/583 Reviewed-By: Michael Dawson Reviewed-By: Chengzhong Wu --- napi-inl.h | 3 +- test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 2 + .../threadsafe_function_ctx.cc | 63 +++++++++++++++++++ .../threadsafe_function_ctx.js | 16 +++++ 6 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 test/threadsafe_function/threadsafe_function_ctx.cc create mode 100644 test/threadsafe_function/threadsafe_function_ctx.js diff --git a/napi-inl.h b/napi-inl.h index 98c578ed1..f72e1daea 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4118,7 +4118,8 @@ inline napi_status ThreadSafeFunction::Abort() { inline ThreadSafeFunction::ConvertibleContext ThreadSafeFunction::GetContext() const { void* context; - napi_get_threadsafe_function_context(_tsfn, &context); + napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); + NAPI_FATAL_IF_FAILED(status, "ThreadSafeFunction::GetContext", "napi_get_threadsafe_function_context"); return ConvertibleContext({ context }); } diff --git a/test/binding.cc b/test/binding.cc index 0999ec7c4..5c3cd6b24 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -40,6 +40,7 @@ Object InitObjectDeprecated(Env env); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED Object InitPromise(Env env); #if (NAPI_VERSION > 3) +Object InitThreadSafeFunctionCtx(Env env); Object InitThreadSafeFunctionExistingTsfn(Env env); Object InitThreadSafeFunctionPtr(Env env); Object InitThreadSafeFunctionSum(Env env); @@ -92,6 +93,7 @@ Object Init(Env env, Object exports) { #endif // !NODE_ADDON_API_DISABLE_DEPRECATED exports.Set("promise", InitPromise(env)); #if (NAPI_VERSION > 3) + exports.Set("threadsafe_function_ctx", InitThreadSafeFunctionCtx(env)); exports.Set("threadsafe_function_existing_tsfn", InitThreadSafeFunctionExistingTsfn(env)); exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env)); exports.Set("threadsafe_function_sum", InitThreadSafeFunctionSum(env)); diff --git a/test/binding.gyp b/test/binding.gyp index a21ef8e95..ced1a6802 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -36,6 +36,7 @@ 'object/object.cc', 'object/set_property.cc', 'promise.cc', + 'threadsafe_function/threadsafe_function_ctx.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', 'threadsafe_function/threadsafe_function_sum.cc', diff --git a/test/index.js b/test/index.js index 2269aed8a..e05c25a65 100644 --- a/test/index.js +++ b/test/index.js @@ -39,6 +39,7 @@ let testModules = [ 'object/object_deprecated', 'object/set_property', 'promise', + 'threadsafe_function/threadsafe_function_ctx', 'threadsafe_function/threadsafe_function_existing_tsfn', 'threadsafe_function/threadsafe_function_ptr', 'threadsafe_function/threadsafe_function_sum', @@ -69,6 +70,7 @@ if (napiVersion < 3) { if (napiVersion < 4) { testModules.splice(testModules.indexOf('asyncprogressworker'), 1); + testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ctx'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_existing_tsfn'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ptr'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_sum'), 1); diff --git a/test/threadsafe_function/threadsafe_function_ctx.cc b/test/threadsafe_function/threadsafe_function_ctx.cc new file mode 100644 index 000000000..bae83baa0 --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_ctx.cc @@ -0,0 +1,63 @@ +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +class TSFNWrap : public ObjectWrap { +public: + static Object Init(Napi::Env env, Object exports); + TSFNWrap(const CallbackInfo &info); + + Napi::Value GetContext(const CallbackInfo & /*info*/) { + Reference *ctx = _tsfn.GetContext(); + return ctx->Value(); + }; + + Napi::Value Release(const CallbackInfo &info) { + Napi::Env env = info.Env(); + _deferred = std::unique_ptr(new Promise::Deferred(env)); + _tsfn.Release(); + return _deferred->Promise(); + }; + +private: + ThreadSafeFunction _tsfn; + std::unique_ptr _deferred; +}; + +Object TSFNWrap::Init(Napi::Env env, Object exports) { + Function func = + DefineClass(env, "TSFNWrap", + {InstanceMethod("getContext", &TSFNWrap::GetContext), + InstanceMethod("release", &TSFNWrap::Release)}); + + exports.Set("TSFNWrap", func); + return exports; +} + +TSFNWrap::TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { + Napi::Env env = info.Env(); + + Reference *_ctx = new Reference; + *_ctx = Persistent(info[0]); + + _tsfn = ThreadSafeFunction::New( + info.Env(), Function::New(env, [](const CallbackInfo & /*info*/) {}), + Object::New(env), "Test", 1, 1, _ctx, + [this](Napi::Env env, Reference *ctx) { + _deferred->Resolve(env.Undefined()); + ctx->Reset(); + delete ctx; + }); +} + +} // namespace + +Object InitThreadSafeFunctionCtx(Env env) { + return TSFNWrap::Init(env, Object::New(env)); +} + +#endif diff --git a/test/threadsafe_function/threadsafe_function_ctx.js b/test/threadsafe_function/threadsafe_function_ctx.js new file mode 100644 index 000000000..d091bbbdd --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_ctx.js @@ -0,0 +1,16 @@ +'use strict'; + +const assert = require('assert'); +const buildType = process.config.target_defaults.default_configuration; + +module.exports = Promise.all[ + test(require(`../build/${buildType}/binding.node`)), + test(require(`../build/${buildType}/binding_noexcept.node`)) +]; + +async function test(binding) { + const ctx = { }; + const tsfn = new binding.threadsafe_function_ctx.TSFNWrap(ctx); + assert(tsfn.getContext() === ctx); + await tsfn.release(); +} From f677794b31876c7b235e69b3a636b59310b2d5c0 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Thu, 21 Nov 2019 13:45:32 +0100 Subject: [PATCH 140/696] Prepare release 2.0.0 --- CHANGELOG.md | 63 +++++++++++++ README.md | 2 +- package.json | 246 +++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 265 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e68334eb2..8a2a6d17d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,68 @@ # node-addon-api Changelog +## 2019-11-21 Version 2.0.0, @NickNaso + +### Notable changes: + +#### API + +- Added `Napi::AsyncProgressWorker` api. +- Added error checking on `Napi::ThreadSafeFunction::GetContext`. +- Added copy constructor to `Napi::ThreadSafeFunction`. +- Added `Napi::ThreadSafeFunction::Ref` and `Napi::ThreadSafeFunction::Unref` to `Napi::ThreadSafeFunction`. +- Added `Napi::Object::AddFinalizer` method. +- Use `napi_add_finalizer()` to attach data when building against N-API 5. +- Added `Napi::Date` api. +- Added `Napi::ObjectWrap::Finalize` method. + +#### Documentation + +- Added documentation for `Napi::AsyncProgressWorker`. +- Improve `Napi::AsyncWorker` documentation. +- Added documentation for `Napi::Object::AddFinalizer` method. +- Improved documentation for `Napi::ThreadSafeFunction`. +- Improved documentation about the usage of CMake as build tool. +- Some minor corrections all over the documentation. + +#### TEST + +- Added test cases for `Napi::AsyncProgressWorker` api. +- Added test cases for `Napi::Date` api. +- Added test cases for new features added to `Napi::ThreadSafeFunction`. + +### Commmits + +* [[`c881168d49`](https://github.com/nodejs/node-addon-api/commit/c881168d49)] - **tsfn**: add error checking on GetContext (#583) (Kevin Eady) [#583](https://github.com/nodejs/node-addon-api/pull/583) +* [[`24d75dd82f`](https://github.com/nodejs/node-addon-api/commit/24d75dd82f)] - Merge pull request #588 from NickNaso/add-asyncprogress-worker-readme (Nicola Del Gobbo) +* [[`aa79e37b62`](https://github.com/nodejs/node-addon-api/commit/aa79e37b62)] - Merge pull request #587 from timrach/patch-1 (Nicola Del Gobbo) +* [[`df75e08c2b`](https://github.com/nodejs/node-addon-api/commit/df75e08c2b)] - **tsfn**: support direct calls to underlying napi\_tsfn (Kevin Eady) [#58](https://github.com/nodejs/node-addon-api/pull/58) +* [[`2298dfae58`](https://github.com/nodejs/node-addon-api/commit/2298dfae58)] - **doc**: Added AsyncProgressWorker to readme (NickNaso) +* [[`b3609d33b6`](https://github.com/nodejs/node-addon-api/commit/b3609d33b6)] - Fix return type and declaration of setter callback (Tim Rach) +* [[`295e560f55`](https://github.com/nodejs/node-addon-api/commit/295e560f55)] - **test**: improve guards for experimental features (legendecas) +* [[`2e71842f63`](https://github.com/nodejs/node-addon-api/commit/2e71842f63)] - **tsfn**: Implement copy constructor (Kevin Eady) [#546](https://github.com/nodejs/node-addon-api/pull/546) +* [[`650562cab9`](https://github.com/nodejs/node-addon-api/commit/650562cab9)] - **src**: implement AsyncProgressWorker (legendecas) [#529](https://github.com/nodejs/node-addon-api/pull/529) +* [[`bdfd14101f`](https://github.com/nodejs/node-addon-api/commit/bdfd14101f)] - **src**: attach data with napi\_add\_finalizer (Gabriel Schulhof) [#577](https://github.com/nodejs/node-addon-api/pull/577) +* [[`9e955a802b`](https://github.com/nodejs/node-addon-api/commit/9e955a802b)] - **doc**: change node.js to Node.js per guideline (#579) (Tobias Nießen) [#579](https://github.com/nodejs/node-addon-api/pull/579) +* [[`b42e21e3a9`](https://github.com/nodejs/node-addon-api/commit/b42e21e3a9)] - **build**: move node/6 to travis allowed failures and add node/13 (#573) (Gabriel Schulhof) +* [[`8d6132f609`](https://github.com/nodejs/node-addon-api/commit/8d6132f609)] - **doc**: improve AsyncWorker docs (#571) (legendecas) [#571](https://github.com/nodejs/node-addon-api/pull/571) +* [[`bc8fc23627`](https://github.com/nodejs/node-addon-api/commit/bc8fc23627)] - **test**: do not run TSFN tests on NAPI\_VERSION \< 4 (legendecas) [#576](https://github.com/nodejs/node-addon-api/pull/576) +* [[`bcc1d58fc4`](https://github.com/nodejs/node-addon-api/commit/bcc1d58fc4)] - implement Object::AddFinalizer (Gabriel Schulhof) +* [[`e9a4bcd52a`](https://github.com/nodejs/node-addon-api/commit/e9a4bcd52a)] - **doc**: updates Make.js doc to current best practices (Jim Schlight) [#558](https://github.com/nodejs/node-addon-api/pull/558) +* [[`b513d1aa7a`](https://github.com/nodejs/node-addon-api/commit/b513d1aa7a)] - **doc**: fix return type of ArrayBuffer::Data (Tobias Nießen) [#552](https://github.com/nodejs/node-addon-api/pull/552) +* [[`34c11cf0a4`](https://github.com/nodejs/node-addon-api/commit/34c11cf0a4)] - **src**: disallow copying, double close of scopes (legendecas) [#566](https://github.com/nodejs/node-addon-api/pull/566) +* [[`ce139a05e8`](https://github.com/nodejs/node-addon-api/commit/ce139a05e8)] - **src**: make failure of closing scopes fatal (legendecas) [#566](https://github.com/nodejs/node-addon-api/pull/566) +* [[`740c79823e`](https://github.com/nodejs/node-addon-api/commit/740c79823e)] - **src**: add Env() to AsyncContext (Rolf Timmermans) [#568](https://github.com/nodejs/node-addon-api/pull/568) +* [[`ea9ce1c801`](https://github.com/nodejs/node-addon-api/commit/ea9ce1c801)] - **tsfn**: add wrappers for Ref and Unref (Kevin Eady) [#561](https://github.com/nodejs/node-addon-api/pull/561) +* [[`2e1769e1a3`](https://github.com/nodejs/node-addon-api/commit/2e1769e1a3)] - **error**: remove unnecessary if condition (legendecas) [#562](https://github.com/nodejs/node-addon-api/pull/562) +* [[`828f223a87`](https://github.com/nodejs/node-addon-api/commit/828f223a87)] - **doc**: fix spelling in ObjectWrap doc (#563) (Tobias Nießen) [#563](https://github.com/nodejs/node-addon-api/pull/563) +* [[`dd9fa8a4a8`](https://github.com/nodejs/node-addon-api/commit/dd9fa8a4a8)] - **doc**: move Arunesh and Taylor to Emeritus (#540) (Michael Dawson) [#540](https://github.com/nodejs/node-addon-api/pull/540) +* [[`cf8b8415df`](https://github.com/nodejs/node-addon-api/commit/cf8b8415df)] - **doc**: add Kevin to the list of collaborators (#539) (Michael Dawson) [#539](https://github.com/nodejs/node-addon-api/pull/539) +* [[`5d6aeae7b5`](https://github.com/nodejs/node-addon-api/commit/5d6aeae7b5)] - **build**: enable travis for fast PR check (legendecas) +* [[`6192e705cd`](https://github.com/nodejs/node-addon-api/commit/6192e705cd)] - **src**: add napi\_date (Mathias Küsel) [#497](https://github.com/nodejs/node-addon-api/pull/497) +* [[`7b1ee96d52`](https://github.com/nodejs/node-addon-api/commit/7b1ee96d52)] - **doc**: update prebuild\_tools.md (Nurbol Alpysbayev) [#527](https://github.com/nodejs/node-addon-api/pull/527) +* [[`0b4f3a5b8c`](https://github.com/nodejs/node-addon-api/commit/0b4f3a5b8c)] - **tsfn**: fix crash on releasing tsfn (legendecas) [#532](https://github.com/nodejs/node-addon-api/pull/532) +* [[`c3c8814d2f`](https://github.com/nodejs/node-addon-api/commit/c3c8814d2f)] - implement virutal ObjectWrap::Finalize (Michael Price) [#515](https://github.com/nodejs/node-addon-api/pull/515) + ## 2019-07-23 Version 1.7.1, @NickNaso ### Notable changes: diff --git a/README.md b/README.md index 701913221..2558c7edc 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 1.7.1** +## **Current version: 2.0.0** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index 96ec6dd43..3074aae08 100644 --- a/package.json +++ b/package.json @@ -3,50 +3,206 @@ "url": "https://github.com/nodejs/node-addon-api/issues" }, "contributors": [ - "Abhishek Kumar Singh (https://github.com/abhi11210646)", - "Alba Mendez (https://github.com/jmendeth)", - "Andrew Petersen (https://github.com/kirbysayshi)", - "Anisha Rohra (https://github.com/anisha-rohra)", - "Anna Henningsen (https://github.com/addaleax)", - "Arnaud Botella (https://github.com/BotellaA)", - "Arunesh Chandra (https://github.com/aruneshchandra)", - "Ben Berman (https://github.com/rivertam)", - "Benjamin Byholm (https://github.com/kkoopa)", - "Bill Gallafent (https://github.com/gallafent)", - "Bruce A. MacNaughton (https://github.com/bmacnaughton)", - "Cory Mickelson (https://github.com/corymickelson)", - "David Halls (https://github.com/davedoesdev)", - "Dongjin Na (https://github.com/nadongguri)", - "Eric Bickle (https://github.com/ebickle)", - "Gabriel Schulhof (https://github.com/gabrielschulhof)", - "Gus Caplan (https://github.com/devsnek)", - "Hitesh Kanwathirtha (https://github.com/digitalinfinity)", - "Jake Barnes (https://github.com/DuBistKomisch)", - "Jake Yoon (https://github.com/yjaeseok)", - "Jason Ginchereau (https://github.com/jasongin)", - "Jim Schlight (https://github.com/jschlight)", - "Jinho Bang (https://github.com/romandev)", - "joshgarde (https://github.com/joshgarde)", - "Kevin Eady (https://github.com/KevinEady)", - "Konstantin Tarkus (https://github.com/koistya)", - "Kyle Farnung (https://github.com/kfarnung)", - "Luciano Martorella (https://github.com/lmartorella)", - "Matteo Collina (https://github.com/mcollina)", - "Michael Dawson (https://github.com/mhdawson)", - "Michele Campus (https://github.com/kYroL01)", - "Mikhail Cheshkov (https://github.com/mcheshkov)", - "Nicola Del Gobbo (https://github.com/NickNaso)", - "Nick Soggin (https://github.com/iSkore)", - "Philipp Renoth (https://github.com/DaAitch)", - "Rolf Timmermans (https://github.com/rolftimmermans)", - "Ross Weir (https://github.com/ross-weir)", - "Ryuichi Okumura (https://github.com/okuryu)", - "Sampson Gao (https://github.com/sampsongao)", - "Sam Roberts (https://github.com/sam-github)", - "Taylor Woll (https://github.com/boingoing)", - "Thomas Gentilhomme (https://github.com/fraxken)", - "Tux3 (https://github.com/tux3)", - "Yohei Kishimoto (https://github.com/morokosi)" + { + "name": "Abhishek Kumar Singh", + "url": "https://github.com/abhi11210646" + }, + { + "name": "Alba Mendez", + "url": "https://github.com/jmendeth" + }, + { + "name": "Andrew Petersen", + "url": "https://github.com/kirbysayshi" + }, + { + "name": "Anisha Rohra", + "url": "https://github.com/anisha-rohra" + }, + { + "name": "Anna Henningsen", + "url": "https://github.com/addaleax" + }, + { + "name": "Arnaud Botella", + "url": "https://github.com/BotellaA" + }, + { + "name": "Arunesh Chandra", + "url": "https://github.com/aruneshchandra" + }, + { + "name": "Ben Berman", + "url": "https://github.com/rivertam" + }, + { + "name": "Benjamin Byholm", + "url": "https://github.com/kkoopa" + }, + { + "name": "Bill Gallafent", + "url": "https://github.com/gallafent" + }, + { + "name": "Bruce A. MacNaughton", + "url": "https://github.com/bmacnaughton" + }, + { + "name": "Cory Mickelson", + "url": "https://github.com/corymickelson" + }, + { + "name": "David Halls", + "url": "https://github.com/davedoesdev" + }, + { + "name": "Dongjin Na", + "url": "https://github.com/nadongguri" + }, + { + "name": "Eric Bickle", + "url": "https://github.com/ebickle" + }, + { + "name": "Gabriel Schulhof", + "url": "https://github.com/gabrielschulhof" + }, + { + "name": "Gus Caplan", + "url": "https://github.com/devsnek" + }, + { + "name": "Hitesh Kanwathirtha", + "url": "https://github.com/digitalinfinity" + }, + { + "name": "Jake Barnes", + "url": "https://github.com/DuBistKomisch" + }, + { + "name": "Jake Yoon", + "url": "https://github.com/yjaeseok" + }, + { + "name": "Jason Ginchereau", + "url": "https://github.com/jasongin" + }, + { + "name": "Jim Schlight", + "url": "https://github.com/jschlight" + }, + { + "name": "Jinho Bang", + "url": "https://github.com/romandev" + }, + { + "name": "joshgarde", + "url": "https://github.com/joshgarde" + }, + { + "name": "Kevin Eady", + "url": "https://github.com/KevinEady" + }, + { + "name": "Konstantin Tarkus", + "url": "https://github.com/koistya" + }, + { + "name": "Kyle Farnung", + "url": "https://github.com/kfarnung" + }, + { + "name": "legendecas", + "url": "https://github.com/legendecas" + }, + { + "name": "Luciano Martorella", + "url": "https://github.com/lmartorella" + }, + { + "name": "Mathias Küsel", + "url": "https://github.com/mathiask88" + }, + { + "name": "Matteo Collina", + "url": "https://github.com/mcollina" + }, + { + "name": "Michael Dawson", + "url": "https://github.com/mhdawson" + }, + { + "name": "Michael Price", + "url": "https://github.com/mikepricedev" + }, + { + "name": "Michele Campus", + "url": "https://github.com/kYroL01" + }, + { + "name": "Mikhail Cheshkov", + "url": "https://github.com/mcheshkov" + }, + { + "name": "Nicola Del Gobbo", + "url": "https://github.com/NickNaso" + }, + { + "name": "Nick Soggin", + "url": "https://github.com/iSkore" + }, + { + "name": "Nurbol Alpysbayev", + "url": "https://github.com/anurbol" + }, + { + "name": "Philipp Renoth", + "url": "https://github.com/DaAitch" + }, + { + "name": "Rolf Timmermans", + "url": "https://github.com/rolftimmermans" + }, + { + "name": "Ross Weir", + "url": "https://github.com/ross-weir" + }, + { + "name": "Ryuichi Okumura", + "url": "https://github.com/okuryu" + }, + { + "name": "Sampson Gao", + "url": "https://github.com/sampsongao" + }, + { + "name": "Sam Roberts", + "url": "https://github.com/sam-github" + }, + { + "name": "Taylor Woll", + "url": "https://github.com/boingoing" + }, + { + "name": "Thomas Gentilhomme", + "url": "https://github.com/fraxken" + }, + { + "name": "Tim Rach", + "url": "https://github.com/timrach" + }, + { + "name": "Tobias Nießen", + "url": "https://github.com/tniessen" + }, + { + "name": "Tux3", + "url": "https://github.com/tux3" + }, + { + "name": "Yohei Kishimoto", + "url": "https://github.com/morokosi" + } ], "dependencies": {}, "description": "Node.js API (N-API)", @@ -84,5 +240,5 @@ "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile" }, - "version": "1.7.1" + "version": "2.0.0" } From 734725e971146cb88dc75342d05c8d5a161ee41f Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Tue, 26 Nov 2019 10:49:47 +0100 Subject: [PATCH 141/696] Correctly define copy assignment operators. --- napi-inl.h | 2 +- napi.h | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index f72e1daea..91c2e8945 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2120,7 +2120,7 @@ inline Error& Error::operator =(Error&& other) { inline Error::Error(const Error& other) : ObjectReference(other) { } -inline Error& Error::operator =(Error& other) { +inline Error& Error::operator =(const Error& other) { Reset(); _env = other.Env(); diff --git a/napi.h b/napi.h index 2fc9b10f3..fe1cd534d 100644 --- a/napi.h +++ b/napi.h @@ -1108,7 +1108,7 @@ namespace Napi { // A reference can be moved but cannot be copied. Reference(Reference&& other); Reference& operator =(Reference&& other); - Reference& operator =(Reference&) = delete; + Reference& operator =(const Reference&) = delete; operator napi_ref() const; bool operator ==(const Reference &other) const; @@ -1153,7 +1153,7 @@ namespace Napi { ObjectReference& operator =(Reference&& other); ObjectReference(ObjectReference&& other); ObjectReference& operator =(ObjectReference&& other); - ObjectReference& operator =(ObjectReference&) = delete; + ObjectReference& operator =(const ObjectReference&) = delete; Napi::Value Get(const char* utf8name) const; Napi::Value Get(const std::string& utf8name) const; @@ -1191,7 +1191,7 @@ namespace Napi { FunctionReference(FunctionReference&& other); FunctionReference& operator =(FunctionReference&& other); FunctionReference(const FunctionReference&) = delete; - FunctionReference& operator =(FunctionReference&) = delete; + FunctionReference& operator =(const FunctionReference&) = delete; Napi::Value operator ()(const std::initializer_list& args) const; @@ -1333,7 +1333,7 @@ namespace Napi { Error(Error&& other); Error& operator =(Error&& other); Error(const Error&); - Error& operator =(Error&); + Error& operator =(const Error&); const std::string& Message() const NAPI_NOEXCEPT; void ThrowAsJavaScriptException() const; @@ -1806,7 +1806,7 @@ namespace Napi { AsyncContext(AsyncContext&& other); AsyncContext& operator =(AsyncContext&& other); AsyncContext(const AsyncContext&) = delete; - AsyncContext& operator =(AsyncContext&) = delete; + AsyncContext& operator =(const AsyncContext&) = delete; operator napi_async_context() const; @@ -1825,7 +1825,7 @@ namespace Napi { AsyncWorker(AsyncWorker&& other); AsyncWorker& operator =(AsyncWorker&& other); AsyncWorker(const AsyncWorker&) = delete; - AsyncWorker& operator =(AsyncWorker&) = delete; + AsyncWorker& operator =(const AsyncWorker&) = delete; operator napi_async_work() const; From cfa71b60f737adfb272e9c46f5cdc220ba2041a2 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 27 Nov 2019 11:53:46 -0800 Subject: [PATCH 142/696] object: add templated property descriptors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add static methods to `PropertyDescriptor` that allows the definition of accessors where the getter/setter is specified as a template parameter rather than a function parameter. This allows us to avoid heap-allocating callback data. PR-URL: https://github.com/nodejs/node-addon-api/pull/610 Reviewed-By: NickNaso Reviewed-By: Tobias Nießen Reviewed-By: Chengzhong Wu --- doc/property_descriptor.md | 73 ++++++++++++++++++++++---- napi-inl.h | 102 +++++++++++++++++++++++++++++++++++++ napi.h | 37 ++++++++++++++ test/object/object.cc | 47 +++++++++++++++++ test/object/object.js | 26 ++++++++++ 5 files changed, 276 insertions(+), 9 deletions(-) diff --git a/doc/property_descriptor.md b/doc/property_descriptor.md index 324b62f74..d826097ad 100644 --- a/doc/property_descriptor.md +++ b/doc/property_descriptor.md @@ -26,15 +26,9 @@ Void Init(Env env) { Object obj = Object::New(env); // Accessor - PropertyDescriptor pd1 = PropertyDescriptor::Accessor(env, - obj, - "pd1", - TestGetter); - PropertyDescriptor pd2 = PropertyDescriptor::Accessor(env, - obj, - "pd2", - TestGetter, - TestSetter); + PropertyDescriptor pd1 = PropertyDescriptor::Accessor("pd1"); + PropertyDescriptor pd2 = + PropertyDescriptor::Accessor("pd2"); // Function PropertyDescriptor pd3 = PropertyDescriptor::Function(env, "function", @@ -51,6 +45,26 @@ Void Init(Env env) { } ``` +## Types + +### PropertyDescriptor::GetterCallback + +```cpp +typedef Napi::Value (*GetterCallback)(const Napi::CallbackInfo& info); +``` + +This is the signature of a getter function to be passed as a template parameter +to `PropertyDescriptor::Accessor`. + +### PropertyDescriptor::SetterCallback + +```cpp +typedef void (*SetterCallback)(const Napi::CallbackInfo& info); +``` + +This is the signature of a setter function to be passed as a template parameter +to `PropertyDescriptor::Accessor`. + ## Methods ### Constructor @@ -63,6 +77,47 @@ Napi::PropertyDescriptor::PropertyDescriptor (napi_property_descriptor desc); ### Accessor +```cpp +template +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +* `[template] Getter`: A getter function. +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a PropertyDescriptor that contains a read-only property. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `napi_value value` +- `Napi::Name` + +```cpp +template < +Napi::PropertyDescriptor::GetterCallback Getter, +Napi::PropertyDescriptor::SetterCallback Setter> +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +* `[template] Getter`: A getter function. +* `[template] Setter`: A setter function. +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a PropertyDescriptor that contains a read-write property. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `napi_value value` +- `Napi::Name` + ```cpp static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, Getter getter, diff --git a/napi-inl.h b/napi-inl.h index 91c2e8945..a8577d6ae 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2732,6 +2732,108 @@ inline void CallbackInfo::SetData(void* data) { // PropertyDescriptor class //////////////////////////////////////////////////////////////////////////////// +template +PropertyDescriptor +PropertyDescriptor::Accessor(const char* utf8name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.utf8name = utf8name; + desc.getter = &GetterCallbackWrapper; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +PropertyDescriptor +PropertyDescriptor::Accessor(const std::string& utf8name, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), attributes, data); +} + +template +PropertyDescriptor +PropertyDescriptor::Accessor(Name name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.name = name; + desc.getter = &GetterCallbackWrapper; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template < +typename PropertyDescriptor::GetterCallback Getter, +typename PropertyDescriptor::SetterCallback Setter> +PropertyDescriptor +PropertyDescriptor::Accessor(const char* utf8name, + napi_property_attributes attributes, + void* data) { + + napi_property_descriptor desc = napi_property_descriptor(); + + desc.utf8name = utf8name; + desc.getter = &GetterCallbackWrapper; + desc.setter = &SetterCallbackWrapper; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template < +typename PropertyDescriptor::GetterCallback Getter, +typename PropertyDescriptor::SetterCallback Setter> +PropertyDescriptor +PropertyDescriptor::Accessor(const std::string& utf8name, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), attributes, data); +} + +template < +typename PropertyDescriptor::GetterCallback Getter, +typename PropertyDescriptor::SetterCallback Setter> +PropertyDescriptor +PropertyDescriptor::Accessor(Name name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.name = name; + desc.getter = &GetterCallbackWrapper; + desc.setter = &SetterCallbackWrapper; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +napi_value +PropertyDescriptor::GetterCallbackWrapper(napi_env env, + napi_callback_info info) { + CallbackInfo cbInfo(env, info); + return Getter(cbInfo); +} + +template +napi_value +PropertyDescriptor::SetterCallbackWrapper(napi_env env, + napi_callback_info info) { + CallbackInfo cbInfo(env, info); + Setter(cbInfo); + return nullptr; +} + template inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, diff --git a/napi.h b/napi.h index fe1cd534d..6e17c7251 100644 --- a/napi.h +++ b/napi.h @@ -1407,6 +1407,9 @@ namespace Napi { class PropertyDescriptor { public: + typedef Napi::Value (*GetterCallback)(const Napi::CallbackInfo& info); + typedef void (*SetterCallback)(const Napi::CallbackInfo& info); + #ifndef NODE_ADDON_API_DISABLE_DEPRECATED template static PropertyDescriptor Accessor(const char* utf8name, @@ -1474,6 +1477,36 @@ namespace Napi { void* data = nullptr); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED + template + static PropertyDescriptor Accessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor(const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor(Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor(const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor(Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template static PropertyDescriptor Accessor(Napi::Env env, Napi::Object object, @@ -1559,6 +1592,10 @@ namespace Napi { operator const napi_property_descriptor&() const; private: + template + static napi_value GetterCallbackWrapper(napi_env env, napi_callback_info info); + template + static napi_value SetterCallbackWrapper(napi_env env, napi_callback_info info); napi_property_descriptor _desc; }; diff --git a/test/object/object.cc b/test/object/object.cc index 32f2c237a..1c9ce59f9 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -85,6 +85,20 @@ void DefineProperties(const CallbackInfo& info) { PropertyDescriptor::Accessor(env, obj, "readwriteAccessor", TestGetter, TestSetter), PropertyDescriptor::Accessor(env, obj, "readonlyAccessorWithUserData", TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), PropertyDescriptor::Accessor(env, obj, "readwriteAccessorWithUserData", TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + + PropertyDescriptor::Accessor("readonlyAccessorT"), + PropertyDescriptor::Accessor( + "readwriteAccessorT"), + PropertyDescriptor::Accessor( + "readonlyAccessorWithUserDataT", + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor< + TestGetterWithUserData, + TestSetterWithUserData>("readwriteAccessorWithUserDataT", + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Value("readonlyValue", trueValue), PropertyDescriptor::Value("readwriteValue", trueValue, napi_writable), PropertyDescriptor::Value("enumerableValue", trueValue, napi_enumerable), @@ -100,6 +114,12 @@ void DefineProperties(const CallbackInfo& info) { std::string str2("readwriteAccessor"); std::string str1a("readonlyAccessorWithUserData"); std::string str2a("readwriteAccessorWithUserData"); + + std::string str1t("readonlyAccessorT"); + std::string str2t("readwriteAccessorT"); + std::string str1at("readonlyAccessorWithUserDataT"); + std::string str2at("readwriteAccessorWithUserDataT"); + std::string str3("readonlyValue"); std::string str4("readwriteValue"); std::string str5("enumerableValue"); @@ -111,6 +131,18 @@ void DefineProperties(const CallbackInfo& info) { PropertyDescriptor::Accessor(env, obj, str2, TestGetter, TestSetter), PropertyDescriptor::Accessor(env, obj, str1a, TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), PropertyDescriptor::Accessor(env, obj, str2a, TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + + PropertyDescriptor::Accessor(str1t), + PropertyDescriptor::Accessor(str2t), + PropertyDescriptor::Accessor(str1at, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor< + TestGetterWithUserData, + TestSetterWithUserData>(str2at, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Value(str3, trueValue), PropertyDescriptor::Value(str4, trueValue, napi_writable), PropertyDescriptor::Value(str5, trueValue, napi_enumerable), @@ -127,6 +159,21 @@ void DefineProperties(const CallbackInfo& info) { Napi::String::New(env, "readonlyAccessorWithUserData"), TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), PropertyDescriptor::Accessor(env, obj, Napi::String::New(env, "readwriteAccessorWithUserData"), TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + + PropertyDescriptor::Accessor( + Napi::String::New(env, "readonlyAccessorT")), + PropertyDescriptor::Accessor( + Napi::String::New(env, "readwriteAccessorT")), + PropertyDescriptor::Accessor( + Napi::String::New(env, "readonlyAccessorWithUserDataT"), + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor< + TestGetterWithUserData, TestSetterWithUserData>( + Napi::String::New(env, "readwriteAccessorWithUserDataT"), + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Value( Napi::String::New(env, "readonlyValue"), trueValue), PropertyDescriptor::Value( diff --git a/test/object/object.js b/test/object/object.js index b4fe57dff..2660e4b6e 100644 --- a/test/object/object.js +++ b/test/object/object.js @@ -22,6 +22,7 @@ function test(binding) { const obj = {}; binding.object.defineProperties(obj, nameType); + // accessors assertPropertyIsNot(obj, 'readonlyAccessor', 'enumerable'); assertPropertyIsNot(obj, 'readonlyAccessor', 'configurable'); assert.strictEqual(obj.readonlyAccessor, true); @@ -44,6 +45,30 @@ function test(binding) { obj.readwriteAccessorWithUserData = -14; assert.strictEqual(obj.readwriteAccessorWithUserData, -14); + // templated accessors + assertPropertyIsNot(obj, 'readonlyAccessorT', 'enumerable'); + assertPropertyIsNot(obj, 'readonlyAccessorT', 'configurable'); + assert.strictEqual(obj.readonlyAccessorT, true); + + assertPropertyIsNot(obj, 'readonlyAccessorWithUserDataT', 'enumerable'); + assertPropertyIsNot(obj, 'readonlyAccessorWithUserDataT', 'configurable'); + assert.strictEqual(obj.readonlyAccessorWithUserDataT, -14, nameType); + + assertPropertyIsNot(obj, 'readwriteAccessorT', 'enumerable'); + assertPropertyIsNot(obj, 'readwriteAccessorT', 'configurable'); + obj.readwriteAccessorT = false; + assert.strictEqual(obj.readwriteAccessorT, false); + obj.readwriteAccessorT = true; + assert.strictEqual(obj.readwriteAccessorT, true); + + assertPropertyIsNot(obj, 'readwriteAccessorWithUserDataT', 'enumerable'); + assertPropertyIsNot(obj, 'readwriteAccessorWithUserDataT', 'configurable'); + obj.readwriteAccessorWithUserDataT = 2; + assert.strictEqual(obj.readwriteAccessorWithUserDataT, 2); + obj.readwriteAccessorWithUserDataT = -14; + assert.strictEqual(obj.readwriteAccessorWithUserDataT, -14); + + // values assertPropertyIsNot(obj, 'readonlyValue', 'writable'); assertPropertyIsNot(obj, 'readonlyValue', 'enumerable'); assertPropertyIsNot(obj, 'readonlyValue', 'configurable'); @@ -65,6 +90,7 @@ function test(binding) { assertPropertyIsNot(obj, 'configurableValue', 'enumerable'); assertPropertyIs(obj, 'configurableValue', 'configurable'); + // functions assertPropertyIsNot(obj, 'function', 'writable'); assertPropertyIsNot(obj, 'function', 'enumerable'); assertPropertyIsNot(obj, 'function', 'configurable'); From ce91e14860ef6ee5c733e2a2e90263e30ac5038e Mon Sep 17 00:00:00 2001 From: Dmitry Ashkadov Date: Fri, 22 Nov 2019 23:46:24 +0300 Subject: [PATCH 143/696] objectwrap: add template methods ObjectWrap was enhanced to support template methods for defining properties and methods of JS class. Now C++ methods and functions may be passed as template parameters for ObjectWrap::InstanceMethod, ObjectWrap::StaticAccessor, etc. There are several benefits: - no need to allocate extra memory for passing C++ function napi callback and use add_finalizer() to free memory; - a compiler can see whole chain of calls up to napi callback that may allow better optimisation. Some examples: ```cpp // Method InstanceMethod<&MyClass::method>("method"); // Read-write property InstanceAccessor<&MyClass::get, &MyClass::set>("rw_prop"); // Read-only property InstanceAccessor<&MyClass::get>("ro_prop"); ``` Fixes: https://github.com/nodejs/node-addon-api/issues/602 PR-URL: https://github.com/nodejs/node-addon-api/pull/604 Reviewed-By: Chengzhong Wu Reviewed-By: Chengzhong Wu --- doc/class_property_descriptor.md | 6 +- doc/object_wrap.md | 280 ++++++++++++++++++++++++++++++- napi-inl.h | 235 ++++++++++++++++++++++++++ napi.h | 93 +++++++++- test/objectwrap.cc | 46 +++++ test/objectwrap.js | 37 ++++ tools/README.md | 8 +- 7 files changed, 694 insertions(+), 11 deletions(-) diff --git a/doc/class_property_descriptor.md b/doc/class_property_descriptor.md index bb492de7d..92336e7ea 100644 --- a/doc/class_property_descriptor.md +++ b/doc/class_property_descriptor.md @@ -26,9 +26,9 @@ class Example : public Napi::ObjectWrap { Napi::Object Example::Init(Napi::Env env, Napi::Object exports) { Napi::Function func = DefineClass(env, "Example", { // Register a class instance accessor with getter and setter functions. - InstanceAccessor("value", &Example::GetValue, &Example::SetValue), - // We can also register a readonly accessor by passing nullptr as the setter. - InstanceAccessor("readOnlyProp", &Example::GetValue, nullptr) + InstanceAccessor<&Example::GetValue, &Example::SetValue>("value"), + // We can also register a readonly accessor by omitting the setter. + InstanceAccessor<&Example::GetValue>("readOnlyProp") }); constructor = Napi::Persistent(func); diff --git a/doc/object_wrap.md b/doc/object_wrap.md index 1db658a91..5a0ec1036 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -34,8 +34,8 @@ class Example : public Napi::ObjectWrap { Napi::Object Example::Init(Napi::Env env, Napi::Object exports) { // This method is used to hook the accessor and method callbacks Napi::Function func = DefineClass(env, "Example", { - InstanceMethod("GetValue", &Example::GetValue), - InstanceMethod("SetValue", &Example::SetValue) + InstanceMethod<&Example::GetValue>("GetValue"), + InstanceMethod<&Example::SetValue>("SetValue") }); // Create a peristent reference to the class constructor. This will allow @@ -289,6 +289,93 @@ One or more of `napi_property_attributes`. Returns `Napi::PropertyDescriptor` object that represents a static method of a JavaScript class. +### StaticMethod + +Creates property descriptor that represents a static method of a JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a static method of a +JavaScript class. This function returns nothing. +- `[in] utf8name`: Null-terminated string that represents the name of a static +method for the class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents the static method of a +JavaScript class. + +### StaticMethod + +Creates property descriptor that represents a static method of a JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a static method of a +JavaScript class. +- `[in] utf8name`: Null-terminated string that represents the name of a static +method for the class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents a static method of a +JavaScript class. + +### StaticMethod + +Creates property descriptor that represents a static method of a JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a static method of a +JavaScript class. +- `[in] name`: Napi:Symbol that represents the name of a static +method for the class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents the static method of a +JavaScript class. + +### StaticMethod + +Creates property descriptor that represents a static method of a JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a static method of a +JavaScript class. +- `[in] name`: Napi:Symbol that represents the name of a static. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents a static method of a +JavaScript class. + ### StaticAccessor Creates property descriptor that represents a static accessor property of a @@ -342,6 +429,57 @@ is invoked. Returns `Napi::PropertyDescriptor` object that represents a static accessor property of a JavaScript class. +### StaticAccessor + +Creates property descriptor that represents a static accessor property of a +JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticAccessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] utf8name`: Null-terminated string that represents the name of a static +accessor property for the class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into getter or setter when +is invoked. + +Returns `Napi::PropertyDescriptor` object that represents a static accessor +property of a JavaScript class. + +### StaticAccessor + +Creates property descriptor that represents a static accessor property of a +JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticAccessor(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] name`: Napi:Symbol that represents the name of a static accessor. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into getter or setter when +is invoked. + +Returns `Napi::PropertyDescriptor` object that represents a static accessor +property of a JavaScript class. + ### InstanceMethod Creates property descriptor that represents an instance method of a JavaScript class. @@ -430,6 +568,94 @@ One or more of `napi_property_attributes`. Returns `Napi::PropertyDescriptor` object that represents an instance method of a JavaScript class. +### InstanceMethod + +Creates property descriptor that represents an instance method of a JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents an instance method of a +JavaScript class. +- `[in] utf8name`: Null-terminated string that represents the name of an instance +method for the class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance method of a +JavaScript class. + +### InstanceMethod + +Creates property descriptor that represents an instance method of a JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents an instance method of a +JavaScript class. +- `[in] utf8name`: Null-terminated string that represents the name of an instance +method for the class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance method of a +JavaScript class. + +### InstanceMethod + +Creates property descriptor that represents an instance method of a JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceMethod(Napi::Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents an instance method of a +JavaScript class. +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance method for the class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance method of a +JavaScript class. + +### InstanceMethod + +Creates property descriptor that represents an instance method of a JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceMethod(Napi::Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents an instance method of a +JavaScript class. +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance method for the class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance method of a +JavaScript class. + ### InstanceAccessor Creates property descriptor that represents an instance accessor property of a @@ -482,6 +708,56 @@ One or more of `napi_property_attributes`. Returns `Napi::PropertyDescriptor` object that represents an instance accessor property of a JavaScript class. +### InstanceAccessor + +Creates property descriptor that represents an instance accessor property of a +JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceAccessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] utf8name`: Null-terminated string that represents the name of an instance +accessor property for the class. +- `[in] attributes`: The attributes associated with the particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into getter or setter when this is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance accessor +property of a JavaScript class. + +### InstanceAccessor + +Creates property descriptor that represents an instance accessor property of a +JavaScript class. + +```cpp +template +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceAccessor(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance accessor. +- `[in] attributes`: The attributes associated with the particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into getter or setter when this is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance accessor +property of a JavaScript class. + ### StaticValue Creates property descriptor that represents an static value property of a diff --git a/napi-inl.h b/napi-inl.h index a8577d6ae..e8196a4d5 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3268,6 +3268,62 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( return desc; } +template +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = &ObjectWrap::WrappedMethod; + desc.data = data; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = &ObjectWrap::WrappedMethod; + desc.data = data; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = &ObjectWrap::WrappedMethod; + desc.data = data; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = &ObjectWrap::WrappedMethod; + desc.data = data; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( const char* utf8name, @@ -3306,6 +3362,38 @@ inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( return desc; } +template +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + const char* utf8name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.data = data; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + Symbol name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.data = data; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( const char* utf8name, @@ -3372,6 +3460,62 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( return desc; } +template +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( + const char* utf8name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = &ObjectWrap::WrappedMethod; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( + const char* utf8name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = &ObjectWrap::WrappedMethod; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( + Symbol name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = &ObjectWrap::WrappedMethod; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( + Symbol name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = &ObjectWrap::WrappedMethod; + desc.data = data; + desc.attributes = attributes; + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( const char* utf8name, @@ -3410,6 +3554,38 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( return desc; } +template +template ::InstanceGetterCallback getter, + typename ObjectWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( + const char* utf8name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = This::WrapGetter(This::GetterTag()); + desc.setter = This::WrapSetter(This::SetterTag()); + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceGetterCallback getter, + typename ObjectWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( + Symbol name, + napi_property_attributes attributes, + void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = This::WrapGetter(This::GetterTag()); + desc.setter = This::WrapSetter(This::SetterTag()); + desc.data = data; + desc.attributes = attributes; + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::StaticValue(const char* utf8name, Napi::Value value, napi_property_attributes attributes) { @@ -3604,6 +3780,65 @@ inline void ObjectWrap::FinalizeCallback(napi_env env, void* data, void* /*hi delete instance; } +template +template ::StaticVoidMethodCallback method> +inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + method(CallbackInfo(env, info)); + return nullptr; + }); +} + +template +template ::StaticMethodCallback method> +inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + return method(CallbackInfo(env, info)); + }); +} + +template +template ::InstanceVoidMethodCallback method> +inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + T* instance = Unwrap(cbInfo.This().As()); + (instance->*method)(cbInfo); + return nullptr; + }); +} + +template +template ::InstanceMethodCallback method> +inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + T* instance = Unwrap(cbInfo.This().As()); + return (instance->*method)(cbInfo); + }); +} + +template +template ::StaticSetterCallback method> +inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + method(cbInfo, cbInfo[0]); + return nullptr; + }); +} + +template +template ::InstanceSetterCallback method> +inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + T* instance = Unwrap(cbInfo.This().As()); + (instance->*method)(cbInfo, cbInfo[0]); + return nullptr; + }); +} + //////////////////////////////////////////////////////////////////////////////// // HandleScope class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 6e17c7251..f770641af 100644 --- a/napi.h +++ b/napi.h @@ -1630,8 +1630,8 @@ namespace Napi { /// public: /// static void Initialize(Napi::Env& env, Napi::Object& target) { /// Napi::Function constructor = DefineClass(env, "Example", { - /// InstanceAccessor("value", &Example::GetSomething, &Example::SetSomething), - /// InstanceMethod("doSomething", &Example::DoSomething), + /// InstanceAccessor<&Example::GetSomething, &Example::SetSomething>("value"), + /// InstanceMethod<&Example::DoSomething>("doSomething"), /// }); /// target.Set("Example", constructor); /// } @@ -1685,6 +1685,22 @@ namespace Napi { StaticMethodCallback method, napi_property_attributes attributes = napi_default, void* data = nullptr); + template + static PropertyDescriptor StaticMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor StaticAccessor(const char* utf8name, StaticGetterCallback getter, StaticSetterCallback setter, @@ -1695,6 +1711,14 @@ namespace Napi { StaticSetterCallback setter, napi_property_attributes attributes = napi_default, void* data = nullptr); + template + static PropertyDescriptor StaticAccessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticAccessor(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor InstanceMethod(const char* utf8name, InstanceVoidMethodCallback method, napi_property_attributes attributes = napi_default, @@ -1711,6 +1735,22 @@ namespace Napi { InstanceMethodCallback method, napi_property_attributes attributes = napi_default, void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor InstanceAccessor(const char* utf8name, InstanceGetterCallback getter, InstanceSetterCallback setter, @@ -1721,6 +1761,14 @@ namespace Napi { InstanceSetterCallback setter, napi_property_attributes attributes = napi_default, void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor StaticValue(const char* utf8name, Napi::Value value, napi_property_attributes attributes = napi_default); @@ -1736,6 +1784,8 @@ namespace Napi { virtual void Finalize(Napi::Env env); private: + using This = ObjectWrap; + static napi_value ConstructorCallbackWrapper(napi_env env, napi_callback_info info); static napi_value StaticVoidMethodCallbackWrapper(napi_env env, napi_callback_info info); static napi_value StaticMethodCallbackWrapper(napi_env env, napi_callback_info info); @@ -1772,6 +1822,45 @@ namespace Napi { StaticAccessorCallbackData; typedef AccessorCallbackData InstanceAccessorCallbackData; + + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + + template struct StaticGetterTag {}; + template struct StaticSetterTag {}; + template struct GetterTag {}; + template struct SetterTag {}; + + template + static napi_callback WrapStaticGetter(StaticGetterTag) noexcept { return &This::WrappedMethod; } + static napi_callback WrapStaticGetter(StaticGetterTag) noexcept { return nullptr; } + + template + static napi_callback WrapStaticSetter(StaticSetterTag) noexcept { return &This::WrappedMethod; } + static napi_callback WrapStaticSetter(StaticSetterTag) noexcept { return nullptr; } + + template + static napi_callback WrapGetter(GetterTag) noexcept { return &This::WrappedMethod; } + static napi_callback WrapGetter(GetterTag) noexcept { return nullptr; } + + template + static napi_callback WrapSetter(SetterTag) noexcept { return &This::WrappedMethod; } + static napi_callback WrapSetter(SetterTag) noexcept { return nullptr; } }; class HandleScope { diff --git a/test/objectwrap.cc b/test/objectwrap.cc index 4a89530e5..0d61171ca 100644 --- a/test/objectwrap.cc +++ b/test/objectwrap.cc @@ -60,25 +60,53 @@ class Test : public Napi::ObjectWrap { return array.Get(Napi::Symbol::WellKnown(info.Env(), "iterator")).As().Call(array, {}); } + void TestVoidMethodT(const Napi::CallbackInfo &info) { + value_ = info[0].ToString(); + } + + Napi::Value TestMethodT(const Napi::CallbackInfo &info) { + return Napi::String::New(info.Env(), value_); + } + + static Napi::Value TestStaticMethodT(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), s_staticMethodText); + } + + static void TestStaticVoidMethodT(const Napi::CallbackInfo& info) { + s_staticMethodText = info[0].ToString(); + } + static void Initialize(Napi::Env env, Napi::Object exports) { Napi::Symbol kTestStaticValueInternal = Napi::Symbol::New(env, "kTestStaticValueInternal"); Napi::Symbol kTestStaticAccessorInternal = Napi::Symbol::New(env, "kTestStaticAccessorInternal"); + Napi::Symbol kTestStaticAccessorTInternal = Napi::Symbol::New(env, "kTestStaticAccessorTInternal"); Napi::Symbol kTestStaticMethodInternal = Napi::Symbol::New(env, "kTestStaticMethodInternal"); + Napi::Symbol kTestStaticMethodTInternal = Napi::Symbol::New(env, "kTestStaticMethodTInternal"); + Napi::Symbol kTestStaticVoidMethodTInternal = Napi::Symbol::New(env, "kTestStaticVoidMethodTInternal"); Napi::Symbol kTestValueInternal = Napi::Symbol::New(env, "kTestValueInternal"); Napi::Symbol kTestAccessorInternal = Napi::Symbol::New(env, "kTestAccessorInternal"); + Napi::Symbol kTestAccessorTInternal = Napi::Symbol::New(env, "kTestAccessorTInternal"); Napi::Symbol kTestMethodInternal = Napi::Symbol::New(env, "kTestMethodInternal"); + Napi::Symbol kTestMethodTInternal = Napi::Symbol::New(env, "kTestMethodTInternal"); + Napi::Symbol kTestVoidMethodTInternal = Napi::Symbol::New(env, "kTestVoidMethodTInternal"); exports.Set("Test", DefineClass(env, "Test", { // expose symbols for testing StaticValue("kTestStaticValueInternal", kTestStaticValueInternal), StaticValue("kTestStaticAccessorInternal", kTestStaticAccessorInternal), + StaticValue("kTestStaticAccessorTInternal", kTestStaticAccessorTInternal), StaticValue("kTestStaticMethodInternal", kTestStaticMethodInternal), + StaticValue("kTestStaticMethodTInternal", kTestStaticMethodTInternal), + StaticValue("kTestStaticVoidMethodTInternal", kTestStaticVoidMethodTInternal), StaticValue("kTestValueInternal", kTestValueInternal), StaticValue("kTestAccessorInternal", kTestAccessorInternal), + StaticValue("kTestAccessorTInternal", kTestAccessorTInternal), StaticValue("kTestMethodInternal", kTestMethodInternal), + StaticValue("kTestMethodTInternal", kTestMethodTInternal), + StaticValue("kTestVoidMethodTInternal", kTestVoidMethodTInternal), // test data StaticValue("testStaticValue", Napi::String::New(env, "value"), napi_enumerable), @@ -88,9 +116,16 @@ class Test : public Napi::ObjectWrap { StaticAccessor("testStaticSetter", nullptr, &StaticSetter, napi_default), StaticAccessor("testStaticGetSet", &StaticGetter, &StaticSetter, napi_enumerable), StaticAccessor(kTestStaticAccessorInternal, &StaticGetter, &StaticSetter, napi_enumerable), + StaticAccessor<&StaticGetter>("testStaticGetterT"), + StaticAccessor<&StaticGetter, &StaticSetter>("testStaticGetSetT"), + StaticAccessor<&StaticGetter, &StaticSetter>(kTestStaticAccessorTInternal), StaticMethod("testStaticMethod", &TestStaticMethod, napi_enumerable), StaticMethod(kTestStaticMethodInternal, &TestStaticMethodInternal, napi_default), + StaticMethod<&TestStaticVoidMethodT>("testStaticVoidMethodT"), + StaticMethod<&TestStaticMethodT>("testStaticMethodT"), + StaticMethod<&TestStaticVoidMethodT>(kTestStaticVoidMethodTInternal), + StaticMethod<&TestStaticMethodT>(kTestStaticMethodTInternal), InstanceValue("testValue", Napi::Boolean::New(env, true), napi_enumerable), InstanceValue(kTestValueInternal, Napi::Boolean::New(env, false), napi_enumerable), @@ -99,9 +134,16 @@ class Test : public Napi::ObjectWrap { InstanceAccessor("testSetter", nullptr, &Test::Setter, napi_default), InstanceAccessor("testGetSet", &Test::Getter, &Test::Setter, napi_enumerable), InstanceAccessor(kTestAccessorInternal, &Test::Getter, &Test::Setter, napi_enumerable), + InstanceAccessor<&Test::Getter>("testGetterT"), + InstanceAccessor<&Test::Getter, &Test::Setter>("testGetSetT"), + InstanceAccessor<&Test::Getter, &Test::Setter>(kTestAccessorInternal), InstanceMethod("testMethod", &Test::TestMethod, napi_enumerable), InstanceMethod(kTestMethodInternal, &Test::TestMethodInternal, napi_default), + InstanceMethod<&Test::TestMethodT>("testMethodT"), + InstanceMethod<&Test::TestVoidMethodT>("testVoidMethodT"), + InstanceMethod<&Test::TestMethodT>(kTestMethodTInternal), + InstanceMethod<&Test::TestVoidMethodT>(kTestVoidMethodTInternal), // conventions InstanceAccessor(Napi::Symbol::WellKnown(env, "toStringTag"), &Test::ToStringTag, nullptr, napi_enumerable), @@ -124,8 +166,12 @@ class Test : public Napi::ObjectWrap { private: std::string value_; Napi::FunctionReference finalizeCb_; + + static std::string s_staticMethodText; }; +std::string Test::s_staticMethodText; + Napi::Object InitObjectWrap(Napi::Env env) { testStaticContextRef = Napi::Persistent(Napi::Object::New(env)); testStaticContextRef.SuppressDestruct(); diff --git a/test/objectwrap.js b/test/objectwrap.js index 533c05f75..de16a6067 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -15,9 +15,11 @@ const test = (binding) => { { obj.testSetter = 'instance getter'; assert.strictEqual(obj.testGetter, 'instance getter'); + assert.strictEqual(obj.testGetterT, 'instance getter'); obj.testSetter = 'instance getter 2'; assert.strictEqual(obj.testGetter, 'instance getter 2'); + assert.strictEqual(obj.testGetterT, 'instance getter 2'); } // read write-only @@ -36,6 +38,9 @@ const test = (binding) => { let error; try { obj.testGetter = 'write'; } catch (e) { error = e; } assert.strictEqual(error.name, 'TypeError'); + + try { obj.testGetterT = 'write'; } catch (e) { error = e; } + assert.strictEqual(error.name, 'TypeError'); } // rw @@ -45,6 +50,12 @@ const test = (binding) => { obj.testGetSet = 'instance getset 2'; assert.strictEqual(obj.testGetSet, 'instance getset 2'); + + obj.testGetSetT = 'instance getset 3'; + assert.strictEqual(obj.testGetSetT, 'instance getset 3'); + + obj.testGetSetT = 'instance getset 4'; + assert.strictEqual(obj.testGetSetT, 'instance getset 4'); } // rw symbol @@ -54,12 +65,22 @@ const test = (binding) => { obj[clazz.kTestAccessorInternal] = 'instance internal getset 2'; assert.strictEqual(obj[clazz.kTestAccessorInternal], 'instance internal getset 2'); + + obj[clazz.kTestAccessorTInternal] = 'instance internal getset 3'; + assert.strictEqual(obj[clazz.kTestAccessorTInternal], 'instance internal getset 3'); + + obj[clazz.kTestAccessorTInternal] = 'instance internal getset 4'; + assert.strictEqual(obj[clazz.kTestAccessorTInternal], 'instance internal getset 4'); } }; const testMethod = (obj, clazz) => { assert.strictEqual(obj.testMethod('method'), 'method instance'); assert.strictEqual(obj[clazz.kTestMethodInternal]('method'), 'method instance internal'); + obj.testVoidMethodT('method<>(const char*)'); + assert.strictEqual(obj.testMethodT(), 'method<>(const char*)'); + obj[clazz.kTestVoidMethodTInternal]('method<>(Symbol)'); + assert.strictEqual(obj[clazz.kTestMethodTInternal](), 'method<>(Symbol)'); }; const testEnumerables = (obj, clazz) => { @@ -112,10 +133,12 @@ const test = (binding) => { const tempObj = {}; clazz.testStaticSetter = tempObj; assert.strictEqual(clazz.testStaticGetter, tempObj); + assert.strictEqual(clazz.testStaticGetterT, tempObj); const tempArray = []; clazz.testStaticSetter = tempArray; assert.strictEqual(clazz.testStaticGetter, tempArray); + assert.strictEqual(clazz.testStaticGetterT, tempArray); } // read write-only @@ -134,6 +157,8 @@ const test = (binding) => { let error; try { clazz.testStaticGetter = 'write'; } catch (e) { error = e; } assert.strictEqual(error.name, 'TypeError'); + try { clazz.testStaticGetterT = 'write'; } catch (e) { error = e; } + assert.strictEqual(error.name, 'TypeError'); } // rw @@ -143,18 +168,30 @@ const test = (binding) => { clazz.testStaticGetSet = 4; assert.strictEqual(clazz.testStaticGetSet, 4); + + clazz.testStaticGetSetT = -9; + assert.strictEqual(clazz.testStaticGetSetT, -9); + + clazz.testStaticGetSetT = -4; + assert.strictEqual(clazz.testStaticGetSetT, -4); } // rw symbol { clazz[clazz.kTestStaticAccessorInternal] = 'static internal getset'; assert.strictEqual(clazz[clazz.kTestStaticAccessorInternal], 'static internal getset'); + clazz[clazz.kTestStaticAccessorTInternal] = 'static internal getset <>'; + assert.strictEqual(clazz[clazz.kTestStaticAccessorTInternal], 'static internal getset <>'); } }; const testStaticMethod = (clazz) => { assert.strictEqual(clazz.testStaticMethod('method'), 'method static'); assert.strictEqual(clazz[clazz.kTestStaticMethodInternal]('method'), 'method static internal'); + clazz.testStaticVoidMethodT('static method<>(const char*)'); + assert.strictEqual(clazz.testStaticMethodT(), 'static method<>(const char*)'); + clazz[clazz.kTestStaticVoidMethodTInternal]('static method<>(Symbol)'); + assert.strictEqual(clazz[clazz.kTestStaticMethodTInternal](), 'static method<>(Symbol)'); }; const testStaticEnumerables = (clazz) => { diff --git a/tools/README.md b/tools/README.md index b71e5d92c..8d110609c 100644 --- a/tools/README.md +++ b/tools/README.md @@ -49,10 +49,10 @@ Napi::FunctionReference constructor; void [ClassName]::Init(Napi::Env env, Napi::Object exports, Napi::Object module) { Napi::HandleScope scope(env); Napi::Function ctor = DefineClass(env, "Canvas", { - InstanceMethod("Func1", &[ClassName]::Func1), - InstanceMethod("Func2", &[ClassName]::Func2), - InstanceAccessor("Value", &[ClassName]::ValueGetter), - StaticMethod("MethodName", &[ClassName]::StaticMethod), + InstanceMethod<&[ClassName]::Func1>("Func1"), + InstanceMethod<&[ClassName]::Func2>("Func2"), + InstanceAccessor<&[ClassName]::ValueGetter>("Value"), + StaticMethod<&[ClassName]::StaticMethod>("MethodName"), InstanceValue("Value", Napi::[Type]::New(env, value)), }); From 3dfb1f0591b2adc31ff90f13c9e14b1f791b07d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Thu, 5 Dec 2019 18:56:58 -0400 Subject: [PATCH 144/696] Change "WG" to "team" As far as I can tell, node-addon-api does not fall under the responsibility of any working group. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2558c7edc..d09b600a6 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around -## WG Members / Collaborators +## Team members ### Active | Name | GitHub Link | From e71d0eadcc8e09b7835b033699d556b2bf248355 Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Fri, 6 Dec 2019 00:00:08 +0100 Subject: [PATCH 145/696] [doc] Fixed links to array documentation (#613) * Fixed links to array documentation --- doc/object.md | 2 +- doc/value.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/object.md b/doc/object.md index 32410544f..ddc97f7f1 100644 --- a/doc/object.md +++ b/doc/object.md @@ -2,7 +2,7 @@ The `Napi::Object` class corresponds to a JavaScript object. It is extended by the following node-addon-api classes that you may use when working with more specific types: -- [`Napi::Value`](value.md) and extends [`Napi::Array`](array.md) +- [`Napi::Value`](value.md) which is extended by [`Napi::Array`](basic_types.md#array) - [`Napi::ArrayBuffer`](array_buffer.md) - [`Napi::Buffer`](buffer.md) - [`Napi::Function`](function.md) diff --git a/doc/value.md b/doc/value.md index 2d25eb74f..99f9e4911 100644 --- a/doc/value.md +++ b/doc/value.md @@ -6,7 +6,7 @@ Value is a the base class upon which other JavaScript values such as Number, Boo The following classes inherit, either directly or indirectly, from `Napi::Value`: -- [`Napi::Array`](array.md) +- [`Napi::Array`](basic_types.md#array) - [`Napi::ArrayBuffer`](array_buffer.md) - [`Napi::Boolean`](boolean.md) - [`Napi::Buffer`](buffer.md) From 3acc4b32f587ea7229d45395a1c81a06a44f953a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Thu, 5 Dec 2019 19:08:20 -0400 Subject: [PATCH 146/696] Fix std::string encoding (#619) 1. "ANSI" is misleading and not a good description of any string encoding. It is commonly used to refer to "Windows code pages" such as Windows-125x, but these code pages have never been standardized by ANSI. 2. It is wrong, we treat std::string as UTF8. PR-URL: https://github.com/nodejs/node-addon-api/pull/619 Reviewed-By: Kevin Eady Reviewed-By: NickNaso Reviewed-By: Michael Dawson --- doc/string.md | 2 +- doc/symbol.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/string.md b/doc/string.md index bf78ac73c..12c958a3c 100644 --- a/doc/string.md +++ b/doc/string.md @@ -62,7 +62,7 @@ Napi::String::New(napi_env env, const char16_t* value, size_t length); - `[in] env`: The `napi_env` environment in which to construct the `Napi::Value` object. - `[in] value`: The C++ primitive from which to instantiate the `Napi::Value`. `value` may be any of: - - `std::string&` - represents an ANSI string. + - `std::string&` - represents a UTF8 string. - `std::u16string&` - represents a UTF16-LE string. - `const char*` - represents a UTF8 string. - `const char16_t*` - represents a UTF16-LE string. diff --git a/doc/symbol.md b/doc/symbol.md index 13abe3e20..7e05fbb09 100644 --- a/doc/symbol.md +++ b/doc/symbol.md @@ -23,7 +23,7 @@ Napi::Symbol::New(napi_env env, napi_value description); - `[in] env`: The `napi_env` environment in which to construct the `Napi::Symbol` object. - `[in] value`: The C++ primitive which represents the description hint for the `Napi::Symbol`. `description` may be any of: - - `std::string&` - ANSI string description. + - `std::string&` - UTF8 string description. - `const char*` - represents a UTF8 string description. - `String` - Node addon API String description. - `napi_value` - N-API `napi_value` description. From c584343217202763a2f870ce17112cf0c4b08b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Thu, 5 Dec 2019 19:12:09 -0400 Subject: [PATCH 147/696] Add GetPropertyNames, HasOwnProperty, Delete (#615) Fixes: https://github.com/nodejs/node-addon-api/issues/614 PR-URL: https://github.com/nodejs/node-addon-api/pull/615 Fixes: https://github.com/nodejs/node-addon-api/issues/614 Reviewed-By: NickNaso Reviewed-By: Michael Dawson --- doc/object.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/doc/object.md b/doc/object.md index ddc97f7f1..c92165aeb 100644 --- a/doc/object.md +++ b/doc/object.md @@ -101,6 +101,22 @@ While the value must be any of the following types: - `bool` - `double` +### Delete() + +```cpp +bool Napi::Object::Delete(____ key); +``` +- `[in] key`: The name of the property to delete. + +Deletes the property associated with the given key. Returns `true` if the property was deleted. + +The `key` can be any of the following types: +- `napi_value` +- [`Napi::Value`](value.md) +- `const char *` +- `const std::string &` +- `uint32_t` + ### Get() ```cpp @@ -171,6 +187,29 @@ void finalizeCallback(Napi::Env env, T* data, Hint* hint); ``` where `data` and `hint` are the pointers that were passed into the call to `AddFinalizer()`. +### GetPropertyNames() +```cpp +Napi::Array Napi::Object::GetPropertyNames() const; +``` + +Returns the names of the enumerable properties of the object as a [`Napi::Array`](basic_types.md#array) of strings. +The properties whose key is a `Symbol` will not be included. + +### HasOwnProperty() +```cpp +bool Napi::Object::HasOwnProperty(____ key); const +``` +- `[in] key` The name of the property to check. + +Returns a `bool` that is *true* if the object has an own property named `key` and *false* otherwise. + +The key can be any of the following types: +- `napi_value` +- [`Napi::Value`](value.md) +- `const char*` +- `const std::string&` +- `uint32_t` + ### DefineProperty() ```cpp From a1b106066e630e23370ead2c922834bc016679fe Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Mon, 25 Nov 2019 09:14:50 -0800 Subject: [PATCH 148/696] src: add templated function factories These variants of `Napi::Function::New` accept the callback as a template parameter rather than a function parameter. This allows us to perform the binding without additional heap-allocation of the function callback data. PR-URL: https://github.com/nodejs/node-addon-api/pull/608 Reviewed-By: Nicola Del Gobbo Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- doc/function.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++- napi-inl.h | 45 +++++++++++++++++++++ napi.h | 23 +++++++++++ test/function.cc | 27 ++++++++++++- test/function.js | 36 +++++++++-------- 5 files changed, 215 insertions(+), 19 deletions(-) diff --git a/doc/function.md b/doc/function.md index efc7ed495..889d5ea14 100644 --- a/doc/function.md +++ b/doc/function.md @@ -25,7 +25,7 @@ Value Fn(const CallbackInfo& info) { } Object Init(Env env, Object exports) { - exports.Set(String::New(env, "fn"), Function::New(env, Fn)); + exports.Set(String::New(env, "fn"), Function::New(env)); } NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) @@ -47,6 +47,27 @@ and in general in situations which don't have an existing JavaScript function on the stack. The `Call` method is used when there is already a JavaScript function on the stack (for example when running a native method called from JavaScript). +## Type definitions + +### Napi::Function::VoidCallback + +This is the type describing a callback returning `void` that will be invoked +from JavaScript. + +```cpp +typedef void (*VoidCallback)(const Napi::CallbackInfo& info); +``` + +### Napi::Function::Callback + +This is the type describing a callback returning a value that will be invoked +from JavaScript. + + +```cpp +typedef Value (*Callback)(const Napi::CallbackInfo& info); +``` + ## Methods ### Constructor @@ -74,6 +95,86 @@ Returns a non-empty `Napi::Function` instance. Creates an instance of a `Napi::Function` object. +```cpp +template +static Napi::Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); +``` + +- `[template] cb`: The native function to invoke when the JavaScript function is +invoked. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. +- `[in] utf8name`: Null-terminated string to be used as the name of the function. +- `[in] data`: User-provided data context. This will be passed back into the +function when invoked later. + +Returns an instance of a `Napi::Function` object. + +### New + +Creates an instance of a `Napi::Function` object. + +```cpp +template +static Napi::Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); +``` + +- `[template] cb`: The native function to invoke when the JavaScript function is +invoked. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. +- `[in] utf8name`: Null-terminated string to be used as the name of the function. +- `[in] data`: User-provided data context. This will be passed back into the +function when invoked later. + +Returns an instance of a `Napi::Function` object. + +### New + +Creates an instance of a `Napi::Function` object. + +```cpp +template +static Napi::Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); +``` + +- `[template] cb`: The native function to invoke when the JavaScript function is +invoked. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. +- `[in] utf8name`: String to be used as the name of the function. +- `[in] data`: User-provided data context. This will be passed back into the +function when invoked later. + +Returns an instance of a `Napi::Function` object. + +### New + +Creates an instance of a `Napi::Function` object. + +```cpp +template +static Napi::Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); +``` + +- `[template] cb`: The native function to invoke when the JavaScript function is +invoked. +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object. +- `[in] utf8name`: String to be used as the name of the function. +- `[in] data`: User-provided data context. This will be passed back into the +function when invoked later. + +Returns an instance of a `Napi::Function` object. + +### New + +Creates an instance of a `Napi::Function` object. + ```cpp template static Napi::Function Napi::Function::New(napi_env env, Callable cb, const char* utf8name = nullptr, void* data = nullptr); diff --git a/napi-inl.h b/napi-inl.h index e8196a4d5..38f75d340 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1766,6 +1766,51 @@ CreateFunction(napi_env env, return status; } +template +inline Function Function::New(napi_env env, const char* utf8name, void* data) { + napi_value result = nullptr; + napi_status status = napi_create_function( + env, utf8name, NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) { + CallbackInfo callbackInfo(env, info); + return details::WrapCallback([&] { + cb(callbackInfo); + return nullptr; + }); + }, data, &result); + NAPI_THROW_IF_FAILED(env, status, Function()); + return Function(env, result); +} + +template +inline Function Function::New(napi_env env, const char* utf8name, void* data) { + napi_value result = nullptr; + napi_status status = napi_create_function( + env, utf8name, NAPI_AUTO_LENGTH, + [](napi_env env, napi_callback_info info) { + CallbackInfo callbackInfo(env, info); + return details::WrapCallback([&] { + return cb(callbackInfo); + }); + }, data, &result); + NAPI_THROW_IF_FAILED(env, status, Function()); + return Function(env, result); +} + +template +inline Function Function::New(napi_env env, + const std::string& utf8name, + void* data) { + return Function::New(env, utf8name.c_str(), data); +} + +template +inline Function Function::New(napi_env env, + const std::string& utf8name, + void* data) { + return Function::New(env, utf8name.c_str(), data); +} + template inline Function Function::New(napi_env env, Callable cb, diff --git a/napi.h b/napi.h index f770641af..3b5bb15df 100644 --- a/napi.h +++ b/napi.h @@ -993,6 +993,29 @@ namespace Napi { class Function : public Object { public: + typedef void (*VoidCallback)(const CallbackInfo& info); + typedef Value (*Callback)(const CallbackInfo& info); + + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + /// Callable must implement operator() accepting a const CallbackInfo& /// and return either void or Value. template diff --git a/test/function.cc b/test/function.cc index 8441e0b5f..b0ae92c7d 100644 --- a/test/function.cc +++ b/test/function.cc @@ -105,6 +105,7 @@ void IsConstructCall(const CallbackInfo& info) { } // end anonymous namespace Object InitFunction(Env env) { + Object result = Object::New(env); Object exports = Object::New(env); exports["voidCallback"] = Function::New(env, VoidCallback, "voidCallback"); exports["valueCallback"] = Function::New(env, ValueCallback, std::string("valueCallback")); @@ -120,5 +121,29 @@ Object InitFunction(Env env) { exports["callConstructorWithArgs"] = Function::New(env, CallConstructorWithArgs); exports["callConstructorWithVector"] = Function::New(env, CallConstructorWithVector); exports["isConstructCall"] = Function::New(env, IsConstructCall); - return exports; + result["plain"] = exports; + + exports = Object::New(env); + exports["voidCallback"] = Function::New(env, "voidCallback"); + exports["valueCallback"] = + Function::New(env, std::string("valueCallback")); + exports["voidCallbackWithData"] = + Function::New(env, nullptr, &testData); + exports["valueCallbackWithData"] = + Function::New(env, nullptr, &testData); + exports["callWithArgs"] = Function::New(env); + exports["callWithVector"] = Function::New(env); + exports["callWithReceiverAndArgs"] = + Function::New(env); + exports["callWithReceiverAndVector"] = + Function::New(env); + exports["callWithInvalidReceiver"] = + Function::New(env); + exports["callConstructorWithArgs"] = + Function::New(env); + exports["callConstructorWithVector"] = + Function::New(env); + exports["isConstructCall"] = Function::New(env); + result["templated"] = exports; + return result; } diff --git a/test/function.js b/test/function.js index 0d75ffca9..8ab742c27 100644 --- a/test/function.js +++ b/test/function.js @@ -2,15 +2,17 @@ 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`)); +test(require(`./build/${buildType}/binding.node`).function.plain); +test(require(`./build/${buildType}/binding_noexcept.node`).function.plain); +test(require(`./build/${buildType}/binding.node`).function.templated); +test(require(`./build/${buildType}/binding_noexcept.node`).function.templated); function test(binding) { let obj = {}; - assert.deepStrictEqual(binding.function.voidCallback(obj), undefined); + assert.deepStrictEqual(binding.voidCallback(obj), undefined); assert.deepStrictEqual(obj, { "foo": "bar" }); - assert.deepStrictEqual(binding.function.valueCallback(), { "foo": "bar" }); + assert.deepStrictEqual(binding.valueCallback(), { "foo": "bar" }); let args = null; let ret = null; @@ -25,50 +27,50 @@ function test(binding) { } ret = 4; - assert.equal(binding.function.callWithArgs(testFunction, 1, 2, 3), 4); + assert.equal(binding.callWithArgs(testFunction, 1, 2, 3), 4); assert.strictEqual(receiver, undefined); assert.deepStrictEqual(args, [ 1, 2, 3 ]); ret = 5; - assert.equal(binding.function.callWithVector(testFunction, 2, 3, 4), 5); + assert.equal(binding.callWithVector(testFunction, 2, 3, 4), 5); assert.strictEqual(receiver, undefined); assert.deepStrictEqual(args, [ 2, 3, 4 ]); ret = 6; - assert.equal(binding.function.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6); + assert.equal(binding.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6); assert.deepStrictEqual(receiver, obj); assert.deepStrictEqual(args, [ 3, 4, 5 ]); ret = 7; - assert.equal(binding.function.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7); + assert.equal(binding.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7); assert.deepStrictEqual(receiver, obj); assert.deepStrictEqual(args, [ 4, 5, 6 ]); assert.throws(() => { - binding.function.callWithInvalidReceiver(); + binding.callWithInvalidReceiver(); }, /Invalid (pointer passed as )?argument/); - obj = binding.function.callConstructorWithArgs(testConstructor, 5, 6, 7); + obj = binding.callConstructorWithArgs(testConstructor, 5, 6, 7); assert(obj instanceof testConstructor); assert.deepStrictEqual(args, [ 5, 6, 7 ]); - obj = binding.function.callConstructorWithVector(testConstructor, 6, 7, 8); + obj = binding.callConstructorWithVector(testConstructor, 6, 7, 8); assert(obj instanceof testConstructor); assert.deepStrictEqual(args, [ 6, 7, 8 ]); obj = {}; - assert.deepStrictEqual(binding.function.voidCallbackWithData(obj), undefined); + assert.deepStrictEqual(binding.voidCallbackWithData(obj), undefined); assert.deepStrictEqual(obj, { "foo": "bar", "data": 1 }); - assert.deepStrictEqual(binding.function.valueCallbackWithData(), { "foo": "bar", "data": 1 }); + assert.deepStrictEqual(binding.valueCallbackWithData(), { "foo": "bar", "data": 1 }); - assert.equal(binding.function.voidCallback.name, 'voidCallback'); - assert.equal(binding.function.valueCallback.name, 'valueCallback'); + assert.equal(binding.voidCallback.name, 'voidCallback'); + assert.equal(binding.valueCallback.name, 'valueCallback'); let testConstructCall = undefined; - binding.function.isConstructCall((result) => { testConstructCall = result; }); + binding.isConstructCall((result) => { testConstructCall = result; }); assert.ok(!testConstructCall); - new binding.function.isConstructCall((result) => { testConstructCall = result; }); + new binding.isConstructCall((result) => { testConstructCall = result; }); assert.ok(testConstructCall); // TODO: Function::MakeCallback tests From ffc71edd54e98607e29c2b22e365e551c953d628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Sat, 30 Nov 2019 23:52:03 -0400 Subject: [PATCH 149/696] Add Env::RunScript This is a thin wrapper around napi_run_script. Refs: https://github.com/nodejs/node/pull/15216 PR-URL: https://github.com/nodejs/node-addon-api/pull/616 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gus Caplan Reviewed-By: Kevin Eady Reviewed-By: Michael Dawson --- doc/env.md | 14 ++++++++++++ napi-inl.h | 16 ++++++++++++++ napi.h | 4 ++++ test/binding.cc | 2 ++ test/binding.gyp | 1 + test/index.js | 1 + test/run_script.cc | 55 ++++++++++++++++++++++++++++++++++++++++++++++ test/run_script.js | 46 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 139 insertions(+) create mode 100644 test/run_script.cc create mode 100644 test/run_script.js diff --git a/doc/env.md b/doc/env.md index 9bde741ca..70c641850 100644 --- a/doc/env.md +++ b/doc/env.md @@ -61,3 +61,17 @@ Napi::Error Napi::Env::GetAndClearPendingException(); ``` Returns an `Napi::Error` object representing the environment's pending exception, if any. + +### RunScript + +```cpp +Napi::Value Napi::Env::RunScript(____ script); +``` +- `[in] script`: A string containing JavaScript code to execute. + +Runs JavaScript code contained in a string and returns its result. + +The `script` can be any of the following types: +- [`Napi::String`](string.md) +- `const char *` +- `const std::string &` diff --git a/napi-inl.h b/napi-inl.h index 38f75d340..7bef465fc 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -319,6 +319,22 @@ inline Error Env::GetAndClearPendingException() { return Error(_env, value); } +inline Value Env::RunScript(const char* utf8script) { + String script = String::New(_env, utf8script); + return RunScript(script); +} + +inline Value Env::RunScript(const std::string& utf8script) { + return RunScript(utf8script.c_str()); +} + +inline Value Env::RunScript(String script) { + napi_value result; + napi_status status = napi_run_script(_env, script, &result); + NAPI_THROW_IF_FAILED(_env, status, Undefined()); + return Value(_env, result); +} + //////////////////////////////////////////////////////////////////////////////// // Value class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 3b5bb15df..c44b3ba92 100644 --- a/napi.h +++ b/napi.h @@ -178,6 +178,10 @@ namespace Napi { bool IsExceptionPending() const; Error GetAndClearPendingException(); + Value RunScript(const char* utf8script); + Value RunScript(const std::string& utf8script); + Value RunScript(String script); + private: napi_env _env; }; diff --git a/test/binding.cc b/test/binding.cc index 5c3cd6b24..ad4650819 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -39,6 +39,7 @@ Object InitObject(Env env); Object InitObjectDeprecated(Env env); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED Object InitPromise(Env env); +Object InitRunScript(Env env); #if (NAPI_VERSION > 3) Object InitThreadSafeFunctionCtx(Env env); Object InitThreadSafeFunctionExistingTsfn(Env env); @@ -92,6 +93,7 @@ Object Init(Env env, Object exports) { exports.Set("object_deprecated", InitObjectDeprecated(env)); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED exports.Set("promise", InitPromise(env)); + exports.Set("run_script", InitRunScript(env)); #if (NAPI_VERSION > 3) exports.Set("threadsafe_function_ctx", InitThreadSafeFunctionCtx(env)); exports.Set("threadsafe_function_existing_tsfn", InitThreadSafeFunctionExistingTsfn(env)); diff --git a/test/binding.gyp b/test/binding.gyp index ced1a6802..d2a1493f9 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -36,6 +36,7 @@ 'object/object.cc', 'object/set_property.cc', 'promise.cc', + 'run_script.cc', 'threadsafe_function/threadsafe_function_ctx.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', diff --git a/test/index.js b/test/index.js index e05c25a65..4fffd1d84 100644 --- a/test/index.js +++ b/test/index.js @@ -39,6 +39,7 @@ let testModules = [ 'object/object_deprecated', 'object/set_property', 'promise', + 'run_script', 'threadsafe_function/threadsafe_function_ctx', 'threadsafe_function/threadsafe_function_existing_tsfn', 'threadsafe_function/threadsafe_function_ptr', diff --git a/test/run_script.cc b/test/run_script.cc new file mode 100644 index 000000000..af47ae1f7 --- /dev/null +++ b/test/run_script.cc @@ -0,0 +1,55 @@ +#include "napi.h" + +using namespace Napi; + +namespace { + +Value RunPlainString(const CallbackInfo& info) { + Env env = info.Env(); + return env.RunScript("1 + 2 + 3"); +} + +Value RunStdString(const CallbackInfo& info) { + Env env = info.Env(); + std::string str = "1 + 2 + 3"; + return env.RunScript(str); +} + +Value RunJsString(const CallbackInfo& info) { + Env env = info.Env(); + return env.RunScript(info[0].As()); +} + +Value RunWithContext(const CallbackInfo& info) { + Env env = info.Env(); + + Array keys = info[1].As().GetPropertyNames(); + std::string code = "("; + for (unsigned int i = 0; i < keys.Length(); i++) { + if (i != 0) code += ","; + code += keys.Get(i).As().Utf8Value(); + } + code += ") => " + info[0].As().Utf8Value(); + + Value ret = env.RunScript(code); + Function fn = ret.As(); + std::vector args; + for (unsigned int i = 0; i < keys.Length(); i++) { + Value key = keys.Get(i); + args.push_back(info[1].As().Get(key)); + } + return fn.Call(args); +} + +} // end anonymous namespace + +Object InitRunScript(Env env) { + Object exports = Object::New(env); + + exports["plainString"] = Function::New(env, RunPlainString); + exports["stdString"] = Function::New(env, RunStdString); + exports["jsString"] = Function::New(env, RunJsString); + exports["withContext"] = Function::New(env, RunWithContext); + + return exports; +} diff --git a/test/run_script.js b/test/run_script.js new file mode 100644 index 000000000..271eeb50c --- /dev/null +++ b/test/run_script.js @@ -0,0 +1,46 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const testUtil = require('./testUtil'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + testUtil.runGCTests([ + 'Plain C string', + () => { + const sum = binding.run_script.plainString(); + assert.strictEqual(sum, 1 + 2 + 3); + }, + + 'std::string', + () => { + const sum = binding.run_script.stdString(); + assert.strictEqual(sum, 1 + 2 + 3); + }, + + 'JavaScript string', + () => { + const sum = binding.run_script.jsString("1 + 2 + 3"); + assert.strictEqual(sum, 1 + 2 + 3); + }, + + 'JavaScript, but not a string', + () => { + assert.throws(() => { + binding.run_script.jsString(true); + }, { + name: 'Error', + message: 'A string was expected' + }); + }, + + 'With context', + () => { + const a = 1, b = 2, c = 3; + const sum = binding.run_script.withContext("a + b + c", { a, b, c }); + assert.strictEqual(sum, a + b + c); + } + ]); +} From 6a0646356d84bdadff5f03d43b5f946a25e7b531 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Tue, 3 Dec 2019 18:01:53 -0800 Subject: [PATCH 150/696] add benchmarking framework Adds the framework for writing benchmarks and two basic benchmarks. PR-URL: https://github.com/nodejs/node-addon-api/pull/623 Reviewed-By: Nicola Del Gobbo Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- README.md | 10 +++ benchmark/README.md | 47 ++++++++++ benchmark/binding.gyp | 25 ++++++ benchmark/function_args.cc | 145 +++++++++++++++++++++++++++++++ benchmark/function_args.js | 52 +++++++++++ benchmark/index.js | 34 ++++++++ benchmark/property_descriptor.cc | 60 +++++++++++++ benchmark/property_descriptor.js | 29 +++++++ common.gypi | 24 +++++ except.gypi | 16 ++++ noexcept.gypi | 16 ++++ package.json | 3 + test/binding.gyp | 52 +---------- 13 files changed, 465 insertions(+), 48 deletions(-) create mode 100644 benchmark/README.md create mode 100644 benchmark/binding.gyp create mode 100644 benchmark/function_args.cc create mode 100644 benchmark/function_args.js create mode 100644 benchmark/index.js create mode 100644 benchmark/property_descriptor.cc create mode 100644 benchmark/property_descriptor.js create mode 100644 common.gypi create mode 100644 except.gypi create mode 100644 noexcept.gypi diff --git a/README.md b/README.md index d09b600a6..22c8eb4c7 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,16 @@ Take a look and get inspired by our **[test suite](https://github.com/nodejs/nod +### **Benchmarks** + +You can run the available benchmarks using the following command: + +``` +npm run-script benchmark +``` + +See [benchmark/README.md](benchmark/README.md) for more details about running and adding benchmarks. + ## **Contributing** We love contributions from the community to **node-addon-api**. diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 000000000..f6e7c27d9 --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,47 @@ +# Benchmarks + +## Running the benchmarks + +From the parent directory, run + +```bash +npm run-script benchmark +``` + +The above script supports the following arguments: + +* `--benchmarks=...`: A semicolon-separated list of benchmark names. These names + will be mapped to file names in this directory by appending `.js`. + +## Adding benchmarks + +The steps below should be followed when adding new benchmarks. + +0. Decide on a name for the benchmark. This name will be used in several places. + This example will use the name `new_benchmark`. + +0. Create files `new_benchmark.cc` and `new_benchmark.js` in this directory. + +0. Copy an existing benchmark in `binding.gyp` and change the target name prefix + and the source file name to `new_benchmark`. This should result in two new + targets which look like this: + + ```gyp + { + 'target_name': 'new_benchmark', + 'sources': [ 'new_benchmark.cc' ], + 'includes': [ '../except.gypi' ], + }, + { + 'target_name': 'new_benchmark_noexcept', + 'sources': [ 'new_benchmark.cc' ], + 'includes': [ '../noexcept.gypi' ], + }, + ``` + + There should always be a pair of targets: one bearing the name of the + benchmark and configured with C++ exceptions enabled, and one bearing the + same name followed by the suffix `_noexcept` and configured with C++ + exceptions disabled. This will ensure that the benchmark can be written to + cover both the case where C++ exceptions are enabled and the case where they + are disabled. diff --git a/benchmark/binding.gyp b/benchmark/binding.gyp new file mode 100644 index 000000000..72f68a13e --- /dev/null +++ b/benchmark/binding.gyp @@ -0,0 +1,25 @@ +{ + 'target_defaults': { 'includes': ['../common.gypi'] }, + 'targets': [ + { + 'target_name': 'function_args', + 'sources': [ 'function_args.cc' ], + 'includes': [ '../except.gypi' ], + }, + { + 'target_name': 'function_args_noexcept', + 'sources': [ 'function_args.cc' ], + 'includes': [ '../noexcept.gypi' ], + }, + { + 'target_name': 'property_descriptor', + 'sources': [ 'property_descriptor.cc' ], + 'includes': [ '../except.gypi' ], + }, + { + 'target_name': 'property_descriptor_noexcept', + 'sources': [ 'property_descriptor.cc' ], + 'includes': [ '../noexcept.gypi' ], + }, + ] +} diff --git a/benchmark/function_args.cc b/benchmark/function_args.cc new file mode 100644 index 000000000..15c6de6fc --- /dev/null +++ b/benchmark/function_args.cc @@ -0,0 +1,145 @@ +#include "napi.h" + +static napi_value NoArgFunction_Core(napi_env env, napi_callback_info info) { + (void) env; + (void) info; + return nullptr; +} + +static napi_value OneArgFunction_Core(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv; + if (napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr) != napi_ok) { + return nullptr; + } + (void) argv; + return nullptr; +} + +static napi_value TwoArgFunction_Core(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value argv[2]; + if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) { + return nullptr; + } + (void) argv[0]; + (void) argv[1]; + return nullptr; +} + +static napi_value ThreeArgFunction_Core(napi_env env, napi_callback_info info) { + size_t argc = 3; + napi_value argv[3]; + if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) { + return nullptr; + } + (void) argv[0]; + (void) argv[1]; + (void) argv[2]; + return nullptr; +} + +static napi_value FourArgFunction_Core(napi_env env, napi_callback_info info) { + size_t argc = 4; + napi_value argv[4]; + if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) { + return nullptr; + } + (void) argv[0]; + (void) argv[1]; + (void) argv[2]; + (void) argv[3]; + return nullptr; +} + +static void NoArgFunction(const Napi::CallbackInfo& info) { + (void) info; +} + +static void OneArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; +} + +static void TwoArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; + Napi::Value argv1 = info[1]; (void) argv1; +} + +static void ThreeArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; + Napi::Value argv1 = info[1]; (void) argv1; + Napi::Value argv2 = info[2]; (void) argv2; +} + +static void FourArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; + Napi::Value argv1 = info[1]; (void) argv1; + Napi::Value argv2 = info[2]; (void) argv2; + Napi::Value argv3 = info[3]; (void) argv3; +} + +static Napi::Object Init(Napi::Env env, Napi::Object exports) { + napi_value no_arg_function, one_arg_function, two_arg_function, + three_arg_function, four_arg_function; + napi_status status; + + status = napi_create_function(env, + "noArgFunction", + NAPI_AUTO_LENGTH, + NoArgFunction_Core, + nullptr, + &no_arg_function); + NAPI_THROW_IF_FAILED(env, status, Napi::Object()); + + status = napi_create_function(env, + "oneArgFunction", + NAPI_AUTO_LENGTH, + OneArgFunction_Core, + nullptr, + &one_arg_function); + NAPI_THROW_IF_FAILED(env, status, Napi::Object()); + + status = napi_create_function(env, + "twoArgFunction", + NAPI_AUTO_LENGTH, + TwoArgFunction_Core, + nullptr, + &two_arg_function); + NAPI_THROW_IF_FAILED(env, status, Napi::Object()); + + status = napi_create_function(env, + "threeArgFunction", + NAPI_AUTO_LENGTH, + ThreeArgFunction_Core, + nullptr, + &three_arg_function); + NAPI_THROW_IF_FAILED(env, status, Napi::Object()); + + status = napi_create_function(env, + "fourArgFunction", + NAPI_AUTO_LENGTH, + FourArgFunction_Core, + nullptr, + &four_arg_function); + NAPI_THROW_IF_FAILED(env, status, Napi::Object()); + + Napi::Object core = Napi::Object::New(env); + core["noArgFunction"] = Napi::Value(env, no_arg_function); + core["oneArgFunction"] = Napi::Value(env, one_arg_function); + core["twoArgFunction"] = Napi::Value(env, two_arg_function); + core["threeArgFunction"] = Napi::Value(env, three_arg_function); + core["fourArgFunction"] = Napi::Value(env, four_arg_function); + exports["core"] = core; + + Napi::Object cplusplus = Napi::Object::New(env); + cplusplus["noArgFunction"] = Napi::Function::New(env, NoArgFunction); + cplusplus["oneArgFunction"] = Napi::Function::New(env, OneArgFunction); + cplusplus["twoArgFunction"] = Napi::Function::New(env, TwoArgFunction); + cplusplus["threeArgFunction"] = Napi::Function::New(env, ThreeArgFunction); + cplusplus["fourArgFunction"] = Napi::Function::New(env, FourArgFunction); + exports["cplusplus"] = cplusplus; + + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/benchmark/function_args.js b/benchmark/function_args.js new file mode 100644 index 000000000..3dee09a68 --- /dev/null +++ b/benchmark/function_args.js @@ -0,0 +1,52 @@ +const path = require('path'); +const Benchmark = require('benchmark'); +const addonName = path.basename(__filename, '.js'); + +[ addonName, addonName + '_noexcept' ] + .forEach((addonName) => { + const rootAddon = require(`./build/Release/${addonName}`); + const implems = Object.keys(rootAddon); + const anObject = {}; + + console.log(`${addonName}: `); + + console.log('no arguments:'); + implems.reduce((suite, implem) => { + const fn = rootAddon[implem].noArgFunction; + return suite.add(implem, () => fn()); + }, new Benchmark.Suite) + .on('cycle', (event) => console.log(String(event.target))) + .run(); + + console.log('one argument:'); + implems.reduce((suite, implem) => { + const fn = rootAddon[implem].oneArgFunction; + return suite.add(implem, () => fn('x')); + }, new Benchmark.Suite) + .on('cycle', (event) => console.log(String(event.target))) + .run(); + + console.log('two arguments:'); + implems.reduce((suite, implem) => { + const fn = rootAddon[implem].twoArgFunction; + return suite.add(implem, () => fn('x', 12)); + }, new Benchmark.Suite) + .on('cycle', (event) => console.log(String(event.target))) + .run(); + + console.log('three arguments:'); + implems.reduce((suite, implem) => { + const fn = rootAddon[implem].threeArgFunction; + return suite.add(implem, () => fn('x', 12, true)); + }, new Benchmark.Suite) + .on('cycle', (event) => console.log(String(event.target))) + .run(); + + console.log('four arguments:'); + implems.reduce((suite, implem) => { + const fn = rootAddon[implem].fourArgFunction; + return suite.add(implem, () => fn('x', 12, true, anObject)); + }, new Benchmark.Suite) + .on('cycle', (event) => console.log(String(event.target))) + .run(); + }); diff --git a/benchmark/index.js b/benchmark/index.js new file mode 100644 index 000000000..e4c7391b1 --- /dev/null +++ b/benchmark/index.js @@ -0,0 +1,34 @@ +'use strict'; + +const { readdirSync } = require('fs'); +const { spawnSync } = require('child_process'); +const path = require('path'); + +let benchmarks = []; + +if (!!process.env.npm_config_benchmarks) { + benchmarks = process.env.npm_config_benchmarks + .split(';') + .map((item) => (item + '.js')); +} + +// Run each file in this directory or the list given on the command line except +// index.js as a Node.js process. +(benchmarks.length > 0 ? benchmarks : readdirSync(__dirname)) + .filter((item) => (item !== 'index.js' && item.match(/\.js$/))) + .map((item) => path.join(__dirname, item)) + .forEach((item) => { + const child = spawnSync(process.execPath, [ + '--expose-gc', + item + ], { stdio: 'inherit' }); + if (child.signal) { + console.error(`Tests aborted with ${child.signal}`); + process.exitCode = 1; + } else { + process.exitCode = child.status; + } + if (child.status !== 0) { + process.exit(process.exitCode); + } + }); diff --git a/benchmark/property_descriptor.cc b/benchmark/property_descriptor.cc new file mode 100644 index 000000000..e4e26e7c9 --- /dev/null +++ b/benchmark/property_descriptor.cc @@ -0,0 +1,60 @@ +#include "napi.h" + +static napi_value Getter_Core(napi_env env, napi_callback_info info) { + (void) info; + napi_value result; + napi_status status = napi_create_uint32(env, 42, &result); + NAPI_THROW_IF_FAILED(env, status, nullptr); + return result; +} + +static napi_value Setter_Core(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv; + napi_status status = + napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr); + NAPI_THROW_IF_FAILED(env, status, nullptr); + (void) argv; + return nullptr; +} + +static Napi::Value Getter(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), 42); +} + +static void Setter(const Napi::CallbackInfo& info) { + (void) info[0]; +} + +static Napi::Object Init(Napi::Env env, Napi::Object exports) { + napi_status status; + napi_property_descriptor core_prop = { + "core", + nullptr, + nullptr, + Getter_Core, + Setter_Core, + nullptr, + napi_enumerable, + nullptr + }; + + status = napi_define_properties(env, exports, 1, &core_prop); + NAPI_THROW_IF_FAILED(env, status, Napi::Object()); + + exports.DefineProperty( + Napi::PropertyDescriptor::Accessor(env, + exports, + "cplusplus", + Getter, + Setter, + napi_enumerable)); + + exports.DefineProperty( + Napi::PropertyDescriptor::Accessor("templated", + napi_enumerable)); + + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/benchmark/property_descriptor.js b/benchmark/property_descriptor.js new file mode 100644 index 000000000..cab510601 --- /dev/null +++ b/benchmark/property_descriptor.js @@ -0,0 +1,29 @@ +const path = require('path'); +const Benchmark = require('benchmark'); +const addonName = path.basename(__filename, '.js'); + +[ addonName, addonName + '_noexcept' ] + .forEach((addonName) => { + const rootAddon = require(`./build/Release/${addonName}`); + const getters = new Benchmark.Suite; + const setters = new Benchmark.Suite; + + console.log(`${addonName}: `); + + Object.keys(rootAddon).forEach((key) => { + getters.add(`${key} getter`, () => { + const x = rootAddon[key]; + }); + setters.add(`${key} setter`, () => { + rootAddon[key] = 5; + }) + }); + + getters + .on('cycle', (event) => console.log(String(event.target))) + .run(); + + setters + .on('cycle', (event) => console.log(String(event.target))) + .run(); + }); diff --git a/common.gypi b/common.gypi new file mode 100644 index 000000000..812198aea --- /dev/null +++ b/common.gypi @@ -0,0 +1,24 @@ +{ + 'variables': { + 'NAPI_VERSION%': " Date: Sun, 1 Dec 2019 11:41:25 -0400 Subject: [PATCH 151/696] Fix code format in tests PR-URL: https://github.com/nodejs/node-addon-api/pull/617 Reviewed-By: Gabriel Schulhof Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- test/basic_types/array.cc | 8 ++++---- test/basic_types/array.js | 2 +- test/testUtil.js | 4 ++-- test/threadsafe_function/threadsafe_function_sum.cc | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/basic_types/array.cc b/test/basic_types/array.cc index fb0074c40..6482c61fd 100644 --- a/test/basic_types/array.cc +++ b/test/basic_types/array.cc @@ -5,9 +5,9 @@ using namespace Napi; Value CreateArray(const CallbackInfo& info) { if (info.Length() > 0) { size_t length = info[0].As().Uint32Value(); - return Array::New(info.Env(), length); + return Array::New(info.Env(), length); } else { - return Array::New(info.Env()); + return Array::New(info.Env()); } } @@ -19,13 +19,13 @@ Value GetLength(const CallbackInfo& info) { Value GetElement(const CallbackInfo& info) { Array array = info[0].As(); size_t index = info[1].As().Uint32Value(); - return array[index]; + return array[index]; } void SetElement(const CallbackInfo& info) { Array array = info[0].As(); size_t index = info[1].As().Uint32Value(); - array[index] = info[2].As(); + array[index] = info[2].As(); } Object InitBasicTypesArray(Env env) { diff --git a/test/basic_types/array.js b/test/basic_types/array.js index 925022a2d..38ccba448 100644 --- a/test/basic_types/array.js +++ b/test/basic_types/array.js @@ -12,7 +12,7 @@ function test(binding) { assert.strictEqual(binding.basic_types_array.getLength(array), 0); // create array with length - const arrayWithLength = binding.basic_types_array.createArray(10); + const arrayWithLength = binding.basic_types_array.createArray(10); assert.strictEqual(binding.basic_types_array.getLength(arrayWithLength), 10); // set function test diff --git a/test/testUtil.js b/test/testUtil.js index db2d700c4..402c5c91d 100644 --- a/test/testUtil.js +++ b/test/testUtil.js @@ -2,7 +2,7 @@ // with an async delay and GC call between each. function runGCTests(tests, i, title) { if (!i) { - i = 0; + i = 0; } if (tests[i]) { @@ -25,5 +25,5 @@ function runGCTests(tests, i, title) { } module.exports = { - runGCTests, + runGCTests, }; diff --git a/test/threadsafe_function/threadsafe_function_sum.cc b/test/threadsafe_function/threadsafe_function_sum.cc index bba57dd47..eac248816 100644 --- a/test/threadsafe_function/threadsafe_function_sum.cc +++ b/test/threadsafe_function/threadsafe_function_sum.cc @@ -28,7 +28,7 @@ void FinalizerCallback(Napi::Env env, TestData* finalizeData){ for (size_t i = 0; i < finalizeData->threads.size(); ++i) { finalizeData->threads[i].join(); } - finalizeData->deferred.Resolve(Boolean::New(env,true)); + finalizeData->deferred.Resolve(Boolean::New(env, true)); delete finalizeData; } From b72f1d69786cf5092db0dd24352645a9652d4615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Thu, 28 Nov 2019 06:19:07 +0100 Subject: [PATCH 152/696] Disable caching in ArrayBuffer Caching the data pointer and the byteLength in the ArrayBuffer class causes it to behave incorrectly when the buffer is detached. PR-URL: https://github.com/nodejs/node-addon-api/pull/611 Reviewed-By: Nicola Del Gobbo Reviewed-By: Michael Dawson --- napi-inl.h | 38 ++++++++++++++------------------------ napi.h | 8 +------- test/arraybuffer.cc | 32 ++++++++++++++++++++++++++++++++ test/arraybuffer.js | 6 ++++++ 4 files changed, 53 insertions(+), 31 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 7bef465fc..8647cd8dc 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1346,7 +1346,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, size_t byteLength) { napi_status status = napi_create_arraybuffer(env, byteLength, &data, &value); NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); - return ArrayBuffer(env, value, data, byteLength); + return ArrayBuffer(env, value); } inline ArrayBuffer ArrayBuffer::New(napi_env env, @@ -1357,7 +1357,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, env, externalData, byteLength, nullptr, nullptr, &value); NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); - return ArrayBuffer(env, value, externalData, byteLength); + return ArrayBuffer(env, value); } template @@ -1380,7 +1380,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); } - return ArrayBuffer(env, value, externalData, byteLength); + return ArrayBuffer(env, value); } template @@ -1404,38 +1404,28 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); } - return ArrayBuffer(env, value, externalData, byteLength); + return ArrayBuffer(env, value); } -inline ArrayBuffer::ArrayBuffer() : Object(), _data(nullptr), _length(0) { +inline ArrayBuffer::ArrayBuffer() : Object() { } inline ArrayBuffer::ArrayBuffer(napi_env env, napi_value value) - : Object(env, value), _data(nullptr), _length(0) { -} - -inline ArrayBuffer::ArrayBuffer(napi_env env, napi_value value, void* data, size_t length) - : Object(env, value), _data(data), _length(length) { + : Object(env, value) { } inline void* ArrayBuffer::Data() { - EnsureInfo(); - return _data; + void* data; + napi_status status = napi_get_arraybuffer_info(_env, _value, &data, nullptr); + NAPI_THROW_IF_FAILED(_env, status, nullptr); + return data; } inline size_t ArrayBuffer::ByteLength() { - EnsureInfo(); - return _length; -} - -inline void ArrayBuffer::EnsureInfo() const { - // The ArrayBuffer instance may have been constructed from a napi_value whose - // length/data are not yet known. Fetch and cache these values just once, - // since they can never change during the lifetime of the ArrayBuffer. - if (_data == nullptr) { - napi_status status = napi_get_arraybuffer_info(_env, _value, &_data, &_length); - NAPI_THROW_IF_FAILED_VOID(_env, status); - } + size_t length; + napi_status status = napi_get_arraybuffer_info(_env, _value, nullptr, &length); + NAPI_THROW_IF_FAILED(_env, status, 0); + return length; } //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index c44b3ba92..62198b1b3 100644 --- a/napi.h +++ b/napi.h @@ -124,6 +124,7 @@ namespace Napi { class String; class Object; class Array; + class ArrayBuffer; class Function; template class Buffer; class Error; @@ -806,13 +807,6 @@ namespace Napi { void* Data(); ///< Gets a pointer to the data buffer. size_t ByteLength(); ///< Gets the length of the array buffer in bytes. - - private: - mutable void* _data; - mutable size_t _length; - - ArrayBuffer(napi_env env, napi_value value, void* data, size_t length); - void EnsureInfo() const; }; /// A JavaScript typed-array value with unknown array type. diff --git a/test/arraybuffer.cc b/test/arraybuffer.cc index e46f8cba3..cc4f1f51c 100644 --- a/test/arraybuffer.cc +++ b/test/arraybuffer.cc @@ -151,6 +151,37 @@ Value CheckEmptyBuffer(const CallbackInfo& info) { return Boolean::New(info.Env(), buffer.IsEmpty()); } +void CheckDetachUpdatesData(const CallbackInfo& info) { + if (!info[0].IsArrayBuffer()) { + Error::New(info.Env(), "A buffer was expected.").ThrowAsJavaScriptException(); + return; + } + + if (!info[1].IsFunction()) { + Error::New(info.Env(), "A function was expected.").ThrowAsJavaScriptException(); + return; + } + + ArrayBuffer buffer = info[0].As(); + Function detach = info[1].As(); + + // This potentially causes the buffer to cache its data pointer and length. + buffer.Data(); + buffer.ByteLength(); + + detach.Call({}); + + if (buffer.Data() != nullptr) { + Error::New(info.Env(), "Incorrect data pointer.").ThrowAsJavaScriptException(); + return; + } + + if (buffer.ByteLength() != 0) { + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return; + } +} + } // end anonymous namespace Object InitArrayBuffer(Env env) { @@ -166,6 +197,7 @@ Object InitArrayBuffer(Env env) { exports["getFinalizeCount"] = Function::New(env, GetFinalizeCount); exports["createBufferWithConstructor"] = Function::New(env, CreateBufferWithConstructor); exports["checkEmptyBuffer"] = Function::New(env, CheckEmptyBuffer); + exports["checkDetachUpdatesData"] = Function::New(env, CheckDetachUpdatesData); return exports; } diff --git a/test/arraybuffer.js b/test/arraybuffer.js index 43604617f..30136980b 100644 --- a/test/arraybuffer.js +++ b/test/arraybuffer.js @@ -61,5 +61,11 @@ function test(binding) { binding.arraybuffer.checkBuffer(test); assert.ok(test instanceof ArrayBuffer); }, + + 'ArrayBuffer updates data pointer and length when detached', + () => { + const mem = new WebAssembly.Memory({ initial: 1 }); + binding.arraybuffer.checkDetachUpdatesData(mem.buffer, () => mem.grow(1)); + }, ]); } From af50ac281bae17f22f32ca14af1cab5f00c891c6 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Thu, 12 Dec 2019 09:30:37 -0800 Subject: [PATCH 153/696] error: do not replace pending exception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only construct a `Napi::Error` from the last non-`napi_ok` error code if there is no exception pending. A consequence for the object property test suite is that it must now expect the exception thrown by the engine when N-API core attempts to convert the undefined value to an object. Fixes: https://github.com/nodejs/node-addon-api/issues/621 PR-URL: https://github.com/nodejs/node-addon-api/pull/629 Reviewed-By: Tobias Nießen Reviewed-By: Chengzhong Wu --- napi-inl.h | 20 +++++++++---------- test/error.cc | 35 +++++++++++++++++++++++++++++++++ test/error.js | 6 ++++++ test/object/delete_property.js | 2 +- test/object/get_property.js | 2 +- test/object/has_own_property.js | 2 +- test/object/has_property.js | 2 +- test/object/set_property.js | 2 +- 8 files changed, 55 insertions(+), 16 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 8647cd8dc..bef889f9a 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2088,12 +2088,19 @@ inline void Buffer::EnsureInfo() const { inline Error Error::New(napi_env env) { napi_status status; napi_value error = nullptr; - + bool is_exception_pending; const napi_extended_error_info* info; + + // We must retrieve the last error info before doing anything else, because + // doing anything else will replace the last error info. status = napi_get_last_error_info(env, &info); NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_last_error_info"); - if (info->error_code == napi_pending_exception) { + status = napi_is_exception_pending(env, &is_exception_pending); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending"); + + // A pending exception takes precedence over any internal error status. + if (is_exception_pending) { status = napi_get_and_clear_last_exception(env, &error); NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception"); } @@ -2101,15 +2108,6 @@ inline Error Error::New(napi_env env) { const char* error_message = info->error_message != nullptr ? info->error_message : "Error in native callback"; - bool isExceptionPending; - status = napi_is_exception_pending(env, &isExceptionPending); - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending"); - - if (isExceptionPending) { - status = napi_get_and_clear_last_exception(env, &error); - NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception"); - } - napi_value message; status = napi_create_string_utf8( env, diff --git a/test/error.cc b/test/error.cc index 44b1a2e96..832cad525 100644 --- a/test/error.cc +++ b/test/error.cc @@ -158,6 +158,40 @@ void ThrowFatalError(const CallbackInfo& /*info*/) { Error::Fatal("Error::ThrowFatalError", "This is a fatal error"); } +void ThrowDefaultError(const CallbackInfo& info) { + napi_value dummy; + napi_env env = info.Env(); + napi_status status = napi_get_undefined(env, &dummy); + NAPI_FATAL_IF_FAILED(status, "ThrowDefaultError", "napi_get_undefined"); + + if (info[0].As().Value()) { + // Provoke N-API into setting an error, then use the `Napi::Error::New` + // factory with only the `env` parameter to throw an exception generated + // from the last error. + uint32_t dummy_uint32; + status = napi_get_value_uint32(env, dummy, &dummy_uint32); + if (status == napi_ok) { + Error::Fatal("ThrowDefaultError", "napi_get_value_uint32"); + } + // We cannot use `NAPI_THROW_IF_FAILED()` here because we do not wish + // control to pass back to the engine if we throw an exception here and C++ + // exceptions are turned on. + Napi::Error::New(env).ThrowAsJavaScriptException(); + } + + // Produce and throw a second error that has different content than the one + // above. If the one above was thrown, then throwing the one below should + // have the effect of re-throwing the one above. + status = napi_get_named_property(env, dummy, "xyzzy", &dummy); + if (status == napi_ok) { + Error::Fatal("ThrowDefaultError", "napi_get_named_property"); + } + + // The macro creates a `Napi::Error` using the factory that takes only the + // env, however, it heeds the exception mechanism to be used. + NAPI_THROW_IF_FAILED_VOID(env, status); +} + } // end anonymous namespace Object InitError(Env env) { @@ -174,5 +208,6 @@ Object InitError(Env env) { exports["catchAndRethrowErrorThatEscapesScope"] = Function::New(env, CatchAndRethrowErrorThatEscapesScope); exports["throwFatalError"] = Function::New(env, ThrowFatalError); + exports["throwDefaultError"] = Function::New(env, ThrowDefaultError); return exports; } diff --git a/test/error.js b/test/error.js index f41874d55..031db081a 100644 --- a/test/error.js +++ b/test/error.js @@ -70,4 +70,10 @@ function test(bindingPath) { assert.ifError(p.error); assert.ok(p.stderr.toString().includes( 'FATAL ERROR: Error::ThrowFatalError This is a fatal error')); + + assert.throws(() => binding.error.throwDefaultError(false), + /Cannot convert undefined or null to object/); + + assert.throws(() => binding.error.throwDefaultError(true), + /A number was expected/); } diff --git a/test/object/delete_property.js b/test/object/delete_property.js index f560d2698..8c313d03e 100644 --- a/test/object/delete_property.js +++ b/test/object/delete_property.js @@ -22,7 +22,7 @@ function test(binding) { function testShouldThrowErrorIfKeyIsInvalid(nativeDeleteProperty) { assert.throws(() => { nativeDeleteProperty(undefined, 'test'); - }, /object was expected/); + }, /Cannot convert undefined or null to object/); } testDeleteProperty(binding.object.deletePropertyWithNapiValue); diff --git a/test/object/get_property.js b/test/object/get_property.js index bec009d74..7ec9b119e 100644 --- a/test/object/get_property.js +++ b/test/object/get_property.js @@ -15,7 +15,7 @@ function test(binding) { function testShouldThrowErrorIfKeyIsInvalid(nativeGetProperty) { assert.throws(() => { nativeGetProperty(undefined, 'test'); - }, /object was expected/); + }, /Cannot convert undefined or null to object/); } testGetProperty(binding.object.getPropertyWithNapiValue); diff --git a/test/object/has_own_property.js b/test/object/has_own_property.js index 11a3ff049..570b0ee46 100644 --- a/test/object/has_own_property.js +++ b/test/object/has_own_property.js @@ -21,7 +21,7 @@ function test(binding) { function testShouldThrowErrorIfKeyIsInvalid(nativeHasOwnProperty) { assert.throws(() => { nativeHasOwnProperty(undefined, 'test'); - }, /object was expected/); + }, /Cannot convert undefined or null to object/); } testHasOwnProperty(binding.object.hasOwnPropertyWithNapiValue); diff --git a/test/object/has_property.js b/test/object/has_property.js index 66024b392..a1b942dfb 100644 --- a/test/object/has_property.js +++ b/test/object/has_property.js @@ -21,7 +21,7 @@ function test(binding) { function testShouldThrowErrorIfKeyIsInvalid(nativeHasProperty) { assert.throws(() => { nativeHasProperty(undefined, 'test'); - }, /object was expected/); + }, /Cannot convert undefined or null to object/); } testHasProperty(binding.object.hasPropertyWithNapiValue); diff --git a/test/object/set_property.js b/test/object/set_property.js index 7606ddf59..9b64cc5dd 100644 --- a/test/object/set_property.js +++ b/test/object/set_property.js @@ -16,7 +16,7 @@ function test(binding) { function testShouldThrowErrorIfKeyIsInvalid(nativeSetProperty) { assert.throws(() => { nativeSetProperty(undefined, 'test', 1); - }, /object was expected/); + }, /Cannot convert undefined or null to object/); } testSetProperty(binding.object.setPropertyWithNapiValue); From 79deefb6f3f83f2ad453da531824ad76fbb02509 Mon Sep 17 00:00:00 2001 From: legendecas Date: Wed, 6 Nov 2019 20:31:36 +0800 Subject: [PATCH 154/696] src: explicitly disallow assign and copy PR-URL: https://github.com/nodejs/node-addon-api/pull/590 Reviewed-By: Gabriel Schulhof --- napi.h | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/napi.h b/napi.h index 62198b1b3..bced8d257 100644 --- a/napi.h +++ b/napi.h @@ -91,6 +91,13 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 #endif // NAPI_CPP_EXCEPTIONS +# define NAPI_DISALLOW_ASSIGN(CLASS) void operator=(const CLASS&) = delete; +# define NAPI_DISALLOW_COPY(CLASS) CLASS(const CLASS&) = delete; + +#define NAPI_DISALLOW_ASSIGN_COPY(CLASS) \ + NAPI_DISALLOW_ASSIGN(CLASS) \ + NAPI_DISALLOW_COPY(CLASS) + #define NAPI_FATAL_IF_FAILED(status, location, message) \ do { \ if ((status) != napi_ok) { \ @@ -1129,7 +1136,7 @@ namespace Napi { // A reference can be moved but cannot be copied. Reference(Reference&& other); Reference& operator =(Reference&& other); - Reference& operator =(const Reference&) = delete; + NAPI_DISALLOW_ASSIGN(Reference) operator napi_ref() const; bool operator ==(const Reference &other) const; @@ -1174,7 +1181,7 @@ namespace Napi { ObjectReference& operator =(Reference&& other); ObjectReference(ObjectReference&& other); ObjectReference& operator =(ObjectReference&& other); - ObjectReference& operator =(const ObjectReference&) = delete; + NAPI_DISALLOW_ASSIGN(ObjectReference) Napi::Value Get(const char* utf8name) const; Napi::Value Get(const std::string& utf8name) const; @@ -1211,8 +1218,7 @@ namespace Napi { FunctionReference& operator =(Reference&& other); FunctionReference(FunctionReference&& other); FunctionReference& operator =(FunctionReference&& other); - FunctionReference(const FunctionReference&) = delete; - FunctionReference& operator =(const FunctionReference&) = delete; + NAPI_DISALLOW_ASSIGN_COPY(FunctionReference) Napi::Value operator ()(const std::initializer_list& args) const; @@ -1402,8 +1408,7 @@ namespace Napi { ~CallbackInfo(); // Disallow copying to prevent multiple free of _dynamicArgs - CallbackInfo(CallbackInfo const &) = delete; - void operator=(CallbackInfo const &) = delete; + NAPI_DISALLOW_ASSIGN_COPY(CallbackInfo) Napi::Env Env() const; Value NewTarget() const; @@ -1891,8 +1896,7 @@ namespace Napi { ~HandleScope(); // Disallow copying to prevent double close of napi_handle_scope - HandleScope(HandleScope const &) = delete; - void operator=(HandleScope const &) = delete; + NAPI_DISALLOW_ASSIGN_COPY(HandleScope) operator napi_handle_scope() const; @@ -1910,8 +1914,7 @@ namespace Napi { ~EscapableHandleScope(); // Disallow copying to prevent double close of napi_escapable_handle_scope - EscapableHandleScope(EscapableHandleScope const &) = delete; - void operator=(EscapableHandleScope const &) = delete; + NAPI_DISALLOW_ASSIGN_COPY(EscapableHandleScope) operator napi_escapable_handle_scope() const; @@ -1931,8 +1934,7 @@ namespace Napi { virtual ~CallbackScope(); // Disallow copying to prevent double close of napi_callback_scope - CallbackScope(CallbackScope const &) = delete; - void operator=(CallbackScope const &) = delete; + NAPI_DISALLOW_ASSIGN_COPY(CallbackScope) operator napi_callback_scope() const; @@ -1952,8 +1954,7 @@ namespace Napi { AsyncContext(AsyncContext&& other); AsyncContext& operator =(AsyncContext&& other); - AsyncContext(const AsyncContext&) = delete; - AsyncContext& operator =(const AsyncContext&) = delete; + NAPI_DISALLOW_ASSIGN_COPY(AsyncContext) operator napi_async_context() const; @@ -1971,8 +1972,7 @@ namespace Napi { // An async worker can be moved but cannot be copied. AsyncWorker(AsyncWorker&& other); AsyncWorker& operator =(AsyncWorker&& other); - AsyncWorker(const AsyncWorker&) = delete; - AsyncWorker& operator =(const AsyncWorker&) = delete; + NAPI_DISALLOW_ASSIGN_COPY(AsyncWorker) operator napi_async_work() const; From 9e0e0f31e483c0d9f00161485760b334b99fc63a Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Mon, 16 Dec 2019 09:18:47 -0800 Subject: [PATCH 155/696] src: remove unnecessary forward declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node-addon-api/pull/633 Reviewed-By: Tobias Nießen Reviewed-By: Chengzhong Wu --- napi.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/napi.h b/napi.h index bced8d257..57057c53a 100644 --- a/napi.h +++ b/napi.h @@ -133,11 +133,9 @@ namespace Napi { class Array; class ArrayBuffer; class Function; - template class Buffer; class Error; class PropertyDescriptor; class CallbackInfo; - template class Reference; class TypedArray; template class TypedArrayOf; From 5eeabb0214f55b21f2385da8a063a2657e64fa66 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Mon, 16 Dec 2019 20:51:03 +0100 Subject: [PATCH 156/696] tsfn: Remove erroneous finalizer cleanup Removes leftover cleanup in finalizer that was part of the original TSFN implementation. Fixes: https://github.com/nodejs/node-addon-api/issues/632 PR-URL: https://github.com/nodejs/node-addon-api/pull/636 Reviewed-By: Gabriel Schulhof --- napi-inl.h | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index bef889f9a..2250b6b1c 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -150,9 +150,6 @@ struct ThreadSafeFinalize { ThreadSafeFinalize* finalizeData = static_cast(rawFinalizeData); finalizeData->callback(Env(env)); - if (finalizeData->tsfn) { - *finalizeData->tsfn = nullptr; - } delete finalizeData; } @@ -166,9 +163,6 @@ struct ThreadSafeFinalize { ThreadSafeFinalize* finalizeData = static_cast(rawFinalizeData); finalizeData->callback(Env(env), finalizeData->data); - if (finalizeData->tsfn) { - *finalizeData->tsfn = nullptr; - } delete finalizeData; } @@ -182,9 +176,6 @@ struct ThreadSafeFinalize { ThreadSafeFinalize* finalizeData = static_cast(rawFinalizeData); finalizeData->callback(Env(env), static_cast(rawContext)); - if (finalizeData->tsfn) { - *finalizeData->tsfn = nullptr; - } delete finalizeData; } @@ -199,15 +190,11 @@ struct ThreadSafeFinalize { static_cast(rawFinalizeData); finalizeData->callback(Env(env), finalizeData->data, static_cast(rawContext)); - if (finalizeData->tsfn) { - *finalizeData->tsfn = nullptr; - } delete finalizeData; } FinalizerDataType* data; Finalizer callback; - napi_threadsafe_function* tsfn; }; #endif @@ -4528,7 +4515,7 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, ThreadSafeFunction tsfn; auto* finalizeData = new details::ThreadSafeFinalize({ data, finalizeCallback, &tsfn._tsfn }); + FinalizerDataType>({ data, finalizeCallback }); napi_status status = napi_create_threadsafe_function(env, callback, resource, Value::From(env, resourceName), maxQueueSize, initialThreadCount, finalizeData, wrapper, context, CallJS, &tsfn._tsfn); From 03759f775931f2c0cd1116578ebc7c2e9c38372e Mon Sep 17 00:00:00 2001 From: legendecas Date: Sat, 14 Dec 2019 14:08:35 +0800 Subject: [PATCH 157/696] ignore benchmark built archives PR-URL: https://github.com/nodejs/node-addon-api/pull/631 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gabriel Schulhof --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index febbb5c96..c10f4dffc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /node_modules /build +/benchmark/build +/benchmark/src From 920d54477922c42f801efc714ab3ae95cde47a5c Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Sat, 14 Dec 2019 10:33:14 -0800 Subject: [PATCH 158/696] benchmark: add templated version of Function PR-URL: https://github.com/nodejs/node-addon-api/pull/637 Reviewed-By: Nicola Del Gobbo Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- benchmark/function_args.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/benchmark/function_args.cc b/benchmark/function_args.cc index 15c6de6fc..7dcc7c781 100644 --- a/benchmark/function_args.cc +++ b/benchmark/function_args.cc @@ -139,6 +139,14 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) { cplusplus["fourArgFunction"] = Napi::Function::New(env, FourArgFunction); exports["cplusplus"] = cplusplus; + Napi::Object templated = Napi::Object::New(env); + templated["noArgFunction"] = Napi::Function::New(env); + templated["oneArgFunction"] = Napi::Function::New(env); + templated["twoArgFunction"] = Napi::Function::New(env); + templated["threeArgFunction"] = Napi::Function::New(env); + templated["fourArgFunction"] = Napi::Function::New(env); + exports["templated"] = templated; + return exports; } From 9af69da01f0f102dfd875320ca4e6e065d8a90a2 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Tue, 31 Dec 2019 21:11:28 -0800 Subject: [PATCH 159/696] remove N-API implementation, v6.x and v8.x support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the followings: * the files associated with the external implementation of N-API * Travis CI jobs for v8.x and v6.x * documentation instructing users to add the external N-API implementation to their dependencies. * conversion tool code that adds the external N-API implementation as a dependency to the user's addon. This move is possible because of v8.x EOL, which means that all supported versions of Node.js now have an internal implementation of N-API. Fixes: https://github.com/nodejs/node-addon-api/issues/463 Fixes: https://github.com/nodejs/node-addon-api/issues/509 Fixes: https://github.com/nodejs/node-addon-api/issues/640 PR-URL: https://github.com/nodejs/node-addon-api/pull/643 Reviewed-By: Nicola Del Gobbo Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson Reviewed-By: Tobias Nießen --- .travis.yml | 3 - common.gypi | 1 - doc/setup.md | 1 - external-napi/node_api.h | 7 - index.js | 47 +- node_api.gyp | 9 + src/nothing.c => nothing.c | 0 package.json | 1 + src/.gitignore | 4 - src/node_api.cc | 3649 ------------------------------------ src/node_api.gyp | 21 - src/node_api.h | 588 ------ src/node_api_types.h | 115 -- src/node_internals.cc | 142 -- src/node_internals.h | 157 -- src/util-inl.h | 38 - src/util.h | 7 - tools/conversion.js | 4 - 18 files changed, 15 insertions(+), 4779 deletions(-) delete mode 100644 external-napi/node_api.h create mode 100644 node_api.gyp rename src/nothing.c => nothing.c (100%) delete mode 100644 src/.gitignore delete mode 100644 src/node_api.cc delete mode 100644 src/node_api.gyp delete mode 100644 src/node_api.h delete mode 100644 src/node_api_types.h delete mode 100644 src/node_internals.cc delete mode 100644 src/node_internals.h delete mode 100644 src/util-inl.h delete mode 100644 src/util.h diff --git a/.travis.yml b/.travis.yml index 008e9edf9..ec04b80ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,6 @@ env: # https://github.com/jasongin/nvs/blob/master/doc/CI.md - NVS_VERSION=1.4.2 matrix: - - NODEJS_VERSION=node/6 - - NODEJS_VERSION=node/8 - NODEJS_VERSION=node/10 - NODEJS_VERSION=node/12 - NODEJS_VERSION=node/13 @@ -22,7 +20,6 @@ matrix: fast_finish: true allow_failures: - env: NODEJS_VERSION=nightly - - env: NODEJS_VERSION=node/6 sudo: false cache: directories: diff --git a/common.gypi b/common.gypi index 812198aea..76f8d251c 100644 --- a/common.gypi +++ b/common.gypi @@ -18,7 +18,6 @@ ], 'defines': ['NODE_MAJOR_VERSION=<@(NODE_MAJOR_VERSION)'], 'include_dirs': [" 8 || - (versionArray[0] == 8 && versionArray[1] >= 6) || - (versionArray[0] == 6 && versionArray[1] >= 15) || - (versionArray[0] == 6 && versionArray[1] >= 14 && versionArray[2] >= 2)); - -// The flag is not needed when the Node version is not 8, nor if the API is -// built-in, because we removed the flag at the same time as creating the final -// incarnation of the built-in API. -var needsFlag = (!isNodeApiBuiltin && versionArray[0] == 8); - -var include = [__dirname]; -var gyp = path.join(__dirname, 'src', 'node_api.gyp'); - -if (isNodeApiBuiltin) { - gyp += ':nothing'; -} else { - gyp += ':node-api'; - include.unshift(path.join(__dirname, 'external-napi')); -} +const path = require('path'); module.exports = { - include: include.map(function(item) { - return '"' + item + '"'; - }).join(' '), - gyp: gyp, - isNodeApiBuiltin: isNodeApiBuiltin, - needsFlag: needsFlag + include: `"${__dirname}"`, + gyp: path.join(__dirname, 'nothing.gyp:nothing'), + isNodeApiBuiltin: true, + needsFlag: false }; diff --git a/node_api.gyp b/node_api.gyp new file mode 100644 index 000000000..4ff0ae7df --- /dev/null +++ b/node_api.gyp @@ -0,0 +1,9 @@ +{ + 'targets': [ + { + 'target_name': 'nothing', + 'type': 'static_library', + 'sources': [ 'nothing.c' ] + } + ] +} diff --git a/src/nothing.c b/nothing.c similarity index 100% rename from src/nothing.c rename to nothing.c diff --git a/package.json b/package.json index a03cef556..5e2d23d98 100644 --- a/package.json +++ b/package.json @@ -211,6 +211,7 @@ "safe-buffer": "^5.1.1" }, "directories": {}, + "gypfile": false, "homepage": "https://github.com/nodejs/node-addon-api", "keywords": [ "n-api", diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index b7a17b3df..000000000 --- a/src/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/Debug -/Release -/*.vcxproj* -/*.sln \ No newline at end of file diff --git a/src/node_api.cc b/src/node_api.cc deleted file mode 100644 index dd16016f3..000000000 --- a/src/node_api.cc +++ /dev/null @@ -1,3649 +0,0 @@ -/****************************************************************************** - * Experimental prototype for demonstrating VM agnostic and ABI stable API - * for native modules to use instead of using Nan and V8 APIs directly. - * - * The current status is "Experimental" and should not be used for - * production applications. The API is still subject to change - * and as an experimental feature is NOT subject to semver. - * - ******************************************************************************/ - -#include -#include -#include // INT_MAX -#include -#include -#include -#include -#include "node_api.h" -#include "node_internals.h" - -#define NAPI_VERSION 1 - -static -napi_status napi_set_last_error(napi_env env, napi_status error_code, - uint32_t engine_error_code = 0, - void* engine_reserved = nullptr); -static -napi_status napi_clear_last_error(napi_env env); - -struct napi_env__ { - explicit napi_env__(v8::Isolate* _isolate): isolate(_isolate), - has_instance_available(true), last_error() {} - ~napi_env__() { - last_exception.Reset(); - has_instance.Reset(); - wrap_template.Reset(); - } - v8::Isolate* isolate; - v8::Persistent last_exception; - v8::Persistent has_instance; - v8::Persistent wrap_template; - bool has_instance_available; - napi_extended_error_info last_error; - int open_handle_scopes = 0; -}; - -#define ENV_OBJECT_TEMPLATE(env, prefix, destination, field_count) \ - do { \ - if ((env)->prefix ## _template.IsEmpty()) { \ - (destination) = v8::ObjectTemplate::New(isolate); \ - (destination)->SetInternalFieldCount((field_count)); \ - (env)->prefix ## _template.Reset(isolate, (destination)); \ - } else { \ - (destination) = v8::Local::New( \ - isolate, env->prefix ## _template); \ - } \ - } while (0) - -#define RETURN_STATUS_IF_FALSE(env, condition, status) \ - do { \ - if (!(condition)) { \ - return napi_set_last_error((env), (status)); \ - } \ - } while (0) - -#define CHECK_ENV(env) \ - if ((env) == nullptr) { \ - return napi_invalid_arg; \ - } - -#define CHECK_ARG(env, arg) \ - RETURN_STATUS_IF_FALSE((env), ((arg) != nullptr), napi_invalid_arg) - -#define CHECK_MAYBE_EMPTY(env, maybe, status) \ - RETURN_STATUS_IF_FALSE((env), !((maybe).IsEmpty()), (status)) - -#define CHECK_MAYBE_NOTHING(env, maybe, status) \ - RETURN_STATUS_IF_FALSE((env), !((maybe).IsNothing()), (status)) - -// NAPI_PREAMBLE is not wrapped in do..while: try_catch must have function scope -#define NAPI_PREAMBLE(env) \ - CHECK_ENV((env)); \ - RETURN_STATUS_IF_FALSE((env), (env)->last_exception.IsEmpty(), \ - napi_pending_exception); \ - napi_clear_last_error((env)); \ - v8impl::TryCatch try_catch((env)) - -#define CHECK_TO_TYPE(env, type, context, result, src, status) \ - do { \ - CHECK_ARG((env), (src)); \ - auto maybe = v8impl::V8LocalValueFromJsValue((src))->To##type((context)); \ - CHECK_MAYBE_EMPTY((env), maybe, (status)); \ - (result) = maybe.ToLocalChecked(); \ - } while (0) - -#define CHECK_TO_FUNCTION(env, result, src) \ - do { \ - CHECK_ARG((env), (src)); \ - v8::Local v8value = v8impl::V8LocalValueFromJsValue((src)); \ - RETURN_STATUS_IF_FALSE((env), v8value->IsFunction(), napi_invalid_arg); \ - (result) = v8value.As(); \ - } while (0) - -#define CHECK_TO_OBJECT(env, context, result, src) \ - CHECK_TO_TYPE((env), Object, (context), (result), (src), napi_object_expected) - -#define CHECK_TO_STRING(env, context, result, src) \ - CHECK_TO_TYPE((env), String, (context), (result), (src), napi_string_expected) - -#define CHECK_TO_NUMBER(env, context, result, src) \ - CHECK_TO_TYPE((env), Number, (context), (result), (src), napi_number_expected) - -#define CHECK_TO_BOOL(env, context, result, src) \ - CHECK_TO_TYPE((env), Boolean, (context), (result), (src), \ - napi_boolean_expected) - -// n-api defines NAPI_AUTO_LENGHTH as the indicator that a string -// is null terminated. For V8 the equivalent is -1. The assert -// validates that our cast of NAPI_AUTO_LENGTH results in -1 as -// needed by V8. -#define CHECK_NEW_FROM_UTF8_LEN(env, result, str, len) \ - do { \ - static_assert(static_cast(NAPI_AUTO_LENGTH) == -1, \ - "Casting NAPI_AUTO_LENGTH to int must result in -1"); \ - RETURN_STATUS_IF_FALSE((env), \ - (len == NAPI_AUTO_LENGTH) || len <= INT_MAX, \ - napi_invalid_arg); \ - auto str_maybe = v8::String::NewFromUtf8( \ - (env)->isolate, (str), v8::NewStringType::kInternalized, \ - static_cast(len)); \ - CHECK_MAYBE_EMPTY((env), str_maybe, napi_generic_failure); \ - (result) = str_maybe.ToLocalChecked(); \ - } while (0) - -#define CHECK_NEW_FROM_UTF8(env, result, str) \ - CHECK_NEW_FROM_UTF8_LEN((env), (result), (str), NAPI_AUTO_LENGTH) - -#define GET_RETURN_STATUS(env) \ - (!try_catch.HasCaught() ? napi_ok \ - : napi_set_last_error((env), napi_pending_exception)) - -#define THROW_RANGE_ERROR_IF_FALSE(env, condition, error, message) \ - do { \ - if (!(condition)) { \ - napi_throw_range_error((env), (error), (message)); \ - return napi_set_last_error((env), napi_generic_failure); \ - } \ - } while (0) - -#define CREATE_TYPED_ARRAY( \ - env, type, size_of_element, buffer, byte_offset, length, out) \ - do { \ - if ((size_of_element) > 1) { \ - THROW_RANGE_ERROR_IF_FALSE( \ - (env), (byte_offset) % (size_of_element) == 0, \ - "ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT", \ - "start offset of "#type" should be a multiple of "#size_of_element); \ - } \ - THROW_RANGE_ERROR_IF_FALSE((env), (length) * (size_of_element) + \ - (byte_offset) <= buffer->ByteLength(), \ - "ERR_NAPI_INVALID_TYPEDARRAY_LENGTH", \ - "Invalid typed array length"); \ - (out) = v8::type::New((buffer), (byte_offset), (length)); \ - } while (0) - -#define NAPI_CALL_INTO_MODULE(env, call, handle_exception) \ - do { \ - int open_handle_scopes = (env)->open_handle_scopes; \ - napi_clear_last_error((env)); \ - call; \ - CHECK_EQ((env)->open_handle_scopes, open_handle_scopes); \ - if (!(env)->last_exception.IsEmpty()) { \ - handle_exception( \ - v8::Local::New((env)->isolate, (env)->last_exception)); \ - (env)->last_exception.Reset(); \ - } \ - } while (0) - -#define NAPI_CALL_INTO_MODULE_THROW(env, call) \ - NAPI_CALL_INTO_MODULE((env), call, (env)->isolate->ThrowException) - -namespace { -namespace v8impl { - -// convert from n-api property attributes to v8::PropertyAttribute -static inline v8::PropertyAttribute V8PropertyAttributesFromDescriptor( - const napi_property_descriptor* descriptor) { - unsigned int attribute_flags = v8::PropertyAttribute::None; - - if (descriptor->getter != nullptr || descriptor->setter != nullptr) { - // The napi_writable attribute is ignored for accessor descriptors, but - // V8 requires the ReadOnly attribute to match nonexistence of a setter. - attribute_flags |= (descriptor->setter == nullptr ? - v8::PropertyAttribute::ReadOnly : v8::PropertyAttribute::None); - } else if ((descriptor->attributes & napi_writable) == 0) { - attribute_flags |= v8::PropertyAttribute::ReadOnly; - } - - if ((descriptor->attributes & napi_enumerable) == 0) { - attribute_flags |= v8::PropertyAttribute::DontEnum; - } - if ((descriptor->attributes & napi_configurable) == 0) { - attribute_flags |= v8::PropertyAttribute::DontDelete; - } - - return static_cast(attribute_flags); -} - -class HandleScopeWrapper { - public: - explicit HandleScopeWrapper(v8::Isolate* isolate) : scope(isolate) {} - - private: - v8::HandleScope scope; -}; - -// In node v0.10 version of v8, there is no EscapableHandleScope and the -// node v0.10 port use HandleScope::Close(Local v) to mimic the behavior -// of a EscapableHandleScope::Escape(Local v), but it is not the same -// semantics. This is an example of where the api abstraction fail to work -// across different versions. -class EscapableHandleScopeWrapper { - public: - explicit EscapableHandleScopeWrapper(v8::Isolate* isolate) - : scope(isolate), escape_called_(false) {} - bool escape_called() const { - return escape_called_; - } - template - v8::Local Escape(v8::Local handle) { - escape_called_ = true; - return scope.Escape(handle); - } - - private: - v8::EscapableHandleScope scope; - bool escape_called_; -}; - -static -napi_handle_scope JsHandleScopeFromV8HandleScope(HandleScopeWrapper* s) { - return reinterpret_cast(s); -} - -static -HandleScopeWrapper* V8HandleScopeFromJsHandleScope(napi_handle_scope s) { - return reinterpret_cast(s); -} - -static -napi_escapable_handle_scope JsEscapableHandleScopeFromV8EscapableHandleScope( - EscapableHandleScopeWrapper* s) { - return reinterpret_cast(s); -} - -static -EscapableHandleScopeWrapper* -V8EscapableHandleScopeFromJsEscapableHandleScope( - napi_escapable_handle_scope s) { - return reinterpret_cast(s); -} - -//=== Conversion between V8 Handles and napi_value ======================== - -// This asserts v8::Local<> will always be implemented with a single -// pointer field so that we can pass it around as a void*. -static_assert(sizeof(v8::Local) == sizeof(napi_value), - "Cannot convert between v8::Local and napi_value"); - -static -napi_deferred JsDeferredFromV8Persistent(v8::Persistent* local) { - return reinterpret_cast(local); -} - -static -v8::Persistent* V8PersistentFromJsDeferred(napi_deferred local) { - return reinterpret_cast*>(local); -} - -static -napi_value JsValueFromV8LocalValue(v8::Local local) { - return reinterpret_cast(*local); -} - -static -v8::Local V8LocalValueFromJsValue(napi_value v) { - v8::Local local; - memcpy(&local, &v, sizeof(v)); - return local; -} - -static inline void trigger_fatal_exception( - napi_env env, v8::Local local_err) { - v8::TryCatch try_catch(env->isolate); - env->isolate->ThrowException(local_err); - node::FatalException(env->isolate, try_catch); -} - -static inline napi_status V8NameFromPropertyDescriptor(napi_env env, - const napi_property_descriptor* p, - v8::Local* result) { - if (p->utf8name != nullptr) { - CHECK_NEW_FROM_UTF8(env, *result, p->utf8name); - } else { - v8::Local property_value = - v8impl::V8LocalValueFromJsValue(p->name); - - RETURN_STATUS_IF_FALSE(env, property_value->IsName(), napi_name_expected); - *result = property_value.As(); - } - - return napi_ok; -} - -// Adapter for napi_finalize callbacks. -class Finalizer { - protected: - Finalizer(napi_env env, - napi_finalize finalize_callback, - void* finalize_data, - void* finalize_hint) - : _env(env), - _finalize_callback(finalize_callback), - _finalize_data(finalize_data), - _finalize_hint(finalize_hint) { - } - - ~Finalizer() { - } - - public: - static Finalizer* New(napi_env env, - napi_finalize finalize_callback = nullptr, - void* finalize_data = nullptr, - void* finalize_hint = nullptr) { - return new Finalizer( - env, finalize_callback, finalize_data, finalize_hint); - } - - static void Delete(Finalizer* finalizer) { - delete finalizer; - } - - // node::Buffer::FreeCallback - static void FinalizeBufferCallback(char* data, void* hint) { - Finalizer* finalizer = static_cast(hint); - if (finalizer->_finalize_callback != nullptr) { - NAPI_CALL_INTO_MODULE_THROW(finalizer->_env, - finalizer->_finalize_callback( - finalizer->_env, - data, - finalizer->_finalize_hint)); - } - - Delete(finalizer); - } - - protected: - napi_env _env; - napi_finalize _finalize_callback; - void* _finalize_data; - void* _finalize_hint; -}; - -// Wrapper around v8::Persistent that implements reference counting. -class Reference : private Finalizer { - private: - Reference(napi_env env, - v8::Local value, - uint32_t initial_refcount, - bool delete_self, - napi_finalize finalize_callback, - void* finalize_data, - void* finalize_hint) - : Finalizer(env, finalize_callback, finalize_data, finalize_hint), - _persistent(env->isolate, value), - _refcount(initial_refcount), - _delete_self(delete_self) { - if (initial_refcount == 0) { - _persistent.SetWeak( - this, FinalizeCallback, v8::WeakCallbackType::kParameter); - _persistent.MarkIndependent(); - } - } - - ~Reference() { - // The V8 Persistent class currently does not reset in its destructor: - // see NonCopyablePersistentTraits::kResetInDestructor = false. - // (Comments there claim that might change in the future.) - // To avoid memory leaks, it is better to reset at this time, however - // care must be taken to avoid attempting this after the Isolate has - // shut down, for example via a static (atexit) destructor. - _persistent.Reset(); - } - - public: - static Reference* New(napi_env env, - v8::Local value, - uint32_t initial_refcount, - bool delete_self, - napi_finalize finalize_callback = nullptr, - void* finalize_data = nullptr, - void* finalize_hint = nullptr) { - return new Reference(env, - value, - initial_refcount, - delete_self, - finalize_callback, - finalize_data, - finalize_hint); - } - - static void Delete(Reference* reference) { - delete reference; - } - - uint32_t Ref() { - if (++_refcount == 1) { - _persistent.ClearWeak(); - } - - return _refcount; - } - - uint32_t Unref() { - if (_refcount == 0) { - return 0; - } - if (--_refcount == 0) { - _persistent.SetWeak( - this, FinalizeCallback, v8::WeakCallbackType::kParameter); - _persistent.MarkIndependent(); - } - - return _refcount; - } - - uint32_t RefCount() { - return _refcount; - } - - v8::Local Get() { - if (_persistent.IsEmpty()) { - return v8::Local(); - } else { - return v8::Local::New(_env->isolate, _persistent); - } - } - - private: - static void FinalizeCallback(const v8::WeakCallbackInfo& data) { - Reference* reference = data.GetParameter(); - reference->_persistent.Reset(); - - // Check before calling the finalize callback, because the callback might - // delete it. - bool delete_self = reference->_delete_self; - napi_env env = reference->_env; - - if (reference->_finalize_callback != nullptr) { - NAPI_CALL_INTO_MODULE_THROW(env, - reference->_finalize_callback( - reference->_env, - reference->_finalize_data, - reference->_finalize_hint)); - } - - if (delete_self) { - Delete(reference); - } - } - - v8::Persistent _persistent; - uint32_t _refcount; - bool _delete_self; -}; - -class TryCatch : public v8::TryCatch { - public: - explicit TryCatch(napi_env env) - : v8::TryCatch(env->isolate), _env(env) {} - - ~TryCatch() { - if (HasCaught()) { - _env->last_exception.Reset(_env->isolate, Exception()); - } - } - - private: - napi_env _env; -}; - -//=== Function napi_callback wrapper ================================= - -// Use this data structure to associate callback data with each N-API function -// exposed to JavaScript. The structure is stored in a v8::External which gets -// passed into our callback wrapper. This reduces the performance impact of -// calling through N-API. -// Ref: benchmark/misc/function_call -// Discussion (incl. perf. data): https://github.com/nodejs/node/pull/21072 -struct CallbackBundle { - // Bind the lifecycle of `this` C++ object to a JavaScript object. - // We never delete a CallbackBundle C++ object directly. - void BindLifecycleTo(v8::Isolate* isolate, v8::Local target) { - handle.Reset(isolate, target); - handle.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); - } - - napi_env env; // Necessary to invoke C++ NAPI callback - void* cb_data; // The user provided callback data - napi_callback function_or_getter; - napi_callback setter; - node::Persistent handle; // Die with this JavaScript object - - private: - static void WeakCallback(v8::WeakCallbackInfo const& info) { - // Use the "WeakCallback mechanism" to delete the C++ `bundle` object. - // This will be called when the v8::External containing `this` pointer - // is being GC-ed. - CallbackBundle* bundle = info.GetParameter(); - if (bundle != nullptr) { - delete bundle; - } - } -}; - -// Base class extended by classes that wrap V8 function and property callback -// info. -class CallbackWrapper { - public: - CallbackWrapper(napi_value this_arg, size_t args_length, void* data) - : _this(this_arg), _args_length(args_length), _data(data) {} - - virtual napi_value GetNewTarget() = 0; - virtual void Args(napi_value* buffer, size_t bufferlength) = 0; - virtual void SetReturnValue(napi_value value) = 0; - - napi_value This() { return _this; } - - size_t ArgsLength() { return _args_length; } - - void* Data() { return _data; } - - protected: - const napi_value _this; - const size_t _args_length; - void* _data; -}; - -template -class CallbackWrapperBase : public CallbackWrapper { - public: - CallbackWrapperBase(const Info& cbinfo, const size_t args_length) - : CallbackWrapper(JsValueFromV8LocalValue(cbinfo.This()), - args_length, - nullptr), - _cbinfo(cbinfo) { - _bundle = reinterpret_cast( - v8::Local::Cast(cbinfo.Data())->Value()); - _data = _bundle->cb_data; - } - - napi_value GetNewTarget() override { return nullptr; } - - protected: - void InvokeCallback() { - napi_callback_info cbinfo_wrapper = reinterpret_cast( - static_cast(this)); - - // All other pointers we need are stored in `_bundle` - napi_env env = _bundle->env; - napi_callback cb = _bundle->*FunctionField; - - napi_value result; - NAPI_CALL_INTO_MODULE_THROW(env, result = cb(env, cbinfo_wrapper)); - - if (result != nullptr) { - this->SetReturnValue(result); - } - } - - const Info& _cbinfo; - CallbackBundle* _bundle; -}; - -class FunctionCallbackWrapper - : public CallbackWrapperBase, - &CallbackBundle::function_or_getter> { - public: - static void Invoke(const v8::FunctionCallbackInfo& info) { - FunctionCallbackWrapper cbwrapper(info); - cbwrapper.InvokeCallback(); - } - - explicit FunctionCallbackWrapper( - const v8::FunctionCallbackInfo& cbinfo) - : CallbackWrapperBase(cbinfo, cbinfo.Length()) {} - - napi_value GetNewTarget() override { - if (_cbinfo.IsConstructCall()) { - return v8impl::JsValueFromV8LocalValue(_cbinfo.NewTarget()); - } else { - return nullptr; - } - } - - /*virtual*/ - void Args(napi_value* buffer, size_t buffer_length) override { - size_t i = 0; - size_t min = std::min(buffer_length, _args_length); - - for (; i < min; i += 1) { - buffer[i] = v8impl::JsValueFromV8LocalValue(_cbinfo[i]); - } - - if (i < buffer_length) { - napi_value undefined = - v8impl::JsValueFromV8LocalValue(v8::Undefined(_cbinfo.GetIsolate())); - for (; i < buffer_length; i += 1) { - buffer[i] = undefined; - } - } - } - - /*virtual*/ - void SetReturnValue(napi_value value) override { - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - _cbinfo.GetReturnValue().Set(val); - } -}; - -class GetterCallbackWrapper - : public CallbackWrapperBase, - &CallbackBundle::function_or_getter> { - public: - static void Invoke(v8::Local property, - const v8::PropertyCallbackInfo& info) { - GetterCallbackWrapper cbwrapper(info); - cbwrapper.InvokeCallback(); - } - - explicit GetterCallbackWrapper( - const v8::PropertyCallbackInfo& cbinfo) - : CallbackWrapperBase(cbinfo, 0) {} - - /*virtual*/ - void Args(napi_value* buffer, size_t buffer_length) override { - if (buffer_length > 0) { - napi_value undefined = - v8impl::JsValueFromV8LocalValue(v8::Undefined(_cbinfo.GetIsolate())); - for (size_t i = 0; i < buffer_length; i += 1) { - buffer[i] = undefined; - } - } - } - - /*virtual*/ - void SetReturnValue(napi_value value) override { - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - _cbinfo.GetReturnValue().Set(val); - } -}; - -class SetterCallbackWrapper - : public CallbackWrapperBase, - &CallbackBundle::setter> { - public: - static void Invoke(v8::Local property, - v8::Local value, - const v8::PropertyCallbackInfo& info) { - SetterCallbackWrapper cbwrapper(info, value); - cbwrapper.InvokeCallback(); - } - - SetterCallbackWrapper(const v8::PropertyCallbackInfo& cbinfo, - const v8::Local& value) - : CallbackWrapperBase(cbinfo, 1), _value(value) {} - - /*virtual*/ - void Args(napi_value* buffer, size_t buffer_length) override { - if (buffer_length > 0) { - buffer[0] = v8impl::JsValueFromV8LocalValue(_value); - - if (buffer_length > 1) { - napi_value undefined = v8impl::JsValueFromV8LocalValue( - v8::Undefined(_cbinfo.GetIsolate())); - for (size_t i = 1; i < buffer_length; i += 1) { - buffer[i] = undefined; - } - } - } - } - - /*virtual*/ - void SetReturnValue(napi_value value) override { - // Ignore any value returned from a setter callback. - } - - private: - const v8::Local& _value; -}; - -// Creates an object to be made available to the static function callback -// wrapper, used to retrieve the native callback function and data pointer. -static -v8::Local CreateFunctionCallbackData(napi_env env, - napi_callback cb, - void* data) { - CallbackBundle* bundle = new CallbackBundle(); - bundle->function_or_getter = cb; - bundle->cb_data = data; - bundle->env = env; - v8::Local cbdata = v8::External::New(env->isolate, bundle); - bundle->BindLifecycleTo(env->isolate, cbdata); - - return cbdata; -} - -// Creates an object to be made available to the static getter/setter -// callback wrapper, used to retrieve the native getter/setter callback -// function and data pointer. -static -v8::Local CreateAccessorCallbackData(napi_env env, - napi_callback getter, - napi_callback setter, - void* data) { - CallbackBundle* bundle = new CallbackBundle(); - bundle->function_or_getter = getter; - bundle->setter = setter; - bundle->cb_data = data; - bundle->env = env; - v8::Local cbdata = v8::External::New(env->isolate, bundle); - bundle->BindLifecycleTo(env->isolate, cbdata); - - return cbdata; -} - -int kWrapperFields = 3; - -// Pointer used to identify items wrapped by N-API. Used by FindWrapper and -// napi_wrap(). -const char napi_wrap_name[] = "N-API Wrapper"; - -// Search the object's prototype chain for the wrapper object. Usually the -// wrapper would be the first in the chain, but it is OK for other objects to -// be inserted in the prototype chain. -static -bool FindWrapper(v8::Local obj, - v8::Local* result = nullptr, - v8::Local* parent = nullptr) { - v8::Local wrapper = obj; - - do { - v8::Local proto = wrapper->GetPrototype(); - if (proto.IsEmpty() || !proto->IsObject()) { - return false; - } - if (parent != nullptr) { - *parent = wrapper; - } - wrapper = proto.As(); - if (wrapper->InternalFieldCount() == kWrapperFields) { - v8::Local external = wrapper->GetInternalField(1); - if (external->IsExternal() && - external.As()->Value() == v8impl::napi_wrap_name) { - break; - } - } - } while (true); - - if (result != nullptr) { - *result = wrapper; - } - return true; -} - -static void DeleteEnv(napi_env env, void* data, void* hint) { - delete env; -} - -static -napi_env GetEnv(v8::Local context) { - napi_env result; - - auto isolate = context->GetIsolate(); - auto global = context->Global(); - - // In the case of the string for which we grab the private and the value of - // the private on the global object we can call .ToLocalChecked() directly - // because we need to stop hard if either of them is empty. - // - // Re https://github.com/nodejs/node/pull/14217#discussion_r128775149 - auto key = v8::Private::ForApi(isolate, - v8::String::NewFromOneByte(isolate, - reinterpret_cast("N-API Environment"), - v8::NewStringType::kInternalized).ToLocalChecked()); - auto value = global->GetPrivate(context, key).ToLocalChecked(); - - if (value->IsExternal()) { - result = static_cast(value.As()->Value()); - } else { - result = new napi_env__(isolate); - auto external = v8::External::New(isolate, result); - - // We must also stop hard if the result of assigning the env to the global - // is either nothing or false. - CHECK(global->SetPrivate(context, key, external).FromJust()); - - // Create a self-destructing reference to external that will get rid of the - // napi_env when external goes out of scope. - Reference::New(result, external, 0, true, DeleteEnv, nullptr, nullptr); - } - - return result; -} - -static -napi_status Unwrap(napi_env env, - napi_value js_object, - void** result, - v8::Local* wrapper, - v8::Local* parent = nullptr) { - CHECK_ARG(env, js_object); - CHECK_ARG(env, result); - - v8::Local value = v8impl::V8LocalValueFromJsValue(js_object); - RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg); - v8::Local obj = value.As(); - - RETURN_STATUS_IF_FALSE( - env, v8impl::FindWrapper(obj, wrapper, parent), napi_invalid_arg); - - v8::Local unwrappedValue = (*wrapper)->GetInternalField(0); - RETURN_STATUS_IF_FALSE(env, unwrappedValue->IsExternal(), napi_invalid_arg); - - *result = unwrappedValue.As()->Value(); - - return napi_ok; -} - -static -napi_status ConcludeDeferred(napi_env env, - napi_deferred deferred, - napi_value result, - bool is_resolved) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::Local context = env->isolate->GetCurrentContext(); - v8::Persistent* deferred_ref = - V8PersistentFromJsDeferred(deferred); - v8::Local v8_deferred = - v8::Local::New(env->isolate, *deferred_ref); - - auto v8_resolver = v8::Local::Cast(v8_deferred); - - v8::Maybe success = is_resolved ? - v8_resolver->Resolve(context, v8impl::V8LocalValueFromJsValue(result)) : - v8_resolver->Reject(context, v8impl::V8LocalValueFromJsValue(result)); - - deferred_ref->Reset(); - delete deferred_ref; - - RETURN_STATUS_IF_FALSE(env, success.FromMaybe(false), napi_generic_failure); - - return GET_RETURN_STATUS(env); -} - -} // end of namespace v8impl - -// Intercepts the Node-V8 module registration callback. Converts parameters -// to NAPI equivalents and then calls the registration callback specified -// by the NAPI module. -void napi_module_register_cb(v8::Local exports, - v8::Local module, - v8::Local context, - void* priv) { - napi_module* mod = static_cast(priv); - - // Create a new napi_env for this module or reference one if a pre-existing - // one is found. - napi_env env = v8impl::GetEnv(context); - - napi_value _exports; - NAPI_CALL_INTO_MODULE_THROW(env, - _exports = mod->nm_register_func(env, - v8impl::JsValueFromV8LocalValue(exports))); - - // If register function returned a non-null exports object different from - // the exports object we passed it, set that as the "exports" property of - // the module. - if (_exports != nullptr && - _exports != v8impl::JsValueFromV8LocalValue(exports)) { - napi_value _module = v8impl::JsValueFromV8LocalValue(module); - napi_set_named_property(env, _module, "exports", _exports); - } -} - -} // end of anonymous namespace - -// Registers a NAPI module. -void napi_module_register(napi_module* mod) { - node::node_module* nm = new node::node_module { - NODE_MODULE_VERSION, - mod->nm_flags, - nullptr, - mod->nm_filename, - nullptr, - napi_module_register_cb, - mod->nm_modname, - mod, // priv - nullptr, - }; - node::node_module_register(nm); -} - -// Warning: Keep in-sync with napi_status enum -static -const char* error_messages[] = {nullptr, - "Invalid argument", - "An object was expected", - "A string was expected", - "A string or symbol was expected", - "A function was expected", - "A number was expected", - "A boolean was expected", - "An array was expected", - "Unknown failure", - "An exception is pending", - "The async work item was cancelled", - "napi_escape_handle already called on scope"}; - -static inline napi_status napi_clear_last_error(napi_env env) { - env->last_error.error_code = napi_ok; - - // TODO(boingoing): Should this be a callback? - env->last_error.engine_error_code = 0; - env->last_error.engine_reserved = nullptr; - return napi_ok; -} - -static inline -napi_status napi_set_last_error(napi_env env, napi_status error_code, - uint32_t engine_error_code, - void* engine_reserved) { - env->last_error.error_code = error_code; - env->last_error.engine_error_code = engine_error_code; - env->last_error.engine_reserved = engine_reserved; - return error_code; -} - -napi_status napi_get_last_error_info(napi_env env, - const napi_extended_error_info** result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - // you must update this assert to reference the last message - // in the napi_status enum each time a new error message is added. - // We don't have a napi_status_last as this would result in an ABI - // change each time a message was added. - static_assert( - node::arraysize(error_messages) == napi_escape_called_twice + 1, - "Count of error messages must match count of error values"); - CHECK_LE(env->last_error.error_code, napi_escape_called_twice); - - // Wait until someone requests the last error information to fetch the error - // message string - env->last_error.error_message = - error_messages[env->last_error.error_code]; - - *result = &(env->last_error); - return napi_ok; -} - -napi_status napi_fatal_exception(napi_env env, napi_value err) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, err); - - v8::Local local_err = v8impl::V8LocalValueFromJsValue(err); - v8impl::trigger_fatal_exception(env, local_err); - - return napi_clear_last_error(env); -} - -NAPI_NO_RETURN void napi_fatal_error(const char* location, - size_t location_len, - const char* message, - size_t message_len) { - std::string location_string; - std::string message_string; - - if (location_len != NAPI_AUTO_LENGTH) { - location_string.assign( - const_cast(location), location_len); - } else { - location_string.assign( - const_cast(location), strlen(location)); - } - - if (message_len != NAPI_AUTO_LENGTH) { - message_string.assign( - const_cast(message), message_len); - } else { - message_string.assign( - const_cast(message), strlen(message)); - } - - node::FatalError(location_string.c_str(), message_string.c_str()); -} - -napi_status napi_create_function(napi_env env, - const char* utf8name, - size_t length, - napi_callback cb, - void* callback_data, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - CHECK_ARG(env, cb); - - v8::Isolate* isolate = env->isolate; - v8::Local return_value; - v8::EscapableHandleScope scope(isolate); - v8::Local cbdata = - v8impl::CreateFunctionCallbackData(env, cb, callback_data); - - RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); - - v8::Local context = isolate->GetCurrentContext(); - v8::MaybeLocal maybe_function = - v8::Function::New(context, - v8impl::FunctionCallbackWrapper::Invoke, - cbdata); - CHECK_MAYBE_EMPTY(env, maybe_function, napi_generic_failure); - - return_value = scope.Escape(maybe_function.ToLocalChecked()); - - if (utf8name != nullptr) { - v8::Local name_string; - CHECK_NEW_FROM_UTF8_LEN(env, name_string, utf8name, length); - return_value->SetName(name_string); - } - - *result = v8impl::JsValueFromV8LocalValue(return_value); - - return GET_RETURN_STATUS(env); -} - -napi_status napi_define_class(napi_env env, - const char* utf8name, - size_t length, - napi_callback constructor, - void* callback_data, - size_t property_count, - const napi_property_descriptor* properties, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - CHECK_ARG(env, constructor); - - v8::Isolate* isolate = env->isolate; - - v8::EscapableHandleScope scope(isolate); - v8::Local cbdata = - v8impl::CreateFunctionCallbackData(env, constructor, callback_data); - - RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); - - v8::Local tpl = v8::FunctionTemplate::New( - isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); - - v8::Local name_string; - CHECK_NEW_FROM_UTF8_LEN(env, name_string, utf8name, length); - tpl->SetClassName(name_string); - - size_t static_property_count = 0; - for (size_t i = 0; i < property_count; i++) { - const napi_property_descriptor* p = properties + i; - - if ((p->attributes & napi_static) != 0) { - // Static properties are handled separately below. - static_property_count++; - continue; - } - - v8::Local property_name; - napi_status status = - v8impl::V8NameFromPropertyDescriptor(env, p, &property_name); - - if (status != napi_ok) { - return napi_set_last_error(env, status); - } - - v8::PropertyAttribute attributes = - v8impl::V8PropertyAttributesFromDescriptor(p); - - // This code is similar to that in napi_define_properties(); the - // difference is it applies to a template instead of an object. - if (p->getter != nullptr || p->setter != nullptr) { - v8::Local cbdata = v8impl::CreateAccessorCallbackData( - env, p->getter, p->setter, p->data); - - tpl->PrototypeTemplate()->SetAccessor( - property_name, - p->getter ? v8impl::GetterCallbackWrapper::Invoke : nullptr, - p->setter ? v8impl::SetterCallbackWrapper::Invoke : nullptr, - cbdata, - v8::AccessControl::DEFAULT, - attributes); - } else if (p->method != nullptr) { - v8::Local cbdata = - v8impl::CreateFunctionCallbackData(env, p->method, p->data); - - RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); - - v8::Local t = - v8::FunctionTemplate::New(isolate, - v8impl::FunctionCallbackWrapper::Invoke, - cbdata, - v8::Signature::New(isolate, tpl)); - - tpl->PrototypeTemplate()->Set(property_name, t, attributes); - } else { - v8::Local value = v8impl::V8LocalValueFromJsValue(p->value); - tpl->PrototypeTemplate()->Set(property_name, value, attributes); - } - } - - *result = v8impl::JsValueFromV8LocalValue(scope.Escape(tpl->GetFunction())); - - if (static_property_count > 0) { - std::vector static_descriptors; - static_descriptors.reserve(static_property_count); - - for (size_t i = 0; i < property_count; i++) { - const napi_property_descriptor* p = properties + i; - if ((p->attributes & napi_static) != 0) { - static_descriptors.push_back(*p); - } - } - - napi_status status = - napi_define_properties(env, - *result, - static_descriptors.size(), - static_descriptors.data()); - if (status != napi_ok) return status; - } - - return GET_RETURN_STATUS(env); -} - -napi_status napi_get_property_names(napi_env env, - napi_value object, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local obj; - CHECK_TO_OBJECT(env, context, obj, object); - - auto maybe_propertynames = obj->GetPropertyNames(context); - - CHECK_MAYBE_EMPTY(env, maybe_propertynames, napi_generic_failure); - - *result = v8impl::JsValueFromV8LocalValue( - maybe_propertynames.ToLocalChecked()); - return GET_RETURN_STATUS(env); -} - -napi_status napi_set_property(napi_env env, - napi_value object, - napi_value key, - napi_value value) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, key); - CHECK_ARG(env, value); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - - v8::Local k = v8impl::V8LocalValueFromJsValue(key); - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - - v8::Maybe set_maybe = obj->Set(context, k, val); - - RETURN_STATUS_IF_FALSE(env, set_maybe.FromMaybe(false), napi_generic_failure); - return GET_RETURN_STATUS(env); -} - -napi_status napi_has_property(napi_env env, - napi_value object, - napi_value key, - bool* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - CHECK_ARG(env, key); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - - v8::Local k = v8impl::V8LocalValueFromJsValue(key); - v8::Maybe has_maybe = obj->Has(context, k); - - CHECK_MAYBE_NOTHING(env, has_maybe, napi_generic_failure); - - *result = has_maybe.FromMaybe(false); - return GET_RETURN_STATUS(env); -} - -napi_status napi_get_property(napi_env env, - napi_value object, - napi_value key, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, key); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local k = v8impl::V8LocalValueFromJsValue(key); - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - - auto get_maybe = obj->Get(context, k); - - CHECK_MAYBE_EMPTY(env, get_maybe, napi_generic_failure); - - v8::Local val = get_maybe.ToLocalChecked(); - *result = v8impl::JsValueFromV8LocalValue(val); - return GET_RETURN_STATUS(env); -} - -napi_status napi_delete_property(napi_env env, - napi_value object, - napi_value key, - bool* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, key); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local k = v8impl::V8LocalValueFromJsValue(key); - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - v8::Maybe delete_maybe = obj->Delete(context, k); - CHECK_MAYBE_NOTHING(env, delete_maybe, napi_generic_failure); - - if (result != NULL) - *result = delete_maybe.FromMaybe(false); - - return GET_RETURN_STATUS(env); -} - -napi_status napi_has_own_property(napi_env env, - napi_value object, - napi_value key, - bool* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, key); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - v8::Local k = v8impl::V8LocalValueFromJsValue(key); - RETURN_STATUS_IF_FALSE(env, k->IsName(), napi_name_expected); - v8::Maybe has_maybe = obj->HasOwnProperty(context, k.As()); - CHECK_MAYBE_NOTHING(env, has_maybe, napi_generic_failure); - *result = has_maybe.FromMaybe(false); - - return GET_RETURN_STATUS(env); -} - -napi_status napi_set_named_property(napi_env env, - napi_value object, - const char* utf8name, - napi_value value) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, value); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - - v8::Local key; - CHECK_NEW_FROM_UTF8(env, key, utf8name); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - - v8::Maybe set_maybe = obj->Set(context, key, val); - - RETURN_STATUS_IF_FALSE(env, set_maybe.FromMaybe(false), napi_generic_failure); - return GET_RETURN_STATUS(env); -} - -napi_status napi_has_named_property(napi_env env, - napi_value object, - const char* utf8name, - bool* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - - v8::Local key; - CHECK_NEW_FROM_UTF8(env, key, utf8name); - - v8::Maybe has_maybe = obj->Has(context, key); - - CHECK_MAYBE_NOTHING(env, has_maybe, napi_generic_failure); - - *result = has_maybe.FromMaybe(false); - return GET_RETURN_STATUS(env); -} - -napi_status napi_get_named_property(napi_env env, - napi_value object, - const char* utf8name, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - - v8::Local key; - CHECK_NEW_FROM_UTF8(env, key, utf8name); - - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - - auto get_maybe = obj->Get(context, key); - - CHECK_MAYBE_EMPTY(env, get_maybe, napi_generic_failure); - - v8::Local val = get_maybe.ToLocalChecked(); - *result = v8impl::JsValueFromV8LocalValue(val); - return GET_RETURN_STATUS(env); -} - -napi_status napi_set_element(napi_env env, - napi_value object, - uint32_t index, - napi_value value) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, value); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - auto set_maybe = obj->Set(context, index, val); - - RETURN_STATUS_IF_FALSE(env, set_maybe.FromMaybe(false), napi_generic_failure); - - return GET_RETURN_STATUS(env); -} - -napi_status napi_has_element(napi_env env, - napi_value object, - uint32_t index, - bool* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - - v8::Maybe has_maybe = obj->Has(context, index); - - CHECK_MAYBE_NOTHING(env, has_maybe, napi_generic_failure); - - *result = has_maybe.FromMaybe(false); - return GET_RETURN_STATUS(env); -} - -napi_status napi_get_element(napi_env env, - napi_value object, - uint32_t index, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - - auto get_maybe = obj->Get(context, index); - - CHECK_MAYBE_EMPTY(env, get_maybe, napi_generic_failure); - - *result = v8impl::JsValueFromV8LocalValue(get_maybe.ToLocalChecked()); - return GET_RETURN_STATUS(env); -} - -napi_status napi_delete_element(napi_env env, - napi_value object, - uint32_t index, - bool* result) { - NAPI_PREAMBLE(env); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local obj; - - CHECK_TO_OBJECT(env, context, obj, object); - v8::Maybe delete_maybe = obj->Delete(context, index); - CHECK_MAYBE_NOTHING(env, delete_maybe, napi_generic_failure); - - if (result != NULL) - *result = delete_maybe.FromMaybe(false); - - return GET_RETURN_STATUS(env); -} - -napi_status napi_define_properties(napi_env env, - napi_value object, - size_t property_count, - const napi_property_descriptor* properties) { - NAPI_PREAMBLE(env); - if (property_count > 0) { - CHECK_ARG(env, properties); - } - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - - v8::Local obj; - CHECK_TO_OBJECT(env, context, obj, object); - - for (size_t i = 0; i < property_count; i++) { - const napi_property_descriptor* p = &properties[i]; - - v8::Local property_name; - napi_status status = - v8impl::V8NameFromPropertyDescriptor(env, p, &property_name); - - if (status != napi_ok) { - return napi_set_last_error(env, status); - } - - v8::PropertyAttribute attributes = - v8impl::V8PropertyAttributesFromDescriptor(p); - - if (p->getter != nullptr || p->setter != nullptr) { - v8::Local cbdata = v8impl::CreateAccessorCallbackData( - env, - p->getter, - p->setter, - p->data); - - auto set_maybe = obj->SetAccessor( - context, - property_name, - p->getter ? v8impl::GetterCallbackWrapper::Invoke : nullptr, - p->setter ? v8impl::SetterCallbackWrapper::Invoke : nullptr, - cbdata, - v8::AccessControl::DEFAULT, - attributes); - - if (!set_maybe.FromMaybe(false)) { - return napi_set_last_error(env, napi_invalid_arg); - } - } else if (p->method != nullptr) { - v8::Local cbdata = - v8impl::CreateFunctionCallbackData(env, p->method, p->data); - - CHECK_MAYBE_EMPTY(env, cbdata, napi_generic_failure); - - v8::MaybeLocal maybe_fn = - v8::Function::New(context, - v8impl::FunctionCallbackWrapper::Invoke, - cbdata); - - CHECK_MAYBE_EMPTY(env, maybe_fn, napi_generic_failure); - - auto define_maybe = obj->DefineOwnProperty( - context, property_name, maybe_fn.ToLocalChecked(), attributes); - - if (!define_maybe.FromMaybe(false)) { - return napi_set_last_error(env, napi_generic_failure); - } - } else { - v8::Local value = v8impl::V8LocalValueFromJsValue(p->value); - - auto define_maybe = - obj->DefineOwnProperty(context, property_name, value, attributes); - - if (!define_maybe.FromMaybe(false)) { - return napi_set_last_error(env, napi_invalid_arg); - } - } - } - - return GET_RETURN_STATUS(env); -} - -napi_status napi_is_array(napi_env env, napi_value value, bool* result) { - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - - *result = val->IsArray(); - return napi_clear_last_error(env); -} - -napi_status napi_get_array_length(napi_env env, - napi_value value, - uint32_t* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - RETURN_STATUS_IF_FALSE(env, val->IsArray(), napi_array_expected); - - v8::Local arr = val.As(); - *result = arr->Length(); - - return GET_RETURN_STATUS(env); -} - -napi_status napi_strict_equals(napi_env env, - napi_value lhs, - napi_value rhs, - bool* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, lhs); - CHECK_ARG(env, rhs); - CHECK_ARG(env, result); - - v8::Local a = v8impl::V8LocalValueFromJsValue(lhs); - v8::Local b = v8impl::V8LocalValueFromJsValue(rhs); - - *result = a->StrictEquals(b); - return GET_RETURN_STATUS(env); -} - -napi_status napi_get_prototype(napi_env env, - napi_value object, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - - v8::Local obj; - CHECK_TO_OBJECT(env, context, obj, object); - - v8::Local val = obj->GetPrototype(); - *result = v8impl::JsValueFromV8LocalValue(val); - return GET_RETURN_STATUS(env); -} - -napi_status napi_create_object(napi_env env, napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = v8impl::JsValueFromV8LocalValue( - v8::Object::New(env->isolate)); - - return napi_clear_last_error(env); -} - -napi_status napi_create_array(napi_env env, napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = v8impl::JsValueFromV8LocalValue( - v8::Array::New(env->isolate)); - - return napi_clear_last_error(env); -} - -napi_status napi_create_array_with_length(napi_env env, - size_t length, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = v8impl::JsValueFromV8LocalValue( - v8::Array::New(env->isolate, length)); - - return napi_clear_last_error(env); -} - -napi_status napi_create_string_latin1(napi_env env, - const char* str, - size_t length, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - auto isolate = env->isolate; - auto str_maybe = - v8::String::NewFromOneByte(isolate, - reinterpret_cast(str), - v8::NewStringType::kInternalized, - length); - CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure); - - *result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked()); - return napi_clear_last_error(env); -} - -napi_status napi_create_string_utf8(napi_env env, - const char* str, - size_t length, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - v8::Local s; - CHECK_NEW_FROM_UTF8_LEN(env, s, str, length); - - *result = v8impl::JsValueFromV8LocalValue(s); - return napi_clear_last_error(env); -} - -napi_status napi_create_string_utf16(napi_env env, - const char16_t* str, - size_t length, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - auto isolate = env->isolate; - auto str_maybe = - v8::String::NewFromTwoByte(isolate, - reinterpret_cast(str), - v8::NewStringType::kInternalized, - length); - CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure); - - *result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked()); - return napi_clear_last_error(env); -} - -napi_status napi_create_double(napi_env env, - double value, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = v8impl::JsValueFromV8LocalValue( - v8::Number::New(env->isolate, value)); - - return napi_clear_last_error(env); -} - -napi_status napi_create_int32(napi_env env, - int32_t value, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = v8impl::JsValueFromV8LocalValue( - v8::Integer::New(env->isolate, value)); - - return napi_clear_last_error(env); -} - -napi_status napi_create_uint32(napi_env env, - uint32_t value, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = v8impl::JsValueFromV8LocalValue( - v8::Integer::NewFromUnsigned(env->isolate, value)); - - return napi_clear_last_error(env); -} - -napi_status napi_create_int64(napi_env env, - int64_t value, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = v8impl::JsValueFromV8LocalValue( - v8::Number::New(env->isolate, static_cast(value))); - - return napi_clear_last_error(env); -} - -napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - - if (value) { - *result = v8impl::JsValueFromV8LocalValue(v8::True(isolate)); - } else { - *result = v8impl::JsValueFromV8LocalValue(v8::False(isolate)); - } - - return napi_clear_last_error(env); -} - -napi_status napi_create_symbol(napi_env env, - napi_value description, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - - if (description == nullptr) { - *result = v8impl::JsValueFromV8LocalValue(v8::Symbol::New(isolate)); - } else { - v8::Local desc = v8impl::V8LocalValueFromJsValue(description); - RETURN_STATUS_IF_FALSE(env, desc->IsString(), napi_string_expected); - - *result = v8impl::JsValueFromV8LocalValue( - v8::Symbol::New(isolate, desc.As())); - } - - return napi_clear_last_error(env); -} - -static napi_status set_error_code(napi_env env, - v8::Local error, - napi_value code, - const char* code_cstring) { - if ((code != nullptr) || (code_cstring != nullptr)) { - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local err_object = error.As(); - - v8::Local code_value = v8impl::V8LocalValueFromJsValue(code); - if (code != nullptr) { - code_value = v8impl::V8LocalValueFromJsValue(code); - RETURN_STATUS_IF_FALSE(env, code_value->IsString(), napi_string_expected); - } else { - CHECK_NEW_FROM_UTF8(env, code_value, code_cstring); - } - - v8::Local code_key; - CHECK_NEW_FROM_UTF8(env, code_key, "code"); - - v8::Maybe set_maybe = err_object->Set(context, code_key, code_value); - RETURN_STATUS_IF_FALSE(env, - set_maybe.FromMaybe(false), - napi_generic_failure); - - // now update the name to be "name [code]" where name is the - // original name and code is the code associated with the Error - v8::Local name_string; - CHECK_NEW_FROM_UTF8(env, name_string, ""); - v8::Local name_key; - CHECK_NEW_FROM_UTF8(env, name_key, "name"); - - auto maybe_name = err_object->Get(context, name_key); - if (!maybe_name.IsEmpty()) { - v8::Local name = maybe_name.ToLocalChecked(); - if (name->IsString()) { - name_string = v8::String::Concat(name_string, name.As()); - } - } - name_string = v8::String::Concat(name_string, - FIXED_ONE_BYTE_STRING(isolate, " [")); - name_string = v8::String::Concat(name_string, code_value.As()); - name_string = v8::String::Concat(name_string, - FIXED_ONE_BYTE_STRING(isolate, "]")); - - set_maybe = err_object->Set(context, name_key, name_string); - RETURN_STATUS_IF_FALSE(env, - set_maybe.FromMaybe(false), - napi_generic_failure); - } - return napi_ok; -} - -napi_status napi_create_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, msg); - CHECK_ARG(env, result); - - v8::Local message_value = v8impl::V8LocalValueFromJsValue(msg); - RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected); - - v8::Local error_obj = - v8::Exception::Error(message_value.As()); - napi_status status = set_error_code(env, error_obj, code, nullptr); - if (status != napi_ok) return status; - - *result = v8impl::JsValueFromV8LocalValue(error_obj); - - return napi_clear_last_error(env); -} - -napi_status napi_create_type_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, msg); - CHECK_ARG(env, result); - - v8::Local message_value = v8impl::V8LocalValueFromJsValue(msg); - RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected); - - v8::Local error_obj = - v8::Exception::TypeError(message_value.As()); - napi_status status = set_error_code(env, error_obj, code, nullptr); - if (status != napi_ok) return status; - - *result = v8impl::JsValueFromV8LocalValue(error_obj); - - return napi_clear_last_error(env); -} - -napi_status napi_create_range_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, msg); - CHECK_ARG(env, result); - - v8::Local message_value = v8impl::V8LocalValueFromJsValue(msg); - RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected); - - v8::Local error_obj = - v8::Exception::RangeError(message_value.As()); - napi_status status = set_error_code(env, error_obj, code, nullptr); - if (status != napi_ok) return status; - - *result = v8impl::JsValueFromV8LocalValue(error_obj); - - return napi_clear_last_error(env); -} - -napi_status napi_typeof(napi_env env, - napi_value value, - napi_valuetype* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local v = v8impl::V8LocalValueFromJsValue(value); - - if (v->IsNumber()) { - *result = napi_number; - } else if (v->IsString()) { - *result = napi_string; - } else if (v->IsFunction()) { - // This test has to come before IsObject because IsFunction - // implies IsObject - *result = napi_function; - } else if (v->IsExternal()) { - // This test has to come before IsObject because IsExternal - // implies IsObject - *result = napi_external; - } else if (v->IsObject()) { - *result = napi_object; - } else if (v->IsBoolean()) { - *result = napi_boolean; - } else if (v->IsUndefined()) { - *result = napi_undefined; - } else if (v->IsSymbol()) { - *result = napi_symbol; - } else if (v->IsNull()) { - *result = napi_null; - } else { - // Should not get here unless V8 has added some new kind of value. - return napi_set_last_error(env, napi_invalid_arg); - } - - return napi_clear_last_error(env); -} - -napi_status napi_get_undefined(napi_env env, napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = v8impl::JsValueFromV8LocalValue( - v8::Undefined(env->isolate)); - - return napi_clear_last_error(env); -} - -napi_status napi_get_null(napi_env env, napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = v8impl::JsValueFromV8LocalValue( - v8::Null(env->isolate)); - - return napi_clear_last_error(env); -} - -// Gets all callback info in a single call. (Ugly, but faster.) -napi_status napi_get_cb_info( - napi_env env, // [in] NAPI environment handle - napi_callback_info cbinfo, // [in] Opaque callback-info handle - size_t* argc, // [in-out] Specifies the size of the provided argv array - // and receives the actual count of args. - napi_value* argv, // [out] Array of values - napi_value* this_arg, // [out] Receives the JS 'this' arg for the call - void** data) { // [out] Receives the data pointer for the callback. - CHECK_ENV(env); - CHECK_ARG(env, cbinfo); - - v8impl::CallbackWrapper* info = - reinterpret_cast(cbinfo); - - if (argv != nullptr) { - CHECK_ARG(env, argc); - info->Args(argv, *argc); - } - if (argc != nullptr) { - *argc = info->ArgsLength(); - } - if (this_arg != nullptr) { - *this_arg = info->This(); - } - if (data != nullptr) { - *data = info->Data(); - } - - return napi_clear_last_error(env); -} - -napi_status napi_get_new_target(napi_env env, - napi_callback_info cbinfo, - napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, cbinfo); - CHECK_ARG(env, result); - - v8impl::CallbackWrapper* info = - reinterpret_cast(cbinfo); - - *result = info->GetNewTarget(); - return napi_clear_last_error(env); -} - -napi_status napi_call_function(napi_env env, - napi_value recv, - napi_value func, - size_t argc, - const napi_value* argv, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, recv); - if (argc > 0) { - CHECK_ARG(env, argv); - } - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - - v8::Local v8recv = v8impl::V8LocalValueFromJsValue(recv); - - v8::Local v8func; - CHECK_TO_FUNCTION(env, v8func, func); - - auto maybe = v8func->Call(context, v8recv, argc, - reinterpret_cast*>(const_cast(argv))); - - if (try_catch.HasCaught()) { - return napi_set_last_error(env, napi_pending_exception); - } else { - if (result != nullptr) { - CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); - *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); - } - return napi_clear_last_error(env); - } -} - -napi_status napi_get_global(napi_env env, napi_value* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - // TODO(ianhall): what if we need the global object from a different - // context in the same isolate? - // Should napi_env be the current context rather than the current isolate? - v8::Local context = isolate->GetCurrentContext(); - *result = v8impl::JsValueFromV8LocalValue(context->Global()); - - return napi_clear_last_error(env); -} - -napi_status napi_throw(napi_env env, napi_value error) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, error); - - v8::Isolate* isolate = env->isolate; - - isolate->ThrowException(v8impl::V8LocalValueFromJsValue(error)); - // any VM calls after this point and before returning - // to the javascript invoker will fail - return napi_clear_last_error(env); -} - -napi_status napi_throw_error(napi_env env, - const char* code, - const char* msg) { - NAPI_PREAMBLE(env); - - v8::Isolate* isolate = env->isolate; - v8::Local str; - CHECK_NEW_FROM_UTF8(env, str, msg); - - v8::Local error_obj = v8::Exception::Error(str); - napi_status status = set_error_code(env, error_obj, nullptr, code); - if (status != napi_ok) return status; - - isolate->ThrowException(error_obj); - // any VM calls after this point and before returning - // to the javascript invoker will fail - return napi_clear_last_error(env); -} - -napi_status napi_throw_type_error(napi_env env, - const char* code, - const char* msg) { - NAPI_PREAMBLE(env); - - v8::Isolate* isolate = env->isolate; - v8::Local str; - CHECK_NEW_FROM_UTF8(env, str, msg); - - v8::Local error_obj = v8::Exception::TypeError(str); - napi_status status = set_error_code(env, error_obj, nullptr, code); - if (status != napi_ok) return status; - - isolate->ThrowException(error_obj); - // any VM calls after this point and before returning - // to the javascript invoker will fail - return napi_clear_last_error(env); -} - -napi_status napi_throw_range_error(napi_env env, - const char* code, - const char* msg) { - NAPI_PREAMBLE(env); - - v8::Isolate* isolate = env->isolate; - v8::Local str; - CHECK_NEW_FROM_UTF8(env, str, msg); - - v8::Local error_obj = v8::Exception::RangeError(str); - napi_status status = set_error_code(env, error_obj, nullptr, code); - if (status != napi_ok) return status; - - isolate->ThrowException(error_obj); - // any VM calls after this point and before returning - // to the javascript invoker will fail - return napi_clear_last_error(env); -} - -napi_status napi_is_error(napi_env env, napi_value value, bool* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot - // throw JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - *result = val->IsNativeError(); - - return napi_clear_last_error(env); -} - -napi_status napi_get_value_double(napi_env env, - napi_value value, - double* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected); - - *result = val.As()->Value(); - - return napi_clear_last_error(env); -} - -napi_status napi_get_value_int32(napi_env env, - napi_value value, - int32_t* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - - if (val->IsInt32()) { - *result = val.As()->Value(); - } else { - RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected); - - // Empty context: https://github.com/nodejs/node/issues/14379 - v8::Local context; - *result = val->Int32Value(context).FromJust(); - } - - return napi_clear_last_error(env); -} - -napi_status napi_get_value_uint32(napi_env env, - napi_value value, - uint32_t* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - - if (val->IsUint32()) { - *result = val.As()->Value(); - } else { - RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected); - - // Empty context: https://github.com/nodejs/node/issues/14379 - v8::Local context; - *result = val->Uint32Value(context).FromJust(); - } - - return napi_clear_last_error(env); -} - -napi_status napi_get_value_int64(napi_env env, - napi_value value, - int64_t* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - - // This is still a fast path very likely to be taken. - if (val->IsInt32()) { - *result = val.As()->Value(); - return napi_clear_last_error(env); - } - - RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected); - - // v8::Value::IntegerValue() converts NaN to INT64_MIN, inconsistent with - // v8::Value::Int32Value() that converts NaN to 0. So special-case NaN here. - double doubleValue = val.As()->Value(); - if (std::isnan(doubleValue)) { - *result = 0; - } else { - // Empty context: https://github.com/nodejs/node/issues/14379 - v8::Local context; - *result = val->IntegerValue(context).FromJust(); - } - - return napi_clear_last_error(env); -} - -napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - RETURN_STATUS_IF_FALSE(env, val->IsBoolean(), napi_boolean_expected); - - *result = val.As()->Value(); - - return napi_clear_last_error(env); -} - -// Copies a JavaScript string into a LATIN-1 string buffer. The result is the -// number of bytes (excluding the null terminator) copied into buf. -// A sufficient buffer size should be greater than the length of string, -// reserving space for null terminator. -// If bufsize is insufficient, the string will be truncated and null terminated. -// If buf is NULL, this method returns the length of the string (in bytes) -// via the result parameter. -// The result argument is optional unless buf is NULL. -napi_status napi_get_value_string_latin1(napi_env env, - napi_value value, - char* buf, - size_t bufsize, - size_t* result) { - CHECK_ENV(env); - CHECK_ARG(env, value); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - RETURN_STATUS_IF_FALSE(env, val->IsString(), napi_string_expected); - - if (!buf) { - CHECK_ARG(env, result); - *result = val.As()->Length(); - } else { - int copied = val.As()->WriteOneByte( - reinterpret_cast(buf), 0, bufsize - 1, - v8::String::NO_NULL_TERMINATION); - - buf[copied] = '\0'; - if (result != nullptr) { - *result = copied; - } - } - - return napi_clear_last_error(env); -} - -// Copies a JavaScript string into a UTF-8 string buffer. The result is the -// number of bytes (excluding the null terminator) copied into buf. -// A sufficient buffer size should be greater than the length of string, -// reserving space for null terminator. -// If bufsize is insufficient, the string will be truncated and null terminated. -// If buf is NULL, this method returns the length of the string (in bytes) -// via the result parameter. -// The result argument is optional unless buf is NULL. -napi_status napi_get_value_string_utf8(napi_env env, - napi_value value, - char* buf, - size_t bufsize, - size_t* result) { - CHECK_ENV(env); - CHECK_ARG(env, value); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - RETURN_STATUS_IF_FALSE(env, val->IsString(), napi_string_expected); - - if (!buf) { - CHECK_ARG(env, result); - *result = val.As()->Utf8Length(); - } else { - int copied = val.As()->WriteUtf8( - buf, bufsize - 1, nullptr, v8::String::REPLACE_INVALID_UTF8 | - v8::String::NO_NULL_TERMINATION); - - buf[copied] = '\0'; - if (result != nullptr) { - *result = copied; - } - } - - return napi_clear_last_error(env); -} - -// Copies a JavaScript string into a UTF-16 string buffer. The result is the -// number of 2-byte code units (excluding the null terminator) copied into buf. -// A sufficient buffer size should be greater than the length of string, -// reserving space for null terminator. -// If bufsize is insufficient, the string will be truncated and null terminated. -// If buf is NULL, this method returns the length of the string (in 2-byte -// code units) via the result parameter. -// The result argument is optional unless buf is NULL. -napi_status napi_get_value_string_utf16(napi_env env, - napi_value value, - char16_t* buf, - size_t bufsize, - size_t* result) { - CHECK_ENV(env); - CHECK_ARG(env, value); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - RETURN_STATUS_IF_FALSE(env, val->IsString(), napi_string_expected); - - if (!buf) { - CHECK_ARG(env, result); - // V8 assumes UTF-16 length is the same as the number of characters. - *result = val.As()->Length(); - } else { - int copied = val.As()->Write( - reinterpret_cast(buf), 0, bufsize - 1, - v8::String::NO_NULL_TERMINATION); - - buf[copied] = '\0'; - if (result != nullptr) { - *result = copied; - } - } - - return napi_clear_last_error(env); -} - -napi_status napi_coerce_to_object(napi_env env, - napi_value value, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local obj; - CHECK_TO_OBJECT(env, context, obj, value); - - *result = v8impl::JsValueFromV8LocalValue(obj); - return GET_RETURN_STATUS(env); -} - -napi_status napi_coerce_to_bool(napi_env env, - napi_value value, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local b; - - CHECK_TO_BOOL(env, context, b, value); - - *result = v8impl::JsValueFromV8LocalValue(b); - return GET_RETURN_STATUS(env); -} - -napi_status napi_coerce_to_number(napi_env env, - napi_value value, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local num; - - CHECK_TO_NUMBER(env, context, num, value); - - *result = v8impl::JsValueFromV8LocalValue(num); - return GET_RETURN_STATUS(env); -} - -napi_status napi_coerce_to_string(napi_env env, - napi_value value, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - v8::Local str; - - CHECK_TO_STRING(env, context, str, value); - - *result = v8impl::JsValueFromV8LocalValue(str); - return GET_RETURN_STATUS(env); -} - -napi_status napi_wrap(napi_env env, - napi_value js_object, - void* native_object, - napi_finalize finalize_cb, - void* finalize_hint, - napi_ref* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, js_object); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - - v8::Local value = v8impl::V8LocalValueFromJsValue(js_object); - RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg); - v8::Local obj = value.As(); - - // If we've already wrapped this object, we error out. - RETURN_STATUS_IF_FALSE(env, !v8impl::FindWrapper(obj), napi_invalid_arg); - - // Create a wrapper object with an internal field to hold the wrapped pointer - // and a second internal field to identify the owner as N-API. - v8::Local wrapper_template; - ENV_OBJECT_TEMPLATE(env, wrap, wrapper_template, v8impl::kWrapperFields); - - auto maybe_object = wrapper_template->NewInstance(context); - CHECK_MAYBE_EMPTY(env, maybe_object, napi_generic_failure); - v8::Local wrapper = maybe_object.ToLocalChecked(); - - // Store the pointer as an external in the wrapper. - wrapper->SetInternalField(0, v8::External::New(isolate, native_object)); - wrapper->SetInternalField(1, v8::External::New(isolate, - reinterpret_cast(const_cast(v8impl::napi_wrap_name)))); - - // Insert the wrapper into the object's prototype chain. - v8::Local proto = obj->GetPrototype(); - CHECK(wrapper->SetPrototype(context, proto).FromJust()); - CHECK(obj->SetPrototype(context, wrapper).FromJust()); - - v8impl::Reference* reference = nullptr; - if (result != nullptr) { - // The returned reference should be deleted via napi_delete_reference() - // ONLY in response to the finalize callback invocation. (If it is deleted - // before then, then the finalize callback will never be invoked.) - // Therefore a finalize callback is required when returning a reference. - CHECK_ARG(env, finalize_cb); - reference = v8impl::Reference::New( - env, obj, 0, false, finalize_cb, native_object, finalize_hint); - *result = reinterpret_cast(reference); - } else if (finalize_cb != nullptr) { - // Create a self-deleting reference just for the finalize callback. - reference = v8impl::Reference::New( - env, obj, 0, true, finalize_cb, native_object, finalize_hint); - } - - if (reference != nullptr) { - wrapper->SetInternalField(2, v8::External::New(isolate, reference)); - } - - return GET_RETURN_STATUS(env); -} - -napi_status napi_unwrap(napi_env env, napi_value obj, void** result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - v8::Local wrapper; - return napi_set_last_error(env, v8impl::Unwrap(env, obj, result, &wrapper)); -} - -napi_status napi_remove_wrap(napi_env env, napi_value obj, void** result) { - NAPI_PREAMBLE(env); - v8::Local wrapper; - v8::Local parent; - napi_status status = v8impl::Unwrap(env, obj, result, &wrapper, &parent); - if (status != napi_ok) { - return napi_set_last_error(env, status); - } - - v8::Local external = wrapper->GetInternalField(2); - if (external->IsExternal()) { - v8impl::Reference::Delete( - static_cast(external.As()->Value())); - } - - if (!parent.IsEmpty()) { - v8::Maybe maybe = parent->SetPrototype( - env->isolate->GetCurrentContext(), wrapper->GetPrototype()); - CHECK_MAYBE_NOTHING(env, maybe, napi_generic_failure); - if (!maybe.FromMaybe(false)) { - return napi_set_last_error(env, napi_generic_failure); - } - } - - return GET_RETURN_STATUS(env); -} - -napi_status napi_create_external(napi_env env, - void* data, - napi_finalize finalize_cb, - void* finalize_hint, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - - v8::Local external_value = v8::External::New(isolate, data); - - // The Reference object will delete itself after invoking the finalizer - // callback. - v8impl::Reference::New(env, - external_value, - 0, - true, - finalize_cb, - data, - finalize_hint); - - *result = v8impl::JsValueFromV8LocalValue(external_value); - - return napi_clear_last_error(env); -} - -napi_status napi_get_value_external(napi_env env, - napi_value value, - void** result) { - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - RETURN_STATUS_IF_FALSE(env, val->IsExternal(), napi_invalid_arg); - - v8::Local external_value = val.As(); - *result = external_value->Value(); - - return napi_clear_last_error(env); -} - -// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. -napi_status napi_create_reference(napi_env env, - napi_value value, - uint32_t initial_refcount, - napi_ref* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local v8_value = v8impl::V8LocalValueFromJsValue(value); - - if (!(v8_value->IsObject() || v8_value->IsFunction())) { - return napi_set_last_error(env, napi_object_expected); - } - - v8impl::Reference* reference = - v8impl::Reference::New(env, v8_value, initial_refcount, false); - - *result = reinterpret_cast(reference); - return napi_clear_last_error(env); -} - -// Deletes a reference. The referenced value is released, and may be GC'd unless -// there are other references to it. -napi_status napi_delete_reference(napi_env env, napi_ref ref) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, ref); - - v8impl::Reference::Delete(reinterpret_cast(ref)); - - return napi_clear_last_error(env); -} - -// Increments the reference count, optionally returning the resulting count. -// After this call the reference will be a strong reference because its -// refcount is >0, and the referenced object is effectively "pinned". -// Calling this when the refcount is 0 and the object is unavailable -// results in an error. -napi_status napi_reference_ref(napi_env env, napi_ref ref, uint32_t* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, ref); - - v8impl::Reference* reference = reinterpret_cast(ref); - uint32_t count = reference->Ref(); - - if (result != nullptr) { - *result = count; - } - - return napi_clear_last_error(env); -} - -// Decrements the reference count, optionally returning the resulting count. If -// the result is 0 the reference is now weak and the object may be GC'd at any -// time if there are no other references. Calling this when the refcount is -// already 0 results in an error. -napi_status napi_reference_unref(napi_env env, napi_ref ref, uint32_t* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, ref); - - v8impl::Reference* reference = reinterpret_cast(ref); - - if (reference->RefCount() == 0) { - return napi_set_last_error(env, napi_generic_failure); - } - - uint32_t count = reference->Unref(); - - if (result != nullptr) { - *result = count; - } - - return napi_clear_last_error(env); -} - -// Attempts to get a referenced value. If the reference is weak, the value might -// no longer be available, in that case the call is still successful but the -// result is NULL. -napi_status napi_get_reference_value(napi_env env, - napi_ref ref, - napi_value* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, ref); - CHECK_ARG(env, result); - - v8impl::Reference* reference = reinterpret_cast(ref); - *result = v8impl::JsValueFromV8LocalValue(reference->Get()); - - return napi_clear_last_error(env); -} - -napi_status napi_open_handle_scope(napi_env env, napi_handle_scope* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = v8impl::JsHandleScopeFromV8HandleScope( - new v8impl::HandleScopeWrapper(env->isolate)); - env->open_handle_scopes++; - return napi_clear_last_error(env); -} - -napi_status napi_close_handle_scope(napi_env env, napi_handle_scope scope) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, scope); - if (env->open_handle_scopes == 0) { - return napi_handle_scope_mismatch; - } - - env->open_handle_scopes--; - delete v8impl::V8HandleScopeFromJsHandleScope(scope); - return napi_clear_last_error(env); -} - -napi_status napi_open_escapable_handle_scope( - napi_env env, - napi_escapable_handle_scope* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = v8impl::JsEscapableHandleScopeFromV8EscapableHandleScope( - new v8impl::EscapableHandleScopeWrapper(env->isolate)); - env->open_handle_scopes++; - return napi_clear_last_error(env); -} - -napi_status napi_close_escapable_handle_scope( - napi_env env, - napi_escapable_handle_scope scope) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, scope); - if (env->open_handle_scopes == 0) { - return napi_handle_scope_mismatch; - } - - delete v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope); - env->open_handle_scopes--; - return napi_clear_last_error(env); -} - -napi_status napi_escape_handle(napi_env env, - napi_escapable_handle_scope scope, - napi_value escapee, - napi_value* result) { - // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw - // JS exceptions. - CHECK_ENV(env); - CHECK_ARG(env, scope); - CHECK_ARG(env, escapee); - CHECK_ARG(env, result); - - v8impl::EscapableHandleScopeWrapper* s = - v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope); - if (!s->escape_called()) { - *result = v8impl::JsValueFromV8LocalValue( - s->Escape(v8impl::V8LocalValueFromJsValue(escapee))); - return napi_clear_last_error(env); - } - return napi_set_last_error(env, napi_escape_called_twice); -} - -napi_status napi_new_instance(napi_env env, - napi_value constructor, - size_t argc, - const napi_value* argv, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, constructor); - if (argc > 0) { - CHECK_ARG(env, argv); - } - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - - v8::Local ctor; - CHECK_TO_FUNCTION(env, ctor, constructor); - - auto maybe = ctor->NewInstance(context, argc, - reinterpret_cast*>(const_cast(argv))); - - CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); - - *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); - return GET_RETURN_STATUS(env); -} - -napi_status napi_instanceof(napi_env env, - napi_value object, - napi_value constructor, - bool* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, object); - CHECK_ARG(env, result); - - *result = false; - - v8::Local ctor; - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - - CHECK_TO_OBJECT(env, context, ctor, constructor); - - if (!ctor->IsFunction()) { - napi_throw_type_error(env, - "ERR_NAPI_CONS_FUNCTION", - "Constructor must be a function"); - - return napi_set_last_error(env, napi_function_expected); - } - - if (env->has_instance_available) { - napi_value value, js_result = nullptr, has_instance = nullptr; - napi_status status = napi_generic_failure; - napi_valuetype value_type; - - // Get "Symbol" from the global object - if (env->has_instance.IsEmpty()) { - status = napi_get_global(env, &value); - if (status != napi_ok) return status; - status = napi_get_named_property(env, value, "Symbol", &value); - if (status != napi_ok) return status; - status = napi_typeof(env, value, &value_type); - if (status != napi_ok) return status; - - // Get "hasInstance" from Symbol - if (value_type == napi_function) { - status = napi_get_named_property(env, value, "hasInstance", &value); - if (status != napi_ok) return status; - status = napi_typeof(env, value, &value_type); - if (status != napi_ok) return status; - - // Store Symbol.hasInstance in a global persistent reference - if (value_type == napi_symbol) { - env->has_instance.Reset(env->isolate, - v8impl::V8LocalValueFromJsValue(value)); - has_instance = value; - } - } - } else { - has_instance = v8impl::JsValueFromV8LocalValue( - v8::Local::New(env->isolate, env->has_instance)); - } - - if (has_instance) { - status = napi_get_property(env, constructor, has_instance, &value); - if (status != napi_ok) return status; - status = napi_typeof(env, value, &value_type); - if (status != napi_ok) return status; - - // Call the function to determine whether the object is an instance of the - // constructor - if (value_type == napi_function) { - status = napi_call_function(env, constructor, value, 1, &object, - &js_result); - if (status != napi_ok) return status; - return napi_get_value_bool(env, js_result, result); - } - } - - env->has_instance_available = false; - } - - // If running constructor[Symbol.hasInstance](object) did not work, we perform - // a traditional instanceof (early Node.js 6.x). - - v8::Local prototype_string; - CHECK_NEW_FROM_UTF8(env, prototype_string, "prototype"); - - auto maybe_prototype = ctor->Get(context, prototype_string); - CHECK_MAYBE_EMPTY(env, maybe_prototype, napi_generic_failure); - - v8::Local prototype_property = maybe_prototype.ToLocalChecked(); - if (!prototype_property->IsObject()) { - napi_throw_type_error( - env, - "ERR_NAPI_CONS_PROTOTYPE_OBJECT", - "Constructor.prototype must be an object"); - - return napi_set_last_error(env, napi_object_expected); - } - - auto maybe_ctor = prototype_property->ToObject(context); - CHECK_MAYBE_EMPTY(env, maybe_ctor, napi_generic_failure); - ctor = maybe_ctor.ToLocalChecked(); - - v8::Local current_obj = v8impl::V8LocalValueFromJsValue(object); - if (!current_obj->StrictEquals(ctor)) { - for (v8::Local original_obj = current_obj; - !(current_obj->IsNull() || current_obj->IsUndefined());) { - if (current_obj->StrictEquals(ctor)) { - *result = !(original_obj->IsNumber() || - original_obj->IsBoolean() || - original_obj->IsString()); - break; - } - v8::Local obj; - CHECK_TO_OBJECT(env, context, obj, v8impl::JsValueFromV8LocalValue( - current_obj)); - current_obj = obj->GetPrototype(); - } - } - - return GET_RETURN_STATUS(env); -} - -napi_status napi_async_init(napi_env env, - napi_value async_resource, - napi_value async_resource_name, - napi_async_context* result) { - CHECK_ENV(env); - CHECK_ARG(env, async_resource_name); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - - v8::Local v8_resource; - if (async_resource != nullptr) { - CHECK_TO_OBJECT(env, context, v8_resource, async_resource); - } else { - v8_resource = v8::Object::New(isolate); - } - - v8::Local v8_resource_name; - CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name); - - // TODO(jasongin): Consider avoiding allocation here by using - // a tagged pointer with 2×31 bit fields instead. - node::async_context* async_context = new node::async_context(); - - *async_context = node::EmitAsyncInit(isolate, v8_resource, v8_resource_name); - *result = reinterpret_cast(async_context); - - return napi_clear_last_error(env); -} - -napi_status napi_async_destroy(napi_env env, - napi_async_context async_context) { - CHECK_ENV(env); - CHECK_ARG(env, async_context); - - v8::Isolate* isolate = env->isolate; - node::async_context* node_async_context = - reinterpret_cast(async_context); - node::EmitAsyncDestroy(isolate, *node_async_context); - - delete node_async_context; - - return napi_clear_last_error(env); -} - -napi_status napi_make_callback(napi_env env, - napi_async_context async_context, - napi_value recv, - napi_value func, - size_t argc, - const napi_value* argv, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, recv); - if (argc > 0) { - CHECK_ARG(env, argv); - } - - v8::Isolate* isolate = env->isolate; - v8::Local context = isolate->GetCurrentContext(); - - v8::Local v8recv; - CHECK_TO_OBJECT(env, context, v8recv, recv); - - v8::Local v8func; - CHECK_TO_FUNCTION(env, v8func, func); - - node::async_context* node_async_context = - reinterpret_cast(async_context); - if (node_async_context == nullptr) { - static node::async_context empty_context = { 0, 0 }; - node_async_context = &empty_context; - } - - v8::MaybeLocal callback_result = node::MakeCallback( - isolate, v8recv, v8func, argc, - reinterpret_cast*>(const_cast(argv)), - *node_async_context); - CHECK_MAYBE_EMPTY(env, callback_result, napi_generic_failure); - - if (result != nullptr) { - *result = v8impl::JsValueFromV8LocalValue( - callback_result.ToLocalChecked()); - } - - return GET_RETURN_STATUS(env); -} - -// Methods to support catching exceptions -napi_status napi_is_exception_pending(napi_env env, bool* result) { - // NAPI_PREAMBLE is not used here: this function must execute when there is a - // pending exception. - CHECK_ENV(env); - CHECK_ARG(env, result); - - *result = !env->last_exception.IsEmpty(); - return napi_clear_last_error(env); -} - -napi_status napi_get_and_clear_last_exception(napi_env env, - napi_value* result) { - // NAPI_PREAMBLE is not used here: this function must execute when there is a - // pending exception. - CHECK_ENV(env); - CHECK_ARG(env, result); - - if (env->last_exception.IsEmpty()) { - return napi_get_undefined(env, result); - } else { - *result = v8impl::JsValueFromV8LocalValue( - v8::Local::New(env->isolate, env->last_exception)); - env->last_exception.Reset(); - } - - return napi_clear_last_error(env); -} - -napi_status napi_create_buffer(napi_env env, - size_t length, - void** data, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - auto maybe = node::Buffer::New(env->isolate, length); - - CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); - - v8::Local buffer = maybe.ToLocalChecked(); - - *result = v8impl::JsValueFromV8LocalValue(buffer); - - if (data != nullptr) { - *data = node::Buffer::Data(buffer); - } - - return GET_RETURN_STATUS(env); -} - -napi_status napi_create_external_buffer(napi_env env, - size_t length, - void* data, - napi_finalize finalize_cb, - void* finalize_hint, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - - // The finalizer object will delete itself after invoking the callback. - v8impl::Finalizer* finalizer = v8impl::Finalizer::New( - env, finalize_cb, nullptr, finalize_hint); - - auto maybe = node::Buffer::New(isolate, - static_cast(data), - length, - v8impl::Finalizer::FinalizeBufferCallback, - finalizer); - - CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); - - *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); - return GET_RETURN_STATUS(env); - // Tell coverity that 'finalizer' should not be freed when we return - // as it will be deleted when the buffer to which it is associated - // is finalized. - // coverity[leaked_storage] -} - -napi_status napi_create_buffer_copy(napi_env env, - size_t length, - const void* data, - void** result_data, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - auto maybe = node::Buffer::Copy(env->isolate, - static_cast(data), length); - - CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); - - v8::Local buffer = maybe.ToLocalChecked(); - *result = v8impl::JsValueFromV8LocalValue(buffer); - - if (result_data != nullptr) { - *result_data = node::Buffer::Data(buffer); - } - - return GET_RETURN_STATUS(env); -} - -napi_status napi_is_buffer(napi_env env, napi_value value, bool* result) { - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - *result = node::Buffer::HasInstance(v8impl::V8LocalValueFromJsValue(value)); - return napi_clear_last_error(env); -} - -napi_status napi_get_buffer_info(napi_env env, - napi_value value, - void** data, - size_t* length) { - CHECK_ENV(env); - CHECK_ARG(env, value); - - v8::Local buffer = v8impl::V8LocalValueFromJsValue(value); - - if (data != nullptr) { - *data = node::Buffer::Data(buffer); - } - if (length != nullptr) { - *length = node::Buffer::Length(buffer); - } - - return napi_clear_last_error(env); -} - -napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result) { - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - *result = val->IsArrayBuffer(); - - return napi_clear_last_error(env); -} - -napi_status napi_create_arraybuffer(napi_env env, - size_t byte_length, - void** data, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local buffer = - v8::ArrayBuffer::New(isolate, byte_length); - - // Optionally return a pointer to the buffer's data, to avoid another call to - // retrieve it. - if (data != nullptr) { - *data = buffer->GetContents().Data(); - } - - *result = v8impl::JsValueFromV8LocalValue(buffer); - return GET_RETURN_STATUS(env); -} - -napi_status napi_create_external_arraybuffer(napi_env env, - void* external_data, - size_t byte_length, - napi_finalize finalize_cb, - void* finalize_hint, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::Isolate* isolate = env->isolate; - v8::Local buffer = - v8::ArrayBuffer::New(isolate, external_data, byte_length); - - if (finalize_cb != nullptr) { - // Create a self-deleting weak reference that invokes the finalizer - // callback. - v8impl::Reference::New(env, - buffer, - 0, - true, - finalize_cb, - external_data, - finalize_hint); - } - - *result = v8impl::JsValueFromV8LocalValue(buffer); - return GET_RETURN_STATUS(env); -} - -napi_status napi_get_arraybuffer_info(napi_env env, - napi_value arraybuffer, - void** data, - size_t* byte_length) { - CHECK_ENV(env); - CHECK_ARG(env, arraybuffer); - - v8::Local value = v8impl::V8LocalValueFromJsValue(arraybuffer); - RETURN_STATUS_IF_FALSE(env, value->IsArrayBuffer(), napi_invalid_arg); - - v8::ArrayBuffer::Contents contents = - value.As()->GetContents(); - - if (data != nullptr) { - *data = contents.Data(); - } - - if (byte_length != nullptr) { - *byte_length = contents.ByteLength(); - } - - return napi_clear_last_error(env); -} - -napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result) { - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - *result = val->IsTypedArray(); - - return napi_clear_last_error(env); -} - -napi_status napi_create_typedarray(napi_env env, - napi_typedarray_type type, - size_t length, - napi_value arraybuffer, - size_t byte_offset, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, arraybuffer); - CHECK_ARG(env, result); - - v8::Local value = v8impl::V8LocalValueFromJsValue(arraybuffer); - RETURN_STATUS_IF_FALSE(env, value->IsArrayBuffer(), napi_invalid_arg); - - v8::Local buffer = value.As(); - v8::Local typedArray; - - switch (type) { - case napi_int8_array: - CREATE_TYPED_ARRAY( - env, Int8Array, 1, buffer, byte_offset, length, typedArray); - break; - case napi_uint8_array: - CREATE_TYPED_ARRAY( - env, Uint8Array, 1, buffer, byte_offset, length, typedArray); - break; - case napi_uint8_clamped_array: - CREATE_TYPED_ARRAY( - env, Uint8ClampedArray, 1, buffer, byte_offset, length, typedArray); - break; - case napi_int16_array: - CREATE_TYPED_ARRAY( - env, Int16Array, 2, buffer, byte_offset, length, typedArray); - break; - case napi_uint16_array: - CREATE_TYPED_ARRAY( - env, Uint16Array, 2, buffer, byte_offset, length, typedArray); - break; - case napi_int32_array: - CREATE_TYPED_ARRAY( - env, Int32Array, 4, buffer, byte_offset, length, typedArray); - break; - case napi_uint32_array: - CREATE_TYPED_ARRAY( - env, Uint32Array, 4, buffer, byte_offset, length, typedArray); - break; - case napi_float32_array: - CREATE_TYPED_ARRAY( - env, Float32Array, 4, buffer, byte_offset, length, typedArray); - break; - case napi_float64_array: - CREATE_TYPED_ARRAY( - env, Float64Array, 8, buffer, byte_offset, length, typedArray); - break; - default: - return napi_set_last_error(env, napi_invalid_arg); - } - - *result = v8impl::JsValueFromV8LocalValue(typedArray); - return GET_RETURN_STATUS(env); -} - -napi_status napi_get_typedarray_info(napi_env env, - napi_value typedarray, - napi_typedarray_type* type, - size_t* length, - void** data, - napi_value* arraybuffer, - size_t* byte_offset) { - CHECK_ENV(env); - CHECK_ARG(env, typedarray); - - v8::Local value = v8impl::V8LocalValueFromJsValue(typedarray); - RETURN_STATUS_IF_FALSE(env, value->IsTypedArray(), napi_invalid_arg); - - v8::Local array = value.As(); - - if (type != nullptr) { - if (value->IsInt8Array()) { - *type = napi_int8_array; - } else if (value->IsUint8Array()) { - *type = napi_uint8_array; - } else if (value->IsUint8ClampedArray()) { - *type = napi_uint8_clamped_array; - } else if (value->IsInt16Array()) { - *type = napi_int16_array; - } else if (value->IsUint16Array()) { - *type = napi_uint16_array; - } else if (value->IsInt32Array()) { - *type = napi_int32_array; - } else if (value->IsUint32Array()) { - *type = napi_uint32_array; - } else if (value->IsFloat32Array()) { - *type = napi_float32_array; - } else if (value->IsFloat64Array()) { - *type = napi_float64_array; - } - } - - if (length != nullptr) { - *length = array->Length(); - } - - v8::Local buffer = array->Buffer(); - if (data != nullptr) { - *data = static_cast(buffer->GetContents().Data()) + - array->ByteOffset(); - } - - if (arraybuffer != nullptr) { - *arraybuffer = v8impl::JsValueFromV8LocalValue(buffer); - } - - if (byte_offset != nullptr) { - *byte_offset = array->ByteOffset(); - } - - return napi_clear_last_error(env); -} - -napi_status napi_create_dataview(napi_env env, - size_t byte_length, - napi_value arraybuffer, - size_t byte_offset, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, arraybuffer); - CHECK_ARG(env, result); - - v8::Local value = v8impl::V8LocalValueFromJsValue(arraybuffer); - RETURN_STATUS_IF_FALSE(env, value->IsArrayBuffer(), napi_invalid_arg); - - v8::Local buffer = value.As(); - if (byte_length + byte_offset > buffer->ByteLength()) { - napi_throw_range_error( - env, - "ERR_NAPI_INVALID_DATAVIEW_ARGS", - "byte_offset + byte_length should be less than or " - "equal to the size in bytes of the array passed in"); - return napi_set_last_error(env, napi_generic_failure); - } - v8::Local DataView = v8::DataView::New(buffer, byte_offset, - byte_length); - - *result = v8impl::JsValueFromV8LocalValue(DataView); - return GET_RETURN_STATUS(env); -} - -napi_status napi_is_dataview(napi_env env, napi_value value, bool* result) { - CHECK_ENV(env); - CHECK_ARG(env, value); - CHECK_ARG(env, result); - - v8::Local val = v8impl::V8LocalValueFromJsValue(value); - *result = val->IsDataView(); - - return napi_clear_last_error(env); -} - -napi_status napi_get_dataview_info(napi_env env, - napi_value dataview, - size_t* byte_length, - void** data, - napi_value* arraybuffer, - size_t* byte_offset) { - CHECK_ENV(env); - CHECK_ARG(env, dataview); - - v8::Local value = v8impl::V8LocalValueFromJsValue(dataview); - RETURN_STATUS_IF_FALSE(env, value->IsDataView(), napi_invalid_arg); - - v8::Local array = value.As(); - - if (byte_length != nullptr) { - *byte_length = array->ByteLength(); - } - - v8::Local buffer = array->Buffer(); - if (data != nullptr) { - *data = static_cast(buffer->GetContents().Data()) + - array->ByteOffset(); - } - - if (arraybuffer != nullptr) { - *arraybuffer = v8impl::JsValueFromV8LocalValue(buffer); - } - - if (byte_offset != nullptr) { - *byte_offset = array->ByteOffset(); - } - - return napi_clear_last_error(env); -} - -napi_status napi_get_version(napi_env env, uint32_t* result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - *result = NAPI_VERSION; - return napi_clear_last_error(env); -} - -napi_status napi_get_node_version(napi_env env, - const napi_node_version** result) { - CHECK_ENV(env); - CHECK_ARG(env, result); - static const napi_node_version version = { - NODE_MAJOR_VERSION, - NODE_MINOR_VERSION, - NODE_PATCH_VERSION, - NODE_RELEASE - }; - *result = &version; - return napi_clear_last_error(env); -} - -napi_status napi_adjust_external_memory(napi_env env, - int64_t change_in_bytes, - int64_t* adjusted_value) { - CHECK_ENV(env); - CHECK_ARG(env, adjusted_value); - - *adjusted_value = env->isolate->AdjustAmountOfExternalAllocatedMemory( - change_in_bytes); - - return napi_clear_last_error(env); -} - -namespace { -namespace uvimpl { - -static napi_status ConvertUVErrorCode(int code) { - switch (code) { - case 0: - return napi_ok; - case UV_EINVAL: - return napi_invalid_arg; - case UV_ECANCELED: - return napi_cancelled; - } - - return napi_generic_failure; -} - -// Wrapper around uv_work_t which calls user-provided callbacks. -class Work : public node::AsyncResource { - private: - explicit Work(napi_env env, - v8::Local async_resource, - v8::Local async_resource_name, - napi_async_execute_callback execute, - napi_async_complete_callback complete = nullptr, - void* data = nullptr) - : AsyncResource(env->isolate, - async_resource, - *v8::String::Utf8Value(async_resource_name)), - _env(env), - _data(data), - _execute(execute), - _complete(complete) { - memset(&_request, 0, sizeof(_request)); - _request.data = this; - } - - ~Work() { } - - public: - static Work* New(napi_env env, - v8::Local async_resource, - v8::Local async_resource_name, - napi_async_execute_callback execute, - napi_async_complete_callback complete, - void* data) { - return new Work(env, async_resource, async_resource_name, - execute, complete, data); - } - - static void Delete(Work* work) { - delete work; - } - - static void ExecuteCallback(uv_work_t* req) { - Work* work = static_cast(req->data); - work->_execute(work->_env, work->_data); - } - - static void CompleteCallback(uv_work_t* req, int status) { - Work* work = static_cast(req->data); - - if (work->_complete != nullptr) { - napi_env env = work->_env; - - // Establish a handle scope here so that every callback doesn't have to. - // Also it is needed for the exception-handling below. - v8::HandleScope scope(env->isolate); - CallbackScope callback_scope(work); - - NAPI_CALL_INTO_MODULE(env, - work->_complete(env, ConvertUVErrorCode(status), work->_data), - [env] (v8::Local local_err) { - // If there was an unhandled exception in the complete callback, - // report it as a fatal exception. (There is no JavaScript on the - // callstack that can possibly handle it.) - v8impl::trigger_fatal_exception(env, local_err); - }); - - // Note: Don't access `work` after this point because it was - // likely deleted by the complete callback. - } - } - - uv_work_t* Request() { - return &_request; - } - - private: - napi_env _env; - void* _data; - uv_work_t _request; - napi_async_execute_callback _execute; - napi_async_complete_callback _complete; -}; - -} // end of namespace uvimpl -} // end of anonymous namespace - -#define CALL_UV(env, condition) \ - do { \ - int result = (condition); \ - napi_status status = uvimpl::ConvertUVErrorCode(result); \ - if (status != napi_ok) { \ - return napi_set_last_error(env, status, result); \ - } \ - } while (0) - -napi_status napi_create_async_work(napi_env env, - napi_value async_resource, - napi_value async_resource_name, - napi_async_execute_callback execute, - napi_async_complete_callback complete, - void* data, - napi_async_work* result) { - CHECK_ENV(env); - CHECK_ARG(env, execute); - CHECK_ARG(env, result); - - v8::Local context = env->isolate->GetCurrentContext(); - - v8::Local resource; - if (async_resource != nullptr) { - CHECK_TO_OBJECT(env, context, resource, async_resource); - } else { - resource = v8::Object::New(env->isolate); - } - - v8::Local resource_name; - CHECK_TO_STRING(env, context, resource_name, async_resource_name); - - uvimpl::Work* work = - uvimpl::Work::New(env, resource, resource_name, - execute, complete, data); - - *result = reinterpret_cast(work); - - return napi_clear_last_error(env); -} - -napi_status napi_delete_async_work(napi_env env, napi_async_work work) { - CHECK_ENV(env); - CHECK_ARG(env, work); - - uvimpl::Work::Delete(reinterpret_cast(work)); - - return napi_clear_last_error(env); -} - -napi_status napi_queue_async_work(napi_env env, napi_async_work work) { - CHECK_ENV(env); - CHECK_ARG(env, work); - - // Consider: Encapsulate the uv_loop_t into an opaque pointer parameter. - // Currently the environment event loop is the same as the UV default loop. - // Someday (if node ever supports multiple isolates), it may be better to get - // the loop from node::Environment::GetCurrent(env->isolate)->event_loop(); - uv_loop_t* event_loop = uv_default_loop(); - - uvimpl::Work* w = reinterpret_cast(work); - - CALL_UV(env, uv_queue_work(event_loop, - w->Request(), - uvimpl::Work::ExecuteCallback, - uvimpl::Work::CompleteCallback)); - - return napi_clear_last_error(env); -} - -napi_status napi_cancel_async_work(napi_env env, napi_async_work work) { - CHECK_ENV(env); - CHECK_ARG(env, work); - - uvimpl::Work* w = reinterpret_cast(work); - - CALL_UV(env, uv_cancel(reinterpret_cast(w->Request()))); - - return napi_clear_last_error(env); -} - -napi_status napi_create_promise(napi_env env, - napi_deferred* deferred, - napi_value* promise) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, deferred); - CHECK_ARG(env, promise); - - auto maybe = v8::Promise::Resolver::New(env->isolate->GetCurrentContext()); - CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); - - auto v8_resolver = maybe.ToLocalChecked(); - auto v8_deferred = new v8::Persistent(); - v8_deferred->Reset(env->isolate, v8_resolver); - - *deferred = v8impl::JsDeferredFromV8Persistent(v8_deferred); - *promise = v8impl::JsValueFromV8LocalValue(v8_resolver->GetPromise()); - return GET_RETURN_STATUS(env); -} - -napi_status napi_resolve_deferred(napi_env env, - napi_deferred deferred, - napi_value resolution) { - return v8impl::ConcludeDeferred(env, deferred, resolution, true); -} - -napi_status napi_reject_deferred(napi_env env, - napi_deferred deferred, - napi_value resolution) { - return v8impl::ConcludeDeferred(env, deferred, resolution, false); -} - -napi_status napi_is_promise(napi_env env, - napi_value promise, - bool* is_promise) { - CHECK_ENV(env); - CHECK_ARG(env, promise); - CHECK_ARG(env, is_promise); - - *is_promise = v8impl::V8LocalValueFromJsValue(promise)->IsPromise(); - - return napi_clear_last_error(env); -} - -napi_status napi_run_script(napi_env env, - napi_value script, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, script); - CHECK_ARG(env, result); - - v8::Local v8_script = v8impl::V8LocalValueFromJsValue(script); - - if (!v8_script->IsString()) { - return napi_set_last_error(env, napi_string_expected); - } - - v8::Local context = env->isolate->GetCurrentContext(); - - auto maybe_script = v8::Script::Compile(context, - v8::Local::Cast(v8_script)); - CHECK_MAYBE_EMPTY(env, maybe_script, napi_generic_failure); - - auto script_result = - maybe_script.ToLocalChecked()->Run(context); - CHECK_MAYBE_EMPTY(env, script_result, napi_generic_failure); - - *result = v8impl::JsValueFromV8LocalValue(script_result.ToLocalChecked()); - return GET_RETURN_STATUS(env); -} diff --git a/src/node_api.gyp b/src/node_api.gyp deleted file mode 100644 index 3de7da141..000000000 --- a/src/node_api.gyp +++ /dev/null @@ -1,21 +0,0 @@ -{ - 'targets': [ - { - 'target_name': 'nothing', - 'type': 'static_library', - 'sources': [ 'nothing.c' ] - }, - { - 'target_name': 'node-api', - 'type': 'static_library', - 'sources': [ - 'node_api.cc', - 'node_internals.cc', - ], - 'defines': [ - 'EXTERNAL_NAPI', - ], - 'cflags_cc': ['-fvisibility=hidden'] - } - ] -} diff --git a/src/node_api.h b/src/node_api.h deleted file mode 100644 index 27028f75c..000000000 --- a/src/node_api.h +++ /dev/null @@ -1,588 +0,0 @@ -/****************************************************************************** - * Experimental prototype for demonstrating VM agnostic and ABI stable API - * for native modules to use instead of using Nan and V8 APIs directly. - * - * The current status is "Experimental" and should not be used for - * production applications. The API is still subject to change - * and as an experimental feature is NOT subject to semver. - * - ******************************************************************************/ -#ifndef SRC_NODE_API_H_ -#define SRC_NODE_API_H_ - -#include -#include -#include "node_api_types.h" - -#ifdef _WIN32 - #ifdef BUILDING_NODE_EXTENSION - #ifdef EXTERNAL_NAPI - // Building external N-API, or native module against external N-API - #define NAPI_EXTERN /* nothing */ - #else - // Building native module against node with built-in N-API - #define NAPI_EXTERN __declspec(dllimport) - #endif - #else - // Building node with built-in N-API - #define NAPI_EXTERN __declspec(dllexport) - #endif -#else - #define NAPI_EXTERN /* nothing */ -#endif - -#ifdef _WIN32 -# define NAPI_MODULE_EXPORT __declspec(dllexport) -#else -# define NAPI_MODULE_EXPORT __attribute__((visibility("default"))) -#endif - -#ifdef __GNUC__ -#define NAPI_NO_RETURN __attribute__((noreturn)) -#else -#define NAPI_NO_RETURN -#endif - - -typedef napi_value (*napi_addon_register_func)(napi_env env, - napi_value exports); - -typedef struct { - int nm_version; - unsigned int nm_flags; - const char* nm_filename; - napi_addon_register_func nm_register_func; - const char* nm_modname; - void* nm_priv; - void* reserved[4]; -} napi_module; - -#define NAPI_MODULE_VERSION 1 - -#if defined(_MSC_VER) -#pragma section(".CRT$XCU", read) -#define NAPI_C_CTOR(fn) \ - static void __cdecl fn(void); \ - __declspec(dllexport, allocate(".CRT$XCU")) void(__cdecl * fn##_)(void) = \ - fn; \ - static void __cdecl fn(void) -#else -#define NAPI_C_CTOR(fn) \ - static void fn(void) __attribute__((constructor)); \ - static void fn(void) -#endif - -#ifdef __cplusplus -#define EXTERN_C_START extern "C" { -#define EXTERN_C_END } -#else -#define EXTERN_C_START -#define EXTERN_C_END -#endif - -#define NAPI_MODULE_X(modname, regfunc, priv, flags) \ - EXTERN_C_START \ - static napi_module _module = \ - { \ - NAPI_MODULE_VERSION, \ - flags, \ - __FILE__, \ - regfunc, \ - #modname, \ - priv, \ - {0}, \ - }; \ - NAPI_C_CTOR(_register_ ## modname) { \ - napi_module_register(&_module); \ - } \ - EXTERN_C_END - -#define NAPI_MODULE(modname, regfunc) \ - NAPI_MODULE_X(modname, regfunc, NULL, 0) - -#define NAPI_AUTO_LENGTH SIZE_MAX - -EXTERN_C_START - -NAPI_EXTERN void napi_module_register(napi_module* mod); - -NAPI_EXTERN napi_status -napi_get_last_error_info(napi_env env, - const napi_extended_error_info** result); - -NAPI_EXTERN napi_status napi_fatal_exception(napi_env env, napi_value err); - -NAPI_EXTERN NAPI_NO_RETURN void napi_fatal_error(const char* location, - size_t location_len, - const char* message, - size_t message_len); - -// Getters for defined singletons -NAPI_EXTERN napi_status napi_get_undefined(napi_env env, napi_value* result); -NAPI_EXTERN napi_status napi_get_null(napi_env env, napi_value* result); -NAPI_EXTERN napi_status napi_get_global(napi_env env, napi_value* result); -NAPI_EXTERN napi_status napi_get_boolean(napi_env env, - bool value, - napi_value* result); - -// Methods to create Primitive types/Objects -NAPI_EXTERN napi_status napi_create_object(napi_env env, napi_value* result); -NAPI_EXTERN napi_status napi_create_array(napi_env env, napi_value* result); -NAPI_EXTERN napi_status napi_create_array_with_length(napi_env env, - size_t length, - napi_value* result); -NAPI_EXTERN napi_status napi_create_double(napi_env env, - double value, - napi_value* result); -NAPI_EXTERN napi_status napi_create_int32(napi_env env, - int32_t value, - napi_value* result); -NAPI_EXTERN napi_status napi_create_uint32(napi_env env, - uint32_t value, - napi_value* result); -NAPI_EXTERN napi_status napi_create_int64(napi_env env, - int64_t value, - napi_value* result); -NAPI_EXTERN napi_status napi_create_string_latin1(napi_env env, - const char* str, - size_t length, - napi_value* result); -NAPI_EXTERN napi_status napi_create_string_utf8(napi_env env, - const char* str, - size_t length, - napi_value* result); -NAPI_EXTERN napi_status napi_create_string_utf16(napi_env env, - const char16_t* str, - size_t length, - napi_value* result); -NAPI_EXTERN napi_status napi_create_symbol(napi_env env, - napi_value description, - napi_value* result); -NAPI_EXTERN napi_status napi_create_function(napi_env env, - const char* utf8name, - size_t length, - napi_callback cb, - void* data, - napi_value* result); -NAPI_EXTERN napi_status napi_create_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result); -NAPI_EXTERN napi_status napi_create_type_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result); -NAPI_EXTERN napi_status napi_create_range_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result); - -// Methods to get the the native napi_value from Primitive type -NAPI_EXTERN napi_status napi_typeof(napi_env env, - napi_value value, - napi_valuetype* result); -NAPI_EXTERN napi_status napi_get_value_double(napi_env env, - napi_value value, - double* result); -NAPI_EXTERN napi_status napi_get_value_int32(napi_env env, - napi_value value, - int32_t* result); -NAPI_EXTERN napi_status napi_get_value_uint32(napi_env env, - napi_value value, - uint32_t* result); -NAPI_EXTERN napi_status napi_get_value_int64(napi_env env, - napi_value value, - int64_t* result); -NAPI_EXTERN napi_status napi_get_value_bool(napi_env env, - napi_value value, - bool* result); - -// Copies LATIN-1 encoded bytes from a string into a buffer. -NAPI_EXTERN napi_status napi_get_value_string_latin1(napi_env env, - napi_value value, - char* buf, - size_t bufsize, - size_t* result); - -// Copies UTF-8 encoded bytes from a string into a buffer. -NAPI_EXTERN napi_status napi_get_value_string_utf8(napi_env env, - napi_value value, - char* buf, - size_t bufsize, - size_t* result); - -// Copies UTF-16 encoded bytes from a string into a buffer. -NAPI_EXTERN napi_status napi_get_value_string_utf16(napi_env env, - napi_value value, - char16_t* buf, - size_t bufsize, - size_t* result); - -// Methods to coerce values -// These APIs may execute user scripts -NAPI_EXTERN napi_status napi_coerce_to_bool(napi_env env, - napi_value value, - napi_value* result); -NAPI_EXTERN napi_status napi_coerce_to_number(napi_env env, - napi_value value, - napi_value* result); -NAPI_EXTERN napi_status napi_coerce_to_object(napi_env env, - napi_value value, - napi_value* result); -NAPI_EXTERN napi_status napi_coerce_to_string(napi_env env, - napi_value value, - napi_value* result); - -// Methods to work with Objects -NAPI_EXTERN napi_status napi_get_prototype(napi_env env, - napi_value object, - napi_value* result); -NAPI_EXTERN napi_status napi_get_property_names(napi_env env, - napi_value object, - napi_value* result); -NAPI_EXTERN napi_status napi_set_property(napi_env env, - napi_value object, - napi_value key, - napi_value value); -NAPI_EXTERN napi_status napi_has_property(napi_env env, - napi_value object, - napi_value key, - bool* result); -NAPI_EXTERN napi_status napi_get_property(napi_env env, - napi_value object, - napi_value key, - napi_value* result); -NAPI_EXTERN napi_status napi_delete_property(napi_env env, - napi_value object, - napi_value key, - bool* result); -NAPI_EXTERN napi_status napi_has_own_property(napi_env env, - napi_value object, - napi_value key, - bool* result); -NAPI_EXTERN napi_status napi_set_named_property(napi_env env, - napi_value object, - const char* utf8name, - napi_value value); -NAPI_EXTERN napi_status napi_has_named_property(napi_env env, - napi_value object, - const char* utf8name, - bool* result); -NAPI_EXTERN napi_status napi_get_named_property(napi_env env, - napi_value object, - const char* utf8name, - napi_value* result); -NAPI_EXTERN napi_status napi_set_element(napi_env env, - napi_value object, - uint32_t index, - napi_value value); -NAPI_EXTERN napi_status napi_has_element(napi_env env, - napi_value object, - uint32_t index, - bool* result); -NAPI_EXTERN napi_status napi_get_element(napi_env env, - napi_value object, - uint32_t index, - napi_value* result); -NAPI_EXTERN napi_status napi_delete_element(napi_env env, - napi_value object, - uint32_t index, - bool* result); -NAPI_EXTERN napi_status -napi_define_properties(napi_env env, - napi_value object, - size_t property_count, - const napi_property_descriptor* properties); - -// Methods to work with Arrays -NAPI_EXTERN napi_status napi_is_array(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status napi_get_array_length(napi_env env, - napi_value value, - uint32_t* result); - -// Methods to compare values -NAPI_EXTERN napi_status napi_strict_equals(napi_env env, - napi_value lhs, - napi_value rhs, - bool* result); - -// Methods to work with Functions -NAPI_EXTERN napi_status napi_call_function(napi_env env, - napi_value recv, - napi_value func, - size_t argc, - const napi_value* argv, - napi_value* result); -NAPI_EXTERN napi_status napi_new_instance(napi_env env, - napi_value constructor, - size_t argc, - const napi_value* argv, - napi_value* result); -NAPI_EXTERN napi_status napi_instanceof(napi_env env, - napi_value object, - napi_value constructor, - bool* result); - -// Methods to work with napi_callbacks - -// Gets all callback info in a single call. (Ugly, but faster.) -NAPI_EXTERN napi_status napi_get_cb_info( - napi_env env, // [in] NAPI environment handle - napi_callback_info cbinfo, // [in] Opaque callback-info handle - size_t* argc, // [in-out] Specifies the size of the provided argv array - // and receives the actual count of args. - napi_value* argv, // [out] Array of values - napi_value* this_arg, // [out] Receives the JS 'this' arg for the call - void** data); // [out] Receives the data pointer for the callback. - -NAPI_EXTERN napi_status napi_get_new_target(napi_env env, - napi_callback_info cbinfo, - napi_value* result); -NAPI_EXTERN napi_status -napi_define_class(napi_env env, - const char* utf8name, - size_t length, - napi_callback constructor, - void* data, - size_t property_count, - const napi_property_descriptor* properties, - napi_value* result); - -// Methods to work with external data objects -NAPI_EXTERN napi_status napi_wrap(napi_env env, - napi_value js_object, - void* native_object, - napi_finalize finalize_cb, - void* finalize_hint, - napi_ref* result); -NAPI_EXTERN napi_status napi_unwrap(napi_env env, - napi_value js_object, - void** result); -NAPI_EXTERN napi_status napi_remove_wrap(napi_env env, - napi_value js_object, - void** result); -NAPI_EXTERN napi_status napi_create_external(napi_env env, - void* data, - napi_finalize finalize_cb, - void* finalize_hint, - napi_value* result); -NAPI_EXTERN napi_status napi_get_value_external(napi_env env, - napi_value value, - void** result); - -// Methods to control object lifespan - -// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. -NAPI_EXTERN napi_status napi_create_reference(napi_env env, - napi_value value, - uint32_t initial_refcount, - napi_ref* result); - -// Deletes a reference. The referenced value is released, and may -// be GC'd unless there are other references to it. -NAPI_EXTERN napi_status napi_delete_reference(napi_env env, napi_ref ref); - -// Increments the reference count, optionally returning the resulting count. -// After this call the reference will be a strong reference because its -// refcount is >0, and the referenced object is effectively "pinned". -// Calling this when the refcount is 0 and the object is unavailable -// results in an error. -NAPI_EXTERN napi_status napi_reference_ref(napi_env env, - napi_ref ref, - uint32_t* result); - -// Decrements the reference count, optionally returning the resulting count. -// If the result is 0 the reference is now weak and the object may be GC'd -// at any time if there are no other references. Calling this when the -// refcount is already 0 results in an error. -NAPI_EXTERN napi_status napi_reference_unref(napi_env env, - napi_ref ref, - uint32_t* result); - -// Attempts to get a referenced value. If the reference is weak, -// the value might no longer be available, in that case the call -// is still successful but the result is NULL. -NAPI_EXTERN napi_status napi_get_reference_value(napi_env env, - napi_ref ref, - napi_value* result); - -NAPI_EXTERN napi_status napi_open_handle_scope(napi_env env, - napi_handle_scope* result); -NAPI_EXTERN napi_status napi_close_handle_scope(napi_env env, - napi_handle_scope scope); -NAPI_EXTERN napi_status -napi_open_escapable_handle_scope(napi_env env, - napi_escapable_handle_scope* result); -NAPI_EXTERN napi_status -napi_close_escapable_handle_scope(napi_env env, - napi_escapable_handle_scope scope); - -NAPI_EXTERN napi_status napi_escape_handle(napi_env env, - napi_escapable_handle_scope scope, - napi_value escapee, - napi_value* result); - -// Methods to support error handling -NAPI_EXTERN napi_status napi_throw(napi_env env, napi_value error); -NAPI_EXTERN napi_status napi_throw_error(napi_env env, - const char* code, - const char* msg); -NAPI_EXTERN napi_status napi_throw_type_error(napi_env env, - const char* code, - const char* msg); -NAPI_EXTERN napi_status napi_throw_range_error(napi_env env, - const char* code, - const char* msg); -NAPI_EXTERN napi_status napi_is_error(napi_env env, - napi_value value, - bool* result); - -// Methods to support catching exceptions -NAPI_EXTERN napi_status napi_is_exception_pending(napi_env env, bool* result); -NAPI_EXTERN napi_status napi_get_and_clear_last_exception(napi_env env, - napi_value* result); - -// Methods to provide node::Buffer functionality with napi types -NAPI_EXTERN napi_status napi_create_buffer(napi_env env, - size_t length, - void** data, - napi_value* result); -NAPI_EXTERN napi_status napi_create_external_buffer(napi_env env, - size_t length, - void* data, - napi_finalize finalize_cb, - void* finalize_hint, - napi_value* result); -NAPI_EXTERN napi_status napi_create_buffer_copy(napi_env env, - size_t length, - const void* data, - void** result_data, - napi_value* result); -NAPI_EXTERN napi_status napi_is_buffer(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status napi_get_buffer_info(napi_env env, - napi_value value, - void** data, - size_t* length); - -// Methods to work with array buffers and typed arrays -NAPI_EXTERN napi_status napi_is_arraybuffer(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status napi_create_arraybuffer(napi_env env, - size_t byte_length, - void** data, - napi_value* result); -NAPI_EXTERN napi_status -napi_create_external_arraybuffer(napi_env env, - void* external_data, - size_t byte_length, - napi_finalize finalize_cb, - void* finalize_hint, - napi_value* result); -NAPI_EXTERN napi_status napi_get_arraybuffer_info(napi_env env, - napi_value arraybuffer, - void** data, - size_t* byte_length); -NAPI_EXTERN napi_status napi_is_typedarray(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status napi_create_typedarray(napi_env env, - napi_typedarray_type type, - size_t length, - napi_value arraybuffer, - size_t byte_offset, - napi_value* result); -NAPI_EXTERN napi_status napi_get_typedarray_info(napi_env env, - napi_value typedarray, - napi_typedarray_type* type, - size_t* length, - void** data, - napi_value* arraybuffer, - size_t* byte_offset); - -NAPI_EXTERN napi_status napi_create_dataview(napi_env env, - size_t length, - napi_value arraybuffer, - size_t byte_offset, - napi_value* result); -NAPI_EXTERN napi_status napi_is_dataview(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status napi_get_dataview_info(napi_env env, - napi_value dataview, - size_t* bytelength, - void** data, - napi_value* arraybuffer, - size_t* byte_offset); - -// Methods to manage simple async operations -NAPI_EXTERN -napi_status napi_create_async_work(napi_env env, - napi_value async_resource, - napi_value async_resource_name, - napi_async_execute_callback execute, - napi_async_complete_callback complete, - void* data, - napi_async_work* result); -NAPI_EXTERN napi_status napi_delete_async_work(napi_env env, - napi_async_work work); -NAPI_EXTERN napi_status napi_queue_async_work(napi_env env, - napi_async_work work); -NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env, - napi_async_work work); - -// Methods for custom handling of async operations -NAPI_EXTERN napi_status napi_async_init(napi_env env, - napi_value async_resource, - napi_value async_resource_name, - napi_async_context* result); - -NAPI_EXTERN napi_status napi_async_destroy(napi_env env, - napi_async_context async_context); - -NAPI_EXTERN napi_status napi_make_callback(napi_env env, - napi_async_context async_context, - napi_value recv, - napi_value func, - size_t argc, - const napi_value* argv, - napi_value* result); - -// version management -NAPI_EXTERN napi_status napi_get_version(napi_env env, uint32_t* result); - -NAPI_EXTERN -napi_status napi_get_node_version(napi_env env, - const napi_node_version** version); - -// Promises -NAPI_EXTERN napi_status napi_create_promise(napi_env env, - napi_deferred* deferred, - napi_value* promise); -NAPI_EXTERN napi_status napi_resolve_deferred(napi_env env, - napi_deferred deferred, - napi_value resolution); -NAPI_EXTERN napi_status napi_reject_deferred(napi_env env, - napi_deferred deferred, - napi_value rejection); -NAPI_EXTERN napi_status napi_is_promise(napi_env env, - napi_value promise, - bool* is_promise); - -// Memory management -NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env, - int64_t change_in_bytes, - int64_t* adjusted_value); - -// Runnig a script -NAPI_EXTERN napi_status napi_run_script(napi_env env, - napi_value script, - napi_value* result); - -EXTERN_C_END - -#endif // SRC_NODE_API_H_ diff --git a/src/node_api_types.h b/src/node_api_types.h deleted file mode 100644 index 230c1f4ae..000000000 --- a/src/node_api_types.h +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef SRC_NODE_API_TYPES_H_ -#define SRC_NODE_API_TYPES_H_ - -#include -#include - -#if !defined __cplusplus || (defined(_MSC_VER) && _MSC_VER < 1900) - typedef uint16_t char16_t; -#endif - -// JSVM API types are all opaque pointers for ABI stability -// typedef undefined structs instead of void* for compile time type safety -typedef struct napi_env__ *napi_env; -typedef struct napi_value__ *napi_value; -typedef struct napi_ref__ *napi_ref; -typedef struct napi_handle_scope__ *napi_handle_scope; -typedef struct napi_escapable_handle_scope__ *napi_escapable_handle_scope; -typedef struct napi_callback_info__ *napi_callback_info; -typedef struct napi_async_context__ *napi_async_context; -typedef struct napi_async_work__ *napi_async_work; -typedef struct napi_deferred__ *napi_deferred; - -typedef enum { - napi_default = 0, - napi_writable = 1 << 0, - napi_enumerable = 1 << 1, - napi_configurable = 1 << 2, - - // Used with napi_define_class to distinguish static properties - // from instance properties. Ignored by napi_define_properties. - napi_static = 1 << 10, -} napi_property_attributes; - -typedef enum { - // ES6 types (corresponds to typeof) - napi_undefined, - napi_null, - napi_boolean, - napi_number, - napi_string, - napi_symbol, - napi_object, - napi_function, - napi_external, -} napi_valuetype; - -typedef enum { - napi_int8_array, - napi_uint8_array, - napi_uint8_clamped_array, - napi_int16_array, - napi_uint16_array, - napi_int32_array, - napi_uint32_array, - napi_float32_array, - napi_float64_array, -} napi_typedarray_type; - -typedef enum { - napi_ok, - napi_invalid_arg, - napi_object_expected, - napi_string_expected, - napi_name_expected, - napi_function_expected, - napi_number_expected, - napi_boolean_expected, - napi_array_expected, - napi_generic_failure, - napi_pending_exception, - napi_cancelled, - napi_escape_called_twice, - napi_handle_scope_mismatch -} napi_status; - -typedef napi_value (*napi_callback)(napi_env env, - napi_callback_info info); -typedef void (*napi_finalize)(napi_env env, - void* finalize_data, - void* finalize_hint); -typedef void (*napi_async_execute_callback)(napi_env env, - void* data); -typedef void (*napi_async_complete_callback)(napi_env env, - napi_status status, - void* data); - -typedef struct { - // One of utf8name or name should be NULL. - const char* utf8name; - napi_value name; - - napi_callback method; - napi_callback getter; - napi_callback setter; - napi_value value; - - napi_property_attributes attributes; - void* data; -} napi_property_descriptor; - -typedef struct { - const char* error_message; - void* engine_reserved; - uint32_t engine_error_code; - napi_status error_code; -} napi_extended_error_info; - -typedef struct { - uint32_t major; - uint32_t minor; - uint32_t patch; - const char* release; -} napi_node_version; - -#endif // SRC_NODE_API_TYPES_H_ diff --git a/src/node_internals.cc b/src/node_internals.cc deleted file mode 100644 index c4a7dd895..000000000 --- a/src/node_internals.cc +++ /dev/null @@ -1,142 +0,0 @@ -#include "node_internals.h" -#include -#include -#include -#include "uv.h" - -#if defined(_MSC_VER) -#define getpid GetCurrentProcessId -#else -#include // getpid -#endif - -#if NODE_MAJOR_VERSION < 8 || NODE_MAJOR_VERSION == 8 && NODE_MINOR_VERSION < 6 -CallbackScope::CallbackScope(void *work) { -} -#endif // NODE_MAJOR_VERSION < 8 - -namespace node { - -#if NODE_MAJOR_VERSION < 8 - -async_context EmitAsyncInit(v8::Isolate* isolate, - v8::Local resource, - v8::Local name, - async_id trigger_async_id) { - return async_context(); -} - -void EmitAsyncDestroy(v8::Isolate* isolate, - async_context asyncContext) { -} - -AsyncResource::AsyncResource(v8::Isolate* isolate, - v8::Local object, - const char *name) { -} - -#endif // NODE_MAJOR_VERSION < 8 - -#if NODE_MAJOR_VERSION < 8 || NODE_MAJOR_VERSION == 8 && NODE_MINOR_VERSION < 6 - -v8::MaybeLocal MakeCallback(v8::Isolate* isolate, - v8::Local recv, - v8::Local callback, - int argc, - v8::Local* argv, - async_context asyncContext) { - return node::MakeCallback(isolate, recv, callback, argc, argv); -} - -#endif // NODE_MAJOR_VERSION < 8 || NODE_MAJOR_VERSION == 8 && NODE_MINOR_VERSION < 6 - -static void PrintErrorString(const char* format, ...) { - va_list ap; - va_start(ap, format); -#ifdef _WIN32 - HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); - - // Check if stderr is something other than a tty/console - if (stderr_handle == INVALID_HANDLE_VALUE || - stderr_handle == nullptr || - uv_guess_handle(_fileno(stderr)) != UV_TTY) { - vfprintf(stderr, format, ap); - va_end(ap); - return; - } - - // Fill in any placeholders - int n = _vscprintf(format, ap); - std::vector out(n + 1); - vsprintf(out.data(), format, ap); - - // Get required wide buffer size - n = MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, nullptr, 0); - - std::vector wbuf(n); - MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, wbuf.data(), n); - - // Don't include the null character in the output - CHECK_GT(n, 0); - WriteConsoleW(stderr_handle, wbuf.data(), n - 1, nullptr, nullptr); -#else - vfprintf(stderr, format, ap); -#endif - va_end(ap); -} - -void DumpBacktrace(FILE* fp) { -} - -NO_RETURN void Abort() { - DumpBacktrace(stderr); - fflush(stderr); - ABORT_NO_BACKTRACE(); -} - -NO_RETURN void Assert(const char* const (*args)[4]) { - auto filename = (*args)[0]; - auto linenum = (*args)[1]; - auto message = (*args)[2]; - auto function = (*args)[3]; - - char exepath[256]; - size_t exepath_size = sizeof(exepath); - if (uv_exepath(exepath, &exepath_size)) - snprintf(exepath, sizeof(exepath), "node"); - - char pid[12] = {0}; - snprintf(pid, sizeof(pid), "[%u]", getpid()); - - fprintf(stderr, "%s%s: %s:%s:%s%s Assertion `%s' failed.\n", - exepath, pid, filename, linenum, - function, *function ? ":" : "", message); - fflush(stderr); - - Abort(); -} - -static void OnFatalError(const char* location, const char* message) { - if (location) { - PrintErrorString("FATAL ERROR: %s %s\n", location, message); - } else { - PrintErrorString("FATAL ERROR: %s\n", message); - } - fflush(stderr); - ABORT(); -} - -NO_RETURN void FatalError(const char* location, const char* message) { - OnFatalError(location, message); - // to suppress compiler warning - ABORT(); -} - -} // namespace node - -#if NODE_MAJOR_VERSION < 6 -v8::Local v8::Private::ForApi(v8::Isolate* isolate, - v8::Local key) { - return v8::Symbol::ForApi(isolate, key); -} -#endif // NODE_MAJOR_VERSION < 6 diff --git a/src/node_internals.h b/src/node_internals.h deleted file mode 100644 index bacccffb7..000000000 --- a/src/node_internals.h +++ /dev/null @@ -1,157 +0,0 @@ -#ifndef SRC_NODE_INTERNALS_H_ -#define SRC_NODE_INTERNALS_H_ - -// -// This is a stripped down shim to allow node_api.cc to build outside of the node source tree. -// - -#include "node_version.h" -#include "util-inl.h" -#include -#include -#include "uv.h" -#include "node.h" -#include - -// Windows 8+ does not like abort() in Release mode -#ifdef _WIN32 -#define ABORT_NO_BACKTRACE() raise(SIGABRT) -#else -#define ABORT_NO_BACKTRACE() abort() -#endif - -#define ABORT() node::Abort() - -#ifdef __GNUC__ -#define LIKELY(expr) __builtin_expect(!!(expr), 1) -#define UNLIKELY(expr) __builtin_expect(!!(expr), 0) -#define PRETTY_FUNCTION_NAME __PRETTY_FUNCTION__ -#else -#define LIKELY(expr) expr -#define UNLIKELY(expr) expr -#define PRETTY_FUNCTION_NAME "" -#endif - -#define STRINGIFY_(x) #x -#define STRINGIFY(x) STRINGIFY_(x) - -#define CHECK(expr) \ - do { \ - if (UNLIKELY(!(expr))) { \ - static const char* const args[] = { __FILE__, STRINGIFY(__LINE__), \ - #expr, PRETTY_FUNCTION_NAME }; \ - node::Assert(&args); \ - } \ - } while (0) - -#define CHECK_EQ(a, b) CHECK((a) == (b)) -#define CHECK_GE(a, b) CHECK((a) >= (b)) -#define CHECK_GT(a, b) CHECK((a) > (b)) -#define CHECK_LE(a, b) CHECK((a) <= (b)) -#define CHECK_LT(a, b) CHECK((a) < (b)) -#define CHECK_NE(a, b) CHECK((a) != (b)) - -#ifdef __GNUC__ -#define NO_RETURN __attribute__((noreturn)) -#else -#define NO_RETURN -#endif - -#ifndef NODE_RELEASE -#define NODE_RELEASE "node" -#endif - -#if NODE_MAJOR_VERSION < 8 || NODE_MAJOR_VERSION == 8 && NODE_MINOR_VERSION < 6 -class CallbackScope { - public: - CallbackScope(void *work); -}; -#endif // NODE_MAJOR_VERSION < 8 - -namespace node { - -// Copied from Node.js' src/node_persistent.h -template -struct ResetInDestructorPersistentTraits { - static const bool kResetInDestructor = true; - template - // Disallow copy semantics by leaving this unimplemented. - inline static void Copy( - const v8::Persistent&, - v8::Persistent>*); -}; - -// v8::Persistent does not reset the object slot in its destructor. That is -// acknowledged as a flaw in the V8 API and expected to change in the future -// but for now node::Persistent is the easier and safer alternative. -template -using Persistent = v8::Persistent>; - -#if NODE_MAJOR_VERSION < 8 || NODE_MAJOR_VERSION == 8 && NODE_MINOR_VERSION < 2 -typedef int async_id; - -typedef struct async_context { - node::async_id async_id; - node::async_id trigger_async_id; -} async_context; -#endif // NODE_MAJOR_VERSION < 8.2 - -#if NODE_MAJOR_VERSION < 8 || NODE_MAJOR_VERSION == 8 && NODE_MINOR_VERSION < 6 -NODE_EXTERN async_context EmitAsyncInit(v8::Isolate* isolate, - v8::Local resource, - v8::Local name, - async_id trigger_async_id = -1); - -NODE_EXTERN void EmitAsyncDestroy(v8::Isolate* isolate, - async_context asyncContext); - -v8::MaybeLocal MakeCallback(v8::Isolate* isolate, - v8::Local recv, - v8::Local callback, - int argc, - v8::Local* argv, - async_context asyncContext); - -#if NODE_MAJOR_VERSION < 8 -class AsyncResource { - public: - AsyncResource(v8::Isolate* isolate, - v8::Local object, - const char *name); -}; -#endif // node version below 8 - -#endif // node version below 8.6 - -// The slightly odd function signature for Assert() is to ease -// instruction cache pressure in calls from ASSERT and CHECK. -NO_RETURN void Abort(); -NO_RETURN void Assert(const char* const (*args)[4]); -void DumpBacktrace(FILE* fp); - -template -constexpr size_t arraysize(const T(&)[N]) { return N; } - -NO_RETURN void FatalError(const char* location, const char* message); - -} // namespace node - -#if NODE_MAJOR_VERSION < 8 -#define NewTarget This -#endif // NODE_MAJOR_VERSION < 8 - -#if NODE_MAJOR_VERSION < 6 -namespace v8 { - namespace Private { - v8::Local ForApi(v8::Isolate* isolate, v8::Local key); - } -} -#define GetPrivate(context, key) Get((context), (key)) -#define SetPrivate(context, key, value) \ - DefineOwnProperty((context), (key), (value), \ - static_cast(v8::DontEnum | \ - v8::DontDelete | \ - v8::ReadOnly)) -#endif // NODE_MAJOR_VERSION < 6 - -#endif // SRC_NODE_INTERNALS_H_ diff --git a/src/util-inl.h b/src/util-inl.h deleted file mode 100644 index 30aad168f..000000000 --- a/src/util-inl.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef SRC_UTIL_INL_H_ -#define SRC_UTIL_INL_H_ - -#include "util.h" -#include "v8.h" - -namespace node { - -inline v8::Local OneByteString(v8::Isolate* isolate, - const char* data, - int length) { - return v8::String::NewFromOneByte(isolate, - reinterpret_cast(data), - v8::NewStringType::kNormal, - length).ToLocalChecked(); -} - -inline v8::Local OneByteString(v8::Isolate* isolate, - const signed char* data, - int length) { - return v8::String::NewFromOneByte(isolate, - reinterpret_cast(data), - v8::NewStringType::kNormal, - length).ToLocalChecked(); -} - -inline v8::Local OneByteString(v8::Isolate* isolate, - const unsigned char* data, - int length) { - return v8::String::NewFromOneByte(isolate, - reinterpret_cast(data), - v8::NewStringType::kNormal, - length).ToLocalChecked(); -} - -} // namespace node - -#endif // SRC_UTIL_INL_H_ diff --git a/src/util.h b/src/util.h deleted file mode 100644 index 6765bc1ba..000000000 --- a/src/util.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef SRC_UTIL_H_ -#define SRC_UTIL_H_ - -#define FIXED_ONE_BYTE_STRING(isolate, string) \ - (node::OneByteString((isolate), (string), sizeof(string) - 1)) - -#endif // SRC_UTIL_H_ diff --git a/tools/conversion.js b/tools/conversion.js index 35afc21ba..4082567c2 100755 --- a/tools/conversion.js +++ b/tools/conversion.js @@ -24,8 +24,6 @@ if (disable != "--disable" && dir != "--disable") { 'binding.gyp': [ [ /([ ]*)'include_dirs': \[/g, '$1\'include_dirs\': [\n$1 \' Date: Wed, 13 Nov 2019 17:34:09 -0800 Subject: [PATCH 160/696] objectwrap: gracefully handle constructor exceptions Ensure that no native instance pointer is associated with the JavaScript object under construction if the native constructor causes a JavaScript exception to be thrown. Two different cases must be taken into consideration: 1. The exception in the constructor was caused by `ObjectWrap::ObjectWrap` when the call to `napi_wrap()` failed. 2. The exception in the constructor was caused by the constructor of the subclass of `ObjectWrap` after `napi_wrap()` was already successful. Fixes: https://github.com/nodejs/node-addon-api/issues/599 Co-authored-by: blagoev PR-URL: https://github.com/nodejs/node-addon-api/pull/600/ Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- napi-inl.h | 58 ++++++++++++++++++++++-- napi.h | 3 ++ test/binding.cc | 3 ++ test/binding.gyp | 1 + test/index.js | 1 + test/objectwrap_constructor_exception.cc | 26 +++++++++++ test/objectwrap_constructor_exception.js | 12 +++++ 7 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 test/objectwrap_constructor_exception.cc create mode 100644 test/objectwrap_constructor_exception.js diff --git a/napi-inl.h b/napi-inl.h index 2250b6b1c..5367a0bec 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2702,6 +2702,37 @@ inline Object FunctionReference::New(const std::vector& args) const // CallbackInfo class //////////////////////////////////////////////////////////////////////////////// +class ObjectWrapConstructionContext { + public: + ObjectWrapConstructionContext(CallbackInfo* info) { + info->_objectWrapConstructionContext = this; + } + + static inline void SetObjectWrapped(const CallbackInfo& info) { + if (info._objectWrapConstructionContext == nullptr) { + Napi::Error::Fatal("ObjectWrapConstructionContext::SetObjectWrapped", + "_objectWrapConstructionContext is NULL"); + } + info._objectWrapConstructionContext->_objectWrapped = true; + } + + inline void Cleanup(const CallbackInfo& info) { + if (_objectWrapped) { + napi_status status = napi_remove_wrap(info.Env(), info.This(), nullptr); + + // There's already a pending exception if we are at this point, so we have + // no choice but to fatally fail here. + NAPI_FATAL_IF_FAILED(status, + "ObjectWrapConstructionContext::Cleanup", + "Failed to remove wrap from unsuccessfully " + "constructed ObjectWrap instance"); + } + } + + private: + bool _objectWrapped = false; +}; + inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) : _env(env), _info(info), _this(nullptr), _dynamicArgs(nullptr), _data(nullptr) { _argc = _staticArgCount; @@ -3106,11 +3137,11 @@ inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { napi_value wrapper = callbackInfo.This(); napi_status status; napi_ref ref; - T* instance = static_cast(this); - status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); + status = napi_wrap(env, wrapper, this, FinalizeCallback, nullptr, &ref); NAPI_THROW_IF_FAILED_VOID(env, status); - Reference* instanceRef = instance; + ObjectWrapConstructionContext::SetObjectWrapped(callbackInfo); + Reference* instanceRef = this; *instanceRef = Reference(env, ref); } @@ -3683,10 +3714,27 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( return nullptr; } - T* instance; napi_value wrapper = details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); - instance = new T(callbackInfo); + ObjectWrapConstructionContext constructionContext(&callbackInfo); +#ifdef NAPI_CPP_EXCEPTIONS + try { + new T(callbackInfo); + } catch (const Error& e) { + // Re-throw the error after removing the failed wrap. + constructionContext.Cleanup(callbackInfo); + throw e; + } +#else + T* instance = new T(callbackInfo); + if (callbackInfo.Env().IsExceptionPending()) { + // We need to clear the exception so that removing the wrap might work. + Error e = callbackInfo.Env().GetAndClearPendingException(); + constructionContext.Cleanup(callbackInfo); + e.ThrowAsJavaScriptException(); + delete instance; + } +# endif // NAPI_CPP_EXCEPTIONS return callbackInfo.This(); }); diff --git a/napi.h b/napi.h index 57057c53a..61375b7c6 100644 --- a/napi.h +++ b/napi.h @@ -138,6 +138,7 @@ namespace Napi { class CallbackInfo; class TypedArray; template class TypedArrayOf; + class ObjectWrapConstructionContext; typedef TypedArrayOf Int8Array; ///< Typed-array of signed 8-bit integers typedef TypedArrayOf Uint8Array; ///< Typed-array of unsigned 8-bit integers @@ -1402,6 +1403,7 @@ namespace Napi { class CallbackInfo { public: + friend class ObjectWrapConstructionContext; CallbackInfo(napi_env env, napi_callback_info info); ~CallbackInfo(); @@ -1427,6 +1429,7 @@ namespace Napi { napi_value _staticArgs[6]; napi_value* _dynamicArgs; void* _data; + ObjectWrapConstructionContext* _objectWrapConstructionContext; }; class PropertyDescriptor { diff --git a/test/binding.cc b/test/binding.cc index ad4650819..bfed9ae5f 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -50,6 +50,7 @@ Object InitThreadSafeFunction(Env env); #endif Object InitTypedArray(Env env); Object InitObjectWrap(Env env); +Object InitObjectWrapConstructorException(Env env); Object InitObjectReference(Env env); Object InitVersionManagement(Env env); Object InitThunkingManual(Env env); @@ -104,6 +105,8 @@ Object Init(Env env, Object exports) { #endif exports.Set("typedarray", InitTypedArray(env)); exports.Set("objectwrap", InitObjectWrap(env)); + exports.Set("objectwrapConstructorException", + InitObjectWrapConstructorException(env)); exports.Set("objectreference", InitObjectReference(env)); exports.Set("version_management", InitVersionManagement(env)); exports.Set("thunking_manual", InitThunkingManual(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 97c47899e..a84ab073d 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -41,6 +41,7 @@ 'threadsafe_function/threadsafe_function.cc', 'typedarray.cc', 'objectwrap.cc', + 'objectwrap_constructor_exception.cc', 'objectreference.cc', 'version_management.cc', 'thunking_manual.cc', diff --git a/test/index.js b/test/index.js index 4fffd1d84..5f8bdea90 100644 --- a/test/index.js +++ b/test/index.js @@ -49,6 +49,7 @@ let testModules = [ 'typedarray', 'typedarray-bigint', 'objectwrap', + 'objectwrap_constructor_exception', 'objectreference', 'version_management' ]; diff --git a/test/objectwrap_constructor_exception.cc b/test/objectwrap_constructor_exception.cc new file mode 100644 index 000000000..d7e1bd517 --- /dev/null +++ b/test/objectwrap_constructor_exception.cc @@ -0,0 +1,26 @@ +#include + +class ConstructorExceptionTest : + public Napi::ObjectWrap { +public: + ConstructorExceptionTest(const Napi::CallbackInfo& info) : + Napi::ObjectWrap(info) { + Napi::Error error = Napi::Error::New(info.Env(), "an exception"); +#ifdef NAPI_DISABLE_CPP_EXCEPTIONS + error.ThrowAsJavaScriptException(); +#else + throw error; +#endif // NAPI_DISABLE_CPP_EXCEPTIONS + } + + static void Initialize(Napi::Env env, Napi::Object exports) { + const char* name = "ConstructorExceptionTest"; + exports.Set(name, DefineClass(env, name, {})); + } +}; + +Napi::Object InitObjectWrapConstructorException(Napi::Env env) { + Napi::Object exports = Napi::Object::New(env); + ConstructorExceptionTest::Initialize(env, exports); + return exports; +} diff --git a/test/objectwrap_constructor_exception.js b/test/objectwrap_constructor_exception.js new file mode 100644 index 000000000..8428c8034 --- /dev/null +++ b/test/objectwrap_constructor_exception.js @@ -0,0 +1,12 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +const test = (binding) => { + const { ConstructorExceptionTest } = binding.objectwrapConstructorException; + assert.throws(() => (new ConstructorExceptionTest()), /an exception/); + global.gc(); +} + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); From 23ff7f0b245c23a01b30ffcf2fcf7b89c6faf2d9 Mon Sep 17 00:00:00 2001 From: legendecas Date: Wed, 6 Nov 2019 20:01:48 +0800 Subject: [PATCH 161/696] src: make OnWorkComplete and OnExecute override-able No breaking changes on existing code were expected. All existing tests shall pass without any touch. Changes on declaration: - Added `Napi::AsyncWorker::OnWorkComplete`. - Added `Napi::AsyncWorker::OnExecute`. PR-URL: https://github.com/nodejs/node-addon-api/pull/589 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- doc/async_worker.md | 29 ++++++++++++++++++++ napi-inl.h | 66 ++++++++++++++++++++++++++------------------- napi.h | 55 +++++++++++++++++++------------------ 3 files changed, 96 insertions(+), 54 deletions(-) diff --git a/doc/async_worker.md b/doc/async_worker.md index 88668af07..2573cd2e6 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -136,6 +136,35 @@ class was created, passing in the error as the first parameter. virtual void Napi::AsyncWorker::OnError(const Napi::Error& e); ``` +### OnWorkComplete + +This method is invoked after the work has completed on JavaScript thread. +The default implementation of this method checks the status of the work and +tries to dispatch the result to `Napi::AsyncWorker::OnOk` or `Napi::AsyncWorker::Error` +if the work has committed an error. If the work was cancelled, neither +`Napi::AsyncWorker::OnOk` nor `Napi::AsyncWorker::Error` will be invoked. +After the result is dispatched, the default implementation will call into +`Napi::AsyncWorker::Destroy` if `SuppressDestruct()` was not called. + +```cpp +virtual void OnWorkComplete(Napi::Env env, napi_status status); +``` + +### OnExecute + +This method is invoked immediately on the work thread when scheduled. +The default implementation of this method just calls the `Napi::AsyncWorker::Execute` +and handles exceptions if cpp exceptions were enabled. + +The `OnExecute` method receives an `napi_env` argument. However, the `napi_env` +must NOT be used within this method, as it does not run on the JavaScript +thread and must not run any method that would cause JavaScript to run. In +practice, this means that almost any use of `napi_env` will be incorrect. + +```cpp +virtual void OnExecute(Napi::Env env); +``` + ### Destroy This method is invoked when the instance must be deallocated. If diff --git a/napi-inl.h b/napi-inl.h index 5367a0bec..c9fa63875 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4120,8 +4120,8 @@ inline AsyncWorker::AsyncWorker(const Object& receiver, _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); NAPI_THROW_IF_FAILED_VOID(_env, status); - status = napi_create_async_work(_env, resource, resource_id, OnExecute, - OnWorkComplete, this, &_work); + status = napi_create_async_work(_env, resource, resource_id, OnAsyncWorkExecute, + OnAsyncWorkComplete, this, &_work); NAPI_THROW_IF_FAILED_VOID(_env, status); } @@ -4146,8 +4146,8 @@ inline AsyncWorker::AsyncWorker(Napi::Env env, _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); NAPI_THROW_IF_FAILED_VOID(_env, status); - status = napi_create_async_work(_env, resource, resource_id, OnExecute, - OnWorkComplete, this, &_work); + status = napi_create_async_work(_env, resource, resource_id, OnAsyncWorkExecute, + OnAsyncWorkComplete, this, &_work); NAPI_THROW_IF_FAILED_VOID(_env, status); } @@ -4234,40 +4234,51 @@ inline void AsyncWorker::SetError(const std::string& error) { inline std::vector AsyncWorker::GetResult(Napi::Env /*env*/) { return {}; } +// The OnAsyncWorkExecute method receives an napi_env argument. However, do NOT +// use it within this method, as it does not run on the JavaScript thread and +// must not run any method that would cause JavaScript to run. In practice, +// this means that almost any use of napi_env will be incorrect. +inline void AsyncWorker::OnAsyncWorkExecute(napi_env env, void* asyncworker) { + AsyncWorker* self = static_cast(asyncworker); + self->OnExecute(env); +} // The OnExecute method receives an napi_env argument. However, do NOT -// use it within this method, as it does not run on the main thread and must -// not run any method that would cause JavaScript to run. In practice, this -// means that almost any use of napi_env will be incorrect. -inline void AsyncWorker::OnExecute(napi_env /*DO_NOT_USE*/, void* this_pointer) { - AsyncWorker* self = static_cast(this_pointer); +// use it within this method, as it does not run on the JavaScript thread and +// must not run any method that would cause JavaScript to run. In practice, +// this means that almost any use of napi_env will be incorrect. +inline void AsyncWorker::OnExecute(Napi::Env /*DO_NOT_USE*/) { #ifdef NAPI_CPP_EXCEPTIONS try { - self->Execute(); + Execute(); } catch (const std::exception& e) { - self->SetError(e.what()); + SetError(e.what()); } #else // NAPI_CPP_EXCEPTIONS - self->Execute(); + Execute(); #endif // NAPI_CPP_EXCEPTIONS } -inline void AsyncWorker::OnWorkComplete( - napi_env /*env*/, napi_status status, void* this_pointer) { - AsyncWorker* self = static_cast(this_pointer); +inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, + napi_status status, + void* asyncworker) { + AsyncWorker* self = static_cast(asyncworker); + self->OnWorkComplete(env, status); +} +inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { if (status != napi_cancelled) { - HandleScope scope(self->_env); + HandleScope scope(_env); details::WrapCallback([&] { - if (self->_error.size() == 0) { - self->OnOK(); + if (_error.size() == 0) { + OnOK(); } else { - self->OnError(Error::New(self->_env, self->_error)); + OnError(Error::New(_env, _error)); } return nullptr; }); } - if (!self->_suppress_destruct) { - self->Destroy(); + if (!_suppress_destruct) { + Destroy(); } } @@ -4632,14 +4643,14 @@ inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, template inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, - const Function& callback) + const Function& callback) : AsyncProgressWorker(receiver, callback, "generic") { } template inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, - const Function& callback, - const char* resource_name) + const Function& callback, + const char* resource_name) : AsyncProgressWorker(receiver, callback, resource_name, @@ -4665,14 +4676,14 @@ inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env) template inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, - const char* resource_name) + const char* resource_name) : AsyncProgressWorker(env, resource_name, Object::New(env)) { } template inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, - const char* resource_name, - const Object& resource) + const char* resource_name, + const Object& resource) : AsyncWorker(env, resource_name, resource), _asyncdata(nullptr), _asyncsize(0) { @@ -4751,7 +4762,6 @@ template inline void AsyncProgressWorker::ExecutionProgress::Send(const T* data, size_t count) const { _worker->SendProgress_(data, count); } - #endif //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 61375b7c6..49435d59b 100644 --- a/napi.h +++ b/napi.h @@ -1986,6 +1986,10 @@ namespace Napi { ObjectReference& Receiver(); FunctionReference& Callback(); + virtual void OnExecute(Napi::Env env); + virtual void OnWorkComplete(Napi::Env env, + napi_status status); + protected: explicit AsyncWorker(const Function& callback); explicit AsyncWorker(const Function& callback, @@ -2019,10 +2023,10 @@ namespace Napi { void SetError(const std::string& error); private: - static void OnExecute(napi_env env, void* this_pointer); - static void OnWorkComplete(napi_env env, - napi_status status, - void* this_pointer); + static inline void OnAsyncWorkExecute(napi_env env, void* asyncworker); + static inline void OnAsyncWorkComplete(napi_env env, + napi_status status, + void* asyncworker); napi_env _env; napi_async_work _work; @@ -2254,33 +2258,32 @@ namespace Napi { }; protected: - explicit AsyncProgressWorker(const Function& callback); - explicit AsyncProgressWorker(const Function& callback, - const char* resource_name); - explicit AsyncProgressWorker(const Function& callback, - const char* resource_name, - const Object& resource); - explicit AsyncProgressWorker(const Object& receiver, - const Function& callback); - explicit AsyncProgressWorker(const Object& receiver, - const Function& callback, - const char* resource_name); - explicit AsyncProgressWorker(const Object& receiver, - const Function& callback, - const char* resource_name, - const Object& resource); + explicit AsyncProgressWorker(const Function& callback); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); // Optional callback of Napi::ThreadSafeFunction only available after NAPI_VERSION 4. // Refs: https://github.com/nodejs/node/pull/27791 #if NAPI_VERSION > 4 - explicit AsyncProgressWorker(Napi::Env env); - explicit AsyncProgressWorker(Napi::Env env, - const char* resource_name); - explicit AsyncProgressWorker(Napi::Env env, - const char* resource_name, - const Object& resource); + explicit AsyncProgressWorker(Napi::Env env); + explicit AsyncProgressWorker(Napi::Env env, + const char* resource_name); + explicit AsyncProgressWorker(Napi::Env env, + const char* resource_name, + const Object& resource); #endif - virtual void Execute(const ExecutionProgress& progress) = 0; virtual void OnProgress(const T* data, size_t count) = 0; From e8935bd8d97ba61cd4c005fab571d9fb39ab9249 Mon Sep 17 00:00:00 2001 From: Guenter Sandner Date: Mon, 6 Jan 2020 09:27:55 +0100 Subject: [PATCH 162/696] test: add test for own properties on ObjectWrap PR-URL: https://github.com/nodejs/node-addon-api/pull/645 Reviewed-By: Gabriel Schulhof Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- test/objectwrap.cc | 15 +++++++++++++++ test/objectwrap.js | 20 +++++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/test/objectwrap.cc b/test/objectwrap.cc index 0d61171ca..92a29ce74 100644 --- a/test/objectwrap.cc +++ b/test/objectwrap.cc @@ -28,7 +28,22 @@ class Test : public Napi::ObjectWrap { if(info.Length() > 0) { finalizeCb_ = Napi::Persistent(info[0].As()); } + // Create an own instance property. + info.This().As().DefineProperty( + Napi::PropertyDescriptor::Accessor(info.Env(), + info.This().As(), + "ownProperty", + OwnPropertyGetter, + napi_enumerable, this)); + + // Create an own instance property with a templated function. + info.This().As().DefineProperty( + Napi::PropertyDescriptor::Accessor("ownPropertyT", + napi_enumerable, this)); + } + static Napi::Value OwnPropertyGetter(const Napi::CallbackInfo& info) { + return static_cast(info.Data())->Getter(info); } void Setter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { diff --git a/test/objectwrap.js b/test/objectwrap.js index de16a6067..e1281d3d2 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -72,6 +72,18 @@ const test = (binding) => { obj[clazz.kTestAccessorTInternal] = 'instance internal getset 4'; assert.strictEqual(obj[clazz.kTestAccessorTInternal], 'instance internal getset 4'); } + + // own property + { + obj.testSetter = 'own property value'; + // Make sure the properties are enumerable. + assert(Object.getOwnPropertyNames(obj).indexOf('ownProperty') >= 0); + assert(Object.getOwnPropertyNames(obj).indexOf('ownPropertyT') >= 0); + + // Make sure the properties return the right value. + assert.strictEqual(obj.ownProperty, 'own property value'); + assert.strictEqual(obj.ownPropertyT, 'own property value'); + } }; const testMethod = (obj, clazz) => { @@ -84,10 +96,12 @@ const test = (binding) => { }; const testEnumerables = (obj, clazz) => { - // Object.keys: only object - assert.deepEqual(Object.keys(obj), []); + // Object.keys: only object without prototype + assert(Object.keys(obj).length === 2); + assert(Object.keys(obj).includes('ownProperty')); + assert(Object.keys(obj).indexOf('ownPropertyT') >= 0); - // for..in: object + prototype + // for..in: object and prototype { const keys = []; for (let key in obj) { From 2fde5c3ca3bd2fefb87be1fc897e0ae606531298 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Wed, 15 Jan 2020 15:16:46 -0500 Subject: [PATCH 163/696] test: update BigInt test for recent change in core https://github.com/nodejs/node/commit/689ab46c646bc8add5560a606ba73d3760f8f924 changed the expected error for one of the BigInt test cases. This is ok because BigInt is still in experimental. However, the result was a failed/hanging test. See https://github.com/nodejs/build/issues/2131 This changes the test to accept either the new or old behaviour. We need that so that older versions of Node.js will also pass the test until the the node core commit above is backported. PR-URL: https://github.com/nodejs/node-addon-api/pull/649 Reviewed-By: Gabriel Schulhof Reviewed-By: Nicola Del Gobbo --- test/bigint.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/bigint.js b/test/bigint.js index e4255172c..0af867b43 100644 --- a/test/bigint.js +++ b/test/bigint.js @@ -46,7 +46,7 @@ function test(binding) { }); assert.throws(TestTooBigBigInt, { - name: 'RangeError', - message: 'Maximum BigInt size exceeded', + name: /^(RangeError|Error)$/, + message: /^(Maximum BigInt size exceeded|Invalid argument)$/, }); } From 4e885069f1721dec1705c898383625dd2907f9e6 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 29 Apr 2019 02:07:04 +0200 Subject: [PATCH 164/696] src: call `napi_remove_wrap()` in `ObjectWrap` dtor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, when the `ObjectWrap` constructor runs, it calls `napi_wrap()`, adding a finalize callback to the freshly created JS object. However, if the `ObjectWrap` instance is prematurely deleted, for example because a subclass constructor throws – which seems like a reasonable scenario – that finalize callback was not removed, possibly leading to a use-after-free crash. This commit adds a call `napi_remove_wrap()` from the `ObjectWrap` destructor, and a test for that scenario. This also changes the code to use the correct pointer type in `FinalizeCallback`, which may not match the incorretct one in cases of multiple inheritance. Fixes: https://github.com/node-ffi-napi/weak-napi/issues/16 PR-URL: https://github.com/nodejs/node-addon-api/pull/475 Reviewed-By: Hitesh Kanwathirtha Reviewed-By: Gabriel Schulhof Reviewed-By: Tobias Nießen Reviewed-By: Michael Dawson Co-authored-by: Gabriel Schulhof --- napi-inl.h | 60 +++++++++-------------------------- test/binding.cc | 2 ++ test/binding.gyp | 1 + test/index.js | 1 + test/objectwrap-removewrap.cc | 45 ++++++++++++++++++++++++++ test/objectwrap-removewrap.js | 17 ++++++++++ test/objectwrap.js | 3 ++ 7 files changed, 84 insertions(+), 45 deletions(-) create mode 100644 test/objectwrap-removewrap.cc create mode 100644 test/objectwrap-removewrap.js diff --git a/napi-inl.h b/napi-inl.h index c9fa63875..2855e80ef 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2702,37 +2702,6 @@ inline Object FunctionReference::New(const std::vector& args) const // CallbackInfo class //////////////////////////////////////////////////////////////////////////////// -class ObjectWrapConstructionContext { - public: - ObjectWrapConstructionContext(CallbackInfo* info) { - info->_objectWrapConstructionContext = this; - } - - static inline void SetObjectWrapped(const CallbackInfo& info) { - if (info._objectWrapConstructionContext == nullptr) { - Napi::Error::Fatal("ObjectWrapConstructionContext::SetObjectWrapped", - "_objectWrapConstructionContext is NULL"); - } - info._objectWrapConstructionContext->_objectWrapped = true; - } - - inline void Cleanup(const CallbackInfo& info) { - if (_objectWrapped) { - napi_status status = napi_remove_wrap(info.Env(), info.This(), nullptr); - - // There's already a pending exception if we are at this point, so we have - // no choice but to fatally fail here. - NAPI_FATAL_IF_FAILED(status, - "ObjectWrapConstructionContext::Cleanup", - "Failed to remove wrap from unsuccessfully " - "constructed ObjectWrap instance"); - } - } - - private: - bool _objectWrapped = false; -}; - inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) : _env(env), _info(info), _this(nullptr), _dynamicArgs(nullptr), _data(nullptr) { _argc = _staticArgCount; @@ -3140,13 +3109,22 @@ inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { status = napi_wrap(env, wrapper, this, FinalizeCallback, nullptr, &ref); NAPI_THROW_IF_FAILED_VOID(env, status); - ObjectWrapConstructionContext::SetObjectWrapped(callbackInfo); Reference* instanceRef = this; *instanceRef = Reference(env, ref); } -template -inline ObjectWrap::~ObjectWrap() {} +template +inline ObjectWrap::~ObjectWrap() { + // If the JS object still exists at this point, remove the finalizer added + // through `napi_wrap()`. + if (!IsEmpty()) { + Object object = Value(); + // It is not valid to call `napi_remove_wrap()` with an empty `object`. + // This happens e.g. during garbage collection. + if (!object.IsEmpty()) + napi_remove_wrap(Env(), object, nullptr); + } +} template inline T* ObjectWrap::Unwrap(Object wrapper) { @@ -3716,23 +3694,15 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( napi_value wrapper = details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); - ObjectWrapConstructionContext constructionContext(&callbackInfo); #ifdef NAPI_CPP_EXCEPTIONS - try { - new T(callbackInfo); - } catch (const Error& e) { - // Re-throw the error after removing the failed wrap. - constructionContext.Cleanup(callbackInfo); - throw e; - } + new T(callbackInfo); #else T* instance = new T(callbackInfo); if (callbackInfo.Env().IsExceptionPending()) { // We need to clear the exception so that removing the wrap might work. Error e = callbackInfo.Env().GetAndClearPendingException(); - constructionContext.Cleanup(callbackInfo); - e.ThrowAsJavaScriptException(); delete instance; + e.ThrowAsJavaScriptException(); } # endif // NAPI_CPP_EXCEPTIONS return callbackInfo.This(); @@ -3859,7 +3829,7 @@ inline napi_value ObjectWrap::InstanceSetterCallbackWrapper( template inline void ObjectWrap::FinalizeCallback(napi_env env, void* data, void* /*hint*/) { - T* instance = reinterpret_cast(data); + ObjectWrap* instance = static_cast*>(data); instance->Finalize(Napi::Env(env)); delete instance; } diff --git a/test/binding.cc b/test/binding.cc index bfed9ae5f..aa9db6e41 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -51,6 +51,7 @@ Object InitThreadSafeFunction(Env env); Object InitTypedArray(Env env); Object InitObjectWrap(Env env); Object InitObjectWrapConstructorException(Env env); +Object InitObjectWrapRemoveWrap(Env env); Object InitObjectReference(Env env); Object InitVersionManagement(Env env); Object InitThunkingManual(Env env); @@ -107,6 +108,7 @@ Object Init(Env env, Object exports) { exports.Set("objectwrap", InitObjectWrap(env)); exports.Set("objectwrapConstructorException", InitObjectWrapConstructorException(env)); + exports.Set("objectwrap_removewrap", InitObjectWrapRemoveWrap(env)); exports.Set("objectreference", InitObjectReference(env)); exports.Set("version_management", InitVersionManagement(env)); exports.Set("thunking_manual", InitThunkingManual(env)); diff --git a/test/binding.gyp b/test/binding.gyp index a84ab073d..b6777808d 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -42,6 +42,7 @@ 'typedarray.cc', 'objectwrap.cc', 'objectwrap_constructor_exception.cc', + 'objectwrap-removewrap.cc', 'objectreference.cc', 'version_management.cc', 'thunking_manual.cc', diff --git a/test/index.js b/test/index.js index 5f8bdea90..1bd1d9144 100644 --- a/test/index.js +++ b/test/index.js @@ -50,6 +50,7 @@ let testModules = [ 'typedarray-bigint', 'objectwrap', 'objectwrap_constructor_exception', + 'objectwrap-removewrap', 'objectreference', 'version_management' ]; diff --git a/test/objectwrap-removewrap.cc b/test/objectwrap-removewrap.cc new file mode 100644 index 000000000..31a8c6870 --- /dev/null +++ b/test/objectwrap-removewrap.cc @@ -0,0 +1,45 @@ +#include +#include + +#ifdef NAPI_CPP_EXCEPTIONS +namespace { + +static int dtor_called = 0; + +class DtorCounter { + public: + ~DtorCounter() { + assert(dtor_called == 0); + dtor_called++; + } +}; + +Napi::Value GetDtorCalled(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), dtor_called); +} + +class Test : public Napi::ObjectWrap { +public: + Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + throw Napi::Error::New(Env(), "Some error"); + } + + static void Initialize(Napi::Env env, Napi::Object exports) { + exports.Set("Test", DefineClass(env, "Test", {})); + exports.Set("getDtorCalled", Napi::Function::New(env, GetDtorCalled)); + } + +private: + DtorCounter dtor_ounter_; +}; + +} // anonymous namespace +#endif // NAPI_CPP_EXCEPTIONS + +Napi::Object InitObjectWrapRemoveWrap(Napi::Env env) { + Napi::Object exports = Napi::Object::New(env); +#ifdef NAPI_CPP_EXCEPTIONS + Test::Initialize(env, exports); +#endif + return exports; +} diff --git a/test/objectwrap-removewrap.js b/test/objectwrap-removewrap.js new file mode 100644 index 000000000..fe7a8274a --- /dev/null +++ b/test/objectwrap-removewrap.js @@ -0,0 +1,17 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +const test = (binding) => { + const Test = binding.objectwrap_removewrap.Test; + const getDtorCalled = binding.objectwrap_removewrap.getDtorCalled; + + assert.strictEqual(getDtorCalled(), 0); + assert.throws(() => { + new Test(); + }); + assert.strictEqual(getDtorCalled(), 1); + global.gc(); // Does not crash. +} + +test(require(`./build/${buildType}/binding.node`)); diff --git a/test/objectwrap.js b/test/objectwrap.js index e1281d3d2..a1a56136f 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -273,6 +273,9 @@ const test = (binding) => { // `Test` is needed for accessing exposed symbols testObj(new Test(), Test); testClass(Test); + + // Make sure the C++ object can be garbage collected without issues. + setImmediate(global.gc); } test(require(`./build/${buildType}/binding.node`)); From 0f8d73048330fa11b2218150e7b5ac4f2ee36e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Tim=C3=A1r=2C=20Dr?= Date: Fri, 17 Jan 2020 08:51:34 +0100 Subject: [PATCH 165/696] doc: fix syntax error in example Add override specifier to virtual functions. PR-URL: https://github.com/nodejs/node-addon-api/pull/650 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gabriel Schulhof Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- doc/async_worker.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/async_worker.md b/doc/async_worker.md index 2573cd2e6..048be9e88 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -371,7 +371,7 @@ The code below shows a basic example of `Napi::AsyncWorker` the implementation: #include #include -use namespace Napi; +using namespace Napi; class EchoWorker : public AsyncWorker { public: @@ -380,12 +380,12 @@ class EchoWorker : public AsyncWorker { ~EchoWorker() {} // This code will be executed on the worker thread - void Execute() { + void Execute() override { // Need to simulate cpu heavy task std::this_thread::sleep_for(std::chrono::seconds(1)); } - void OnOK() { + void OnOK() override { HandleScope scope(Env()); Callback().Call({Env().Null(), String::New(Env(), echo)}); } From 46484202caf180d7c16734b6c5a3f0cc88ee6dd0 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sat, 18 Jan 2020 13:01:31 +0100 Subject: [PATCH 166/696] test: user data in function property descriptor PR-URL: https://github.com/nodejs/node-addon-api/pull/652 Reviewed-By: Gabriel Schulhof Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- test/object/object.cc | 10 ++++++++++ test/object/object.js | 1 + 2 files changed, 11 insertions(+) diff --git a/test/object/object.cc b/test/object/object.cc index 1c9ce59f9..2c0ce420b 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -64,6 +64,11 @@ Value TestFunction(const CallbackInfo& info) { return Boolean::New(info.Env(), true); } +Value TestFunctionWithUserData(const CallbackInfo& info) { + UserDataHolder* holder = reinterpret_cast(info.Data()); + return Number::New(info.Env(), holder->value); +} + Array GetPropertyNames(const CallbackInfo& info) { Object obj = info[0].As(); Array arr = obj.GetPropertyNames(); @@ -104,6 +109,7 @@ void DefineProperties(const CallbackInfo& info) { PropertyDescriptor::Value("enumerableValue", trueValue, napi_enumerable), PropertyDescriptor::Value("configurableValue", trueValue, napi_configurable), PropertyDescriptor::Function(env, obj, "function", TestFunction), + PropertyDescriptor::Function(env, obj, "functionWithUserData", TestFunctionWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), }); } else if (nameType.Utf8Value() == "string") { // VS2013 has lifetime issues when passing temporary objects into the constructor of another @@ -125,6 +131,7 @@ void DefineProperties(const CallbackInfo& info) { std::string str5("enumerableValue"); std::string str6("configurableValue"); std::string str7("function"); + std::string str8("functionWithUserData"); obj.DefineProperties({ PropertyDescriptor::Accessor(env, obj, str1, TestGetter), @@ -148,6 +155,7 @@ void DefineProperties(const CallbackInfo& info) { PropertyDescriptor::Value(str5, trueValue, napi_enumerable), PropertyDescriptor::Value(str6, trueValue, napi_configurable), PropertyDescriptor::Function(env, obj, str7, TestFunction), + PropertyDescriptor::Function(env, obj, str8, TestFunctionWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), }); } else if (nameType.Utf8Value() == "value") { obj.DefineProperties({ @@ -184,6 +192,8 @@ void DefineProperties(const CallbackInfo& info) { Napi::String::New(env, "configurableValue"), trueValue, napi_configurable), PropertyDescriptor::Function(env, obj, Napi::String::New(env, "function"), TestFunction), + PropertyDescriptor::Function(env, obj, + Napi::String::New(env, "functionWithUserData"), TestFunctionWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), }); } } diff --git a/test/object/object.js b/test/object/object.js index 2660e4b6e..8741e27f1 100644 --- a/test/object/object.js +++ b/test/object/object.js @@ -95,6 +95,7 @@ function test(binding) { assertPropertyIsNot(obj, 'function', 'enumerable'); assertPropertyIsNot(obj, 'function', 'configurable'); assert.strictEqual(obj.function(), true); + assert.strictEqual(obj.functionWithUserData(), obj.readonlyAccessorWithUserDataT); } testDefineProperties('literal'); From 7ac6e21801cad39d28e9e6afdae5249af78cd002 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 24 Jan 2020 22:45:36 +0100 Subject: [PATCH 167/696] gyp: fix gypfile name in index.js Refs: https://github.com/nodejs/node-addon-api/pull/643 PR-URL: https://github.com/nodejs/node-addon-api/pull/658 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 402858dfc..393fa348e 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,7 @@ const path = require('path'); module.exports = { include: `"${__dirname}"`, - gyp: path.join(__dirname, 'nothing.gyp:nothing'), + gyp: path.join(__dirname, 'node_api.gyp:nothing'), isNodeApiBuiltin: true, needsFlag: false }; From 4d816183daadd1fec9411f9a3c566b098059e02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Tim=C3=A1r=2C=20Dr?= Date: Fri, 24 Jan 2020 07:41:05 +0100 Subject: [PATCH 168/696] doc: fix example code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node-addon-api/pull/657 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson Reviewed-By: Tobias Nießen --- doc/async_worker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/async_worker.md b/doc/async_worker.md index 048be9e88..d7753ac8c 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -409,7 +409,7 @@ The following code shows an example of how to create and use an `Napi::AsyncWork // Include EchoWorker class // .. -use namespace Napi; +using namespace Napi; Value Echo(const CallbackInfo& info) { // You need to validate the arguments here. From 7f56a78ff7eab77f1316696aef1de3304b70e639 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 29 Jan 2020 19:20:18 -0800 Subject: [PATCH 169/696] objectwrap: remove wrap only on failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `napi_remove_wrap()` was intended for objects that are alive for which the native addon wishes to withdraw its native pointer, and perhaps replace it with another. Therefore we need not `napi_remove_wrap()` during gc/env-cleanup. It is sufficient to `napi_delete_reference()`, as `Reference` already does. We need only `napi_remove_wrap()` if the construction failed and therefore no gc callback will ever happen. This change also removes references to `ObjectWrapConstructionContext` from the header because the class is not used anymore. Fixes: https://github.com/nodejs/node-addon-api/issues/660 Reviewed-By: Tobias Nießen Reviewed-By: Chengzhong Wu Reviewed-By: Kevin Eady Reviewed-By: Michael Dawson --- napi-inl.h | 9 ++++++--- napi.h | 5 ++--- test/objectwrap-removewrap.cc | 10 +++++----- test/objectwrap-removewrap.js | 22 ++++++++++++++++++++-- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 2855e80ef..dca1c4e5a 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3121,8 +3121,9 @@ inline ObjectWrap::~ObjectWrap() { Object object = Value(); // It is not valid to call `napi_remove_wrap()` with an empty `object`. // This happens e.g. during garbage collection. - if (!object.IsEmpty()) + if (!object.IsEmpty() && _construction_failed) { napi_remove_wrap(Env(), object, nullptr); + } } } @@ -3694,15 +3695,17 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( napi_value wrapper = details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); + T* instance = new T(callbackInfo); #ifdef NAPI_CPP_EXCEPTIONS - new T(callbackInfo); + instance->_construction_failed = false; #else - T* instance = new T(callbackInfo); if (callbackInfo.Env().IsExceptionPending()) { // We need to clear the exception so that removing the wrap might work. Error e = callbackInfo.Env().GetAndClearPendingException(); delete instance; e.ThrowAsJavaScriptException(); + } else { + instance->_construction_failed = false; } # endif // NAPI_CPP_EXCEPTIONS return callbackInfo.This(); diff --git a/napi.h b/napi.h index 49435d59b..ff7b82226 100644 --- a/napi.h +++ b/napi.h @@ -138,7 +138,6 @@ namespace Napi { class CallbackInfo; class TypedArray; template class TypedArrayOf; - class ObjectWrapConstructionContext; typedef TypedArrayOf Int8Array; ///< Typed-array of signed 8-bit integers typedef TypedArrayOf Uint8Array; ///< Typed-array of unsigned 8-bit integers @@ -1403,7 +1402,6 @@ namespace Napi { class CallbackInfo { public: - friend class ObjectWrapConstructionContext; CallbackInfo(napi_env env, napi_callback_info info); ~CallbackInfo(); @@ -1429,7 +1427,6 @@ namespace Napi { napi_value _staticArgs[6]; napi_value* _dynamicArgs; void* _data; - ObjectWrapConstructionContext* _objectWrapConstructionContext; }; class PropertyDescriptor { @@ -1888,6 +1885,8 @@ namespace Napi { template static napi_callback WrapSetter(SetterTag) noexcept { return &This::WrappedMethod; } static napi_callback WrapSetter(SetterTag) noexcept { return nullptr; } + + bool _construction_failed = true; }; class HandleScope { diff --git a/test/objectwrap-removewrap.cc b/test/objectwrap-removewrap.cc index 31a8c6870..fdcec07c7 100644 --- a/test/objectwrap-removewrap.cc +++ b/test/objectwrap-removewrap.cc @@ -1,7 +1,6 @@ #include #include -#ifdef NAPI_CPP_EXCEPTIONS namespace { static int dtor_called = 0; @@ -21,7 +20,11 @@ Napi::Value GetDtorCalled(const Napi::CallbackInfo& info) { class Test : public Napi::ObjectWrap { public: Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { +#ifdef NAPI_CPP_EXCEPTIONS throw Napi::Error::New(Env(), "Some error"); +#else + Napi::Error::New(Env(), "Some error").ThrowAsJavaScriptException(); +#endif } static void Initialize(Napi::Env env, Napi::Object exports) { @@ -30,16 +33,13 @@ class Test : public Napi::ObjectWrap { } private: - DtorCounter dtor_ounter_; + DtorCounter dtor_counter_; }; } // anonymous namespace -#endif // NAPI_CPP_EXCEPTIONS Napi::Object InitObjectWrapRemoveWrap(Napi::Env env) { Napi::Object exports = Napi::Object::New(env); -#ifdef NAPI_CPP_EXCEPTIONS Test::Initialize(env, exports); -#endif return exports; } diff --git a/test/objectwrap-removewrap.js b/test/objectwrap-removewrap.js index fe7a8274a..01c8b9169 100644 --- a/test/objectwrap-removewrap.js +++ b/test/objectwrap-removewrap.js @@ -1,8 +1,16 @@ 'use strict'; + +if (process.argv[2] === 'child') { + // Create a single wrapped instance then exit. + return new (require(process.argv[3]).objectwrap.Test)(); +} + const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); +const { spawnSync } = require('child_process'); -const test = (binding) => { +const test = (bindingName) => { + const binding = require(bindingName); const Test = binding.objectwrap_removewrap.Test; const getDtorCalled = binding.objectwrap_removewrap.getDtorCalled; @@ -12,6 +20,16 @@ const test = (binding) => { }); assert.strictEqual(getDtorCalled(), 1); global.gc(); // Does not crash. + + // Start a child process that creates a single wrapped instance to ensure that + // it is properly freed at its exit. It must not segfault. + // Re: https://github.com/nodejs/node-addon-api/issues/660 + const child = spawnSync(process.execPath, [ + __filename, 'child', bindingName + ]); + assert.strictEqual(child.signal, null); + assert.strictEqual(child.status, 0); } -test(require(`./build/${buildType}/binding.node`)); +test(`./build/${buildType}/binding.node`); +test(`./build/${buildType}/binding_noexcept.node`); From baaaa8452c0326f652d6bff2e8f58a2ba8c5f59b Mon Sep 17 00:00:00 2001 From: legendecas Date: Tue, 18 Feb 2020 06:15:29 +0800 Subject: [PATCH 170/696] doc: link threadsafe function from JS function * doc: link threadsafe function from JS function --- doc/function.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/function.md b/doc/function.md index 889d5ea14..c1b0fc9fb 100644 --- a/doc/function.md +++ b/doc/function.md @@ -11,6 +11,10 @@ functions that were created in JavaScript and passed to the native add-on. The `Napi::Function` class inherits its behavior from the `Napi::Object` class (for more info see: [`Napi::Object`](object.md)). +> For callbacks that will be called with asynchronous events from a +> non-JavaScript thread, please refer to [`Napi::ThreadSafeFunction`][] for more +> examples. + ## Example ```cpp @@ -393,3 +397,5 @@ Napi::Value Napi::Function::operator ()(const std::initializer_list& - `[in] args`: Initializer list of JavaScript values as `napi_value`. Returns a `Napi::Value` representing the JavaScript value returned by the function. + +[`Napi::ThreadSafeFunction`]: ./threadsafe_function.md From cb498bbe7f8aa601e95c7946df3db6dedf15f709 Mon Sep 17 00:00:00 2001 From: ikokostya Date: Tue, 18 Feb 2020 01:16:52 +0300 Subject: [PATCH 171/696] doc: Add Napi::BigInt::New() overload for uint64_t --- doc/bigint.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/bigint.md b/doc/bigint.md index cc981040b..ac403490e 100644 --- a/doc/bigint.md +++ b/doc/bigint.md @@ -8,6 +8,7 @@ A JavaScript BigInt value. ```cpp static Napi::BigInt Napi::BigInt::New(Napi::Env env, int64_t value); +static Napi::BigInt Napi::BigInt::New(Napi::Env env, uint64_t value); ``` - `[in] env`: The environment in which to construct the `Napi::BigInt` object. From d43da6ac2b4bf8519afcad08b59a9223257e6979 Mon Sep 17 00:00:00 2001 From: legendecas Date: Tue, 3 Mar 2020 07:06:16 +0800 Subject: [PATCH 172/696] doc: add @legendecas to active member list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 22c8eb4c7..6ab3ccecd 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around | Name | GitHub Link | | ------------------- | ----------------------------------------------------- | | Anna Henningsen | [addaleax](https://github.com/addaleax) | +| Chengzhong Wu | [legendecas](https://github.com/legendecas) | | Gabriel Schulhof | [gabrielschulhof](https://github.com/gabrielschulhof) | | Hitesh Kanwathirtha | [digitalinfinity](https://github.com/digitalinfinity) | | Jim Schlight | [jschlight](https://github.com/jschlight) | From ab018444aef47795771854d983b1367629696e15 Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 11 Oct 2019 19:49:16 +0800 Subject: [PATCH 173/696] src: implement AsyncProgressQueueWorker PR-URL: https://github.com/nodejs/node-addon-api/pull/585 Fixes: https://github.com/nodejs/node-addon-api/issues/582 Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- ...ess_worker.md => async_worker_variants.md} | 118 ++++++++- napi-inl.h | 237 ++++++++++++++++-- napi.h | 108 +++++++- test/asyncprogressqueueworker.cc | 96 +++++++ test/asyncprogressqueueworker.js | 63 +++++ test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 2 + 8 files changed, 595 insertions(+), 32 deletions(-) rename doc/{async_progress_worker.md => async_worker_variants.md} (76%) create mode 100644 test/asyncprogressqueueworker.cc create mode 100644 test/asyncprogressqueueworker.js diff --git a/doc/async_progress_worker.md b/doc/async_worker_variants.md similarity index 76% rename from doc/async_progress_worker.md rename to doc/async_worker_variants.md index 296b51b7d..a1fb6787e 100644 --- a/doc/async_progress_worker.md +++ b/doc/async_worker_variants.md @@ -272,12 +272,12 @@ called and are executed as part of the event loop. The code below shows a basic example of the `Napi::AsyncProgressWorker` implementation: ```cpp -#include +#include #include #include -use namespace Napi; +using namespace Napi; class EchoWorker : public AsyncProgressWorker { public: @@ -323,7 +323,7 @@ The following code shows an example of how to create and use an `Napi::AsyncProg // Include EchoWorker class // .. -use namespace Napi; +using namespace Napi; Value Echo(const CallbackInfo& info) { // We need to validate the arguments here @@ -341,4 +341,116 @@ asynchronous task ends and other data needed for the computation. Once created, the only other action needed is to call the `Napi::AsyncProgressWorker::Queue` method that will queue the created worker for execution. +# AsyncProgressQueueWorker + +`Napi::AsyncProgressQueueWorker` acts exactly like `Napi::AsyncProgressWorker` +except that each progress committed by `Napi::AsyncProgressQueueWorker::ExecutionProgress::Send` +during `Napi::AsyncProgressQueueWorker::Execute` is guaranteed to be +processed by `Napi::AsyncProgressQueueWorker::OnProgress` on the JavaScript +thread in the order it was committed. + +For the most basic use, only the `Napi::AsyncProgressQueueWorker::Execute` and +`Napi::AsyncProgressQueueWorker::OnProgress` method must be implemented in a subclass. + +# AsyncProgressQueueWorker::ExecutionProcess + +A bridge class created before the worker thread execution of `Napi::AsyncProgressQueueWorker::Execute`. + +## Methods + +### Send + +`Napi::AsyncProgressQueueWorker::ExecutionProcess::Send` takes two arguments, a pointer +to a generic type of data, and a `size_t` to indicate how many items the pointer is +pointing to. + +The data pointed to will be copied to internal slots of `Napi::AsyncProgressQueueWorker` so +after the call to `Napi::AsyncProgressQueueWorker::ExecutionProcess::Send` the data can +be safely released. + +`Napi::AsyncProgressQueueWorker::ExecutionProcess::Send` guarantees invocation +of `Napi::AsyncProgressQueueWorker::OnProgress`, which means multiple `Send` +call will result in the in-order invocation of `Napi::AsyncProgressQueueWorker::OnProgress` +with each data item. + +```cpp +void Napi::AsyncProgressQueueWorker::ExecutionProcess::Send(const T* data, size_t count) const; +``` + +## Example + +The code below shows a basic example of the `Napi::AsyncProgressQueueWorker` implementation: + +```cpp +#include + +#include +#include + +using namespace Napi; + +class EchoWorker : public AsyncProgressQueueWorker { + public: + EchoWorker(Function& callback, std::string& echo) + : AsyncProgressQueueWorker(callback), echo(echo) {} + + ~EchoWorker() {} + // This code will be executed on the worker thread + void Execute(const ExecutionProgress& progress) { + // Need to simulate cpu heavy task + for (uint32_t i = 0; i < 100; ++i) { + progress.Send(&i, 1) + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } + + void OnOK() { + HandleScope scope(Env()); + Callback().Call({Env().Null(), String::New(Env(), echo)}); + } + + void OnProgress(const uint32_t* data, size_t /* count */) { + HandleScope scope(Env()); + Callback().Call({Env().Null(), Env().Null(), Number::New(Env(), *data)}); + } + + private: + std::string echo; +}; +``` + +The `EchoWorker`'s constructor calls the base class' constructor to pass in the +callback that the `Napi::AsyncProgressQueueWorker` base class will store +persistently. When the work on the `Napi::AsyncProgressQueueWorker::Execute` +method is done the `Napi::AsyncProgressQueueWorker::OnOk` method is called and +the results are returned back to JavaScript when the stored callback is invoked +with its associated environment. + +The following code shows an example of how to create and use an +`Napi::AsyncProgressQueueWorker`. + +```cpp +#include + +// Include EchoWorker class +// .. + +using namespace Napi; + +Value Echo(const CallbackInfo& info) { + // We need to validate the arguments here. + Function cb = info[1].As(); + std::string in = info[0].As(); + EchoWorker* wk = new EchoWorker(cb, in); + wk->Queue(); + return info.Env().Undefined(); +} +``` + +The implementation of a `Napi::AsyncProgressQueueWorker` can be used by creating a +new instance and passing to its constructor the callback to execute when the +asynchronous task ends and other data needed for the computation. Once created, +the only other action needed is to call the `Napi::AsyncProgressQueueWorker::Queue` +method that will queue the created worker for execution. + [`Napi::AsyncWorker`]: ./async_worker.md diff --git a/napi-inl.h b/napi-inl.h index dca1c4e5a..82846c257 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4590,9 +4590,89 @@ inline void ThreadSafeFunction::CallJS(napi_env env, } //////////////////////////////////////////////////////////////////////////////// -// Async Progress Worker class +// Async Progress Worker Base class //////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(receiver, callback, resource_name, resource) { + // Fill all possible arguments to work around ambiguous ThreadSafeFunction::New signatures. + _tsfn = ThreadSafeFunction::New(callback.Env(), + callback, + resource, + resource_name, + queue_size, + /** initialThreadCount */ 1, + /** context */ this, + OnThreadSafeFunctionFinalize, + /** finalizeData */ this); +} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase(Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(env, resource_name, resource) { + // TODO: Once the changes to make the callback optional for threadsafe + // functions are available on all versions we can remove the dummy Function here. + Function callback; + // Fill all possible arguments to work around ambiguous ThreadSafeFunction::New signatures. + _tsfn = ThreadSafeFunction::New(env, + callback, + resource, + resource_name, + queue_size, + /** initialThreadCount */ 1, + /** context */ this, + OnThreadSafeFunctionFinalize, + /** finalizeData */ this); +} +#endif + +template +inline AsyncProgressWorkerBase::~AsyncProgressWorkerBase() { + // Abort pending tsfn call. + // Don't send progress events after we've already completed. + // It's ok to call ThreadSafeFunction::Abort and ThreadSafeFunction::Release duplicated. + _tsfn.Abort(); +} +template +inline void AsyncProgressWorkerBase::OnAsyncWorkProgress(Napi::Env /* env */, + Napi::Function /* jsCallback */, + void* data) { + ThreadSafeData* tsd = static_cast(data); + tsd->asyncprogressworker()->OnWorkProgress(tsd->data()); +} + +template +inline napi_status AsyncProgressWorkerBase::NonBlockingCall(DataType* data) { + auto tsd = new AsyncProgressWorkerBase::ThreadSafeData(this, data); + return _tsfn.NonBlockingCall(tsd, OnAsyncWorkProgress); +} + +template +inline void AsyncProgressWorkerBase::OnWorkComplete(Napi::Env /* env */, napi_status status) { + _work_completed = true; + _complete_status = status; + _tsfn.Release(); +} + +template +inline void AsyncProgressWorkerBase::OnThreadSafeFunctionFinalize(Napi::Env env, void* /* data */, AsyncProgressWorkerBase* context) { + if (context->_work_completed) { + context->AsyncWorker::OnWorkComplete(env, context->_complete_status); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Worker class +//////////////////////////////////////////////////////////////////////////////// template inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback) : AsyncProgressWorker(callback, "generic") { @@ -4635,10 +4715,9 @@ inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, const Function& callback, const char* resource_name, const Object& resource) - : AsyncWorker(receiver, callback, resource_name, resource), + : AsyncProgressWorkerBase(receiver, callback, resource_name, resource), _asyncdata(nullptr), _asyncsize(0) { - _tsfn = ThreadSafeFunction::New(callback.Env(), callback, resource_name, 1, 1); } #if NAPI_VERSION > 4 @@ -4657,27 +4736,19 @@ template inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, const char* resource_name, const Object& resource) - : AsyncWorker(env, resource_name, resource), + : AsyncProgressWorkerBase(env, resource_name, resource), _asyncdata(nullptr), _asyncsize(0) { - // TODO: Once the changes to make the callback optional for threadsafe - // functions are no longer optional we can remove the dummy Function here. - Function callback; - _tsfn = ThreadSafeFunction::New(env, callback, resource_name, 1, 1); } #endif template inline AsyncProgressWorker::~AsyncProgressWorker() { - // Abort pending tsfn call. - // Don't send progress events after we've already completed. - _tsfn.Abort(); { - std::lock_guard lock(_mutex); + std::lock_guard lock(this->_mutex); _asyncdata = nullptr; _asyncsize = 0; } - _tsfn.Release(); } template @@ -4687,20 +4758,18 @@ inline void AsyncProgressWorker::Execute() { } template -inline void AsyncProgressWorker::WorkProgress_(Napi::Env /* env */, Napi::Function /* jsCallback */, void* _data) { - AsyncProgressWorker* self = static_cast(_data); - +inline void AsyncProgressWorker::OnWorkProgress(void*) { T* data; size_t size; { - std::lock_guard lock(self->_mutex); - data = self->_asyncdata; - size = self->_asyncsize; - self->_asyncdata = nullptr; - self->_asyncsize = 0; + std::lock_guard lock(this->_mutex); + data = this->_asyncdata; + size = this->_asyncsize; + this->_asyncdata = nullptr; + this->_asyncsize = 0; } - self->OnProgress(data, size); + this->OnProgress(data, size); delete[] data; } @@ -4711,19 +4780,19 @@ inline void AsyncProgressWorker::SendProgress_(const T* data, size_t count) { T* old_data; { - std::lock_guard lock(_mutex); + std::lock_guard lock(this->_mutex); old_data = _asyncdata; _asyncdata = new_data; _asyncsize = count; } - _tsfn.NonBlockingCall(this, WorkProgress_); + this->NonBlockingCall(nullptr); delete[] old_data; } template inline void AsyncProgressWorker::Signal() const { - _tsfn.NonBlockingCall(this, WorkProgress_); + this->NonBlockingCall(nullptr); } template @@ -4735,6 +4804,124 @@ template inline void AsyncProgressWorker::ExecutionProgress::Send(const T* data, size_t count) const { _worker->SendProgress_(data, count); } + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Queue Worker class +//////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Function& callback) + : AsyncProgressQueueWorker(callback, "generic") { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Function& callback, + const char* resource_name) + : AsyncProgressQueueWorker(callback, resource_name, Object::New(callback.Env())) { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressQueueWorker(Object::New(callback.Env()), + callback, + resource_name, + resource) { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Object& receiver, + const Function& callback) + : AsyncProgressQueueWorker(receiver, callback, "generic") { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name) + : AsyncProgressQueueWorker(receiver, + callback, + resource_name, + Object::New(callback.Env())) { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase>(receiver, callback, resource_name, resource, /** unlimited queue size */0) { +} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env) + : AsyncProgressQueueWorker(env, "generic") { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name) + : AsyncProgressQueueWorker(env, resource_name, Object::New(env)) { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase>(env, resource_name, resource, /** unlimited queue size */0) { +} +#endif + +template +inline void AsyncProgressQueueWorker::Execute() { + ExecutionProgress progress(this); + Execute(progress); +} + +template +inline void AsyncProgressQueueWorker::OnWorkProgress(std::pair* datapair) { + if (datapair == nullptr) { + return; + } + + T *data = datapair->first; + size_t size = datapair->second; + + this->OnProgress(data, size); + delete datapair; + delete[] data; +} + +template +inline void AsyncProgressQueueWorker::SendProgress_(const T* data, size_t count) { + T* new_data = new T[count]; + std::copy(data, data + count, new_data); + + auto pair = new std::pair(new_data, count); + this->NonBlockingCall(pair); +} + +template +inline void AsyncProgressQueueWorker::Signal() const { + this->NonBlockingCall(nullptr); +} + +template +inline void AsyncProgressQueueWorker::OnWorkComplete(Napi::Env env, napi_status status) { + // Draining queued items in TSFN. + AsyncProgressWorkerBase>::OnWorkComplete(env, status); +} + +template +inline void AsyncProgressQueueWorker::ExecutionProgress::Signal() const { + _worker->Signal(); +} + +template +inline void AsyncProgressQueueWorker::ExecutionProgress::Send(const T* data, size_t count) const { + _worker->SendProgress_(data, count); +} #endif //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index ff7b82226..e4b964f87 100644 --- a/napi.h +++ b/napi.h @@ -2241,8 +2241,55 @@ namespace Napi { napi_threadsafe_function _tsfn; }; + template + class AsyncProgressWorkerBase : public AsyncWorker { + public: + virtual void OnWorkProgress(DataType* data) = 0; + class ThreadSafeData { + public: + ThreadSafeData(AsyncProgressWorkerBase* asyncprogressworker, DataType* data) + : _asyncprogressworker(asyncprogressworker), _data(data) {} + + AsyncProgressWorkerBase* asyncprogressworker() { return _asyncprogressworker; }; + DataType* data() { return _data; }; + + private: + AsyncProgressWorkerBase* _asyncprogressworker; + DataType* _data; + }; + void OnWorkComplete(Napi::Env env, napi_status status) override; + protected: + explicit AsyncProgressWorkerBase(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); + virtual ~AsyncProgressWorkerBase(); + +// Optional callback of Napi::ThreadSafeFunction only available after NAPI_VERSION 4. +// Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressWorkerBase(Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); +#endif + + static inline void OnAsyncWorkProgress(Napi::Env env, + Napi::Function jsCallback, + void* data); + + napi_status NonBlockingCall(DataType* data); + + private: + ThreadSafeFunction _tsfn; + bool _work_completed = false; + napi_status _complete_status; + static inline void OnThreadSafeFunctionFinalize(Napi::Env env, void* data, AsyncProgressWorkerBase* context); + }; + template - class AsyncProgressWorker : public AsyncWorker { + class AsyncProgressWorker : public AsyncProgressWorkerBase { public: virtual ~AsyncProgressWorker(); @@ -2256,6 +2303,8 @@ namespace Napi { AsyncProgressWorker* const _worker; }; + void OnWorkProgress(void*) override; + protected: explicit AsyncProgressWorker(const Function& callback); explicit AsyncProgressWorker(const Function& callback, @@ -2287,8 +2336,6 @@ namespace Napi { virtual void OnProgress(const T* data, size_t count) = 0; private: - static void WorkProgress_(Napi::Env env, Napi::Function jsCallback, void* data); - void Execute() override; void Signal() const; void SendProgress_(const T* data, size_t count); @@ -2296,7 +2343,60 @@ namespace Napi { std::mutex _mutex; T* _asyncdata; size_t _asyncsize; - ThreadSafeFunction _tsfn; + }; + + template + class AsyncProgressQueueWorker : public AsyncProgressWorkerBase> { + public: + virtual ~AsyncProgressQueueWorker() {}; + + class ExecutionProgress { + friend class AsyncProgressQueueWorker; + public: + void Signal() const; + void Send(const T* data, size_t count) const; + private: + explicit ExecutionProgress(AsyncProgressQueueWorker* worker) : _worker(worker) {} + AsyncProgressQueueWorker* const _worker; + }; + + void OnWorkComplete(Napi::Env env, napi_status status) override; + void OnWorkProgress(std::pair*) override; + + protected: + explicit AsyncProgressQueueWorker(const Function& callback); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + +// Optional callback of Napi::ThreadSafeFunction only available after NAPI_VERSION 4. +// Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressQueueWorker(Napi::Env env); + explicit AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name); + explicit AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name, + const Object& resource); +#endif + virtual void Execute(const ExecutionProgress& progress) = 0; + virtual void OnProgress(const T* data, size_t count) = 0; + + private: + void Execute() override; + void Signal() const; + void SendProgress_(const T* data, size_t count); }; #endif diff --git a/test/asyncprogressqueueworker.cc b/test/asyncprogressqueueworker.cc new file mode 100644 index 000000000..b30863301 --- /dev/null +++ b/test/asyncprogressqueueworker.cc @@ -0,0 +1,96 @@ +#include "napi.h" + +#include +#include +#include +#include + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +struct ProgressData { + int32_t progress; +}; + +class TestWorker : public AsyncProgressQueueWorker { +public: + static Napi::Value CreateWork(const CallbackInfo& info) { + int32_t times = info[0].As().Int32Value(); + Function cb = info[1].As(); + Function progress = info[2].As(); + + TestWorker* worker = new TestWorker(cb, + progress, + "TestResource", + Object::New(info.Env()), + times); + + return Napi::External::New(info.Env(), worker); + } + + static void QueueWork(const CallbackInfo& info) { + auto wrap = info[0].As>(); + auto worker = wrap.Data(); + worker->Queue(); + } + + static void CancelWork(const CallbackInfo& info) { + auto wrap = info[0].As>(); + auto worker = wrap.Data(); + // We cannot cancel a worker if it got started. So we have to do a quick cancel. + worker->Queue(); + worker->Cancel(); + } + +protected: + void Execute(const ExecutionProgress& progress) override { + using namespace std::chrono_literals; + std::this_thread::sleep_for(1s); + + if (_times < 0) { + SetError("test error"); + } + ProgressData data{0}; + for (int32_t idx = 0; idx < _times; idx++) { + data.progress = idx; + progress.Send(&data, 1); + } + } + + void OnProgress(const ProgressData* data, size_t /* count */) override { + Napi::Env env = Env(); + if (!_js_progress_cb.IsEmpty()) { + Number progress = Number::New(env, data->progress); + _js_progress_cb.Call(Receiver().Value(), { progress }); + } + } + +private: + TestWorker(Function cb, + Function progress, + const char* resource_name, + const Object& resource, + int32_t times) + : AsyncProgressQueueWorker(cb, resource_name, resource), + _times(times) { + _js_progress_cb.Reset(progress, 1); + } + + int32_t _times; + FunctionReference _js_progress_cb; +}; + +} // namespace + +Object InitAsyncProgressQueueWorker(Env env) { + Object exports = Object::New(env); + exports["createWork"] = Function::New(env, TestWorker::CreateWork); + exports["queueWork"] = Function::New(env, TestWorker::QueueWork); + exports["cancelWork"] = Function::New(env, TestWorker::CancelWork); + return exports; +} + +#endif diff --git a/test/asyncprogressqueueworker.js b/test/asyncprogressqueueworker.js new file mode 100644 index 000000000..6fa65520e --- /dev/null +++ b/test/asyncprogressqueueworker.js @@ -0,0 +1,63 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const common = require('./common') +const assert = require('assert'); +const os = require('os'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test({ asyncprogressqueueworker }) { + success(asyncprogressqueueworker); + fail(asyncprogressqueueworker); + cancel(asyncprogressqueueworker); + return; +} + +function success(binding) { + const expected = [0, 1, 2, 3]; + const actual = []; + const worker = binding.createWork(expected.length, + common.mustCall((err) => { + if (err) { + assert.fail(err); + } + // All queued items shall be invoked before complete callback. + assert.deepEqual(actual, expected); + }), + common.mustCall((_progress) => { + actual.push(_progress); + }, expected.length) + ); + binding.queueWork(worker); +} + +function fail(binding) { + const worker = binding.createWork(-1, + common.mustCall((err) => { + assert.throws(() => { throw err }, /test error/) + }), + () => { + assert.fail('unexpected progress report'); + } + ); + binding.queueWork(worker); +} + +function cancel(binding) { + // make sure the work we are going to cancel will not be + // able to start by using all the threads in the pool. + for (let i = 0; i < os.cpus().length; ++i) { + const worker = binding.createWork(-1, () => {}, () => {}); + binding.queueWork(worker); + } + const worker = binding.createWork(-1, + () => { + assert.fail('unexpected callback'); + }, + () => { + assert.fail('unexpected progress report'); + } + ); + binding.cancelWork(worker); +} diff --git a/test/binding.cc b/test/binding.cc index aa9db6e41..111bcce01 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -5,6 +5,7 @@ using namespace Napi; Object InitArrayBuffer(Env env); Object InitAsyncContext(Env env); #if (NAPI_VERSION > 3) +Object InitAsyncProgressQueueWorker(Env env); Object InitAsyncProgressWorker(Env env); #endif Object InitAsyncWorker(Env env); @@ -60,6 +61,7 @@ Object Init(Env env, Object exports) { exports.Set("arraybuffer", InitArrayBuffer(env)); exports.Set("asynccontext", InitAsyncContext(env)); #if (NAPI_VERSION > 3) + exports.Set("asyncprogressqueueworker", InitAsyncProgressQueueWorker(env)); exports.Set("asyncprogressworker", InitAsyncProgressWorker(env)); #endif exports.Set("asyncworker", InitAsyncWorker(env)); diff --git a/test/binding.gyp b/test/binding.gyp index b6777808d..2d6ac9549 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -4,6 +4,7 @@ 'sources': [ 'arraybuffer.cc', 'asynccontext.cc', + 'asyncprogressqueueworker.cc', 'asyncprogressworker.cc', 'asyncworker.cc', 'asyncworker-persistent.cc', diff --git a/test/index.js b/test/index.js index 1bd1d9144..e96ac5bf8 100644 --- a/test/index.js +++ b/test/index.js @@ -10,6 +10,7 @@ process.config.target_defaults.default_configuration = let testModules = [ 'arraybuffer', 'asynccontext', + 'asyncprogressqueueworker', 'asyncprogressworker', 'asyncworker', 'asyncworker-nocallback', @@ -72,6 +73,7 @@ if (napiVersion < 3) { } if (napiVersion < 4) { + testModules.splice(testModules.indexOf('asyncprogressqueueworker'), 1); testModules.splice(testModules.indexOf('asyncprogressworker'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ctx'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_existing_tsfn'), 1); From 89e62a9154b6057ed086a227db25caf26ac8c92a Mon Sep 17 00:00:00 2001 From: legendecas Date: Sun, 15 Mar 2020 23:12:51 +0800 Subject: [PATCH 174/696] doc: recommend tags of addon helpers PR-URL: https://github.com/nodejs/node-addon-api/pull/683 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6ab3ccecd..52e0c3137 100644 --- a/README.md +++ b/README.md @@ -160,8 +160,6 @@ npm run-script dev:incremental Take a look and get inspired by our **[test suite](https://github.com/nodejs/node-addon-api/tree/master/test)** - - ### **Benchmarks** You can run the available benchmarks using the following command: @@ -172,16 +170,26 @@ npm run-script benchmark See [benchmark/README.md](benchmark/README.md) for more details about running and adding benchmarks. -## **Contributing** - -We love contributions from the community to **node-addon-api**. -See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around extending this module. + ### **More resource and info about native Addons** - **[C++ Addons](https://nodejs.org/dist/latest/docs/api/addons.html)** - **[N-API](https://nodejs.org/dist/latest/docs/api/n-api.html)** - **[N-API - Next Generation Node API for Native Modules](https://youtu.be/-Oniup60Afs)** +As node-addon-api's core mission is to expose the plain C N-API as C++ +wrappers, tools that facilitate n-api/node-addon-api providing more +convenient patterns on developing a Node.js add-ons with n-api/node-addon-api +can be published to NPM as standalone packages. It is also recommended to tag +such packages with `node-addon-api` to provide more visibility to the community. + +Quick links to NPM searches: [keywords:node-addon-api](https://www.npmjs.com/search?q=keywords%3Anode-addon-api). + +## **Contributing** + +We love contributions from the community to **node-addon-api**! +See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around extending this module. + ## Team members From 78196e018b5cb34d2b82a4fb6880d48154a9d8e6 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 22 Mar 2020 19:33:46 +0100 Subject: [PATCH 175/696] tsfn: implement ThreadSafeFunctionEx --- napi-inl.h | 125 ++++++++++++++++++ napi.h | 74 +++++++++++ test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 2 + .../threadsafe_function_ex.cc | 93 +++++++++++++ .../threadsafe_function_ex.js | 17 +++ 7 files changed, 314 insertions(+) create mode 100644 test/threadsafe_function/threadsafe_function_ex.cc create mode 100644 test/threadsafe_function/threadsafe_function_ex.js diff --git a/napi-inl.h b/napi-inl.h index 82846c257..4bd4845cc 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4256,6 +4256,131 @@ inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { } #if (NAPI_VERSION > 3) +//////////////////////////////////////////////////////////////////////////////// +// ThreadSafeFunctionEx class +//////////////////////////////////////////////////////////////////////////////// + +// static +template +template +inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_threadsafe_function_call_js call_js_cb) { + return New(env, callback, resource, resourceName, maxQueueSize, + initialThreadCount, context, finalizeCallback, data, + details::ThreadSafeFinalize::FinalizeFinalizeWrapperWithDataAndContext, + call_js_cb); +} + +template +inline ThreadSafeFunctionEx::ThreadSafeFunctionEx() + : _tsfn() { +} + +template +inline ThreadSafeFunctionEx::ThreadSafeFunctionEx( + napi_threadsafe_function tsfn) + : _tsfn(tsfn) { +} + +template +inline ThreadSafeFunctionEx::operator napi_threadsafe_function() const { + return _tsfn; +} + +template +template +inline napi_status ThreadSafeFunctionEx::BlockingCall( + DataType* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); +} + +template +template +inline napi_status ThreadSafeFunctionEx::NonBlockingCall( + DataType* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); +} + +template +inline void ThreadSafeFunctionEx::Ref(napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_ref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +template +inline void ThreadSafeFunctionEx::Unref(napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_unref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +template +inline napi_status ThreadSafeFunctionEx::Acquire() const { + return napi_acquire_threadsafe_function(_tsfn); +} + +template +inline napi_status ThreadSafeFunctionEx::Release() { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); +} + +template +inline napi_status ThreadSafeFunctionEx::Abort() { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); +} + +template +inline ContextType* ThreadSafeFunctionEx::GetContext() const { + void* context; + napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); + NAPI_FATAL_IF_FAILED(status, "ThreadSafeFunctionEx::GetContext", "napi_get_threadsafe_function_context"); + return static_cast(context); +} + +// static +template +template +inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper, + napi_threadsafe_function_call_js call_js_cb) { + static_assert(details::can_make_string::value + || std::is_convertible::value, + "Resource name should be convertible to the string type"); + + ThreadSafeFunctionEx tsfn; + auto* finalizeData = new details::ThreadSafeFinalize({ data, finalizeCallback }); + napi_status status = napi_create_threadsafe_function(env, callback, resource, + Value::From(env, resourceName), maxQueueSize, initialThreadCount, + finalizeData, wrapper, context, call_js_cb, &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunctionEx()); + } + + return tsfn; +} + //////////////////////////////////////////////////////////////////////////////// // ThreadSafeFunction class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index e4b964f87..4973321e7 100644 --- a/napi.h +++ b/napi.h @@ -2036,6 +2036,80 @@ namespace Napi { }; #if (NAPI_VERSION > 3) + + template + class ThreadSafeFunctionEx { + public: + // This API may only be called from the main thread. + template + static ThreadSafeFunctionEx New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_threadsafe_function_call_js call_js_cb); + + ThreadSafeFunctionEx(); + ThreadSafeFunctionEx(napi_threadsafe_function tsFunctionValue); + + operator napi_threadsafe_function() const; + + // // This API may be called from any thread. + // napi_status BlockingCall() const; + + // This API may be called from any thread. + template + napi_status BlockingCall(DataType* data = nullptr) const; + + // // This API may be called from any thread. + // napi_status NonBlockingCall() const; + + // This API may be called from any thread. + template + napi_status NonBlockingCall(DataType* data = nullptr) const; + + // This API may only be called from the main thread. + void Ref(napi_env env) const; + + // This API may only be called from the main thread. + void Unref(napi_env env) const; + + // This API may be called from any thread. + napi_status Acquire() const; + + // This API may be called from any thread. + napi_status Release(); + + // This API may be called from any thread. + napi_status Abort(); + + // This API may be called from any thread. + ContextType* GetContext() const; + + private: + using CallbackWrapper = std::function; + + template + static ThreadSafeFunctionEx New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper, + napi_threadsafe_function_call_js call_js_cb); + + napi_threadsafe_function _tsfn; + }; + class ThreadSafeFunction { public: // This API may only be called from the main thread. diff --git a/test/binding.cc b/test/binding.cc index 111bcce01..499d9435a 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -43,6 +43,7 @@ Object InitPromise(Env env); Object InitRunScript(Env env); #if (NAPI_VERSION > 3) Object InitThreadSafeFunctionCtx(Env env); +Object InitThreadSafeFunctionEx(Env env); Object InitThreadSafeFunctionExistingTsfn(Env env); Object InitThreadSafeFunctionPtr(Env env); Object InitThreadSafeFunctionSum(Env env); @@ -100,6 +101,7 @@ Object Init(Env env, Object exports) { exports.Set("run_script", InitRunScript(env)); #if (NAPI_VERSION > 3) exports.Set("threadsafe_function_ctx", InitThreadSafeFunctionCtx(env)); + exports.Set("threadsafe_function_ex", InitThreadSafeFunctionEx(env)); exports.Set("threadsafe_function_existing_tsfn", InitThreadSafeFunctionExistingTsfn(env)); exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env)); exports.Set("threadsafe_function_sum", InitThreadSafeFunctionSum(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 2d6ac9549..790bef5fa 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -35,6 +35,7 @@ 'promise.cc', 'run_script.cc', 'threadsafe_function/threadsafe_function_ctx.cc', + 'threadsafe_function/threadsafe_function_ex.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', 'threadsafe_function/threadsafe_function_sum.cc', diff --git a/test/index.js b/test/index.js index e96ac5bf8..d91b54581 100644 --- a/test/index.js +++ b/test/index.js @@ -42,6 +42,7 @@ let testModules = [ 'promise', 'run_script', 'threadsafe_function/threadsafe_function_ctx', + 'threadsafe_function/threadsafe_function_ex', 'threadsafe_function/threadsafe_function_existing_tsfn', 'threadsafe_function/threadsafe_function_ptr', 'threadsafe_function/threadsafe_function_sum', @@ -76,6 +77,7 @@ if (napiVersion < 4) { testModules.splice(testModules.indexOf('asyncprogressqueueworker'), 1); testModules.splice(testModules.indexOf('asyncprogressworker'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ctx'), 1); + testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ex'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_existing_tsfn'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ptr'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_sum'), 1); diff --git a/test/threadsafe_function/threadsafe_function_ex.cc b/test/threadsafe_function/threadsafe_function_ex.cc new file mode 100644 index 000000000..84ce1b77a --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_ex.cc @@ -0,0 +1,93 @@ +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +using TSFNContext = Reference; + +namespace { + +struct CallJsData { + CallJsData(Napi::Env env) : deferred(Promise::Deferred::New(env)) { }; + + void resolve(TSFNContext* context) { + deferred.Resolve(context->Value()); + }; + Promise::Deferred deferred; +}; + +class TSFNWrap : public ObjectWrap { +public: + static Object Init(Napi::Env env, Object exports); + TSFNWrap(const CallbackInfo &info); + + Napi::Value GetContextByCall(const CallbackInfo &info) { + Napi::Env env = info.Env(); + std::unique_ptr callData = std::make_unique(env); + auto& deferred = callData->deferred; + _tsfn.BlockingCall(callData.release()); + return deferred.Promise(); + }; + + Napi::Value GetContextFromTsfn(const CallbackInfo &info) { + return _tsfn.GetContext()->Value(); + }; + + Napi::Value Release(const CallbackInfo &info) { + _tsfn.Release(); + return _deferred.Promise(); + }; + +private: + ThreadSafeFunctionEx _tsfn; + Promise::Deferred _deferred; +}; + +Object TSFNWrap::Init(Napi::Env env, Object exports) { + Function func = DefineClass( + env, "TSFNWrap", + {InstanceMethod("getContextByCall", &TSFNWrap::GetContextByCall), + InstanceMethod("getContextFromTsfn", &TSFNWrap::GetContextFromTsfn), + InstanceMethod("release", &TSFNWrap::Release)}); + + exports.Set("TSFNWrap", func); + return exports; +} + +TSFNWrap::TSFNWrap(const CallbackInfo &info) + : ObjectWrap(info), + _deferred(Promise::Deferred::New(info.Env())) { + Napi::Env env = info.Env(); + + TSFNContext *ctx = new Reference; + *ctx = Persistent(info[0]); + + _tsfn = ThreadSafeFunctionEx::New( + info.Env(), // napi_env env, + Function::New( + env, + [](const CallbackInfo & /*info*/) {}), // const Function& callback, + Value(), // const Object& resource, + "Test", // ResourceString resourceName, + 1, // size_t maxQueueSize, + 1, // size_t initialThreadCount, + ctx, // ContextType* context, + + [this](Napi::Env env, void*, TSFNContext *ctx) { // Finalizer finalizeCallback, + _deferred.Resolve(env.Undefined()); + delete ctx; + }, + static_cast(nullptr), // FinalizerDataType* data, + [](napi_env env, napi_value js_callback, void *context, void *data) { // call_js_cb + std::unique_ptr callData(static_cast(data)); + callData->resolve(static_cast(context)); + }); +} +} // namespace + +Object InitThreadSafeFunctionEx(Env env) { + return TSFNWrap::Init(env, Object::New(env)); +} + +#endif diff --git a/test/threadsafe_function/threadsafe_function_ex.js b/test/threadsafe_function/threadsafe_function_ex.js new file mode 100644 index 000000000..63fd7dc5c --- /dev/null +++ b/test/threadsafe_function/threadsafe_function_ex.js @@ -0,0 +1,17 @@ +'use strict'; + +const assert = require('assert'); +const buildType = process.config.target_defaults.default_configuration; + +module.exports = Promise.all([ + test(require(`../build/${buildType}/binding.node`)), + test(require(`../build/${buildType}/binding_noexcept.node`)) +]); + +async function test(binding) { + const ctx = { }; + const tsfn = new binding.threadsafe_function_ex.TSFNWrap(ctx); + assert(ctx === await tsfn.getContextByCall(),"getContextByCall context not equal"); + assert(ctx === tsfn.getContextFromTsfn(),"getContextFromTsfn context not equal"); + await tsfn.release(); +} From 90ebd80ae1f7d703b11529433c5cf3d72ee97491 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 22 Mar 2020 20:02:42 +0100 Subject: [PATCH 176/696] fix unused parameter errors --- test/threadsafe_function/threadsafe_function_ex.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/threadsafe_function/threadsafe_function_ex.cc b/test/threadsafe_function/threadsafe_function_ex.cc index 84ce1b77a..fa7efb993 100644 --- a/test/threadsafe_function/threadsafe_function_ex.cc +++ b/test/threadsafe_function/threadsafe_function_ex.cc @@ -30,11 +30,11 @@ class TSFNWrap : public ObjectWrap { return deferred.Promise(); }; - Napi::Value GetContextFromTsfn(const CallbackInfo &info) { + Napi::Value GetContextFromTsfn(const CallbackInfo &) { return _tsfn.GetContext()->Value(); }; - Napi::Value Release(const CallbackInfo &info) { + Napi::Value Release(const CallbackInfo &) { _tsfn.Release(); return _deferred.Promise(); }; @@ -79,7 +79,7 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info) delete ctx; }, static_cast(nullptr), // FinalizerDataType* data, - [](napi_env env, napi_value js_callback, void *context, void *data) { // call_js_cb + [](napi_env, napi_value, void *context, void *data) { // call_js_cb std::unique_ptr callData(static_cast(data)); callData->resolve(static_cast(context)); }); From 6b8dd47c551d2ecc1e923cba6fbce8f3d66eea3a Mon Sep 17 00:00:00 2001 From: NickNaso Date: Mon, 23 Mar 2020 11:19:55 +0100 Subject: [PATCH 177/696] Added badge section to documentation. --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 52e0c3137..a67051d00 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Examples](#examples)** - **[Tests](#tests)** - **[More resource and info about native Addons](#resources)** +- **[Badges](#badges)** - **[Code of Conduct](CODE_OF_CONDUCT.md)** - **[Contributors](#contributors)** - **[License](#license)** @@ -185,6 +186,24 @@ such packages with `node-addon-api` to provide more visibility to the community. Quick links to NPM searches: [keywords:node-addon-api](https://www.npmjs.com/search?q=keywords%3Anode-addon-api). + + +### **Badges** + +The use of badges is recommended to indicate the minimum version of N-API +required for the module. This helps to determine which Node.js major versions are +supported. Addon maintainers can consult the [N-API support matrix][] to determine +which Node.js versions provide a given N-API version. The following badges are +available: + +![N-API v1 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v1%20Badge.svg) +![N-API v2 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v2%20Badge.svg) +![N-API v3 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v3%20Badge.svg) +![N-API v4 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v4%20Badge.svg) +![N-API v5 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v5%20Badge.svg) +![N-API v6 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v6%20Badge.svg) +![N-API Experimental Version Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20Experimental%20Version%20Badge.svg) + ## **Contributing** We love contributions from the community to **node-addon-api**! @@ -220,3 +239,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around Licensed under [MIT](./LICENSE.md) [ABI stability guide]: https://nodejs.org/en/docs/guides/abi-stability/ +[N-API support matrix]: https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix From 6c97913d1f44ffa334c9f3d4f36a56d7cc0e9096 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Sat, 28 Mar 2020 15:11:58 -0400 Subject: [PATCH 178/696] Fix minor typo in object_lifetime_management.md --- doc/object_lifetime_management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/object_lifetime_management.md b/doc/object_lifetime_management.md index 4ab19ecd1..a888ff59e 100644 --- a/doc/object_lifetime_management.md +++ b/doc/object_lifetime_management.md @@ -69,7 +69,7 @@ for (int i = 0; i < LOOP_MAX; i++) { Napi::HandleScope scope(info.Env()); std::string name = std::string("inner-scope") + std::to_string(i); Napi::Value newValue = Napi::String::New(info.Env(), name.c_str()); - // do something with neValue + // do something with newValue }; ``` From fc339c4c46ec2d618d66a715bee9d1a5e7157514 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Tue, 31 Mar 2020 14:57:20 +0200 Subject: [PATCH 179/696] wip --- napi-inl.h | 31 +++++++++++++++++-- napi.h | 22 +++++++++---- .../threadsafe_function_ex.cc | 4 +-- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 4bd4845cc..41097e8f1 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4272,7 +4272,7 @@ inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New( ContextType* context, Finalizer finalizeCallback, FinalizerDataType* data, - napi_threadsafe_function_call_js call_js_cb) { + ThreadSafeFunctionCallJS call_js_cb) { return New(env, callback, resource, resourceName, maxQueueSize, initialThreadCount, context, finalizeCallback, data, details::ThreadSafeFinalize ThreadSafeFunctionEx::New( Finalizer finalizeCallback, FinalizerDataType* data, napi_finalize wrapper, - napi_threadsafe_function_call_js call_js_cb) { + ThreadSafeFunctionCallJS call_js_cb) { static_assert(details::can_make_string::value || std::is_convertible::value, "Resource name should be convertible to the string type"); @@ -4372,7 +4372,15 @@ inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New( FinalizerDataType>({ data, finalizeCallback }); napi_status status = napi_create_threadsafe_function(env, callback, resource, Value::From(env, resourceName), maxQueueSize, initialThreadCount, - finalizeData, wrapper, context, call_js_cb, &tsfn._tsfn); + finalizeData, wrapper, context, + // [=](napi_env env, napi_value jsCallback, void* context, void* data) { + // if (env == nullptr && jsCallback == nullptr) { + // return; + // } + // call_js_cb( Napi::Env(env), Function(env, jsCallback), static_cast(context), data); + // }, + CallJS, + &tsfn._tsfn); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunctionEx()); @@ -4381,6 +4389,23 @@ inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New( return tsfn; } +template +inline void +ThreadSafeFunctionEx::CallJS(napi_env env, napi_value jsCallback, + void *context, void *data) { + if (env == nullptr && jsCallback == nullptr) { + return; + } + + if (data != nullptr) { + auto* callbackWrapper = static_cast(data); + (*callbackWrapper)(env, Function(env, jsCallback)); + delete callbackWrapper; + } else if (jsCallback != nullptr) { + Function(env, jsCallback).Call({}); + } +} + //////////////////////////////////////////////////////////////////////////////// // ThreadSafeFunction class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 4973321e7..c4b1228a4 100644 --- a/napi.h +++ b/napi.h @@ -2037,9 +2037,13 @@ namespace Napi { #if (NAPI_VERSION > 3) - template + + template class ThreadSafeFunctionEx { public: + + using ThreadSafeFunctionCallJS = std::function; + // This API may only be called from the main thread. template static ThreadSafeFunctionEx New(napi_env env, @@ -2051,7 +2055,7 @@ namespace Napi { ContextType* context, Finalizer finalizeCallback, FinalizerDataType* data, - napi_threadsafe_function_call_js call_js_cb); + ThreadSafeFunctionCallJS call_js_cb); ThreadSafeFunctionEx(); ThreadSafeFunctionEx(napi_threadsafe_function tsFunctionValue); @@ -2062,14 +2066,12 @@ namespace Napi { // napi_status BlockingCall() const; // This API may be called from any thread. - template napi_status BlockingCall(DataType* data = nullptr) const; // // This API may be called from any thread. // napi_status NonBlockingCall() const; // This API may be called from any thread. - template napi_status NonBlockingCall(DataType* data = nullptr) const; // This API may only be called from the main thread. @@ -2091,7 +2093,7 @@ namespace Napi { ContextType* GetContext() const; private: - using CallbackWrapper = std::function; + // using CallbackWrapper = std::function; template @@ -2105,7 +2107,15 @@ namespace Napi { Finalizer finalizeCallback, FinalizerDataType* data, napi_finalize wrapper, - napi_threadsafe_function_call_js call_js_cb); + ThreadSafeFunctionCallJS call_js_cb); + + static void CallJS(napi_env env, + napi_value jsCallback, + void* context, + void* data); + + protected: + void CallJS napi_threadsafe_function _tsfn; }; diff --git a/test/threadsafe_function/threadsafe_function_ex.cc b/test/threadsafe_function/threadsafe_function_ex.cc index fa7efb993..fccf2401f 100644 --- a/test/threadsafe_function/threadsafe_function_ex.cc +++ b/test/threadsafe_function/threadsafe_function_ex.cc @@ -79,9 +79,9 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info) delete ctx; }, static_cast(nullptr), // FinalizerDataType* data, - [](napi_env, napi_value, void *context, void *data) { // call_js_cb + [](Napi::Env, Napi::Value, TSFNContext *context, void *data) { // call_js_cb std::unique_ptr callData(static_cast(data)); - callData->resolve(static_cast(context)); + callData->resolve(context); }); } } // namespace From fd530b217e9f7dec68c391e53af51658537a40f0 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Tue, 7 Apr 2020 18:03:51 +0200 Subject: [PATCH 180/696] wip --- napi-inl.h | 107 ++++++++++-------- napi.h | 32 ++---- .../threadsafe_function_ex.cc | 37 +++--- .../threadsafe_function_ex.js | 7 +- 4 files changed, 97 insertions(+), 86 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 41097e8f1..0ee2910b3 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4261,9 +4261,9 @@ inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { //////////////////////////////////////////////////////////////////////////////// // static -template +template template -inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New(napi_env env, +inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New(napi_env env, const Function& callback, const Object& resource, ResourceString resourceName, @@ -4271,78 +4271,74 @@ inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New( size_t initialThreadCount, ContextType* context, Finalizer finalizeCallback, - FinalizerDataType* data, - ThreadSafeFunctionCallJS call_js_cb) { + FinalizerDataType* data) { return New(env, callback, resource, resourceName, maxQueueSize, initialThreadCount, context, finalizeCallback, data, details::ThreadSafeFinalize::FinalizeFinalizeWrapperWithDataAndContext, - call_js_cb); + FinalizerDataType>::FinalizeFinalizeWrapperWithDataAndContext); } -template -inline ThreadSafeFunctionEx::ThreadSafeFunctionEx() +template +inline ThreadSafeFunctionEx::ThreadSafeFunctionEx() : _tsfn() { } -template -inline ThreadSafeFunctionEx::ThreadSafeFunctionEx( +template +inline ThreadSafeFunctionEx::ThreadSafeFunctionEx( napi_threadsafe_function tsfn) : _tsfn(tsfn) { } -template -inline ThreadSafeFunctionEx::operator napi_threadsafe_function() const { +template +inline ThreadSafeFunctionEx::operator napi_threadsafe_function() const { return _tsfn; } -template -template -inline napi_status ThreadSafeFunctionEx::BlockingCall( +template +inline napi_status ThreadSafeFunctionEx::BlockingCall( DataType* data) const { return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); } -template -template -inline napi_status ThreadSafeFunctionEx::NonBlockingCall( +template +inline napi_status ThreadSafeFunctionEx::NonBlockingCall( DataType* data) const { return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); } -template -inline void ThreadSafeFunctionEx::Ref(napi_env env) const { +template +inline void ThreadSafeFunctionEx::Ref(napi_env env) const { if (_tsfn != nullptr) { napi_status status = napi_ref_threadsafe_function(env, _tsfn); NAPI_THROW_IF_FAILED_VOID(env, status); } } -template -inline void ThreadSafeFunctionEx::Unref(napi_env env) const { +template +inline void ThreadSafeFunctionEx::Unref(napi_env env) const { if (_tsfn != nullptr) { napi_status status = napi_unref_threadsafe_function(env, _tsfn); NAPI_THROW_IF_FAILED_VOID(env, status); } } -template -inline napi_status ThreadSafeFunctionEx::Acquire() const { +template +inline napi_status ThreadSafeFunctionEx::Acquire() const { return napi_acquire_threadsafe_function(_tsfn); } -template -inline napi_status ThreadSafeFunctionEx::Release() { +template +inline napi_status ThreadSafeFunctionEx::Release() { return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); } -template -inline napi_status ThreadSafeFunctionEx::Abort() { +template +inline napi_status ThreadSafeFunctionEx::Abort() { return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); } -template -inline ContextType* ThreadSafeFunctionEx::GetContext() const { +template +inline ContextType* ThreadSafeFunctionEx::GetContext() const { void* context; napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); NAPI_FATAL_IF_FAILED(status, "ThreadSafeFunctionEx::GetContext", "napi_get_threadsafe_function_context"); @@ -4350,9 +4346,9 @@ inline ContextType* ThreadSafeFunctionEx::GetContext() const { } // static -template +template template -inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New(napi_env env, +inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New(napi_env env, const Function& callback, const Object& resource, ResourceString resourceName, @@ -4361,13 +4357,14 @@ inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New( ContextType* context, Finalizer finalizeCallback, FinalizerDataType* data, - napi_finalize wrapper, - ThreadSafeFunctionCallJS call_js_cb) { + napi_finalize wrapper) { static_assert(details::can_make_string::value || std::is_convertible::value, "Resource name should be convertible to the string type"); - ThreadSafeFunctionEx tsfn; + ThreadSafeFunctionEx tsfn; + // details::ThreadSafeCallJs cb{call_js_cb}; + auto* finalizeData = new details::ThreadSafeFinalize({ data, finalizeCallback }); napi_status status = napi_create_threadsafe_function(env, callback, resource, @@ -4379,33 +4376,45 @@ inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New( // } // call_js_cb( Napi::Env(env), Function(env, jsCallback), static_cast(context), data); // }, - CallJS, + CallJsInternal, &tsfn._tsfn); if (status != napi_ok) { delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunctionEx()); + NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunctionEx()); } return tsfn; } -template -inline void -ThreadSafeFunctionEx::CallJS(napi_env env, napi_value jsCallback, +template +void ThreadSafeFunctionEx::CallJsInternal(napi_env env, napi_value jsCallback, void *context, void *data) { - if (env == nullptr && jsCallback == nullptr) { - return; - } - if (data != nullptr) { - auto* callbackWrapper = static_cast(data); - (*callbackWrapper)(env, Function(env, jsCallback)); - delete callbackWrapper; - } else if (jsCallback != nullptr) { - Function(env, jsCallback).Call({}); + if (CallJs == nullptr && jsCallback != nullptr) { + Function(env, jsCallback).Call(0, nullptr); + } else { + CallJs(env, Function(env, jsCallback), static_cast(context), static_cast(data)); } } + +// template +// inline void +// ThreadSafeFunctionEx::CallJS(napi_env env, napi_value jsCallback, +// void *context, void *data) { +// if (env == nullptr && jsCallback == nullptr) { +// return; +// } + +// if (data != nullptr) { +// auto* callbackWrapper = static_cast(data); +// (*callbackWrapper)(env, Function(env, jsCallback)); +// delete callbackWrapper; +// } else if (jsCallback != nullptr) { +// Function(env, jsCallback).Call({}); +// } +// } + //////////////////////////////////////////////////////////////////////////////// // ThreadSafeFunction class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index c4b1228a4..17dacc87b 100644 --- a/napi.h +++ b/napi.h @@ -2036,17 +2036,13 @@ namespace Napi { }; #if (NAPI_VERSION > 3) - - - template + template class ThreadSafeFunctionEx { public: - using ThreadSafeFunctionCallJS = std::function; - // This API may only be called from the main thread. template - static ThreadSafeFunctionEx New(napi_env env, + static ThreadSafeFunctionEx New(napi_env env, const Function& callback, const Object& resource, ResourceString resourceName, @@ -2054,11 +2050,10 @@ namespace Napi { size_t initialThreadCount, ContextType* context, Finalizer finalizeCallback, - FinalizerDataType* data, - ThreadSafeFunctionCallJS call_js_cb); + FinalizerDataType* data); - ThreadSafeFunctionEx(); - ThreadSafeFunctionEx(napi_threadsafe_function tsFunctionValue); + ThreadSafeFunctionEx(); + ThreadSafeFunctionEx(napi_threadsafe_function tsFunctionValue); operator napi_threadsafe_function() const; @@ -2093,11 +2088,10 @@ namespace Napi { ContextType* GetContext() const; private: - // using CallbackWrapper = std::function; template - static ThreadSafeFunctionEx New(napi_env env, + static ThreadSafeFunctionEx New(napi_env env, const Function& callback, const Object& resource, ResourceString resourceName, @@ -2106,17 +2100,13 @@ namespace Napi { ContextType* context, Finalizer finalizeCallback, FinalizerDataType* data, - napi_finalize wrapper, - ThreadSafeFunctionCallJS call_js_cb); - - static void CallJS(napi_env env, - napi_value jsCallback, - void* context, - void* data); + napi_finalize wrapper); + static void CallJsInternal(napi_env env, + napi_value jsCallback, + void* context, + void* data); protected: - void CallJS - napi_threadsafe_function _tsfn; }; diff --git a/test/threadsafe_function/threadsafe_function_ex.cc b/test/threadsafe_function/threadsafe_function_ex.cc index fccf2401f..b66148d1c 100644 --- a/test/threadsafe_function/threadsafe_function_ex.cc +++ b/test/threadsafe_function/threadsafe_function_ex.cc @@ -4,19 +4,28 @@ using namespace Napi; -using TSFNContext = Reference; - namespace { +// Context of our TSFN. +using TSFNContext = Reference; + +// Data passed to ThreadSafeFunctionEx::[Non]BlockingCall struct CallJsData { - CallJsData(Napi::Env env) : deferred(Promise::Deferred::New(env)) { }; + CallJsData(Napi::Env env) : deferred(Promise::Deferred::New(env)){}; - void resolve(TSFNContext* context) { - deferred.Resolve(context->Value()); - }; Promise::Deferred deferred; }; +// CallJs callback function +static void CallJs(Napi::Env /*env*/, Napi::Function /*jsCallback*/, + TSFNContext *context, CallJsData *data) { + data->deferred.Resolve(context->Value()); +} + +// Full type of our ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx; + +// A JS-accessible wrap that holds a TSFN. class TSFNWrap : public ObjectWrap { public: static Object Init(Napi::Env env, Object exports); @@ -25,7 +34,7 @@ class TSFNWrap : public ObjectWrap { Napi::Value GetContextByCall(const CallbackInfo &info) { Napi::Env env = info.Env(); std::unique_ptr callData = std::make_unique(env); - auto& deferred = callData->deferred; + auto &deferred = callData->deferred; _tsfn.BlockingCall(callData.release()); return deferred.Promise(); }; @@ -40,7 +49,7 @@ class TSFNWrap : public ObjectWrap { }; private: - ThreadSafeFunctionEx _tsfn; + TSFN _tsfn; Promise::Deferred _deferred; }; @@ -63,7 +72,7 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info) TSFNContext *ctx = new Reference; *ctx = Persistent(info[0]); - _tsfn = ThreadSafeFunctionEx::New( + _tsfn = ThreadSafeFunctionEx::New( info.Env(), // napi_env env, Function::New( env, @@ -74,15 +83,13 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info) 1, // size_t initialThreadCount, ctx, // ContextType* context, - [this](Napi::Env env, void*, TSFNContext *ctx) { // Finalizer finalizeCallback, + [this](Napi::Env env, void *, + TSFNContext *ctx) { // Finalizer finalizeCallback, _deferred.Resolve(env.Undefined()); delete ctx; }, - static_cast(nullptr), // FinalizerDataType* data, - [](Napi::Env, Napi::Value, TSFNContext *context, void *data) { // call_js_cb - std::unique_ptr callData(static_cast(data)); - callData->resolve(context); - }); + static_cast(nullptr) // FinalizerDataType* data, + ); } } // namespace diff --git a/test/threadsafe_function/threadsafe_function_ex.js b/test/threadsafe_function/threadsafe_function_ex.js index 63fd7dc5c..8b6eb87aa 100644 --- a/test/threadsafe_function/threadsafe_function_ex.js +++ b/test/threadsafe_function/threadsafe_function_ex.js @@ -5,13 +5,18 @@ const buildType = process.config.target_defaults.default_configuration; module.exports = Promise.all([ test(require(`../build/${buildType}/binding.node`)), - test(require(`../build/${buildType}/binding_noexcept.node`)) + // test(require(`../build/${buildType}/binding_noexcept.node`)) ]); async function test(binding) { const ctx = { }; const tsfn = new binding.threadsafe_function_ex.TSFNWrap(ctx); + console.log("1"); assert(ctx === await tsfn.getContextByCall(),"getContextByCall context not equal"); + console.log("2"); assert(ctx === tsfn.getContextFromTsfn(),"getContextFromTsfn context not equal"); + console.log("3"); + console.log("releasing"); await tsfn.release(); + console.log("released!"); } From 561b9687ffe615fb1f41fecab7b18ff9710091b6 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Tue, 7 Apr 2020 19:27:22 +0200 Subject: [PATCH 181/696] make CallJS template parameter --- napi-inl.h | 209 +++++++++--------- napi.h | 6 - test/binding.cc | 6 +- test/binding.gyp | 3 +- test/index.js | 6 +- .../threadsafe_function_ex.js | 22 -- .../context.cc} | 31 ++- test/threadsafe_function_ex/context.js | 17 ++ test/threadsafe_function_ex/simple.cc | 66 ++++++ test/threadsafe_function_ex/simple.js | 15 ++ 10 files changed, 224 insertions(+), 157 deletions(-) delete mode 100644 test/threadsafe_function/threadsafe_function_ex.js rename test/{threadsafe_function/threadsafe_function_ex.cc => threadsafe_function_ex/context.cc} (72%) create mode 100644 test/threadsafe_function_ex/context.js create mode 100644 test/threadsafe_function_ex/simple.cc create mode 100644 test/threadsafe_function_ex/simple.js diff --git a/napi-inl.h b/napi-inl.h index 0ee2910b3..3e57acf2e 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4257,164 +4257,161 @@ inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { #if (NAPI_VERSION > 3) //////////////////////////////////////////////////////////////////////////////// -// ThreadSafeFunctionEx class +// ThreadSafeFunctionEx class //////////////////////////////////////////////////////////////////////////////// // static -template -template -inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data) { - return New(env, callback, resource, resourceName, maxQueueSize, - initialThreadCount, context, finalizeCallback, data, - details::ThreadSafeFinalize::FinalizeFinalizeWrapperWithDataAndContext); -} - -template -inline ThreadSafeFunctionEx::ThreadSafeFunctionEx() - : _tsfn() { -} - -template -inline ThreadSafeFunctionEx::ThreadSafeFunctionEx( - napi_threadsafe_function tsfn) - : _tsfn(tsfn) { -} - -template -inline ThreadSafeFunctionEx::operator napi_threadsafe_function() const { +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, const Function &callback, const Object &resource, + ResourceString resourceName, size_t maxQueueSize, size_t initialThreadCount, + ContextType *context, Finalizer finalizeCallback, FinalizerDataType *data) { + return New( + env, callback, resource, resourceName, maxQueueSize, initialThreadCount, + context, finalizeCallback, data, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext); +} + +template +inline ThreadSafeFunctionEx::ThreadSafeFunctionEx() + : _tsfn() {} + +template +inline ThreadSafeFunctionEx:: + ThreadSafeFunctionEx(napi_threadsafe_function tsfn) + : _tsfn(tsfn) {} + +template +inline ThreadSafeFunctionEx:: +operator napi_threadsafe_function() const { return _tsfn; } -template -inline napi_status ThreadSafeFunctionEx::BlockingCall( - DataType* data) const { +template +inline napi_status +ThreadSafeFunctionEx::BlockingCall( + DataType *data) const { return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); } -template -inline napi_status ThreadSafeFunctionEx::NonBlockingCall( - DataType* data) const { +template +inline napi_status +ThreadSafeFunctionEx::NonBlockingCall( + DataType *data) const { return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); } -template -inline void ThreadSafeFunctionEx::Ref(napi_env env) const { +template +inline void +ThreadSafeFunctionEx::Ref(napi_env env) const { if (_tsfn != nullptr) { napi_status status = napi_ref_threadsafe_function(env, _tsfn); NAPI_THROW_IF_FAILED_VOID(env, status); } } -template -inline void ThreadSafeFunctionEx::Unref(napi_env env) const { +template +inline void +ThreadSafeFunctionEx::Unref(napi_env env) const { if (_tsfn != nullptr) { napi_status status = napi_unref_threadsafe_function(env, _tsfn); NAPI_THROW_IF_FAILED_VOID(env, status); } } -template -inline napi_status ThreadSafeFunctionEx::Acquire() const { +template +inline napi_status +ThreadSafeFunctionEx::Acquire() const { return napi_acquire_threadsafe_function(_tsfn); } -template -inline napi_status ThreadSafeFunctionEx::Release() { +template +inline napi_status +ThreadSafeFunctionEx::Release() { return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); } -template -inline napi_status ThreadSafeFunctionEx::Abort() { +template +inline napi_status +ThreadSafeFunctionEx::Abort() { return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); } -template -inline ContextType* ThreadSafeFunctionEx::GetContext() const { - void* context; +template +inline ContextType * +ThreadSafeFunctionEx::GetContext() const { + void *context; napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); - NAPI_FATAL_IF_FAILED(status, "ThreadSafeFunctionEx::GetContext", "napi_get_threadsafe_function_context"); - return static_cast(context); + NAPI_FATAL_IF_FAILED(status, "ThreadSafeFunctionEx::GetContext", + "napi_get_threadsafe_function_context"); + return static_cast(context); } // static -template -template -inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data, - napi_finalize wrapper) { - static_assert(details::can_make_string::value - || std::is_convertible::value, - "Resource name should be convertible to the string type"); +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, const Function &callback, const Object &resource, + ResourceString resourceName, size_t maxQueueSize, size_t initialThreadCount, + ContextType *context, Finalizer finalizeCallback, FinalizerDataType *data, + napi_finalize wrapper) { + static_assert(details::can_make_string::value || + std::is_convertible::value, + "Resource name should be convertible to the string type"); ThreadSafeFunctionEx tsfn; - // details::ThreadSafeCallJs cb{call_js_cb}; - auto* finalizeData = new details::ThreadSafeFinalize({ data, finalizeCallback }); - napi_status status = napi_create_threadsafe_function(env, callback, resource, - Value::From(env, resourceName), maxQueueSize, initialThreadCount, - finalizeData, wrapper, context, - // [=](napi_env env, napi_value jsCallback, void* context, void* data) { - // if (env == nullptr && jsCallback == nullptr) { - // return; - // } - // call_js_cb( Napi::Env(env), Function(env, jsCallback), static_cast(context), data); - // }, - CallJsInternal, - &tsfn._tsfn); + auto *finalizeData = new details::ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = napi_create_threadsafe_function( + env, callback, resource, Value::From(env, resourceName), maxQueueSize, + initialThreadCount, finalizeData, wrapper, context, CallJsInternal, + &tsfn._tsfn); if (status != napi_ok) { delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunctionEx()); + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); } return tsfn; } -template -void ThreadSafeFunctionEx::CallJsInternal(napi_env env, napi_value jsCallback, - void *context, void *data) { +template +void ThreadSafeFunctionEx::CallJsInternal( + napi_env env, napi_value jsCallback, void *context, void *data) { - if (CallJs == nullptr && jsCallback != nullptr) { - Function(env, jsCallback).Call(0, nullptr); + if (CallJs == nullptr) { + if (jsCallback != nullptr) { + Function(env, jsCallback).Call(0, nullptr); + } } else { - CallJs(env, Function(env, jsCallback), static_cast(context), static_cast(data)); + CallJs(env, Function(env, jsCallback), static_cast(context), + static_cast(data)); } } - -// template -// inline void -// ThreadSafeFunctionEx::CallJS(napi_env env, napi_value jsCallback, -// void *context, void *data) { -// if (env == nullptr && jsCallback == nullptr) { -// return; -// } - -// if (data != nullptr) { -// auto* callbackWrapper = static_cast(data); -// (*callbackWrapper)(env, Function(env, jsCallback)); -// delete callbackWrapper; -// } else if (jsCallback != nullptr) { -// Function(env, jsCallback).Call({}); -// } -// } - //////////////////////////////////////////////////////////////////////////////// // ThreadSafeFunction class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 17dacc87b..57bc183c2 100644 --- a/napi.h +++ b/napi.h @@ -2057,15 +2057,9 @@ namespace Napi { operator napi_threadsafe_function() const; - // // This API may be called from any thread. - // napi_status BlockingCall() const; - // This API may be called from any thread. napi_status BlockingCall(DataType* data = nullptr) const; - // // This API may be called from any thread. - // napi_status NonBlockingCall() const; - // This API may be called from any thread. napi_status NonBlockingCall(DataType* data = nullptr) const; diff --git a/test/binding.cc b/test/binding.cc index 499d9435a..09a8c754c 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -43,12 +43,13 @@ Object InitPromise(Env env); Object InitRunScript(Env env); #if (NAPI_VERSION > 3) Object InitThreadSafeFunctionCtx(Env env); -Object InitThreadSafeFunctionEx(Env env); Object InitThreadSafeFunctionExistingTsfn(Env env); Object InitThreadSafeFunctionPtr(Env env); Object InitThreadSafeFunctionSum(Env env); Object InitThreadSafeFunctionUnref(Env env); Object InitThreadSafeFunction(Env env); +Object InitThreadSafeFunctionExContext(Env env); +Object InitThreadSafeFunctionExSimple(Env env); #endif Object InitTypedArray(Env env); Object InitObjectWrap(Env env); @@ -101,12 +102,13 @@ Object Init(Env env, Object exports) { exports.Set("run_script", InitRunScript(env)); #if (NAPI_VERSION > 3) exports.Set("threadsafe_function_ctx", InitThreadSafeFunctionCtx(env)); - exports.Set("threadsafe_function_ex", InitThreadSafeFunctionEx(env)); exports.Set("threadsafe_function_existing_tsfn", InitThreadSafeFunctionExistingTsfn(env)); exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env)); exports.Set("threadsafe_function_sum", InitThreadSafeFunctionSum(env)); exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env)); exports.Set("threadsafe_function", InitThreadSafeFunction(env)); + exports.Set("threadsafe_function_ex_context", InitThreadSafeFunctionExContext(env)); + exports.Set("threadsafe_function_ex_simple", InitThreadSafeFunctionExSimple(env)); #endif exports.Set("typedarray", InitTypedArray(env)); exports.Set("objectwrap", InitObjectWrap(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 790bef5fa..dda3c7154 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -34,8 +34,9 @@ 'object/set_property.cc', 'promise.cc', 'run_script.cc', + 'threadsafe_function_ex/context.cc', + 'threadsafe_function_ex/simple.cc', 'threadsafe_function/threadsafe_function_ctx.cc', - 'threadsafe_function/threadsafe_function_ex.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', 'threadsafe_function/threadsafe_function_sum.cc', diff --git a/test/index.js b/test/index.js index d91b54581..fd9340613 100644 --- a/test/index.js +++ b/test/index.js @@ -41,8 +41,9 @@ let testModules = [ 'object/set_property', 'promise', 'run_script', + 'threadsafe_function_ex/context', + 'threadsafe_function_ex/simple', 'threadsafe_function/threadsafe_function_ctx', - 'threadsafe_function/threadsafe_function_ex', 'threadsafe_function/threadsafe_function_existing_tsfn', 'threadsafe_function/threadsafe_function_ptr', 'threadsafe_function/threadsafe_function_sum', @@ -77,12 +78,13 @@ if (napiVersion < 4) { testModules.splice(testModules.indexOf('asyncprogressqueueworker'), 1); testModules.splice(testModules.indexOf('asyncprogressworker'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ctx'), 1); - testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ex'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_existing_tsfn'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ptr'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_sum'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_unref'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); + testModules.splice(testModules.indexOf('threadsafe_function_ex/context'), 1); + testModules.splice(testModules.indexOf('threadsafe_function_ex/simple'), 1); } if (napiVersion < 5) { diff --git a/test/threadsafe_function/threadsafe_function_ex.js b/test/threadsafe_function/threadsafe_function_ex.js deleted file mode 100644 index 8b6eb87aa..000000000 --- a/test/threadsafe_function/threadsafe_function_ex.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; - -module.exports = Promise.all([ - test(require(`../build/${buildType}/binding.node`)), - // test(require(`../build/${buildType}/binding_noexcept.node`)) -]); - -async function test(binding) { - const ctx = { }; - const tsfn = new binding.threadsafe_function_ex.TSFNWrap(ctx); - console.log("1"); - assert(ctx === await tsfn.getContextByCall(),"getContextByCall context not equal"); - console.log("2"); - assert(ctx === tsfn.getContextFromTsfn(),"getContextFromTsfn context not equal"); - console.log("3"); - console.log("releasing"); - await tsfn.release(); - console.log("released!"); -} diff --git a/test/threadsafe_function/threadsafe_function_ex.cc b/test/threadsafe_function_ex/context.cc similarity index 72% rename from test/threadsafe_function/threadsafe_function_ex.cc rename to test/threadsafe_function_ex/context.cc index b66148d1c..19df41882 100644 --- a/test/threadsafe_function/threadsafe_function_ex.cc +++ b/test/threadsafe_function_ex/context.cc @@ -9,21 +9,18 @@ namespace { // Context of our TSFN. using TSFNContext = Reference; -// Data passed to ThreadSafeFunctionEx::[Non]BlockingCall -struct CallJsData { - CallJsData(Napi::Env env) : deferred(Promise::Deferred::New(env)){}; - - Promise::Deferred deferred; -}; +// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall +using TSFNData = Promise::Deferred; // CallJs callback function static void CallJs(Napi::Env /*env*/, Napi::Function /*jsCallback*/, - TSFNContext *context, CallJsData *data) { - data->deferred.Resolve(context->Value()); + TSFNContext *context, TSFNData *data) { + data->Resolve(context->Value()); + delete data; } // Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; +using TSFN = ThreadSafeFunctionEx; // A JS-accessible wrap that holds a TSFN. class TSFNWrap : public ObjectWrap { @@ -33,10 +30,9 @@ class TSFNWrap : public ObjectWrap { Napi::Value GetContextByCall(const CallbackInfo &info) { Napi::Env env = info.Env(); - std::unique_ptr callData = std::make_unique(env); - auto &deferred = callData->deferred; - _tsfn.BlockingCall(callData.release()); - return deferred.Promise(); + auto* callData = new TSFNData(env); + _tsfn.NonBlockingCall( callData ); + return callData->Promise(); }; Napi::Value GetContextFromTsfn(const CallbackInfo &) { @@ -69,10 +65,9 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info) _deferred(Promise::Deferred::New(info.Env())) { Napi::Env env = info.Env(); - TSFNContext *ctx = new Reference; - *ctx = Persistent(info[0]); + TSFNContext *context = new TSFNContext(Persistent(info[0])); - _tsfn = ThreadSafeFunctionEx::New( + _tsfn = TSFN::New( info.Env(), // napi_env env, Function::New( env, @@ -81,7 +76,7 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info) "Test", // ResourceString resourceName, 1, // size_t maxQueueSize, 1, // size_t initialThreadCount, - ctx, // ContextType* context, + context, // ContextType* context, [this](Napi::Env env, void *, TSFNContext *ctx) { // Finalizer finalizeCallback, @@ -93,7 +88,7 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info) } } // namespace -Object InitThreadSafeFunctionEx(Env env) { +Object InitThreadSafeFunctionExContext(Env env) { return TSFNWrap::Init(env, Object::New(env)); } diff --git a/test/threadsafe_function_ex/context.js b/test/threadsafe_function_ex/context.js new file mode 100644 index 000000000..3e068cabd --- /dev/null +++ b/test/threadsafe_function_ex/context.js @@ -0,0 +1,17 @@ +'use strict'; + +const assert = require('assert'); +const buildType = process.config.target_defaults.default_configuration; + +module.exports = Promise.all([ + test(require(`../build/${buildType}/binding.node`)), + test(require(`../build/${buildType}/binding_noexcept.node`)) +]); + +async function test(binding) { + const ctx = {}; + const tsfn = new binding.threadsafe_function_ex_context.TSFNWrap(ctx); + assert(ctx === await tsfn.getContextByCall(), "getContextByCall context not equal"); + assert(ctx === tsfn.getContextFromTsfn(), "getContextFromTsfn context not equal"); + await tsfn.release(); +} diff --git a/test/threadsafe_function_ex/simple.cc b/test/threadsafe_function_ex/simple.cc new file mode 100644 index 000000000..178635be3 --- /dev/null +++ b/test/threadsafe_function_ex/simple.cc @@ -0,0 +1,66 @@ +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +// Full type of our ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx<>; + +// A JS-accessible wrap that holds a TSFN. +class TSFNWrap : public ObjectWrap { +public: + static Object Init(Napi::Env env, Object exports); + TSFNWrap(const CallbackInfo &info); + + Napi::Value Release(const CallbackInfo &) { + _tsfn.Release(); + return _deferred.Promise(); + }; + + Napi::Value Call(const CallbackInfo &info) { + _tsfn.NonBlockingCall(); + return info.Env().Undefined(); + }; + +private: + TSFN _tsfn; + Promise::Deferred _deferred; +}; + +Object TSFNWrap::Init(Napi::Env env, Object exports) { + Function func = DefineClass(env, "TSFNWrap", + {InstanceMethod("call", &TSFNWrap::Call), + InstanceMethod("release", &TSFNWrap::Release)}); + + exports.Set("TSFNWrap", func); + return exports; +} + +TSFNWrap::TSFNWrap(const CallbackInfo &info) + : ObjectWrap(info), + _deferred(Promise::Deferred::New(info.Env())) { + + _tsfn = TSFN::New( + info.Env(), // napi_env env, + Function(), // const Function& callback, + Value(), // const Object& resource, + "Test", // ResourceString resourceName, + 1, // size_t maxQueueSize, + 1, // size_t initialThreadCount, + static_cast(nullptr), // ContextType* context, + [this](Napi::Env env, // Finalizer finalizeCallback, + void * /*data*/, + void * /*ctx*/) { _deferred.Resolve(env.Undefined()); }, + static_cast(nullptr) // FinalizerDataType* data, + ); +} +} // namespace + +Object InitThreadSafeFunctionExSimple(Env env) { + return TSFNWrap::Init(env, Object::New(env)); +} + +#endif diff --git a/test/threadsafe_function_ex/simple.js b/test/threadsafe_function_ex/simple.js new file mode 100644 index 000000000..ece0929e7 --- /dev/null +++ b/test/threadsafe_function_ex/simple.js @@ -0,0 +1,15 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; + +module.exports = Promise.all([ + test(require(`../build/${buildType}/binding.node`)), + test(require(`../build/${buildType}/binding_noexcept.node`)) +]); + +async function test(binding) { + const ctx = {}; + const tsfn = new binding.threadsafe_function_ex_simple.TSFNWrap(ctx); + tsfn.call(); + await tsfn.release(); +} From e1a827ae295d4e426ab6c77272c3f3e87e31817c Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Tue, 7 Apr 2020 14:53:33 -0700 Subject: [PATCH 182/696] src: fix AsyncProgressQueueWorker compilation (#696) We need to cast the `nullptr` to the templated type of the `AsyncProgressQueueWorker`. Fixes: https://github.com/nodejs/node-addon-api/issues/695 PR-URL: https://github.com/nodejs/node-addon-api/pull/696 Signed-off-by: Gabriel Schulhof --- napi-inl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napi-inl.h b/napi-inl.h index 82846c257..f8657ae3f 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4792,7 +4792,7 @@ inline void AsyncProgressWorker::SendProgress_(const T* data, size_t count) { template inline void AsyncProgressWorker::Signal() const { - this->NonBlockingCall(nullptr); + this->NonBlockingCall(static_cast(nullptr)); } template From 630d88bcea966ff4bce45aac49ffdd6cc3a12fe5 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Wed, 8 Apr 2020 18:04:24 +0200 Subject: [PATCH 183/696] statically check CallJs; add no-Finalizer overload --- napi-inl.h | 42 +++++++++++++++++++++------ napi.h | 14 +++++++-- test/threadsafe_function_ex/simple.cc | 18 ++++-------- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 3e57acf2e..78540a437 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -196,6 +196,22 @@ struct ThreadSafeFinalize { FinalizerDataType* data; Finalizer callback; }; + +template +typename std::enable_if::type +static inline CallJsWrapper(napi_env env, napi_value jsCallback, void *context, void *data) { + call(env, Function(env, jsCallback), static_cast(context), + static_cast(data)); +} + +template +typename std::enable_if::type +static inline CallJsWrapper(napi_env env, napi_value jsCallback, void * /*context*/, + void * /*data*/) { + if (jsCallback != nullptr) { + Function(env, jsCallback).Call(0, nullptr); + } +} #endif template @@ -4260,6 +4276,20 @@ inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { // ThreadSafeFunctionEx class //////////////////////////////////////////////////////////////////////////////// +// static +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, const Function &callback, const Object &resource, + ResourceString resourceName, size_t maxQueueSize, size_t initialThreadCount, + ContextType *context) { + return New( + env, callback, resource, resourceName, maxQueueSize, initialThreadCount, + context, [](Env, void *, ContextType *) {}, static_cast(nullptr)); +} + // static template @@ -4397,19 +4427,13 @@ ThreadSafeFunctionEx::New( return tsfn; } +// static template void ThreadSafeFunctionEx::CallJsInternal( napi_env env, napi_value jsCallback, void *context, void *data) { - - if (CallJs == nullptr) { - if (jsCallback != nullptr) { - Function(env, jsCallback).Call(0, nullptr); - } - } else { - CallJs(env, Function(env, jsCallback), static_cast(context), - static_cast(data)); - } + details::CallJsWrapper( + env, jsCallback, context, data); } //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 57bc183c2..2eb8d3c73 100644 --- a/napi.h +++ b/napi.h @@ -2041,7 +2041,17 @@ namespace Napi { public: // This API may only be called from the main thread. - template + template + static ThreadSafeFunctionEx New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + template static ThreadSafeFunctionEx New(napi_env env, const Function& callback, const Object& resource, @@ -2050,7 +2060,7 @@ namespace Napi { size_t initialThreadCount, ContextType* context, Finalizer finalizeCallback, - FinalizerDataType* data); + FinalizerDataType* data = nullptr); ThreadSafeFunctionEx(); ThreadSafeFunctionEx(napi_threadsafe_function tsFunctionValue); diff --git a/test/threadsafe_function_ex/simple.cc b/test/threadsafe_function_ex/simple.cc index 178635be3..40cf20d3e 100644 --- a/test/threadsafe_function_ex/simple.cc +++ b/test/threadsafe_function_ex/simple.cc @@ -43,18 +43,12 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info) : ObjectWrap(info), _deferred(Promise::Deferred::New(info.Env())) { - _tsfn = TSFN::New( - info.Env(), // napi_env env, - Function(), // const Function& callback, - Value(), // const Object& resource, - "Test", // ResourceString resourceName, - 1, // size_t maxQueueSize, - 1, // size_t initialThreadCount, - static_cast(nullptr), // ContextType* context, - [this](Napi::Env env, // Finalizer finalizeCallback, - void * /*data*/, - void * /*ctx*/) { _deferred.Resolve(env.Undefined()); }, - static_cast(nullptr) // FinalizerDataType* data, + _tsfn = TSFN::New(info.Env(), // napi_env env, + Function(), // const Function& callback, + Value(), // const Object& resource, + "Test", // ResourceString resourceName, + 1, // size_t maxQueueSize, + 1 // size_t initialThreadCount ); } } // namespace From cdb662506c001feed535d9d577f6c535826b8cf7 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Mon, 13 Apr 2020 11:07:27 -0400 Subject: [PATCH 184/696] doc: fix typo in bigint.md (#700) --- doc/bigint.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/bigint.md b/doc/bigint.md index ac403490e..92607e7fd 100644 --- a/doc/bigint.md +++ b/doc/bigint.md @@ -48,7 +48,7 @@ Returns a new empty JavaScript `Napi::BigInt`. ### Int64Value ```cpp -int64_t Napi::BitInt::Int64Value(bool* lossless) const; +int64_t Napi::BigInt::Int64Value(bool* lossless) const; ``` - `[out] lossless`: Indicates whether the `BigInt` value was converted losslessly. From fedc8195e3a33e32a8a6b4f8b8285cdc6a104a44 Mon Sep 17 00:00:00 2001 From: Azlan Mukhtar Date: Sat, 11 Apr 2020 16:57:10 +0800 Subject: [PATCH 185/696] doc: fix semicolon missing in async_worker.md PR-URL: https://github.com/nodejs/node-addon-api/pull/701 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gabriel Schulhof Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- doc/async_worker_variants.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/async_worker_variants.md b/doc/async_worker_variants.md index a1fb6787e..8b892eaee 100644 --- a/doc/async_worker_variants.md +++ b/doc/async_worker_variants.md @@ -399,7 +399,7 @@ class EchoWorker : public AsyncProgressQueueWorker { void Execute(const ExecutionProgress& progress) { // Need to simulate cpu heavy task for (uint32_t i = 0; i < 100; ++i) { - progress.Send(&i, 1) + progress.Send(&i, 1); std::this_thread::sleep_for(std::chrono::seconds(1)); } } From 9ff352c3f3ede093ec52f527ed05495271d8ff79 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Fri, 17 Apr 2020 14:32:34 +0200 Subject: [PATCH 186/696] add tsfn test with tsfn cb function call --- test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 2 + test/threadsafe_function_ex/call.cc | 83 +++++++++++++++++++++++++++++ test/threadsafe_function_ex/call.js | 18 +++++++ 5 files changed, 106 insertions(+) create mode 100644 test/threadsafe_function_ex/call.cc create mode 100644 test/threadsafe_function_ex/call.js diff --git a/test/binding.cc b/test/binding.cc index 09a8c754c..1c98637cf 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -48,6 +48,7 @@ Object InitThreadSafeFunctionPtr(Env env); Object InitThreadSafeFunctionSum(Env env); Object InitThreadSafeFunctionUnref(Env env); Object InitThreadSafeFunction(Env env); +Object InitThreadSafeFunctionExCall(Env env); Object InitThreadSafeFunctionExContext(Env env); Object InitThreadSafeFunctionExSimple(Env env); #endif @@ -107,6 +108,7 @@ Object Init(Env env, Object exports) { exports.Set("threadsafe_function_sum", InitThreadSafeFunctionSum(env)); exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env)); exports.Set("threadsafe_function", InitThreadSafeFunction(env)); + exports.Set("threadsafe_function_ex_call", InitThreadSafeFunctionExCall(env)); exports.Set("threadsafe_function_ex_context", InitThreadSafeFunctionExContext(env)); exports.Set("threadsafe_function_ex_simple", InitThreadSafeFunctionExSimple(env)); #endif diff --git a/test/binding.gyp b/test/binding.gyp index dda3c7154..15654cda1 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -34,6 +34,7 @@ 'object/set_property.cc', 'promise.cc', 'run_script.cc', + 'threadsafe_function_ex/call.cc', 'threadsafe_function_ex/context.cc', 'threadsafe_function_ex/simple.cc', 'threadsafe_function/threadsafe_function_ctx.cc', diff --git a/test/index.js b/test/index.js index fd9340613..bb54e29ba 100644 --- a/test/index.js +++ b/test/index.js @@ -41,6 +41,7 @@ let testModules = [ 'object/set_property', 'promise', 'run_script', + 'threadsafe_function_ex/call', 'threadsafe_function_ex/context', 'threadsafe_function_ex/simple', 'threadsafe_function/threadsafe_function_ctx', @@ -83,6 +84,7 @@ if (napiVersion < 4) { testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_sum'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_unref'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); + testModules.splice(testModules.indexOf('threadsafe_function_ex/call'), 1); testModules.splice(testModules.indexOf('threadsafe_function_ex/context'), 1); testModules.splice(testModules.indexOf('threadsafe_function_ex/simple'), 1); } diff --git a/test/threadsafe_function_ex/call.cc b/test/threadsafe_function_ex/call.cc new file mode 100644 index 000000000..26951e45a --- /dev/null +++ b/test/threadsafe_function_ex/call.cc @@ -0,0 +1,83 @@ +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +// Context of our TSFN. +using TSFNContext = void; + +// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall +struct TSFNData { + Reference data; + Promise::Deferred deferred; +}; + +// CallJs callback function +static void CallJs(Napi::Env env, Napi::Function jsCallback, + TSFNContext * /*context*/, TSFNData *data) { + jsCallback.Call(env.Undefined(), {data->data.Value()}); + data->deferred.Resolve(data->data.Value()); + delete data; +} + +// Full type of our ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx; + +// A JS-accessible wrap that holds a TSFN. +class TSFNWrap : public ObjectWrap { +public: + static Object Init(Napi::Env env, Object exports); + TSFNWrap(const CallbackInfo &info); + + Napi::Value DoCall(const CallbackInfo &info) { + Napi::Env env = info.Env(); + TSFNData *data = + new TSFNData{Napi::Reference(Persistent(info[0])), + Promise::Deferred::New(env)}; + _tsfn.NonBlockingCall(data); + return data->deferred.Promise(); + }; + + Napi::Value Release(const CallbackInfo &) { + _tsfn.Release(); + return _deferred.Promise(); + }; + +private: + TSFN _tsfn; + Promise::Deferred _deferred; +}; + +Object TSFNWrap::Init(Napi::Env env, Object exports) { + Function func = DefineClass(env, "TSFNWrap", + {InstanceMethod("doCall", &TSFNWrap::DoCall), + InstanceMethod("release", &TSFNWrap::Release)}); + + exports.Set("TSFNWrap", func); + return exports; +} + +TSFNWrap::TSFNWrap(const CallbackInfo &info) + : ObjectWrap(info), + _deferred(Promise::Deferred::New(info.Env())) { + Napi::Env env = info.Env(); + Function callback = info[0].As(); + + _tsfn = TSFN::New(env, // napi_env env, + callback, // const Function& callback, + Value(), // const Object& resource, + "Test", // ResourceString resourceName, + 0, // size_t maxQueueSize, + 1 // size_t initialThreadCount, + ); +} +} // namespace + +Object InitThreadSafeFunctionExCall(Env env) { + return TSFNWrap::Init(env, Object::New(env)); +} + +#endif diff --git a/test/threadsafe_function_ex/call.js b/test/threadsafe_function_ex/call.js new file mode 100644 index 000000000..152637548 --- /dev/null +++ b/test/threadsafe_function_ex/call.js @@ -0,0 +1,18 @@ +'use strict'; + +const assert = require('assert'); +const buildType = process.config.target_defaults.default_configuration; + +module.exports = Promise.all([ + test(require(`../build/${buildType}/binding.node`)), + test(require(`../build/${buildType}/binding_noexcept.node`)) +]); + +async function test(binding) { + const data = {}; + const tsfn = new binding.threadsafe_function_ex_call.TSFNWrap(tsfnData => { + assert(data === tsfnData, "Data in and out of tsfn call do not equal"); + }); + await tsfn.doCall(data); + await tsfn.release(); +} From 4de23c9d6b66681390ba7d09e395390d159d308a Mon Sep 17 00:00:00 2001 From: Yulong Wang Date: Sat, 18 Apr 2020 12:00:47 -0700 Subject: [PATCH 187/696] doc: fix support bigint64/biguint64 guards PR-URL: https://github.com/nodejs/node-addon-api/pull/705 Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof Reviewed-By: Chengzhong Wu Reviewed-By: Nicola Del Gobbo --- napi-inl.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/napi-inl.h b/napi-inl.h index f8657ae3f..e7b02b2d2 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1628,6 +1628,10 @@ inline uint8_t TypedArray::ElementSize() const { case napi_float32_array: return 4; case napi_float64_array: +#if (NAPI_VERSION > 5) + case napi_bigint64_array: + case napi_biguint64_array: +#endif // (NAPI_VERSION > 5) return 8; default: return 0; From a64e8a56417d3f9c073ec2db19ccd31af453ebe0 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Fri, 24 Apr 2020 14:25:49 -0700 Subject: [PATCH 188/696] ci: move travis from 13 to 14 (#707) Co-authored-by: Gabriel Schulhof --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ec04b80ea..56f9c903c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ env: matrix: - NODEJS_VERSION=node/10 - NODEJS_VERSION=node/12 - - NODEJS_VERSION=node/13 + - NODEJS_VERSION=node/14 - NODEJS_VERSION=nightly matrix: fast_finish: true From 82a96502a4637e581607b41ab777f88a73f4f683 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Mon, 30 Mar 2020 09:22:42 -0700 Subject: [PATCH 189/696] src: change guards to NAPI_VERSION > 5 Since we have made the decision that we shall include `BigInt` into N-API 6, we can change the guards for the `BigInt` and `BigInt`-based typed array wrappers accordingly, and end our reliance on guarding by `NODE_MAJOR_VERSION`. Signed-off-by: Gabriel Schulhof PR-URL: https://github.com/nodejs/node-addon-api/pull/697 Reviewed-By: Nicola Del Gobbo Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- common.gypi | 2 -- napi-inl.h | 14 ++++---------- napi.h | 35 ++++++++++------------------------- test/bigint.cc | 5 +---- test/binding.cc | 10 ++-------- test/index.js | 15 +++++---------- test/typedarray.cc | 26 ++++---------------------- 7 files changed, 26 insertions(+), 81 deletions(-) diff --git a/common.gypi b/common.gypi index 76f8d251c..088f961ea 100644 --- a/common.gypi +++ b/common.gypi @@ -1,7 +1,6 @@ { 'variables': { 'NAPI_VERSION%': " 5 inline bool Value::IsBigInt() const { return Type() == napi_bigint; } -#endif // NAPI_EXPERIMENTAL +#endif // NAPI_VERSION > 5 #if (NAPI_VERSION > 4) inline bool Value::IsDate() const { @@ -624,10 +621,7 @@ inline double Number::DoubleValue() const { return result; } -// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. -// Once it is no longer experimental guard with the NAPI_VERSION in which it is -// released instead. -#ifdef NAPI_EXPERIMENTAL +#if NAPI_VERSION > 5 //////////////////////////////////////////////////////////////////////////////// // BigInt Class //////////////////////////////////////////////////////////////////////////////// @@ -688,7 +682,7 @@ inline void BigInt::ToWords(int* sign_bit, size_t* word_count, uint64_t* words) _env, _value, sign_bit, word_count, words); NAPI_THROW_IF_FAILED_VOID(_env, status); } -#endif // NAPI_EXPERIMENTAL +#endif // NAPI_VERSION > 5 #if (NAPI_VERSION > 4) //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index e4b964f87..7828a2b3f 100644 --- a/napi.h +++ b/napi.h @@ -119,12 +119,9 @@ namespace Napi { class Value; class Boolean; class Number; -// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. -// Once it is no longer experimental guard with the NAPI_VERSION in which it is -// released instead. -#ifdef NAPI_EXPERIMENTAL +#if NAPI_VERSION > 5 class BigInt; -#endif // NAPI_EXPERIMENTAL +#endif // NAPI_VERSION > 5 #if (NAPI_VERSION > 4) class Date; #endif @@ -147,13 +144,10 @@ namespace Napi { typedef TypedArrayOf Uint32Array; ///< Typed-array of unsigned 32-bit integers typedef TypedArrayOf Float32Array; ///< Typed-array of 32-bit floating-point values typedef TypedArrayOf Float64Array; ///< Typed-array of 64-bit floating-point values -// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. -// Once it is no longer experimental guard with the NAPI_VERSION in which it is -// released instead. -#ifdef NAPI_EXPERIMENTAL +#if NAPI_VERSION > 5 typedef TypedArrayOf BigInt64Array; ///< Typed array of signed 64-bit integers typedef TypedArrayOf BigUint64Array; ///< Typed array of unsigned 64-bit integers -#endif // NAPI_EXPERIMENTAL +#endif // NAPI_VERSION > 5 /// Defines the signature of a N-API C++ module's registration callback (init) function. typedef Object (*ModuleRegisterCallback)(Env env, Object exports); @@ -257,12 +251,9 @@ namespace Napi { bool IsNull() const; ///< Tests if a value is a null JavaScript value. bool IsBoolean() const; ///< Tests if a value is a JavaScript boolean. bool IsNumber() const; ///< Tests if a value is a JavaScript number. -// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. -// Once it is no longer experimental guard with the NAPI_VERSION in which it is -// released instead. -#ifdef NAPI_EXPERIMENTAL +#if NAPI_VERSION > 5 bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint. -#endif // NAPI_EXPERIMENTAL +#endif // NAPI_VERSION > 5 #if (NAPI_VERSION > 4) bool IsDate() const; ///< Tests if a value is a JavaScript date. #endif @@ -335,10 +326,7 @@ namespace Napi { double DoubleValue() const; ///< Converts a Number value to a 64-bit floating-point value. }; -// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. -// Once it is no longer experimental guard with the NAPI_VERSION in which it is -// released instead. -#ifdef NAPI_EXPERIMENTAL +#if NAPI_VERSION > 5 /// A JavaScript bigint value. class BigInt : public Value { public: @@ -377,7 +365,7 @@ namespace Napi { /// be needed to store this BigInt (i.e. the return value of `WordCount()`). void ToWords(int* sign_bit, size_t* word_count, uint64_t* words); }; -#endif // NAPI_EXPERIMENTAL +#endif // NAPI_VERSION > 5 #if (NAPI_VERSION > 4) /// A JavaScript date value. @@ -859,13 +847,10 @@ namespace Napi { : std::is_same::value ? napi_uint32_array : std::is_same::value ? napi_float32_array : std::is_same::value ? napi_float64_array -// Currently experimental guard with the definition of NAPI_EXPERIMENTAL. -// Once it is no longer experimental guard with the NAPI_VERSION in which it is -// released instead. -#ifdef NAPI_EXPERIMENTAL +#if NAPI_VERSION > 5 : std::is_same::value ? napi_bigint64_array : std::is_same::value ? napi_biguint64_array -#endif // NAPI_EXPERIMENTAL +#endif // NAPI_VERSION > 5 : unknown_array_type; } /// !endcond diff --git a/test/bigint.cc b/test/bigint.cc index a62ed3c30..1f89db84a 100644 --- a/test/bigint.cc +++ b/test/bigint.cc @@ -1,7 +1,4 @@ -// Currently experimental guard with NODE_MAJOR_VERISION in which it was -// released. Once it is no longer experimental guard with the NAPI_VERSION -// in which it is released instead. -#if (NODE_MAJOR_VERSION >= 10) +#if (NAPI_VERSION > 5) #define NAPI_EXPERIMENTAL #include "napi.h" diff --git a/test/binding.cc b/test/binding.cc index 111bcce01..8732ae7bd 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -14,10 +14,7 @@ Object InitBasicTypesArray(Env env); Object InitBasicTypesBoolean(Env env); Object InitBasicTypesNumber(Env env); Object InitBasicTypesValue(Env env); -// Currently experimental guard with NODE_MAJOR_VERISION in which it was -// released. Once it is no longer experimental guard with the NAPI_VERSION -// in which it is released instead. -#if (NODE_MAJOR_VERSION >= 10) +#if (NAPI_VERSION > 5) Object InitBigInt(Env env); #endif Object InitBuffer(Env env); @@ -70,10 +67,7 @@ Object Init(Env env, Object exports) { exports.Set("basic_types_boolean", InitBasicTypesBoolean(env)); exports.Set("basic_types_number", InitBasicTypesNumber(env)); exports.Set("basic_types_value", InitBasicTypesValue(env)); -// Currently experimental guard with NODE_MAJOR_VERISION in which it was -// released. Once it is no longer experimental guard with the NAPI_VERSION -// in which it is released instead. -#if (NODE_MAJOR_VERSION >= 10) +#if (NAPI_VERSION > 5) exports.Set("bigint", InitBigInt(env)); #endif #if (NAPI_VERSION > 4) diff --git a/test/index.js b/test/index.js index e96ac5bf8..0fc38d280 100644 --- a/test/index.js +++ b/test/index.js @@ -57,15 +57,6 @@ let testModules = [ ]; const napiVersion = Number(process.versions.napi) -const nodeMajorVersion = Number(process.versions.node.match(/\d+/)[0]) - -if (nodeMajorVersion < 10) { - // Currently experimental guard with NODE_MAJOR_VERISION in which it was - // released. Once it is no longer experimental guard with the NAPI_VERSION - // in which it is released instead. - testModules.splice(testModules.indexOf('bigint'), 1); - testModules.splice(testModules.indexOf('typedarray-bigint'), 1); -} if (napiVersion < 3) { testModules.splice(testModules.indexOf('callbackscope'), 1); @@ -87,9 +78,13 @@ if (napiVersion < 5) { testModules.splice(testModules.indexOf('date'), 1); } +if (napiVersion < 6) { + testModules.splice(testModules.indexOf('bigint'), 1); + testModules.splice(testModules.indexOf('typedarray-bigint'), 1); +} + if (typeof global.gc === 'function') { console.log(`Testing with N-API Version '${napiVersion}'.`); - console.log(`Testing with Node.js Major Version '${nodeMajorVersion}'.\n`); console.log('Starting test suite\n'); diff --git a/test/typedarray.cc b/test/typedarray.cc index 3b16ca2f5..08c667699 100644 --- a/test/typedarray.cc +++ b/test/typedarray.cc @@ -1,9 +1,3 @@ -// Currently experimental guard with NODE_MAJOR_VERISION in which it was -// released. Once it is no longer experimental guard with the NAPI_VERSION -// in which it is released instead. -#if (NODE_MAJOR_VERSION >= 10) -#define NAPI_EXPERIMENTAL -#endif #include "napi.h" using namespace Napi; @@ -70,10 +64,7 @@ Value CreateTypedArray(const CallbackInfo& info) { NAPI_TYPEDARRAY_NEW(Float64Array, info.Env(), length, napi_float64_array) : NAPI_TYPEDARRAY_NEW_BUFFER(Float64Array, info.Env(), length, buffer, bufferOffset, napi_float64_array); -// Currently experimental guard with NODE_MAJOR_VERISION in which it was -// released. Once it is no longer experimental guard with the NAPI_VERSION -// in which it is released instead. -#if (NODE_MAJOR_VERSION >= 10) +#if (NAPI_VERSION > 5) } else if (arrayType == "bigint64") { return buffer.IsUndefined() ? NAPI_TYPEDARRAY_NEW(BigInt64Array, info.Env(), length, napi_bigint64_array) : @@ -107,10 +98,7 @@ Value GetTypedArrayType(const CallbackInfo& info) { case napi_uint32_array: return String::New(info.Env(), "uint32"); case napi_float32_array: return String::New(info.Env(), "float32"); case napi_float64_array: return String::New(info.Env(), "float64"); -// Currently experimental guard with NODE_MAJOR_VERISION in which it was -// released. Once it is no longer experimental guard with the NAPI_VERSION -// in which it is released instead. -#if (NODE_MAJOR_VERSION >= 10) +#if (NAPI_VERSION > 5) case napi_bigint64_array: return String::New(info.Env(), "bigint64"); case napi_biguint64_array: return String::New(info.Env(), "biguint64"); #endif @@ -150,10 +138,7 @@ Value GetTypedArrayElement(const CallbackInfo& info) { return Number::New(info.Env(), array.As()[index]); case napi_float64_array: return Number::New(info.Env(), array.As()[index]); -// Currently experimental guard with NODE_MAJOR_VERISION in which it was -// released. Once it is no longer experimental guard with the NAPI_VERSION -// in which it is released instead. -#if (NODE_MAJOR_VERSION >= 10) +#if (NAPI_VERSION > 5) case napi_bigint64_array: return BigInt::New(info.Env(), array.As()[index]); case napi_biguint64_array: @@ -197,10 +182,7 @@ void SetTypedArrayElement(const CallbackInfo& info) { case napi_float64_array: array.As()[index] = value.DoubleValue(); break; -// Currently experimental guard with NODE_MAJOR_VERISION in which it was -// released. Once it is no longer experimental guard with the NAPI_VERSION -// in which it is released instead. -#if (NODE_MAJOR_VERSION >= 10) +#if (NAPI_VERSION > 5) case napi_bigint64_array: { bool lossless; array.As()[index] = value.As().Int64Value(&lossless); From 9c9accfbbe8c27f969d569f78758a8c47837321b Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Thu, 30 Jan 2020 18:54:24 -0800 Subject: [PATCH 190/696] src: add support for addon instance data Support `napi_get_instance_data()` and `napi_set_instance_data()`. Signed-off-by: Gabriel Schulhof Fixes: https://github.com/nodejs/node-addon-api/issues/654 PR-URL: https://github.com/nodejs/node-addon-api/pull/663 Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- napi-inl.h | 42 ++++++++++++++++++++ napi.h | 22 +++++++++++ test/addon_data.cc | 97 ++++++++++++++++++++++++++++++++++++++++++++++ test/addon_data.js | 42 ++++++++++++++++++++ test/binding.cc | 6 +++ test/binding.gyp | 1 + test/index.js | 2 + 7 files changed, 212 insertions(+) create mode 100644 test/addon_data.cc create mode 100644 test/addon_data.js diff --git a/napi-inl.h b/napi-inl.h index b3db7e918..7e4d158db 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -322,6 +322,48 @@ inline Value Env::RunScript(String script) { return Value(_env, result); } +#if NAPI_VERSION > 5 +template fini> +inline void Env::SetInstanceData(T* data) { + napi_status status = + napi_set_instance_data(_env, data, [](napi_env env, void* data, void*) { + fini(env, static_cast(data)); + }, nullptr); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +template fini> +inline void Env::SetInstanceData(DataType* data, HintType* hint) { + napi_status status = + napi_set_instance_data(_env, data, + [](napi_env env, void* data, void* hint) { + fini(env, static_cast(data), static_cast(hint)); + }, hint); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +template +inline T* Env::GetInstanceData() { + void* data = nullptr; + + napi_status status = napi_get_instance_data(_env, &data); + NAPI_THROW_IF_FAILED(_env, status, nullptr); + + return static_cast(data); +} + +template void Env::DefaultFini(Env, T* data) { + delete data; +} + +template +void Env::DefaultFiniWithHint(Env, DataType* data, HintType*) { + delete data; +} +#endif // NAPI_VERSION > 5 + //////////////////////////////////////////////////////////////////////////////// // Value class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 7828a2b3f..de4d82f36 100644 --- a/napi.h +++ b/napi.h @@ -166,6 +166,12 @@ namespace Napi { /// /// In the V8 JavaScript engine, a N-API environment approximately corresponds to an Isolate. class Env { +#if NAPI_VERSION > 5 + private: + template static void DefaultFini(Env, T* data); + template + static void DefaultFiniWithHint(Env, DataType* data, HintType* hint); +#endif // NAPI_VERSION > 5 public: Env(napi_env env); @@ -182,6 +188,22 @@ namespace Napi { Value RunScript(const std::string& utf8script); Value RunScript(String script); +#if NAPI_VERSION > 5 + template T* GetInstanceData(); + + template using Finalizer = void (*)(Env, T*); + template fini = Env::DefaultFini> + void SetInstanceData(T* data); + + template + using FinalizerWithHint = void (*)(Env, DataType*, HintType*); + template fini = + Env::DefaultFiniWithHint> + void SetInstanceData(DataType* data, HintType* hint); +#endif // NAPI_VERSION > 5 + private: napi_env _env; }; diff --git a/test/addon_data.cc b/test/addon_data.cc new file mode 100644 index 000000000..d160a5946 --- /dev/null +++ b/test/addon_data.cc @@ -0,0 +1,97 @@ +#if (NAPI_VERSION > 5) +#include +#include "napi.h" + +// An overly elaborate way to get/set a boolean stored in the instance data: +// 0. A boolean named "verbose" is stored in the instance data. The constructor +// for JS `VerboseIndicator` instances is also stored in the instance data. +// 1. Add a property named "verbose" onto exports served by a getter/setter. +// 2. The getter returns a object of type VerboseIndicator, which itself has a +// property named "verbose", also served by a getter/setter: +// * The getter returns a boolean, indicating whether "verbose" is set. +// * The setter sets "verbose" on the instance data. +// 3. The setter sets "verbose" on the instance data. + +class Addon { + public: + class VerboseIndicator : public Napi::ObjectWrap { + public: + VerboseIndicator(const Napi::CallbackInfo& info): + Napi::ObjectWrap(info) { + info.This().As()["verbose"] = + Napi::Boolean::New(info.Env(), + info.Env().GetInstanceData()->verbose); + } + + Napi::Value Getter(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), + info.Env().GetInstanceData()->verbose); + } + + void Setter(const Napi::CallbackInfo& info, const Napi::Value& val) { + info.Env().GetInstanceData()->verbose = val.As(); + } + + static Napi::FunctionReference Init(Napi::Env env) { + return Napi::Persistent(DefineClass(env, "VerboseIndicator", { + InstanceAccessor< + &VerboseIndicator::Getter, + &VerboseIndicator::Setter>("verbose") + })); + } + }; + + static Napi::Value Getter(const Napi::CallbackInfo& info) { + return info.Env().GetInstanceData()->VerboseIndicator.New({}); + } + + static void Setter(const Napi::CallbackInfo& info) { + info.Env().GetInstanceData()->verbose = info[0].As(); + } + + Addon(Napi::Env env): VerboseIndicator(VerboseIndicator::Init(env)) {} + ~Addon() { + if (verbose) { + fprintf(stderr, "addon_data: Addon::~Addon\n"); + } + } + + static void DeleteAddon(Napi::Env, Addon* addon, uint32_t* hint) { + delete addon; + fprintf(stderr, "hint: %d\n", *hint); + delete hint; + } + + static Napi::Object Init(Napi::Env env, Napi::Value jshint) { + if (!jshint.IsNumber()) { + NAPI_THROW(Napi::Error::New(env, "Expected number"), Napi::Object()); + } + uint32_t hint = jshint.As(); + if (hint == 0) + env.SetInstanceData(new Addon(env)); + else + env.SetInstanceData(new Addon(env), + new uint32_t(hint)); + Napi::Object result = Napi::Object::New(env); + result.DefineProperties({ + Napi::PropertyDescriptor::Accessor("verbose"), + }); + + return result; + } + + private: + bool verbose = false; + Napi::FunctionReference VerboseIndicator; +}; + +// We use an addon factory so we can cover both the case where there is an +// instance data hint and the case where there isn't. +static Napi::Value AddonFactory(const Napi::CallbackInfo& info) { + return Addon::Init(info.Env(), info[0]); +} + +Napi::Object InitAddonData(Napi::Env env) { + return Napi::Function::New(env, AddonFactory); +} +#endif // (NAPI_VERSION > 5) diff --git a/test/addon_data.js b/test/addon_data.js new file mode 100644 index 000000000..0a3852696 --- /dev/null +++ b/test/addon_data.js @@ -0,0 +1,42 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const { spawn } = require('child_process'); +const readline = require('readline'); +const path = require('path'); + +test(path.resolve(__dirname, `./build/${buildType}/binding.node`)); +test(path.resolve(__dirname, `./build/${buildType}/binding_noexcept.node`)); + +// Make sure the instance data finalizer is called at process exit. If the hint +// is non-zero, it will be printed out by the child process. +function testFinalizer(bindingName, hint, expected) { + bindingName = bindingName.split('\\').join('\\\\'); + const child = spawn(process.execPath, [ + '-e', + `require('${bindingName}').addon_data(${hint}).verbose = true;` + ]); + const actual = []; + readline + .createInterface({ input: child.stderr }) + .on('line', (line) => { + if (expected.indexOf(line) >= 0) { + actual.push(line); + } + }) + .on('close', () => assert.deepStrictEqual(expected, actual)); +} + +function test(bindingName) { + const binding = require(bindingName).addon_data(0); + + // Make sure it is possible to get/set instance data. + assert.strictEqual(binding.verbose.verbose, false); + binding.verbose = true; + assert.strictEqual(binding.verbose.verbose, true); + binding.verbose = false; + assert.strictEqual(binding.verbose.verbose, false); + + testFinalizer(bindingName, 0, ['addon_data: Addon::~Addon']); + testFinalizer(bindingName, 42, ['addon_data: Addon::~Addon', 'hint: 42']); +} diff --git a/test/binding.cc b/test/binding.cc index 8732ae7bd..ea1094638 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -2,6 +2,9 @@ using namespace Napi; +#if (NAPI_VERSION > 5) +Object InitAddonData(Env env); +#endif Object InitArrayBuffer(Env env); Object InitAsyncContext(Env env); #if (NAPI_VERSION > 3) @@ -55,6 +58,9 @@ Object InitVersionManagement(Env env); Object InitThunkingManual(Env env); Object Init(Env env, Object exports) { +#if (NAPI_VERSION > 5) + exports.Set("addon_data", InitAddonData(env)); +#endif exports.Set("arraybuffer", InitArrayBuffer(env)); exports.Set("asynccontext", InitAsyncContext(env)); #if (NAPI_VERSION > 3) diff --git a/test/binding.gyp b/test/binding.gyp index 2d6ac9549..8dafe13fc 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -2,6 +2,7 @@ 'target_defaults': { 'includes': ['../common.gypi'], 'sources': [ + 'addon_data.cc', 'arraybuffer.cc', 'asynccontext.cc', 'asyncprogressqueueworker.cc', diff --git a/test/index.js b/test/index.js index 0fc38d280..5fc752e3a 100644 --- a/test/index.js +++ b/test/index.js @@ -8,6 +8,7 @@ process.config.target_defaults.default_configuration = // FIXME: We might need a way to load test modules automatically without // explicit declaration as follows. let testModules = [ + 'addon_data', 'arraybuffer', 'asynccontext', 'asyncprogressqueueworker', @@ -81,6 +82,7 @@ if (napiVersion < 5) { if (napiVersion < 6) { testModules.splice(testModules.indexOf('bigint'), 1); testModules.splice(testModules.indexOf('typedarray-bigint'), 1); + testModules.splice(testModules.indexOf('addon_data'), 1); } if (typeof global.gc === 'function') { From 187318e37f4a81f1b0f9f414e86f16dd0dc75748 Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Wed, 29 Apr 2020 23:46:53 +0200 Subject: [PATCH 191/696] doc: Removed references to Node.js lower than 10.x. (#709) * Removed references to Node.js lower than 10.x. --- README.md | 11 +++++++++-- doc/creating_a_release.md | 10 +++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a67051d00..93379a198 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ It is important to remember that *other* Node.js interfaces such as `libuv` (included in a project via `#include `) are not ABI-stable across Node.js major versions. Thus, an addon must use N-API and/or `node-addon-api` exclusively and build against a version of Node.js that includes an -implementation of N-API (meaning a version of Node.js newer than 6.14.2) in +implementation of N-API (meaning an active LTS version of Node.js) in order to benefit from ABI stability across Node.js major versions. Node.js provides an [ABI stability guide][] containing a detailed explanation of ABI stability in general, and the N-API ABI stability guarantee in particular. @@ -47,7 +47,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 2.0.0** +## **Current version: 3.0.0** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) @@ -55,6 +55,13 @@ to ideas specified in the **ECMA262 Language Specification**. +node-addon-api is based on [N-API](https://nodejs.org/api/n-api.html) and supports using different N-API versions. +This allows addons built with it to run with Node.js versions which support the targeted N-API version. +**However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that +every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. + +The oldest Node.js version supported by the current version of node-addon-api is Node.js 10.x. + ## Setup - [Installation and usage](doc/setup.md) - [node-gyp](doc/node-gyp.md) diff --git a/doc/creating_a_release.md b/doc/creating_a_release.md index bc9a859e0..beb67d287 100644 --- a/doc/creating_a_release.md +++ b/doc/creating_a_release.md @@ -13,7 +13,7 @@ tools: * [Changelog maker](https://www.npmjs.com/package/changelog-maker) -If not please follow the instruction reported in the tool's documentation to +If not please follow the instruction reported in the tool's documentation to install it. ## Publish new release @@ -27,13 +27,13 @@ new release. Give people some time to comment or suggest PRs that should land fi * Update the version in **package.json** appropriately. -* Update the [README.md](https://github.com/nodejs/node-addon-api/blob/master/README.md) +* Update the [README.md](https://github.com/nodejs/node-addon-api/blob/master/README.md) to show the new version as the latest. * Generate the changelog for the new version using **changelog maker** tool. From the route folder of the repo launch the following command: - ```bash + ```bash > changelog-maker ``` * Use the output generated by **changelog maker** to pdate the [CHANGELOG.md](https://github.com/nodejs/node-addon-api/blob/master/CHANGELOG.md) @@ -43,8 +43,8 @@ following the style used in publishing the previous release. * Validate all tests pass by running npm test on master. -* Use **[CI](https://ci.nodejs.org/view/x%20-%20Abi%20stable%20module%20API/job/node-test-node-addon-api/)** -to validate tests pass for latest 11, 10, 8, 6 releases (note there are still some issues on SmartOS and +* Use **[CI](https://ci.nodejs.org/view/x%20-%20Abi%20stable%20module%20API/job/node-test-node-addon-api-new/)** +to validate tests pass (note there are still some issues on SmartOS and Windows in the testing). * Do a clean checkout of node-addon-api. From 081cdc2f732ce759df777ffd69e0b4c2bd318b0d Mon Sep 17 00:00:00 2001 From: NickNaso Date: Thu, 30 Apr 2020 01:45:34 +0200 Subject: [PATCH 192/696] Prepare release 3.0.0. --- CHANGELOG.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++++--- package.json | 30 ++++++++++++++- 2 files changed, 129 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a2a6d17d..7566304ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,99 @@ # node-addon-api Changelog +## 2020-04-30 Version 3.0.0, @NickNaso + +### Notable changes: + +#### API + +- `Napi::Object` added templated property descriptors. +- `Napi::ObjectWrap` added templated methods. +- `Napi::ObjectWrap` the wrap is removed only on failure. +- `Napi::ObjectWrap` the constructor's exceptions are gracefully handled. +- `Napi::Function` added templated factory functions. +- Added `Env::RunScript` method to run JavaScript code contained in a string. +- Added templated version of `Napi::Function`. +- Added benchmarking framework. +- Added support for natove addon instance data. +- Added `Napi::AsyncProgressQueueWorker` api. +- Changed the guards to `NAPI_VERSION > 5`. +- Removed N-API implementation (v6.x and v8.x support). +- `Napi::AsyncWorker::OnWorkComplete` and `Napi::AsyncWorker::OnExecute` methods +are override-able. +- Removed erroneous finalizer cleanup in `Napi::ThreadSafeFunction`. +- Disabled cahcing in `Napi::ArrayBuffer`. +- Explicitly disallow assign and copy operator. +- Some minor corrections and improvements. + +#### Documentation + +- Updated documentation for `Napi::Object`. +- Updated documentation for `Napi::Function`. +- Updated documentation for `Napi::ObjectWrap`. +- Added documentation on how to add benchmark. +- Added documentation for `Napi::AsyncProgressQueueWorker`. +- Added suggestion about tags to use on NPM. +- Added reference to N-API badges. +- Some minor corrections all over the documentation. + +#### TEST + +- Updated test cases for `Napi::Object`. +- Updated test cases for `Napi::Function`. +- Updated test cases for `Napi::ObjectWrap`. +- Updated test cases for `Napi::Env`. +- Added test cases for `Napi::AsyncProgressQueueWorker`. +- Some minor corrections all over the test suite. + +### Commits + +* [[`187318e37f`](https://github.com/nodejs/node-addon-api/commit/187318e37f)] - **doc**: Removed references to Node.js lower than 10.x. (#709) (Nicola Del Gobbo) +* [[`9c9accfbbe`](https://github.com/nodejs/node-addon-api/commit/9c9accfbbe)] - **src**: add support for addon instance data (Gabriel Schulhof) [#663](https://github.com/nodejs/node-addon-api/pull/663) +* [[`82a96502a4`](https://github.com/nodejs/node-addon-api/commit/82a96502a4)] - **src**: change guards to NAPI\_VERSION \> 5 (Gabriel Schulhof) [#697](https://github.com/nodejs/node-addon-api/pull/697) +* [[`a64e8a5641`](https://github.com/nodejs/node-addon-api/commit/a64e8a5641)] - **ci**: move travis from 13 to 14 (#707) (Gabriel Schulhof) +* [[`4de23c9d6b`](https://github.com/nodejs/node-addon-api/commit/4de23c9d6b)] - **doc**: fix support bigint64/biguint64 guards (Yulong Wang) [#705](https://github.com/nodejs/node-addon-api/pull/705) +* [[`fedc8195e3`](https://github.com/nodejs/node-addon-api/commit/fedc8195e3)] - **doc**: fix semicolon missing in async\_worker.md (Azlan Mukhtar) [#701](https://github.com/nodejs/node-addon-api/pull/701) +* [[`cdb662506c`](https://github.com/nodejs/node-addon-api/commit/cdb662506c)] - **doc**: fix typo in bigint.md (#700) (Kelvin) +* [[`e1a827ae29`](https://github.com/nodejs/node-addon-api/commit/e1a827ae29)] - **src**: fix AsyncProgressQueueWorker compilation (#696) (Gabriel Schulhof) [#696](https://github.com/nodejs/node-addon-api/pull/696) +* [[`2c3d5df463`](https://github.com/nodejs/node-addon-api/commit/2c3d5df463)] - Merge pull request #692 from kelvinhammond/patch-1 (Nicola Del Gobbo) +* [[`623e876949`](https://github.com/nodejs/node-addon-api/commit/623e876949)] - Merge pull request #688 from NickNaso/badges (Nicola Del Gobbo) +* [[`6c97913d1f`](https://github.com/nodejs/node-addon-api/commit/6c97913d1f)] - Fix minor typo in object\_lifetime\_management.md (Kelvin) +* [[`6b8dd47c55`](https://github.com/nodejs/node-addon-api/commit/6b8dd47c55)] - Added badge section to documentation. (NickNaso) +* [[`89e62a9154`](https://github.com/nodejs/node-addon-api/commit/89e62a9154)] - **doc**: recommend tags of addon helpers (legendecas) [#683](https://github.com/nodejs/node-addon-api/pull/683) +* [[`ab018444ae`](https://github.com/nodejs/node-addon-api/commit/ab018444ae)] - **src**: implement AsyncProgressQueueWorker (legendecas) [#585](https://github.com/nodejs/node-addon-api/pull/585) +* [[`d43da6ac2b`](https://github.com/nodejs/node-addon-api/commit/d43da6ac2b)] - **doc**: add @legendecas to active member list (legendecas) +* [[`cb498bbe7f`](https://github.com/nodejs/node-addon-api/commit/cb498bbe7f)] - **doc**: Add Napi::BigInt::New() overload for uint64\_t (ikokostya) +* [[`baaaa8452c`](https://github.com/nodejs/node-addon-api/commit/baaaa8452c)] - **doc**: link threadsafe function from JS function (legendecas) +* [[`7f56a78ff7`](https://github.com/nodejs/node-addon-api/commit/7f56a78ff7)] - **objectwrap**: remove wrap only on failure (Gabriel Schulhof) +* [[`4d816183da`](https://github.com/nodejs/node-addon-api/commit/4d816183da)] - **doc**: fix example code (András Timár, Dr) [#657](https://github.com/nodejs/node-addon-api/pull/657) +* [[`7ac6e21801`](https://github.com/nodejs/node-addon-api/commit/7ac6e21801)] - **gyp**: fix gypfile name in index.js (Anna Henningsen) [#658](https://github.com/nodejs/node-addon-api/pull/658) +* [[`46484202ca`](https://github.com/nodejs/node-addon-api/commit/46484202ca)] - **test**: user data in function property descriptor (Kevin Eady) [#652](https://github.com/nodejs/node-addon-api/pull/652) +* [[`0f8d730483`](https://github.com/nodejs/node-addon-api/commit/0f8d730483)] - **doc**: fix syntax error in example (András Timár, Dr) [#650](https://github.com/nodejs/node-addon-api/pull/650) +* [[`4e885069f1`](https://github.com/nodejs/node-addon-api/commit/4e885069f1)] - **src**: call `napi\_remove\_wrap()` in `ObjectWrap` dtor (Anna Henningsen) [#475](https://github.com/nodejs/node-addon-api/pull/475) +* [[`2fde5c3ca3`](https://github.com/nodejs/node-addon-api/commit/2fde5c3ca3)] - **test**: update BigInt test for recent change in core (Michael Dawson) [#649](https://github.com/nodejs/node-addon-api/pull/649) +* [[`e8935bd8d9`](https://github.com/nodejs/node-addon-api/commit/e8935bd8d9)] - **test**: add test for own properties on ObjectWrap (Guenter Sandner) [#645](https://github.com/nodejs/node-addon-api/pull/645) +* [[`23ff7f0b24`](https://github.com/nodejs/node-addon-api/commit/23ff7f0b24)] - **src**: make OnWorkComplete and OnExecute override-able (legendecas) [#589](https://github.com/nodejs/node-addon-api/pull/589) +* [[`86384f94d3`](https://github.com/nodejs/node-addon-api/commit/86384f94d3)] - **objectwrap**: gracefully handle constructor exceptions (Gabriel Schulhof) +* [[`9af69da01f`](https://github.com/nodejs/node-addon-api/commit/9af69da01f)] - remove N-API implementation, v6.x and v8.x support (Gabriel Schulhof) [#643](https://github.com/nodejs/node-addon-api/pull/643) +* [[`920d544779`](https://github.com/nodejs/node-addon-api/commit/920d544779)] - **benchmark**: add templated version of Function (Gabriel Schulhof) [#637](https://github.com/nodejs/node-addon-api/pull/637) +* [[`03759f7759`](https://github.com/nodejs/node-addon-api/commit/03759f7759)] - ignore benchmark built archives (legendecas) [#631](https://github.com/nodejs/node-addon-api/pull/631) +* [[`5eeabb0214`](https://github.com/nodejs/node-addon-api/commit/5eeabb0214)] - **tsfn**: Remove erroneous finalizer cleanup (Kevin Eady) [#636](https://github.com/nodejs/node-addon-api/pull/636) +* [[`9e0e0f31e4`](https://github.com/nodejs/node-addon-api/commit/9e0e0f31e4)] - **src**: remove unnecessary forward declarations (Gabriel Schulhof) [#633](https://github.com/nodejs/node-addon-api/pull/633) +* [[`79deefb6f3`](https://github.com/nodejs/node-addon-api/commit/79deefb6f3)] - **src**: explicitly disallow assign and copy (legendecas) [#590](https://github.com/nodejs/node-addon-api/pull/590) +* [[`af50ac281b`](https://github.com/nodejs/node-addon-api/commit/af50ac281b)] - **error**: do not replace pending exception (Gabriel Schulhof) [#629](https://github.com/nodejs/node-addon-api/pull/629) +* [[`b72f1d6978`](https://github.com/nodejs/node-addon-api/commit/b72f1d6978)] - Disable caching in ArrayBuffer (Tobias Nießen) [#611](https://github.com/nodejs/node-addon-api/pull/611) +* [[`0e7483eb7b`](https://github.com/nodejs/node-addon-api/commit/0e7483eb7b)] - Fix code format in tests (Tobias Nießen) [#617](https://github.com/nodejs/node-addon-api/pull/617) +* [[`6a0646356d`](https://github.com/nodejs/node-addon-api/commit/6a0646356d)] - add benchmarking framework (Gabriel Schulhof) [#623](https://github.com/nodejs/node-addon-api/pull/623) +* [[`ffc71edd54`](https://github.com/nodejs/node-addon-api/commit/ffc71edd54)] - Add Env::RunScript (Tobias Nießen) [#616](https://github.com/nodejs/node-addon-api/pull/616) +* [[`a1b106066e`](https://github.com/nodejs/node-addon-api/commit/a1b106066e)] - **src**: add templated function factories (Gabriel Schulhof) [#608](https://github.com/nodejs/node-addon-api/pull/608) +* [[`c584343217`](https://github.com/nodejs/node-addon-api/commit/c584343217)] - Add GetPropertyNames, HasOwnProperty, Delete (#615) (Tobias Nießen) [#615](https://github.com/nodejs/node-addon-api/pull/615) +* [[`3acc4b32f5`](https://github.com/nodejs/node-addon-api/commit/3acc4b32f5)] - Fix std::string encoding (#619) (Tobias Nießen) [#619](https://github.com/nodejs/node-addon-api/pull/619) +* [[`e71d0eadcc`](https://github.com/nodejs/node-addon-api/commit/e71d0eadcc)] - \[doc\] Fixed links to array documentation (#613) (Nicola Del Gobbo) +* [[`3dfb1f0591`](https://github.com/nodejs/node-addon-api/commit/3dfb1f0591)] - Change "WG" to "team" (Tobias Nießen) +* [[`ce91e14860`](https://github.com/nodejs/node-addon-api/commit/ce91e14860)] - **objectwrap**: add template methods (Dmitry Ashkadov) [#604](https://github.com/nodejs/node-addon-api/pull/604) +* [[`cfa71b60f7`](https://github.com/nodejs/node-addon-api/commit/cfa71b60f7)] - **object**: add templated property descriptors (Gabriel Schulhof) [#610](https://github.com/nodejs/node-addon-api/pull/610) +* [[`734725e971`](https://github.com/nodejs/node-addon-api/commit/734725e971)] - Correctly define copy assignment operators. (Rolf Timmermans) + ## 2019-11-21 Version 2.0.0, @NickNaso ### Notable changes: @@ -30,7 +124,7 @@ - Added test cases for `Napi::Date` api. - Added test cases for new features added to `Napi::ThreadSafeFunction`. -### Commmits +### Commits * [[`c881168d49`](https://github.com/nodejs/node-addon-api/commit/c881168d49)] - **tsfn**: add error checking on GetContext (#583) (Kevin Eady) [#583](https://github.com/nodejs/node-addon-api/pull/583) * [[`24d75dd82f`](https://github.com/nodejs/node-addon-api/commit/24d75dd82f)] - Merge pull request #588 from NickNaso/add-asyncprogress-worker-readme (Nicola Del Gobbo) @@ -71,7 +165,7 @@ - Fixed compilation problems that happen on Node.js with N-API version less than 4. -### Commmits +### Commits * [[`c20bcbd069`](https://github.com/nodejs/node-addon-api/commit/c20bcbd069)] - Merge pull request #518 from NickNaso/master (Nicola Del Gobbo) * [[`6720d57253`](https://github.com/nodejs/node-addon-api/commit/6720d57253)] - Create the native threadsafe\_function for test only for N-API greater than 3. (NickNaso) @@ -99,7 +193,7 @@ - Added test case for bool operator. - Fixed test case for `Napi::ObjectWrap`. -### Commmits +### Commits * [[`717c9ab163`](https://github.com/nodejs/node-addon-api/commit/717c9ab163)] - **AsyncWorker**: add GetResult() method (Kevin Eady) [#512](https://github.com/nodejs/node-addon-api/pull/512) * [[`d9d991bbc9`](https://github.com/nodejs/node-addon-api/commit/d9d991bbc9)] - **doc**: add ThreadSafeFunction to main README (#513) (Kevin Eady) [#513](https://github.com/nodejs/node-addon-api/pull/513) @@ -136,7 +230,7 @@ - Some minor corrections all over the documentation. -### Commmits +### Commits * [[`83b41c2fe4`](https://github.com/nodejs/node-addon-api/commit/83b41c2fe4)] - Document adding -fvisibility=hidden flag for macOS users (Nicola Del Gobbo) [#460](https://github.com/nodejs/node-addon-api/pull/460) * [[`1ed7ad8769`](https://github.com/nodejs/node-addon-api/commit/1ed7ad8769)] - **doc**: correct return type of Int32Value to int32\_t (Bill Gallafent) [#459](https://github.com/nodejs/node-addon-api/pull/459) @@ -185,7 +279,7 @@ - Removed unused member on `Napi::CallbackScope`. - Enabled `Napi::CallbackScope` only with N-API v3. -### Commmits +### Commits * [[`e7cd292a74`](https://github.com/nodejs/node-addon-api/commit/e7cd292a74)] - **src**: remove unused CallbackScope member (Gabriel Schulhof) [#391](https://github.com/nodejs/node-addon-api/pull/391) * [[`d47399fe25`](https://github.com/nodejs/node-addon-api/commit/d47399fe25)] - **src**: guard CallbackScope with N-API v3 (Michael Dawson) [#395](https://github.com/nodejs/node-addon-api/pull/395) @@ -210,7 +304,7 @@ associated with a callback in place when making certain N-API calls - Added tests for `Napi::Array` class. - Added tests for `Napi::ArrayBuffer` class. -### Commmits +### Commits * [[`8ce605c657`](https://github.com/nodejs/node-addon-api/commit/8ce605c657)] - **build**: avoid using package-lock.json (Jaeseok Yoon) [#359](https://github.com/nodejs/node-addon-api/pull/359) * [[`fa3a6150b3`](https://github.com/nodejs/node-addon-api/commit/fa3a6150b3)] - **src**: use MakeCallback() -\> Call() in AsyncWorker (Jinho Bang) [#361](https://github.com/nodejs/node-addon-api/pull/361) diff --git a/package.json b/package.json index 5e2d23d98..4cc4b149c 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,10 @@ "name": "Alba Mendez", "url": "https://github.com/jmendeth" }, + { + "name": "András Timár, Dr", + "url": "https://github.com/timarandras" + }, { "name": "Andrew Petersen", "url": "https://github.com/kirbysayshi" @@ -31,6 +35,10 @@ "name": "Arunesh Chandra", "url": "https://github.com/aruneshchandra" }, + { + "name": "Azlan Mukhtar", + "url": "https://github.com/azlan" + }, { "name": "Ben Berman", "url": "https://github.com/rivertam" @@ -55,6 +63,10 @@ "name": "David Halls", "url": "https://github.com/davedoesdev" }, + { + "name": "Dmitry Ashkadov", + "url": "https://github.com/dmitryash" + }, { "name": "Dongjin Na", "url": "https://github.com/nadongguri" @@ -67,6 +79,10 @@ "name": "Gabriel Schulhof", "url": "https://github.com/gabrielschulhof" }, + { + "name": "Guenter Sandner", + "url": "https://github.com/gms1" + }, { "name": "Gus Caplan", "url": "https://github.com/devsnek" @@ -75,6 +91,10 @@ "name": "Hitesh Kanwathirtha", "url": "https://github.com/digitalinfinity" }, + { + "name": "ikokostya", + "url": "https://github.com/ikokostya" + }, { "name": "Jake Barnes", "url": "https://github.com/DuBistKomisch" @@ -99,6 +119,10 @@ "name": "joshgarde", "url": "https://github.com/joshgarde" }, + { + "name": "Kelvin", + "url": "https://github.com/kelvinhammond" + }, { "name": "Kevin Eady", "url": "https://github.com/KevinEady" @@ -202,6 +226,10 @@ { "name": "Yohei Kishimoto", "url": "https://github.com/morokosi" + }, + { + "name": "Yulong Wang", + "url": "https://github.com/fs-eire" } ], "dependencies": {}, @@ -244,5 +272,5 @@ "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile" }, - "version": "2.0.0" + "version": "3.0.0" } From 381c0da60c99b3bc845767142629a036145eff32 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Sat, 25 Apr 2020 21:33:07 -0700 Subject: [PATCH 193/696] doc: add instance data APIs Signed-off-by: Gabriel Schulhof Re: https://github.com/nodejs/node-addon-api/issues/567#issuecomment-619478767 PR-URL: https://github.com/nodejs/node-addon-api/pull/708 Reviewed-By: Nicola Del Gobbo Reviewed-By: Chengzhong Wu --- doc/env.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/doc/env.md b/doc/env.md index 70c641850..66575ea2c 100644 --- a/doc/env.md +++ b/doc/env.md @@ -75,3 +75,58 @@ The `script` can be any of the following types: - [`Napi::String`](string.md) - `const char *` - `const std::string &` + +### GetInstanceData +```cpp +template T* GetInstanceData(); +``` + +Returns the instance data that was previously associated with the environment, +or `nullptr` if none was associated. + +### SetInstanceData + +```cpp +template using Finalizer = void (*)(Env, T*); +template fini = Env::DefaultFini> +void SetInstanceData(T* data); +``` + +- `[template] fini`: A function to call when the instance data is to be deleted. +Accepts a function of the form `void CleanupData(Napi::Env env, T* data)`. If +not given, the default finalizer will be used, which simply uses the `delete` +operator to destroy `T*` when the addon instance is unloaded. +- `[in] data`: A pointer to data that will be associated with the instance of +the addon for the duration of its lifecycle. + +Associates a data item stored at `T* data` with the current instance of the +addon. The item will be passed to the function `fini` which gets called when an +instance of the addon is unloaded. + +### SetInstanceData + +```cpp +template +using FinalizerWithHint = void (*)(Env, DataType*, HintType*); +template fini = + Env::DefaultFiniWithHint> +void SetInstanceData(DataType* data, HintType* hint); +``` + +- `[template] fini`: A function to call when the instance data is to be deleted. +Accepts a function of the form +`void CleanupData(Napi::Env env, DataType* data, HintType* hint)`. If not given, +the default finalizer will be used, which simply uses the `delete` operator to +destroy `T*` when the addon instance is unloaded. +- `[in] data`: A pointer to data that will be associated with the instance of +the addon for the duration of its lifecycle. +- `[in] hint`: A pointer to data that will be associated with the instance of +the addon for the duration of its lifecycle and will be passed as a hint to +`fini` when the addon instance is unloaded. + +Associates a data item stored at `T* data` with the current instance of the +addon. The item will be passed to the function `fini` which gets called when an +instance of the addon is unloaded. This overload accepts an additional hint to +be passed to `fini`. From 45cb1d9748d6305598bb2ad27188135e8bbddad3 Mon Sep 17 00:00:00 2001 From: Jeroen Janssen Date: Wed, 6 May 2020 23:05:46 +0200 Subject: [PATCH 194/696] Correct AsyncProgressWorker link in README (#716) * Correct AsyncProgressWorker link in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93379a198..206c7f108 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ The following is the documentation for node-addon-api. - [Async Operations](doc/async_operations.md) - [AsyncWorker](doc/async_worker.md) - [AsyncContext](doc/async_context.md) - - [AsyncProgressWorker](doc/async_progress_worker.md) + - [AsyncWorker Variants](doc/async_worker_variants.md) - [Thread-safe Functions](doc/threadsafe_function.md) - [Promises](doc/promises.md) - [Version management](doc/version_management.md) From beccf2145dbff6c7febb16844e95a268f7930bdb Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Mon, 25 May 2020 12:11:18 -0400 Subject: [PATCH 195/696] test: fix up delays for array buffer test Signed-off-by: Michael Dawson PR-URL: https://github.com/nodejs/node-addon-api/pull/737 Refs: https://github.com/nodejs/node-addon-api/issues/735 Reviewed-By: Anna Henningsen --- test/arraybuffer.js | 4 ++++ test/buffer.js | 4 ++++ test/index.js | 11 +++++++++-- test/testUtil.js | 14 +++++++++++++- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/test/arraybuffer.js b/test/arraybuffer.js index 30136980b..4d0681ac3 100644 --- a/test/arraybuffer.js +++ b/test/arraybuffer.js @@ -39,6 +39,8 @@ function test(binding) { }, () => { global.gc(); + }, + () => { assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()); }, @@ -51,6 +53,8 @@ function test(binding) { }, () => { global.gc(); + }, + () => { assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()); }, diff --git a/test/buffer.js b/test/buffer.js index 0c6e64895..9b94a9069 100644 --- a/test/buffer.js +++ b/test/buffer.js @@ -48,6 +48,8 @@ function test(binding) { }, () => { global.gc(); + }, + () => { assert.strictEqual(1, binding.buffer.getFinalizeCount()); }, @@ -60,6 +62,8 @@ function test(binding) { }, () => { global.gc(); + }, + () => { assert.strictEqual(1, binding.buffer.getFinalizeCount()); }, ]); diff --git a/test/index.js b/test/index.js index 5fc752e3a..78c1c9779 100644 --- a/test/index.js +++ b/test/index.js @@ -58,6 +58,7 @@ let testModules = [ ]; const napiVersion = Number(process.versions.napi) +const majorNodeVersion = process.versions.node.split('.')[0] if (napiVersion < 3) { testModules.splice(testModules.indexOf('callbackscope'), 1); @@ -98,8 +99,14 @@ if (typeof global.gc === 'function') { console.log('\nAll tests passed!'); } else { - // Make it easier to run with the correct (version-dependent) command-line args. - const child = require('./napi_child').spawnSync(process.argv[0], [ '--expose-gc', __filename ], { + // Construct the correct (version-dependent) command-line args. + let args = ['--expose-gc', '--no-concurrent-array-buffer-freeing']; + if (majorNodeVersion >= 14) { + args.push('--no-concurrent-array-buffer-sweeping'); + } + args.push(__filename); + + const child = require('./napi_child').spawnSync(process.argv[0], args, { stdio: 'inherit', }); diff --git a/test/testUtil.js b/test/testUtil.js index 402c5c91d..dfd7fab26 100644 --- a/test/testUtil.js +++ b/test/testUtil.js @@ -1,5 +1,17 @@ // Run each test function in sequence, // with an async delay and GC call between each. + +function tick(x, cb) { + function ontick() { + if (--x === 0) { + if (typeof cb === 'function') cb(); + } else { + setImmediate(ontick); + } + } + setImmediate(ontick); +}; + function runGCTests(tests, i, title) { if (!i) { i = 0; @@ -18,7 +30,7 @@ function runGCTests(tests, i, title) { } setImmediate(() => { global.gc(); - runGCTests(tests, i + 1, title); + tick(10, runGCTests(tests, i + 1, title)); }); } } From 2da3023bf0d9a69dc993afd6f87565432dab8f33 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Mon, 8 Jun 2020 15:25:14 +0200 Subject: [PATCH 196/696] test: Initial commit of TSFNEx threadsafe test --- test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 2 + test/threadsafe_function_ex/threadsafe.cc | 183 ++++++++++++++++++++++ test/threadsafe_function_ex/threadsafe.js | 170 ++++++++++++++++++++ 5 files changed, 358 insertions(+) create mode 100644 test/threadsafe_function_ex/threadsafe.cc create mode 100644 test/threadsafe_function_ex/threadsafe.js diff --git a/test/binding.cc b/test/binding.cc index 1c98637cf..3fb26726c 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -51,6 +51,7 @@ Object InitThreadSafeFunction(Env env); Object InitThreadSafeFunctionExCall(Env env); Object InitThreadSafeFunctionExContext(Env env); Object InitThreadSafeFunctionExSimple(Env env); +Object InitThreadSafeFunctionExThreadSafe(Env env); #endif Object InitTypedArray(Env env); Object InitObjectWrap(Env env); @@ -111,6 +112,7 @@ Object Init(Env env, Object exports) { exports.Set("threadsafe_function_ex_call", InitThreadSafeFunctionExCall(env)); exports.Set("threadsafe_function_ex_context", InitThreadSafeFunctionExContext(env)); exports.Set("threadsafe_function_ex_simple", InitThreadSafeFunctionExSimple(env)); + exports.Set("threadsafe_function_ex_threadsafe", InitThreadSafeFunctionExThreadSafe(env)); #endif exports.Set("typedarray", InitTypedArray(env)); exports.Set("objectwrap", InitObjectWrap(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 15654cda1..fdd7bf263 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -37,6 +37,7 @@ 'threadsafe_function_ex/call.cc', 'threadsafe_function_ex/context.cc', 'threadsafe_function_ex/simple.cc', + 'threadsafe_function_ex/threadsafe.cc', 'threadsafe_function/threadsafe_function_ctx.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', diff --git a/test/index.js b/test/index.js index bb54e29ba..772ef310d 100644 --- a/test/index.js +++ b/test/index.js @@ -44,6 +44,7 @@ let testModules = [ 'threadsafe_function_ex/call', 'threadsafe_function_ex/context', 'threadsafe_function_ex/simple', + 'threadsafe_function_ex/threadsafe', 'threadsafe_function/threadsafe_function_ctx', 'threadsafe_function/threadsafe_function_existing_tsfn', 'threadsafe_function/threadsafe_function_ptr', @@ -87,6 +88,7 @@ if (napiVersion < 4) { testModules.splice(testModules.indexOf('threadsafe_function_ex/call'), 1); testModules.splice(testModules.indexOf('threadsafe_function_ex/context'), 1); testModules.splice(testModules.indexOf('threadsafe_function_ex/simple'), 1); + testModules.splice(testModules.indexOf('threadsafe_function_ex/threadsafe'), 1); } if (napiVersion < 5) { diff --git a/test/threadsafe_function_ex/threadsafe.cc b/test/threadsafe_function_ex/threadsafe.cc new file mode 100644 index 000000000..370fb1ac1 --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe.cc @@ -0,0 +1,183 @@ +#include +#include +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +constexpr size_t ARRAY_LENGTH = 10; +constexpr size_t MAX_QUEUE_SIZE = 2; + +static std::thread threadsEx[2]; +static ThreadSafeFunction tsfnEx; + +struct ThreadSafeFunctionInfo { + enum CallType { + DEFAULT, + BLOCKING, + NON_BLOCKING + } type; + bool abort; + bool startSecondary; + FunctionReference jsFinalizeCallback; + uint32_t maxQueueSize; +} tsfnInfoEx; + +// Thread data to transmit to JS +static int intsEx[ARRAY_LENGTH]; + +static void SecondaryThreadEx() { + if (tsfnEx.Release() != napi_ok) { + Error::Fatal("SecondaryThread", "ThreadSafeFunction.Release() failed"); + } +} + +// Source thread producing the data +static void DataSourceThreadEx() { + ThreadSafeFunctionInfo* info = tsfnEx.GetContext(); + + if (info->startSecondary) { + if (tsfnEx.Acquire() != napi_ok) { + Error::Fatal("DataSourceThread", "ThreadSafeFunction.Acquire() failed"); + } + + threadsEx[1] = std::thread(SecondaryThreadEx); + } + + bool queueWasFull = false; + bool queueWasClosing = false; + for (int index = ARRAY_LENGTH - 1; index > -1 && !queueWasClosing; index--) { + napi_status status = napi_generic_failure; + auto callback = [](Env env, Function jsCallback, int* data) { + jsCallback.Call({ Number::New(env, *data) }); + }; + + switch (info->type) { + case ThreadSafeFunctionInfo::DEFAULT: + status = tsfnEx.BlockingCall(); + break; + case ThreadSafeFunctionInfo::BLOCKING: + status = tsfnEx.BlockingCall(&intsEx[index], callback); + break; + case ThreadSafeFunctionInfo::NON_BLOCKING: + status = tsfnEx.NonBlockingCall(&intsEx[index], callback); + break; + } + + if (info->maxQueueSize == 0) { + // Let's make this thread really busy for 200 ms to give the main thread a + // chance to abort. + auto start = std::chrono::high_resolution_clock::now(); + constexpr auto MS_200 = std::chrono::milliseconds(200); + for (; std::chrono::high_resolution_clock::now() - start < MS_200;); + } + + switch (status) { + case napi_queue_full: + queueWasFull = true; + index++; + // fall through + + case napi_ok: + continue; + + case napi_closing: + queueWasClosing = true; + break; + + default: + Error::Fatal("DataSourceThread", "ThreadSafeFunction.*Call() failed"); + } + } + + if (info->type == ThreadSafeFunctionInfo::NON_BLOCKING && !queueWasFull) { + Error::Fatal("DataSourceThread", "Queue was never full"); + } + + if (info->abort && !queueWasClosing) { + Error::Fatal("DataSourceThread", "Queue was never closing"); + } + + if (!queueWasClosing && tsfnEx.Release() != napi_ok) { + Error::Fatal("DataSourceThread", "ThreadSafeFunction.Release() failed"); + } +} + +static Value StopThreadEx(const CallbackInfo& info) { + tsfnInfoEx.jsFinalizeCallback = Napi::Persistent(info[0].As()); + bool abort = info[1].As(); + if (abort) { + tsfnEx.Abort(); + } else { + tsfnEx.Release(); + } + return Value(); +} + +// Join the thread and inform JS that we're done. +static void JoinTheThreadsEx(Env /* env */, + std::thread* theThreads, + ThreadSafeFunctionInfo* info) { + theThreads[0].join(); + if (info->startSecondary) { + theThreads[1].join(); + } + + info->jsFinalizeCallback.Call({}); + info->jsFinalizeCallback.Reset(); +} + +static Value StartThreadInternalEx(const CallbackInfo& info, + ThreadSafeFunctionInfo::CallType type) { + tsfnInfoEx.type = type; + tsfnInfoEx.abort = info[1].As(); + tsfnInfoEx.startSecondary = info[2].As(); + tsfnInfoEx.maxQueueSize = info[3].As().Uint32Value(); + + tsfnEx = ThreadSafeFunction::New(info.Env(), info[0].As(), + "Test", tsfnInfoEx.maxQueueSize, 2, &tsfnInfoEx, JoinTheThreadsEx, threadsEx); + + threadsEx[0] = std::thread(DataSourceThreadEx); + + return Value(); +} + +static Value ReleaseEx(const CallbackInfo& /* info */) { + if (tsfnEx.Release() != napi_ok) { + Error::Fatal("Release", "ThreadSafeFunction.Release() failed"); + } + return Value(); +} + +static Value StartThreadEx(const CallbackInfo& info) { + return StartThreadInternalEx(info, ThreadSafeFunctionInfo::BLOCKING); +} + +static Value StartThreadNonblockingEx(const CallbackInfo& info) { + return StartThreadInternalEx(info, ThreadSafeFunctionInfo::NON_BLOCKING); +} + +static Value StartThreadNoNativeEx(const CallbackInfo& info) { + return StartThreadInternalEx(info, ThreadSafeFunctionInfo::DEFAULT); +} + +Object InitThreadSafeFunctionExThreadSafe(Env env) { + for (size_t index = 0; index < ARRAY_LENGTH; index++) { + intsEx[index] = index; + } + + Object exports = Object::New(env); + exports["ARRAY_LENGTH"] = Number::New(env, ARRAY_LENGTH); + exports["MAX_QUEUE_SIZE"] = Number::New(env, MAX_QUEUE_SIZE); + exports["startThread"] = Function::New(env, StartThreadEx); + exports["startThreadNoNative"] = Function::New(env, StartThreadNoNativeEx); + exports["startThreadNonblocking"] = + Function::New(env, StartThreadNonblockingEx); + exports["stopThread"] = Function::New(env, StopThreadEx); + exports["release"] = Function::New(env, ReleaseEx); + + return exports; +} + +#endif diff --git a/test/threadsafe_function_ex/threadsafe.js b/test/threadsafe_function_ex/threadsafe.js new file mode 100644 index 000000000..2649d580d --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe.js @@ -0,0 +1,170 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const common = require('../common'); + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + const expectedArray = (function(arrayLength) { + const result = []; + for (let index = 0; index < arrayLength; index++) { + result.push(arrayLength - 1 - index); + } + return result; + })(binding.threadsafe_function_ex_threadsafe.ARRAY_LENGTH); + + function testWithJSMarshaller({ + threadStarter, + quitAfter, + abort, + maxQueueSize, + launchSecondary }) { + return new Promise((resolve) => { + const array = []; + binding.threadsafe_function_ex_threadsafe[threadStarter](function testCallback(value) { + array.push(value); + if (array.length === quitAfter) { + setImmediate(() => { + binding.threadsafe_function_ex_threadsafe.stopThread(common.mustCall(() => { + resolve(array); + }), !!abort); + }); + } + }, !!abort, !!launchSecondary, maxQueueSize); + if (threadStarter === 'startThreadNonblocking') { + // Let's make this thread really busy for a short while to ensure that + // the queue fills and the thread receives a napi_queue_full. + const start = Date.now(); + while (Date.now() - start < 200); + } + }); + } + + new Promise(function testWithoutJSMarshaller(resolve) { + let callCount = 0; + binding.threadsafe_function_ex_threadsafe.startThreadNoNative(function testCallback() { + callCount++; + + // The default call-into-JS implementation passes no arguments. + assert.strictEqual(arguments.length, 0); + if (callCount === binding.threadsafe_function_ex_threadsafe.ARRAY_LENGTH) { + setImmediate(() => { + binding.threadsafe_function_ex_threadsafe.stopThread(common.mustCall(() => { + resolve(); + }), false); + }); + } + }, false /* abort */, false /* launchSecondary */, + binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE); + }) + + // Start the thread in blocking mode, and assert that all values are passed. + // Quit after it's done. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, + quitAfter: binding.threadsafe_function_ex_threadsafe.ARRAY_LENGTH + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in blocking mode with an infinite queue, and assert that + // all values are passed. Quit after it's done. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: 0, + quitAfter: binding.threadsafe_function_ex_threadsafe.ARRAY_LENGTH + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in non-blocking mode, and assert that all values are + // passed. Quit after it's done. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, + quitAfter: binding.threadsafe_function_ex_threadsafe.ARRAY_LENGTH + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in blocking mode, and assert that all values are passed. + // Quit early, but let the thread finish. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, + quitAfter: 1 + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in blocking mode with an infinite queue, and assert that + // all values are passed. Quit early, but let the thread finish. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: 0, + quitAfter: 1 + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + + // Start the thread in non-blocking mode, and assert that all values are + // passed. Quit early, but let the thread finish. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, + quitAfter: 1 + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in blocking mode, and assert that all values are passed. + // Quit early, but let the thread finish. Launch a secondary thread to test + // the reference counter incrementing functionality. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, + launchSecondary: true + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in non-blocking mode, and assert that all values are + // passed. Quit early, but let the thread finish. Launch a secondary thread + // to test the reference counter incrementing functionality. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, + launchSecondary: true + })) + .then((result) => assert.deepStrictEqual(result, expectedArray)) + + // Start the thread in blocking mode, and assert that it could not finish. + // Quit early by aborting. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, + abort: true + })) + .then((result) => assert.strictEqual(result.indexOf(0), -1)) + + // Start the thread in blocking mode with an infinite queue, and assert that + // it could not finish. Quit early by aborting. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: 0, + abort: true + })) + .then((result) => assert.strictEqual(result.indexOf(0), -1)) + + // Start the thread in non-blocking mode, and assert that it could not finish. + // Quit early and aborting. + .then(() => testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, + abort: true + })) + .then((result) => assert.strictEqual(result.indexOf(0), -1)) +} From 91e885948e5c99cd503eafa9d43211f3ff92a81f Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Mon, 8 Jun 2020 16:06:35 +0200 Subject: [PATCH 197/696] test: Modify TSFNEx threadsafe to use TSFNEx --- test/threadsafe_function_ex/threadsafe.cc | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/test/threadsafe_function_ex/threadsafe.cc b/test/threadsafe_function_ex/threadsafe.cc index 370fb1ac1..108191a22 100644 --- a/test/threadsafe_function_ex/threadsafe.cc +++ b/test/threadsafe_function_ex/threadsafe.cc @@ -10,7 +10,6 @@ constexpr size_t ARRAY_LENGTH = 10; constexpr size_t MAX_QUEUE_SIZE = 2; static std::thread threadsEx[2]; -static ThreadSafeFunction tsfnEx; struct ThreadSafeFunctionInfo { enum CallType { @@ -24,6 +23,19 @@ struct ThreadSafeFunctionInfo { uint32_t maxQueueSize; } tsfnInfoEx; +static void TSFNCallJS(Env env, Function jsCallback, + ThreadSafeFunctionInfo * /* context */, int *data) { + // If called with no data + if (data == nullptr) { + jsCallback.Call({}); + } else { + jsCallback.Call({Number::New(env, *data)}); + } +} + +using TSFN = ThreadSafeFunctionEx; +static TSFN tsfnEx; + // Thread data to transmit to JS static int intsEx[ARRAY_LENGTH]; @@ -49,19 +61,16 @@ static void DataSourceThreadEx() { bool queueWasClosing = false; for (int index = ARRAY_LENGTH - 1; index > -1 && !queueWasClosing; index--) { napi_status status = napi_generic_failure; - auto callback = [](Env env, Function jsCallback, int* data) { - jsCallback.Call({ Number::New(env, *data) }); - }; switch (info->type) { case ThreadSafeFunctionInfo::DEFAULT: status = tsfnEx.BlockingCall(); break; case ThreadSafeFunctionInfo::BLOCKING: - status = tsfnEx.BlockingCall(&intsEx[index], callback); + status = tsfnEx.BlockingCall(&intsEx[index]); break; case ThreadSafeFunctionInfo::NON_BLOCKING: - status = tsfnEx.NonBlockingCall(&intsEx[index], callback); + status = tsfnEx.NonBlockingCall(&intsEx[index]); break; } @@ -135,7 +144,7 @@ static Value StartThreadInternalEx(const CallbackInfo& info, tsfnInfoEx.startSecondary = info[2].As(); tsfnInfoEx.maxQueueSize = info[3].As().Uint32Value(); - tsfnEx = ThreadSafeFunction::New(info.Env(), info[0].As(), + tsfnEx = TSFN::New(info.Env(), info[0].As(), Object::New(info.Env()), "Test", tsfnInfoEx.maxQueueSize, 2, &tsfnInfoEx, JoinTheThreadsEx, threadsEx); threadsEx[0] = std::thread(DataSourceThreadEx); From 692fbe161371a597840e9cca61f51881bbacf894 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Mon, 8 Jun 2020 16:07:12 +0200 Subject: [PATCH 198/696] src,test: Add SIGTRAP to TSFNEx::CallJS --- napi-inl.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/napi-inl.h b/napi-inl.h index cd5294485..31a063507 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace Napi { @@ -4432,6 +4433,9 @@ template void ThreadSafeFunctionEx::CallJsInternal( napi_env env, napi_value jsCallback, void *context, void *data) { + if (env == nullptr) { + raise(SIGTRAP); + } details::CallJsWrapper( env, jsCallback, context, data); } From 31504c862b2b77e73ab805df68098d3af5a7403a Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Mon, 8 Jun 2020 21:48:18 +0200 Subject: [PATCH 199/696] doc: fix minor typo in object_wrap.md (#741) PR-URL: https://github.com/nodejs/node-addon-api/pull/741 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- doc/object_wrap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/object_wrap.md b/doc/object_wrap.md index 5a0ec1036..cdf7ce1e2 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -137,7 +137,7 @@ static T* Napi::ObjectWrap::Unwrap(Napi::Object wrapper); * `[in] wrapper`: The JavaScript object that wraps the native instance. -Returns a native instace wrapped in a JavaScript object. Given the +Returns a native instance wrapped in a JavaScript object. Given the Napi:Object, this allows a method to get a pointer to the wrapped C++ object and then reference fields, call methods, etc. within that class. In many cases calling Unwrap is not required, as methods can From ba7ad37d4462c5f3b61a8a7750df2906b7914609 Mon Sep 17 00:00:00 2001 From: David Halls Date: Sat, 16 May 2020 08:36:06 +0100 Subject: [PATCH 200/696] src: fix ObjectWrap inheritance - fix wrap/unwrap of objects inheriting from non-ObjectWrap PR-URL: https://github.com/nodejs/node-addon-api/pull/732 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- napi-inl.h | 7 +++--- test/binding.cc | 2 ++ test/binding.gyp | 1 + test/index.js | 1 + test/objectwrap_multiple_inheritance.cc | 30 +++++++++++++++++++++++++ test/objectwrap_multiple_inheritance.js | 15 +++++++++++++ 6 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 test/objectwrap_multiple_inheritance.cc create mode 100644 test/objectwrap_multiple_inheritance.js diff --git a/napi-inl.h b/napi-inl.h index 7e4d158db..90a295005 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -3146,10 +3146,11 @@ inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { napi_value wrapper = callbackInfo.This(); napi_status status; napi_ref ref; - status = napi_wrap(env, wrapper, this, FinalizeCallback, nullptr, &ref); + T* instance = static_cast(this); + status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); NAPI_THROW_IF_FAILED_VOID(env, status); - Reference* instanceRef = this; + Reference* instanceRef = instance; *instanceRef = Reference(env, ref); } @@ -3872,7 +3873,7 @@ inline napi_value ObjectWrap::InstanceSetterCallbackWrapper( template inline void ObjectWrap::FinalizeCallback(napi_env env, void* data, void* /*hint*/) { - ObjectWrap* instance = static_cast*>(data); + T* instance = static_cast(data); instance->Finalize(Napi::Env(env)); delete instance; } diff --git a/test/binding.cc b/test/binding.cc index ea1094638..829b45e6b 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -53,6 +53,7 @@ Object InitTypedArray(Env env); Object InitObjectWrap(Env env); Object InitObjectWrapConstructorException(Env env); Object InitObjectWrapRemoveWrap(Env env); +Object InitObjectWrapMultipleInheritance(Env env); Object InitObjectReference(Env env); Object InitVersionManagement(Env env); Object InitThunkingManual(Env env); @@ -111,6 +112,7 @@ Object Init(Env env, Object exports) { exports.Set("objectwrapConstructorException", InitObjectWrapConstructorException(env)); exports.Set("objectwrap_removewrap", InitObjectWrapRemoveWrap(env)); + exports.Set("objectwrap_multiple_inheritance", InitObjectWrapMultipleInheritance(env)); exports.Set("objectreference", InitObjectReference(env)); exports.Set("version_management", InitVersionManagement(env)); exports.Set("thunking_manual", InitThunkingManual(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 8dafe13fc..2c6f791b1 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -45,6 +45,7 @@ 'objectwrap.cc', 'objectwrap_constructor_exception.cc', 'objectwrap-removewrap.cc', + 'objectwrap_multiple_inheritance.cc', 'objectreference.cc', 'version_management.cc', 'thunking_manual.cc', diff --git a/test/index.js b/test/index.js index 78c1c9779..09b40c25d 100644 --- a/test/index.js +++ b/test/index.js @@ -53,6 +53,7 @@ let testModules = [ 'objectwrap', 'objectwrap_constructor_exception', 'objectwrap-removewrap', + 'objectwrap_multiple_inheritance', 'objectreference', 'version_management' ]; diff --git a/test/objectwrap_multiple_inheritance.cc b/test/objectwrap_multiple_inheritance.cc new file mode 100644 index 000000000..67913eb4e --- /dev/null +++ b/test/objectwrap_multiple_inheritance.cc @@ -0,0 +1,30 @@ +#include + +class TestMIBase { +public: + TestMIBase() : test(0) {} + virtual void dummy() {} + uint32_t test; +}; + +class TestMI : public TestMIBase, public Napi::ObjectWrap { +public: + TestMI(const Napi::CallbackInfo& info) : + Napi::ObjectWrap(info) {} + + Napi::Value GetTest(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), test); + } + + static void Initialize(Napi::Env env, Napi::Object exports) { + exports.Set("TestMI", DefineClass(env, "TestMI", { + InstanceAccessor<&TestMI::GetTest>("test") + })); + } +}; + +Napi::Object InitObjectWrapMultipleInheritance(Napi::Env env) { + Napi::Object exports = Napi::Object::New(env); + TestMI::Initialize(env, exports); + return exports; +} diff --git a/test/objectwrap_multiple_inheritance.js b/test/objectwrap_multiple_inheritance.js new file mode 100644 index 000000000..87c669eb3 --- /dev/null +++ b/test/objectwrap_multiple_inheritance.js @@ -0,0 +1,15 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +const test = bindingName => { + const binding = require(bindingName); + const TestMI = binding.objectwrap_multiple_inheritance.TestMI; + const testmi = new TestMI(); + + assert.strictEqual(testmi.test, 0); +} + +test(`./build/${buildType}/binding.node`); +test(`./build/${buildType}/binding_noexcept.node`); From d463f02bc78bd90d4149a1888e19e9a8159b44d7 Mon Sep 17 00:00:00 2001 From: Ferdinand Holzer Date: Sun, 24 May 2020 22:25:01 +0200 Subject: [PATCH 201/696] src: fix testEnumerables on ObjectWrap PR-URL: https://github.com/nodejs/node-addon-api/pull/736 Reviewed-By: Michael Dawson --- test/objectwrap.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/objectwrap.js b/test/objectwrap.js index a1a56136f..02abf60b9 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -108,11 +108,15 @@ const test = (binding) => { keys.push(key); } - assert(keys.length = 4); - assert(obj.testGetSet); - assert(obj.testGetter); - assert(obj.testValue); - assert(obj.testMethod); + assert(keys.length == 6); + // on prototype + assert(keys.includes("testGetSet")); + assert(keys.includes("testGetter")); + assert(keys.includes("testValue")); + assert(keys.includes("testMethod")); + // on object only + assert(keys.includes("ownProperty")); + assert(keys.includes("ownPropertyT")); } }; From 36e1af96d52d9fd934ec1d569a7860172ffcaae1 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Tue, 12 May 2020 20:16:20 -0400 Subject: [PATCH 202/696] src: fix use of Reference with typed arrays Fixes: https://github.com/nodejs/node-addon-api/issues/702 Previously calling Value() on a Reference for a TypedArray that the enderlying object had been collected would result in an error due to a failure in creating the return value. Signed-off-by: Michael Dawson PR-URL: https://github.com/nodejs/node-addon-api/pull/726 Fixes: https://github.com/nodejs/node-addon-api/issues/702 Reviewed-By: Chengzhong Wu --- napi-inl.h | 10 ++++++++-- test/binding.cc | 2 ++ test/binding.gyp | 1 + test/index.js | 1 + test/reference.cc | 24 ++++++++++++++++++++++++ test/reference.js | 15 +++++++++++++++ 6 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 test/reference.cc create mode 100644 test/reference.js diff --git a/napi-inl.h b/napi-inl.h index 90a295005..649be98ac 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1740,8 +1740,14 @@ inline TypedArrayOf::TypedArrayOf() : TypedArray(), _data(nullptr) { template inline TypedArrayOf::TypedArrayOf(napi_env env, napi_value value) : TypedArray(env, value), _data(nullptr) { - napi_status status = napi_get_typedarray_info( - _env, _value, &_type, &_length, reinterpret_cast(&_data), nullptr, nullptr); + napi_status status = napi_ok; + if (value != nullptr) { + status = napi_get_typedarray_info( + _env, _value, &_type, &_length, reinterpret_cast(&_data), nullptr, nullptr); + } else { + _type = TypedArrayTypeForPrimitiveType(); + _length = 0; + } NAPI_THROW_IF_FAILED_VOID(_env, status); } diff --git a/test/binding.cc b/test/binding.cc index 829b45e6b..0eb22abbf 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -55,6 +55,7 @@ Object InitObjectWrapConstructorException(Env env); Object InitObjectWrapRemoveWrap(Env env); Object InitObjectWrapMultipleInheritance(Env env); Object InitObjectReference(Env env); +Object InitReference(Env env); Object InitVersionManagement(Env env); Object InitThunkingManual(Env env); @@ -114,6 +115,7 @@ Object Init(Env env, Object exports) { exports.Set("objectwrap_removewrap", InitObjectWrapRemoveWrap(env)); exports.Set("objectwrap_multiple_inheritance", InitObjectWrapMultipleInheritance(env)); exports.Set("objectreference", InitObjectReference(env)); + exports.Set("reference", InitReference(env)); exports.Set("version_management", InitVersionManagement(env)); exports.Set("thunking_manual", InitThunkingManual(env)); return exports; diff --git a/test/binding.gyp b/test/binding.gyp index 2c6f791b1..797d81139 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -47,6 +47,7 @@ 'objectwrap-removewrap.cc', 'objectwrap_multiple_inheritance.cc', 'objectreference.cc', + 'reference.cc', 'version_management.cc', 'thunking_manual.cc', ], diff --git a/test/index.js b/test/index.js index 09b40c25d..e930eab5c 100644 --- a/test/index.js +++ b/test/index.js @@ -55,6 +55,7 @@ let testModules = [ 'objectwrap-removewrap', 'objectwrap_multiple_inheritance', 'objectreference', + 'reference', 'version_management' ]; diff --git a/test/reference.cc b/test/reference.cc new file mode 100644 index 000000000..f93263295 --- /dev/null +++ b/test/reference.cc @@ -0,0 +1,24 @@ +#include "napi.h" + +using namespace Napi; + +static Reference> weak; + +void CreateWeakArray(const CallbackInfo& info) { + weak = Weak(Buffer::New(info.Env(), 1)); + weak.SuppressDestruct(); +} + +napi_value AccessWeakArrayEmpty(const CallbackInfo& info) { + Buffer value = weak.Value(); + return Napi::Boolean::New(info.Env(), value.IsEmpty()); +} + +Object InitReference(Env env) { + Object exports = Object::New(env); + + exports["createWeakArray"] = Function::New(env, CreateWeakArray); + exports["accessWeakArrayEmpty"] = Function::New(env, AccessWeakArrayEmpty); + + return exports; +} diff --git a/test/reference.js b/test/reference.js new file mode 100644 index 000000000..3a59e850f --- /dev/null +++ b/test/reference.js @@ -0,0 +1,15 @@ +'use strict'; + + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const testUtil = require('./testUtil'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + binding.reference.createWeakArray(); + global.gc(); + assert.strictEqual(true, binding.reference.accessWeakArrayEmpty()); +}; From 4c01af2d8722c4bc36c2c051c2b2621f47553431 Mon Sep 17 00:00:00 2001 From: Kasumi Hanazuki Date: Wed, 10 Jun 2020 08:50:32 +0900 Subject: [PATCH 203/696] Fix typo in CHANGELOG (#715) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7566304ad..7775216d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,14 +14,14 @@ - Added `Env::RunScript` method to run JavaScript code contained in a string. - Added templated version of `Napi::Function`. - Added benchmarking framework. -- Added support for natove addon instance data. +- Added support for native addon instance data. - Added `Napi::AsyncProgressQueueWorker` api. - Changed the guards to `NAPI_VERSION > 5`. - Removed N-API implementation (v6.x and v8.x support). - `Napi::AsyncWorker::OnWorkComplete` and `Napi::AsyncWorker::OnExecute` methods are override-able. - Removed erroneous finalizer cleanup in `Napi::ThreadSafeFunction`. -- Disabled cahcing in `Napi::ArrayBuffer`. +- Disabled caching in `Napi::ArrayBuffer`. - Explicitly disallow assign and copy operator. - Some minor corrections and improvements. From 89181da235299b39f9c22d5d8894f9b05de5db63 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Fri, 12 Jun 2020 21:16:16 +0200 Subject: [PATCH 204/696] test: fix TSFNEx tests --- napi-inl.h | 3 - test/threadsafe_function_ex/call.cc | 12 ++- test/threadsafe_function_ex/context.cc | 12 ++- test/threadsafe_function_ex/threadsafe.cc | 97 ++++++++++++----------- 4 files changed, 68 insertions(+), 56 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 261202565..e5c6399af 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4473,9 +4473,6 @@ template void ThreadSafeFunctionEx::CallJsInternal( napi_env env, napi_value jsCallback, void *context, void *data) { - if (env == nullptr) { - raise(SIGTRAP); - } details::CallJsWrapper( env, jsCallback, context, data); } diff --git a/test/threadsafe_function_ex/call.cc b/test/threadsafe_function_ex/call.cc index 26951e45a..2e41799b8 100644 --- a/test/threadsafe_function_ex/call.cc +++ b/test/threadsafe_function_ex/call.cc @@ -18,9 +18,15 @@ struct TSFNData { // CallJs callback function static void CallJs(Napi::Env env, Napi::Function jsCallback, TSFNContext * /*context*/, TSFNData *data) { - jsCallback.Call(env.Undefined(), {data->data.Value()}); - data->deferred.Resolve(data->data.Value()); - delete data; + if (!(env == nullptr || jsCallback == nullptr)) { + if (data != nullptr) { + jsCallback.Call(env.Undefined(), {data->data.Value()}); + data->deferred.Resolve(data->data.Value()); + } + } + if (data != nullptr) { + delete data; + } } // Full type of our ThreadSafeFunctionEx diff --git a/test/threadsafe_function_ex/context.cc b/test/threadsafe_function_ex/context.cc index 19df41882..c67ebaba2 100644 --- a/test/threadsafe_function_ex/context.cc +++ b/test/threadsafe_function_ex/context.cc @@ -13,10 +13,16 @@ using TSFNContext = Reference; using TSFNData = Promise::Deferred; // CallJs callback function -static void CallJs(Napi::Env /*env*/, Napi::Function /*jsCallback*/, +static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, TSFNContext *context, TSFNData *data) { - data->Resolve(context->Value()); - delete data; + if (env != nullptr) { + if (data != nullptr) { + data->Resolve(context->Value()); + } + } + if (data != nullptr) { + delete data; + } } // Full type of our ThreadSafeFunctionEx diff --git a/test/threadsafe_function_ex/threadsafe.cc b/test/threadsafe_function_ex/threadsafe.cc index 108191a22..cf3eddca8 100644 --- a/test/threadsafe_function_ex/threadsafe.cc +++ b/test/threadsafe_function_ex/threadsafe.cc @@ -9,9 +9,9 @@ using namespace Napi; constexpr size_t ARRAY_LENGTH = 10; constexpr size_t MAX_QUEUE_SIZE = 2; -static std::thread threadsEx[2]; +static std::thread threads[2]; -struct ThreadSafeFunctionInfo { +static struct ThreadSafeFunctionInfo { enum CallType { DEFAULT, BLOCKING, @@ -21,40 +21,43 @@ struct ThreadSafeFunctionInfo { bool startSecondary; FunctionReference jsFinalizeCallback; uint32_t maxQueueSize; -} tsfnInfoEx; +} tsfnInfo; static void TSFNCallJS(Env env, Function jsCallback, ThreadSafeFunctionInfo * /* context */, int *data) { - // If called with no data - if (data == nullptr) { - jsCallback.Call({}); - } else { - jsCallback.Call({Number::New(env, *data)}); + // A null environment signifies the threadsafe function has been finalized. + if (!(env == nullptr || jsCallback == nullptr)) { + // If called with no data + if (data == nullptr) { + jsCallback.Call({}); + } else { + jsCallback.Call({Number::New(env, *data)}); + } } } using TSFN = ThreadSafeFunctionEx; -static TSFN tsfnEx; +static TSFN tsfn; // Thread data to transmit to JS -static int intsEx[ARRAY_LENGTH]; +static int ints[ARRAY_LENGTH]; -static void SecondaryThreadEx() { - if (tsfnEx.Release() != napi_ok) { +static void SecondaryThread() { + if (tsfn.Release() != napi_ok) { Error::Fatal("SecondaryThread", "ThreadSafeFunction.Release() failed"); } } // Source thread producing the data -static void DataSourceThreadEx() { - ThreadSafeFunctionInfo* info = tsfnEx.GetContext(); +static void DataSourceThread() { + ThreadSafeFunctionInfo* info = tsfn.GetContext(); if (info->startSecondary) { - if (tsfnEx.Acquire() != napi_ok) { + if (tsfn.Acquire() != napi_ok) { Error::Fatal("DataSourceThread", "ThreadSafeFunction.Acquire() failed"); } - threadsEx[1] = std::thread(SecondaryThreadEx); + threads[1] = std::thread(SecondaryThread); } bool queueWasFull = false; @@ -64,13 +67,13 @@ static void DataSourceThreadEx() { switch (info->type) { case ThreadSafeFunctionInfo::DEFAULT: - status = tsfnEx.BlockingCall(); + status = tsfn.BlockingCall(); break; case ThreadSafeFunctionInfo::BLOCKING: - status = tsfnEx.BlockingCall(&intsEx[index]); + status = tsfn.BlockingCall(&ints[index]); break; case ThreadSafeFunctionInfo::NON_BLOCKING: - status = tsfnEx.NonBlockingCall(&intsEx[index]); + status = tsfn.NonBlockingCall(&ints[index]); break; } @@ -108,24 +111,24 @@ static void DataSourceThreadEx() { Error::Fatal("DataSourceThread", "Queue was never closing"); } - if (!queueWasClosing && tsfnEx.Release() != napi_ok) { + if (!queueWasClosing && tsfn.Release() != napi_ok) { Error::Fatal("DataSourceThread", "ThreadSafeFunction.Release() failed"); } } -static Value StopThreadEx(const CallbackInfo& info) { - tsfnInfoEx.jsFinalizeCallback = Napi::Persistent(info[0].As()); +static Value StopThread(const CallbackInfo& info) { + tsfnInfo.jsFinalizeCallback = Napi::Persistent(info[0].As()); bool abort = info[1].As(); if (abort) { - tsfnEx.Abort(); + tsfn.Abort(); } else { - tsfnEx.Release(); + tsfn.Release(); } return Value(); } // Join the thread and inform JS that we're done. -static void JoinTheThreadsEx(Env /* env */, +static void JoinTheThreads(Env /* env */, std::thread* theThreads, ThreadSafeFunctionInfo* info) { theThreads[0].join(); @@ -137,54 +140,54 @@ static void JoinTheThreadsEx(Env /* env */, info->jsFinalizeCallback.Reset(); } -static Value StartThreadInternalEx(const CallbackInfo& info, +static Value StartThreadInternal(const CallbackInfo& info, ThreadSafeFunctionInfo::CallType type) { - tsfnInfoEx.type = type; - tsfnInfoEx.abort = info[1].As(); - tsfnInfoEx.startSecondary = info[2].As(); - tsfnInfoEx.maxQueueSize = info[3].As().Uint32Value(); + tsfnInfo.type = type; + tsfnInfo.abort = info[1].As(); + tsfnInfo.startSecondary = info[2].As(); + tsfnInfo.maxQueueSize = info[3].As().Uint32Value(); - tsfnEx = TSFN::New(info.Env(), info[0].As(), Object::New(info.Env()), - "Test", tsfnInfoEx.maxQueueSize, 2, &tsfnInfoEx, JoinTheThreadsEx, threadsEx); + tsfn = TSFN::New(info.Env(), info[0].As(), Object::New(info.Env()), + "Test", tsfnInfo.maxQueueSize, 2, &tsfnInfo, JoinTheThreads, threads); - threadsEx[0] = std::thread(DataSourceThreadEx); + threads[0] = std::thread(DataSourceThread); return Value(); } -static Value ReleaseEx(const CallbackInfo& /* info */) { - if (tsfnEx.Release() != napi_ok) { +static Value Release(const CallbackInfo& /* info */) { + if (tsfn.Release() != napi_ok) { Error::Fatal("Release", "ThreadSafeFunction.Release() failed"); } return Value(); } -static Value StartThreadEx(const CallbackInfo& info) { - return StartThreadInternalEx(info, ThreadSafeFunctionInfo::BLOCKING); +static Value StartThread(const CallbackInfo& info) { + return StartThreadInternal(info, ThreadSafeFunctionInfo::BLOCKING); } -static Value StartThreadNonblockingEx(const CallbackInfo& info) { - return StartThreadInternalEx(info, ThreadSafeFunctionInfo::NON_BLOCKING); +static Value StartThreadNonblocking(const CallbackInfo& info) { + return StartThreadInternal(info, ThreadSafeFunctionInfo::NON_BLOCKING); } -static Value StartThreadNoNativeEx(const CallbackInfo& info) { - return StartThreadInternalEx(info, ThreadSafeFunctionInfo::DEFAULT); +static Value StartThreadNoNative(const CallbackInfo& info) { + return StartThreadInternal(info, ThreadSafeFunctionInfo::DEFAULT); } Object InitThreadSafeFunctionExThreadSafe(Env env) { for (size_t index = 0; index < ARRAY_LENGTH; index++) { - intsEx[index] = index; + ints[index] = index; } Object exports = Object::New(env); exports["ARRAY_LENGTH"] = Number::New(env, ARRAY_LENGTH); exports["MAX_QUEUE_SIZE"] = Number::New(env, MAX_QUEUE_SIZE); - exports["startThread"] = Function::New(env, StartThreadEx); - exports["startThreadNoNative"] = Function::New(env, StartThreadNoNativeEx); + exports["startThread"] = Function::New(env, StartThread); + exports["startThreadNoNative"] = Function::New(env, StartThreadNoNative); exports["startThreadNonblocking"] = - Function::New(env, StartThreadNonblockingEx); - exports["stopThread"] = Function::New(env, StopThreadEx); - exports["release"] = Function::New(env, ReleaseEx); + Function::New(env, StartThreadNonblocking); + exports["stopThread"] = Function::New(env, StopThread); + exports["release"] = Function::New(env, Release); return exports; } From 8fcdb4d72c44d0a3aba4990b1e46fc7abbec8e8d Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Fri, 12 Jun 2020 22:52:56 +0200 Subject: [PATCH 205/696] test: add barebones new tsfn test for use in docs --- test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 2 + test/threadsafe_function_ex/example.cc | 103 +++++++++++++++++++++++++ test/threadsafe_function_ex/example.js | 20 +++++ 5 files changed, 128 insertions(+) create mode 100644 test/threadsafe_function_ex/example.cc create mode 100644 test/threadsafe_function_ex/example.js diff --git a/test/binding.cc b/test/binding.cc index 4b42f1c33..fd2ab85b0 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -50,6 +50,7 @@ Object InitThreadSafeFunctionUnref(Env env); Object InitThreadSafeFunction(Env env); Object InitThreadSafeFunctionExCall(Env env); Object InitThreadSafeFunctionExContext(Env env); +Object InitThreadSafeFunctionExExample(Env env); Object InitThreadSafeFunctionExSimple(Env env); Object InitThreadSafeFunctionExThreadSafe(Env env); #endif @@ -111,6 +112,7 @@ Object Init(Env env, Object exports) { exports.Set("threadsafe_function", InitThreadSafeFunction(env)); exports.Set("threadsafe_function_ex_call", InitThreadSafeFunctionExCall(env)); exports.Set("threadsafe_function_ex_context", InitThreadSafeFunctionExContext(env)); + exports.Set("threadsafe_function_ex_example", InitThreadSafeFunctionExExample(env)); exports.Set("threadsafe_function_ex_simple", InitThreadSafeFunctionExSimple(env)); exports.Set("threadsafe_function_ex_threadsafe", InitThreadSafeFunctionExThreadSafe(env)); #endif diff --git a/test/binding.gyp b/test/binding.gyp index 72f41fafa..288ad14d5 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -37,6 +37,7 @@ 'run_script.cc', 'threadsafe_function_ex/call.cc', 'threadsafe_function_ex/context.cc', + 'threadsafe_function_ex/example.cc', 'threadsafe_function_ex/simple.cc', 'threadsafe_function_ex/threadsafe.cc', 'threadsafe_function/threadsafe_function_ctx.cc', diff --git a/test/index.js b/test/index.js index 9f5cac225..bc8470a61 100644 --- a/test/index.js +++ b/test/index.js @@ -44,6 +44,7 @@ let testModules = [ 'run_script', 'threadsafe_function_ex/call', 'threadsafe_function_ex/context', + 'threadsafe_function_ex/example', 'threadsafe_function_ex/simple', 'threadsafe_function_ex/threadsafe', 'threadsafe_function/threadsafe_function_ctx', @@ -80,6 +81,7 @@ if (napiVersion < 4) { testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); testModules.splice(testModules.indexOf('threadsafe_function_ex/call'), 1); testModules.splice(testModules.indexOf('threadsafe_function_ex/context'), 1); + testModules.splice(testModules.indexOf('threadsafe_function_ex/example'), 1); testModules.splice(testModules.indexOf('threadsafe_function_ex/simple'), 1); testModules.splice(testModules.indexOf('threadsafe_function_ex/threadsafe'), 1); } diff --git a/test/threadsafe_function_ex/example.cc b/test/threadsafe_function_ex/example.cc new file mode 100644 index 000000000..1445ae688 --- /dev/null +++ b/test/threadsafe_function_ex/example.cc @@ -0,0 +1,103 @@ +/** + * This test is programmatically represents the example shown in + * `doc/threadsafe_function_ex.md` + */ + +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +// Context of our TSFN. +struct Context {}; + +// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall +using DataType = int; + +// Callback function +static void Callback(Napi::Env env, Napi::Function jsCallback, Context *context, + DataType *data) { + // Check that the threadsafe function has not been finalized. Node calls this + // callback for items remaining on the queue once finalization has completed. + if (!(env == nullptr || jsCallback == nullptr)) { + } + if (data != nullptr) { + delete data; + } +} + +// Full type of our ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx; + +// A JS-accessible wrap that holds a TSFN. +class TSFNWrap : public ObjectWrap { + +public: + static Object Init(Napi::Env env, Object exports); + TSFNWrap(const CallbackInfo &info); + +private: + Napi::Value Start(const CallbackInfo &info); + Napi::Value Release(const CallbackInfo &info); +}; + +/** + * @brief Initialize `TSFNWrap` on the environment. + * + * @param env + * @param exports + * @return Object + */ +Object TSFNWrap::Init(Napi::Env env, Object exports) { + Function func = DefineClass(env, "TSFNWrap", + {InstanceMethod("start", &TSFNWrap::Start), + InstanceMethod("release", &TSFNWrap::Release)}); + + exports.Set("TSFNWrap", func); + return exports; +} + +/** + * @brief Construct a new TSFNWrap::TSFNWrap object + * + * @param info + */ +TSFNWrap::TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) {} +} // namespace + +/** + * @brief Instance method `TSFNWrap#start` + * + * @param info + * @return undefined + */ +Napi::Value TSFNWrap::Start(const CallbackInfo &info) { + Napi::Env env = info.Env(); + return env.Undefined(); +}; + +/** + * @brief Instance method `TSFNWrap#release` + * + * @param info + * @return undefined + */ +Napi::Value TSFNWrap::Release(const CallbackInfo &info) { + Napi::Env env = info.Env(); + return env.Undefined(); +}; + +/** + * @brief Module initialization function + * + * @param env + * @return Object + */ +Object InitThreadSafeFunctionExExample(Env env) { + return TSFNWrap::Init(env, Object::New(env)); +} + +#endif diff --git a/test/threadsafe_function_ex/example.js b/test/threadsafe_function_ex/example.js new file mode 100644 index 000000000..836120029 --- /dev/null +++ b/test/threadsafe_function_ex/example.js @@ -0,0 +1,20 @@ +'use strict'; + +/** + * This test is programmatically represents the example shown in + * `doc/threadsafe_function_ex.md` + */ + +const assert = require('assert'); +const buildType = process.config.target_defaults.default_configuration; + +module.exports = Promise.all([ + test(require(`../build/${buildType}/binding.node`)), + test(require(`../build/${buildType}/binding_noexcept.node`)) +]); + +async function test(binding) { + const tsfn = new binding.threadsafe_function_ex_example.TSFNWrap(); + await tsfn.start(); + await tsfn.release(); +} From cc8de121c0abb3994855352398c9a4647a5b1efa Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sat, 13 Jun 2020 06:09:22 +0200 Subject: [PATCH 206/696] implement optional function callback --- common.gypi | 2 +- napi-inl.h | 348 +++++++++++++++++++++++--- napi.h | 179 ++++++++++--- test/threadsafe_function_ex/call.cc | 1 - test/threadsafe_function_ex/simple.cc | 23 +- 5 files changed, 466 insertions(+), 87 deletions(-) diff --git a/common.gypi b/common.gypi index 088f961ea..9e35b384a 100644 --- a/common.gypi +++ b/common.gypi @@ -1,6 +1,6 @@ { 'variables': { - 'NAPI_VERSION%': " class //////////////////////////////////////////////////////////////////////////////// -// static +// Starting with NAPI 4, the JavaScript function `func` parameter of +// `napi_create_threadsafe_function` is optional. +#if NAPI_VERSION > 4 +// static, with Callback [missing] Resource [missing] Finalizer [missing] +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, ResourceString resourceName, size_t maxQueueSize, + size_t initialThreadCount, ContextType *context) { + ThreadSafeFunctionEx tsfn; + + napi_status status = napi_create_threadsafe_function( + env, nullptr, nullptr, String::From(env, resourceName), maxQueueSize, + initialThreadCount, nullptr, nullptr, context, + CallJsInternal, &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; +} + +// static, with Callback [nullptr] Resource [missing] Finalizer [missing] +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, std::nullptr_t callback, ResourceString resourceName, size_t maxQueueSize, + size_t initialThreadCount, ContextType *context) { + ThreadSafeFunctionEx tsfn; + + napi_status status = napi_create_threadsafe_function( + env, nullptr, nullptr, String::From(env, resourceName), maxQueueSize, + initialThreadCount, nullptr, nullptr, context, + CallJsInternal, &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [passed] Finalizer [missing] +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, const Object &resource, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, ContextType *context) { + ThreadSafeFunctionEx tsfn; + + napi_status status = napi_create_threadsafe_function( + env, nullptr, resource, String::From(env, resourceName), maxQueueSize, + initialThreadCount, nullptr, nullptr, context, CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; +} + +// static, with Callback [nullptr] Resource [passed] Finalizer [missing] +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, std::nullptr_t callback, const Object &resource, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, ContextType *context) { + ThreadSafeFunctionEx tsfn; + + napi_status status = napi_create_threadsafe_function( + env, nullptr, resource, String::From(env, resourceName), maxQueueSize, + initialThreadCount, nullptr, nullptr, context, CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [missing] Finalizer [passed] +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, ResourceString resourceName, size_t maxQueueSize, + size_t initialThreadCount, ContextType *context, Finalizer finalizeCallback, + FinalizerDataType *data) { + ThreadSafeFunctionEx tsfn; + + auto *finalizeData = new details::ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = napi_create_threadsafe_function( + env, nullptr, nullptr, String::From(env, resourceName), maxQueueSize, + initialThreadCount, finalizeData, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext, + context, CallJsInternal, &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; +} + +// static, with Callback [nullptr] Resource [missing] Finalizer [passed] +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, std::nullptr_t callback, ResourceString resourceName, size_t maxQueueSize, + size_t initialThreadCount, ContextType *context, Finalizer finalizeCallback, + FinalizerDataType *data) { + ThreadSafeFunctionEx tsfn; + + auto *finalizeData = new details::ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = napi_create_threadsafe_function( + env, nullptr, nullptr, String::From(env, resourceName), maxQueueSize, + initialThreadCount, finalizeData, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext, + context, CallJsInternal, &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [passed] Finalizer [passed] +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, const Object &resource, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, ContextType *context, + Finalizer finalizeCallback, FinalizerDataType *data) { + ThreadSafeFunctionEx tsfn; + + auto *finalizeData = new details::ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = napi_create_threadsafe_function( + env, nullptr, resource, String::From(env, resourceName), maxQueueSize, + initialThreadCount, finalizeData, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext, + context, CallJsInternal, &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; +} + +// static, with Callback [nullptr] Resource [passed] Finalizer [passed] +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, std::nullptr_t callback, const Object &resource, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, ContextType *context, + Finalizer finalizeCallback, FinalizerDataType *data) { + ThreadSafeFunctionEx tsfn; + + auto *finalizeData = new details::ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = napi_create_threadsafe_function( + env, nullptr, resource, String::From(env, resourceName), maxQueueSize, + initialThreadCount, finalizeData, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext, + context, CallJsInternal, &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; +} +#endif + +// static, with Callback [passed] Resource [missing] Finalizer [missing] +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, const Function &callback, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, ContextType *context) { + ThreadSafeFunctionEx tsfn; + + napi_status status = napi_create_threadsafe_function( + env, callback, nullptr, String::From(env, resourceName), maxQueueSize, + initialThreadCount, nullptr, nullptr, context, CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; +} + +// static, with Callback [x] Resource [x] Finalizer [missing] template template @@ -4326,12 +4560,51 @@ ThreadSafeFunctionEx::New( napi_env env, const Function &callback, const Object &resource, ResourceString resourceName, size_t maxQueueSize, size_t initialThreadCount, ContextType *context) { - return New( - env, callback, resource, resourceName, maxQueueSize, initialThreadCount, - context, [](Env, void *, ContextType *) {}, static_cast(nullptr)); + ThreadSafeFunctionEx tsfn; + + napi_status status = napi_create_threadsafe_function( + env, callback, resource, String::From(env, resourceName), maxQueueSize, + initialThreadCount, nullptr, nullptr, context, CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; } -// static +// static, with Callback [x] Resource [missing ] Finalizer [x] +template +template +inline ThreadSafeFunctionEx +ThreadSafeFunctionEx::New( + napi_env env, const Function &callback, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, ContextType *context, + Finalizer finalizeCallback, FinalizerDataType *data) { + ThreadSafeFunctionEx tsfn; + + auto *finalizeData = new details::ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = napi_create_threadsafe_function( + env, callback, nullptr, String::From(env, resourceName), maxQueueSize, + initialThreadCount, finalizeData, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext, + context, CallJsInternal, &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; +} + +// static, with: Callback [x] Resource [x] Finalizer [x] template template ::New( napi_env env, const Function &callback, const Object &resource, ResourceString resourceName, size_t maxQueueSize, size_t initialThreadCount, ContextType *context, Finalizer finalizeCallback, FinalizerDataType *data) { - return New( - env, callback, resource, resourceName, maxQueueSize, initialThreadCount, - context, finalizeCallback, data, + ThreadSafeFunctionEx tsfn; + + auto *finalizeData = new details::ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = napi_create_threadsafe_function( + env, callback, resource, String::From(env, resourceName), maxQueueSize, + initialThreadCount, finalizeData, details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext); + FinalizeFinalizeWrapperWithDataAndContext, + context, CallJsInternal, &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, + ThreadSafeFunctionEx()); + } + + return tsfn; } template ::GetContext() const { // static template -template -inline ThreadSafeFunctionEx -ThreadSafeFunctionEx::New( - napi_env env, const Function &callback, const Object &resource, - ResourceString resourceName, size_t maxQueueSize, size_t initialThreadCount, - ContextType *context, Finalizer finalizeCallback, FinalizerDataType *data, - napi_finalize wrapper) { - static_assert(details::can_make_string::value || - std::is_convertible::value, - "Resource name should be convertible to the string type"); - - ThreadSafeFunctionEx tsfn; - - auto *finalizeData = new details::ThreadSafeFinalize( - {data, finalizeCallback}); - napi_status status = napi_create_threadsafe_function( - env, callback, resource, Value::From(env, resourceName), maxQueueSize, - initialThreadCount, finalizeData, wrapper, context, CallJsInternal, - &tsfn._tsfn); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, - ThreadSafeFunctionEx()); - } - - return tsfn; +void ThreadSafeFunctionEx::CallJsInternal( + napi_env env, napi_value jsCallback, void *context, void *data) { + details::CallJsWrapper( + env, jsCallback, context, data); } // static template -void ThreadSafeFunctionEx::CallJsInternal( - napi_env env, napi_value jsCallback, void *context, void *data) { - details::CallJsWrapper( - env, jsCallback, context, data); +typename ThreadSafeFunctionEx::DefaultFunctionType +ThreadSafeFunctionEx::DefaultFunctionFactory( + Napi::Env env) { +#if NAPI_VERSION > 4 + return nullptr; +#else + return Function::New(env, [](const CallbackInfo &cb) {}); +#endif } //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index eed310432..1fb7178f8 100644 --- a/napi.h +++ b/napi.h @@ -2043,42 +2043,151 @@ namespace Napi { }; #if (NAPI_VERSION > 3) - template + template class ThreadSafeFunctionEx { + private: +#if NAPI_VERSION > 4 + using DefaultFunctionType = std::nullptr_t; +#else + using DefaultFunctionType = const Napi::Function; +#endif + public: + // This API may only be called from the main thread. + // Helper function that returns nullptr if running N-API 5+, otherwise a + // non-empty, no-op Function. This provides the ability to specify at + // compile-time a callback parameter to `New` that safely does no action. + static DefaultFunctionType DefaultFunctionFactory(Napi::Env env); +#if NAPI_VERSION > 4 // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [missing] Finalizer [missing] template - static ThreadSafeFunctionEx New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context = nullptr); + static ThreadSafeFunctionEx + New(napi_env env, ResourceString resourceName, size_t maxQueueSize, + size_t initialThreadCount, ContextType *context = nullptr); // This API may only be called from the main thread. - template - static ThreadSafeFunctionEx New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data = nullptr); + // Callback [nullptr] Resource [missing] Finalizer [missing] + template + static ThreadSafeFunctionEx + New(napi_env env, std::nullptr_t callback, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, + ContextType *context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [passed] Finalizer [missing] + template + static ThreadSafeFunctionEx + New(napi_env env, const Object &resource, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, + ContextType *context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [nullptr] Resource [passed] Finalizer [missing] + template + static ThreadSafeFunctionEx + New(napi_env env, std::nullptr_t callback, const Object &resource, + ResourceString resourceName, size_t maxQueueSize, + size_t initialThreadCount, ContextType *context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [missing] Finalizer [passed] + template + static ThreadSafeFunctionEx + New(napi_env env, ResourceString resourceName, size_t maxQueueSize, + size_t initialThreadCount, ContextType *context, + Finalizer finalizeCallback, FinalizerDataType *data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [nullptr] Resource [missing] Finalizer [passed] + template + static ThreadSafeFunctionEx + New(napi_env env, std::nullptr_t callback, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, ContextType *context, + Finalizer finalizeCallback, FinalizerDataType *data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [passed] Finalizer [passed] + template + static ThreadSafeFunctionEx + New(napi_env env, const Object &resource, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, ContextType *context, + Finalizer finalizeCallback, FinalizerDataType *data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [nullptr] Resource [passed] Finalizer [passed] + template + static ThreadSafeFunctionEx + New(napi_env env, std::nullptr_t callback, const Object &resource, + ResourceString resourceName, size_t maxQueueSize, + size_t initialThreadCount, ContextType *context, + Finalizer finalizeCallback, FinalizerDataType *data = nullptr); +#endif + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [missing] Finalizer [missing] + template + static ThreadSafeFunctionEx + New(napi_env env, const Function &callback, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, + ContextType *context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [passed] Finalizer [missing] + template + static ThreadSafeFunctionEx + New(napi_env env, const Function &callback, const Object &resource, + ResourceString resourceName, size_t maxQueueSize, + size_t initialThreadCount, ContextType *context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [missing] Finalizer [passed] + template + static ThreadSafeFunctionEx + New(napi_env env, const Function &callback, ResourceString resourceName, + size_t maxQueueSize, size_t initialThreadCount, ContextType *context, + Finalizer finalizeCallback, FinalizerDataType *data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [passed] Finalizer [passed] + template + static ThreadSafeFunctionEx + New(napi_env env, const Function &callback, const Object &resource, + ResourceString resourceName, size_t maxQueueSize, + size_t initialThreadCount, ContextType *context, + Finalizer finalizeCallback, FinalizerDataType *data = nullptr); ThreadSafeFunctionEx(); - ThreadSafeFunctionEx(napi_threadsafe_function tsFunctionValue); + ThreadSafeFunctionEx( + napi_threadsafe_function tsFunctionValue); operator napi_threadsafe_function() const; // This API may be called from any thread. - napi_status BlockingCall(DataType* data = nullptr) const; + napi_status BlockingCall(DataType *data = nullptr) const; // This API may be called from any thread. - napi_status NonBlockingCall(DataType* data = nullptr) const; + napi_status NonBlockingCall(DataType *data = nullptr) const; // This API may only be called from the main thread. void Ref(napi_env env) const; @@ -2096,27 +2205,21 @@ namespace Napi { napi_status Abort(); // This API may be called from any thread. - ContextType* GetContext() const; + ContextType *GetContext() const; private: + template + static ThreadSafeFunctionEx + New(napi_env env, const Function &callback, const Object &resource, + ResourceString resourceName, size_t maxQueueSize, + size_t initialThreadCount, ContextType *context, + Finalizer finalizeCallback, FinalizerDataType *data, + napi_finalize wrapper); - template - static ThreadSafeFunctionEx New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context, - Finalizer finalizeCallback, - FinalizerDataType* data, - napi_finalize wrapper); + static void CallJsInternal(napi_env env, napi_value jsCallback, + void *context, void *data); - static void CallJsInternal(napi_env env, - napi_value jsCallback, - void* context, - void* data); protected: napi_threadsafe_function _tsfn; }; diff --git a/test/threadsafe_function_ex/call.cc b/test/threadsafe_function_ex/call.cc index 2e41799b8..7d799a93d 100644 --- a/test/threadsafe_function_ex/call.cc +++ b/test/threadsafe_function_ex/call.cc @@ -74,7 +74,6 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info) _tsfn = TSFN::New(env, // napi_env env, callback, // const Function& callback, - Value(), // const Object& resource, "Test", // ResourceString resourceName, 0, // size_t maxQueueSize, 1 // size_t initialThreadCount, diff --git a/test/threadsafe_function_ex/simple.cc b/test/threadsafe_function_ex/simple.cc index 40cf20d3e..961facf14 100644 --- a/test/threadsafe_function_ex/simple.cc +++ b/test/threadsafe_function_ex/simple.cc @@ -43,13 +43,24 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info) : ObjectWrap(info), _deferred(Promise::Deferred::New(info.Env())) { - _tsfn = TSFN::New(info.Env(), // napi_env env, - Function(), // const Function& callback, - Value(), // const Object& resource, - "Test", // ResourceString resourceName, - 1, // size_t maxQueueSize, - 1 // size_t initialThreadCount + auto env = info.Env(); +#if NAPI_VERSION == 4 + // A threadsafe function on N-API 4 still requires a callback function. + _tsfn = + TSFN::New(env, // napi_env env, + TSFN::DefaultFunctionFactory( + env), // N-API 5+: nullptr; else: const Function& callback, + "Test", // ResourceString resourceName, + 1, // size_t maxQueueSize, + 1 // size_t initialThreadCount + ); +#else + _tsfn = TSFN::New(env, // napi_env env, + "Test", // ResourceString resourceName, + 1, // size_t maxQueueSize, + 1 // size_t initialThreadCount ); +#endif } } // namespace From 6706f969023660c0e790bcf063be7385b3065bd3 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sat, 13 Jun 2020 12:26:01 +0200 Subject: [PATCH 207/696] clean up optional callback implementation --- test/threadsafe_function_ex/call.js | 9 + test/threadsafe_function_ex/context.cc | 4 +- test/threadsafe_function_ex/context.js | 10 ++ test/threadsafe_function_ex/simple.cc | 190 +++++++++++++++++++++- test/threadsafe_function_ex/simple.js | 52 +++++- test/threadsafe_function_ex/threadsafe.js | 3 + 6 files changed, 259 insertions(+), 9 deletions(-) diff --git a/test/threadsafe_function_ex/call.js b/test/threadsafe_function_ex/call.js index 152637548..344369370 100644 --- a/test/threadsafe_function_ex/call.js +++ b/test/threadsafe_function_ex/call.js @@ -8,6 +8,15 @@ module.exports = Promise.all([ test(require(`../build/${buildType}/binding_noexcept.node`)) ]); +/** + * This test ensures the data sent to the NonBlockingCall and the data received + * in the JavaScript callback are the same. + * - Creates a contexted threadsafe function with callback. + * - Makes one call, and waits for call to complete. + * - The callback forwards the item's data to the given JavaScript function in + * the test. + * - Asserts the data is the same. + */ async function test(binding) { const data = {}; const tsfn = new binding.threadsafe_function_ex_call.TSFNWrap(tsfnData => { diff --git a/test/threadsafe_function_ex/context.cc b/test/threadsafe_function_ex/context.cc index c67ebaba2..1170eb913 100644 --- a/test/threadsafe_function_ex/context.cc +++ b/test/threadsafe_function_ex/context.cc @@ -36,8 +36,8 @@ class TSFNWrap : public ObjectWrap { Napi::Value GetContextByCall(const CallbackInfo &info) { Napi::Env env = info.Env(); - auto* callData = new TSFNData(env); - _tsfn.NonBlockingCall( callData ); + auto *callData = new TSFNData(env); + _tsfn.NonBlockingCall(callData); return callData->Promise(); }; diff --git a/test/threadsafe_function_ex/context.js b/test/threadsafe_function_ex/context.js index 3e068cabd..95e3b8914 100644 --- a/test/threadsafe_function_ex/context.js +++ b/test/threadsafe_function_ex/context.js @@ -8,6 +8,16 @@ module.exports = Promise.all([ test(require(`../build/${buildType}/binding_noexcept.node`)) ]); +/** + * The context provided to the threadsafe function's constructor is accessible + * on both the threadsafe function's callback as well the threadsafe function + * itself. This test ensures the context across all three are the same. + * - Creates a contexted threadsafe function with callback. + * - The callback forwards the item's data to the given JavaScript function in + * the test. + * - Makes one call, and waits for call to complete. + * - Asserts the contexts are the same. + */ async function test(binding) { const ctx = {}; const tsfn = new binding.threadsafe_function_ex_context.TSFNWrap(ctx); diff --git a/test/threadsafe_function_ex/simple.cc b/test/threadsafe_function_ex/simple.cc index 961facf14..fb62f756b 100644 --- a/test/threadsafe_function_ex/simple.cc +++ b/test/threadsafe_function_ex/simple.cc @@ -4,7 +4,7 @@ using namespace Napi; -namespace { +namespace simple { // Full type of our ThreadSafeFunctionEx using TSFN = ThreadSafeFunctionEx<>; @@ -62,10 +62,194 @@ TSFNWrap::TSFNWrap(const CallbackInfo &info) ); #endif } -} // namespace +} // namespace simple +namespace existing { + +struct DataType { + Promise::Deferred deferred; + bool reject; +}; + +// CallJs callback function provided to `napi_create_threadsafe_function`. It is +// _NOT_ used by `Napi::ThreadSafeFunctionEx<>`. +static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, + void *data) { + DataType *casted = static_cast(data); + if (env != nullptr) { + if (data != nullptr) { + napi_value undefined; + napi_status status = napi_get_undefined(env, &undefined); + NAPI_THROW_IF_FAILED(env, status); + if (casted->reject) { + casted->deferred.Reject(undefined); + } else { + casted->deferred.Resolve(undefined); + } + } + } + if (casted != nullptr) { + delete casted; + } +} + +// Full type of our ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx; + +// A JS-accessible wrap that holds a TSFN. +class ExistingTSFNWrap : public ObjectWrap { +public: + static Object Init(Napi::Env env, Object exports); + ExistingTSFNWrap(const CallbackInfo &info); + + Napi::Value Release(const CallbackInfo &) { + _tsfn.Release(); + return _deferred.Promise(); + }; + + Napi::Value Call(const CallbackInfo &info) { + auto *data = + new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; + _tsfn.NonBlockingCall(data); + return data->deferred.Promise(); + }; + +private: + TSFN _tsfn; + Promise::Deferred _deferred; +}; + +Object ExistingTSFNWrap::Init(Napi::Env env, Object exports) { + Function func = + DefineClass(env, "ExistingTSFNWrap", + {InstanceMethod("call", &ExistingTSFNWrap::Call), + InstanceMethod("release", &ExistingTSFNWrap::Release)}); + + exports.Set("ExistingTSFNWrap", func); + return exports; +} + +ExistingTSFNWrap::ExistingTSFNWrap(const CallbackInfo &info) + : ObjectWrap(info), + _deferred(Promise::Deferred::New(info.Env())) { + + auto env = info.Env(); +#if NAPI_VERSION == 4 + napi_threadsafe_function napi_tsfn; + auto status = napi_create_threadsafe_function( + info.Env(), TSFN::DefaultFunctionFactory(env), nullptr, + String::From(info.Env(), "Test"), 0, 1, nullptr, nullptr, nullptr, CallJs, + &napi_tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED(env, status); + } + // A threadsafe function on N-API 4 still requires a callback function. + _tsfn = TSFN(napi_tsfn); +#else + napi_threadsafe_function napi_tsfn; + auto status = napi_create_threadsafe_function( + info.Env(), nullptr, nullptr, String::From(info.Env(), "Test"), 0, 1, + nullptr, nullptr, nullptr, CallJs, &napi_tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED(env, status); + } + _tsfn = TSFN(napi_tsfn); +#endif +} +} // namespace existing + +namespace empty { +#if NAPI_VERSION > 4 + +using Context = void; + +struct DataType { + Promise::Deferred deferred; + bool reject; +}; + +// CallJs callback function +static void CallJs(Napi::Env env, Function jsCallback, Context * /*context*/, + DataType *data) { + if (env != nullptr) { + if (data != nullptr) { + if (data->reject) { + data->deferred.Reject(env.Undefined()); + } else { + data->deferred.Resolve(env.Undefined()); + } + } + } + if (data != nullptr) { + delete data; + } +} + +// Full type of our ThreadSafeFunctionEx +using EmptyTSFN = ThreadSafeFunctionEx; + +// A JS-accessible wrap that holds a TSFN. +class EmptyTSFNWrap : public ObjectWrap { +public: + static Object Init(Napi::Env env, Object exports); + EmptyTSFNWrap(const CallbackInfo &info); + + Napi::Value Release(const CallbackInfo &) { + _tsfn.Release(); + return _deferred.Promise(); + }; + + Napi::Value Call(const CallbackInfo &info) { + if (info.Length() == 0 || !info[0].IsBoolean()) { + NAPI_THROW( + Napi::TypeError::New(info.Env(), "Expected argument 0 to be boolean"), + Value()); + } + + auto *data = + new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; + _tsfn.NonBlockingCall(data); + return data->deferred.Promise(); + }; + +private: + EmptyTSFN _tsfn; + Promise::Deferred _deferred; +}; + +Object EmptyTSFNWrap::Init(Napi::Env env, Object exports) { + Function func = + DefineClass(env, "EmptyTSFNWrap", + {InstanceMethod("call", &EmptyTSFNWrap::Call), + InstanceMethod("release", &EmptyTSFNWrap::Release)}); + + exports.Set("EmptyTSFNWrap", func); + return exports; +} + +EmptyTSFNWrap::EmptyTSFNWrap(const CallbackInfo &info) + : ObjectWrap(info), + _deferred(Promise::Deferred::New(info.Env())) { + + auto env = info.Env(); + _tsfn = EmptyTSFN::New(env, // napi_env env, + "Test", // ResourceString resourceName, + 0, // size_t maxQueueSize, + 1 // size_t initialThreadCount + ); +} +#endif +} // namespace empty Object InitThreadSafeFunctionExSimple(Env env) { - return TSFNWrap::Init(env, Object::New(env)); + +#if NAPI_VERSION > 4 + return empty::EmptyTSFNWrap::Init( + env, existing::ExistingTSFNWrap::Init( + env, simple::TSFNWrap::Init(env, Object::New(env)))); +#else + return existing::ExistingTSFNWrap::Init( + env, simple::TSFNWrap::Init(env, Object::New(env))); +#endif } #endif diff --git a/test/threadsafe_function_ex/simple.js b/test/threadsafe_function_ex/simple.js index ece0929e7..b3a6b0cf5 100644 --- a/test/threadsafe_function_ex/simple.js +++ b/test/threadsafe_function_ex/simple.js @@ -1,15 +1,59 @@ 'use strict'; +const assert = require('assert'); const buildType = process.config.target_defaults.default_configuration; module.exports = Promise.all([ test(require(`../build/${buildType}/binding.node`)), test(require(`../build/${buildType}/binding_noexcept.node`)) -]); +].reduce((p, c) => p.concat(c)), []); -async function test(binding) { - const ctx = {}; - const tsfn = new binding.threadsafe_function_ex_simple.TSFNWrap(ctx); +function test(binding) { + return [ + // testSimple(binding), + testEmpty(binding) + ]; +} + +/** + * A simple, fire-and-forget test. + * - Creates a threadsafe function with no context or callback. + * - The node-addon-api 'no callback' feature is implemented by passing either a + * no-op `Function` on N-API 4 or `std::nullptr` on N-API 5+ to the underlying + * `napi_create_threadsafe_function` call. + * - Makes one call, releases, then waits for finalization. + * - Inherently ignores the state of the item once it has been added to the + * queue. Since there are no callbacks or context, it is impossible to capture + * the state. + */ +async function testSimple(binding) { + const tsfn = new binding.threadsafe_function_ex_simple.TSFNWrap(); tsfn.call(); await tsfn.release(); } + +/** + * **ONLY ON N-API 5+**. The optional JavaScript function callback feature is + * not available in N-API <= 4. + * - Creates a threadsafe function with no JavaScript context or callback. + * - Makes two calls, expecting the first to resolve and the second to reject. + * - Waits for Node to process the items on the queue prior releasing the + * threadsafe function. + */ +async function testEmpty(binding) { + const { EmptyTSFNWrap } = binding.threadsafe_function_ex_simple; + + if (typeof EmptyTSFNWrap === 'function') { + const tsfn = new binding.threadsafe_function_ex_simple.EmptyTSFNWrap(); + await tsfn.call(false /* reject */); + let caught = false; + try { + await tsfn.call(true /* reject */); + } catch (ex) { + caught = true; + } + + assert.ok(caught, 'The promise rejection was not caught'); + await tsfn.release(); + } +} diff --git a/test/threadsafe_function_ex/threadsafe.js b/test/threadsafe_function_ex/threadsafe.js index 2649d580d..f9789db58 100644 --- a/test/threadsafe_function_ex/threadsafe.js +++ b/test/threadsafe_function_ex/threadsafe.js @@ -7,6 +7,9 @@ const common = require('../common'); test(require(`../build/${buildType}/binding.node`)); test(require(`../build/${buildType}/binding_noexcept.node`)); +/** + * This spec replicates the non-`Ex` multi-threaded spec using the `Ex` API. + */ function test(binding) { const expectedArray = (function(arrayLength) { const result = []; From 89d2dea08eae53e8d130028fc7863d22f4d06148 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 14 Jun 2020 14:19:14 +0200 Subject: [PATCH 208/696] test: wip with tsfnex tests --- test/binding.cc | 8 +- test/binding.gyp | 4 +- test/index.js | 66 +-- test/threadsafe_function_ex/README.md | 5 + test/threadsafe_function_ex/call.cc | 88 ---- test/threadsafe_function_ex/call.js | 27 -- test/threadsafe_function_ex/context.cc | 101 ---- test/threadsafe_function_ex/context.js | 27 -- test/threadsafe_function_ex/example.cc | 103 ----- test/threadsafe_function_ex/example.js | 20 - test/threadsafe_function_ex/index.js | 5 + test/threadsafe_function_ex/simple.cc | 255 ----------- test/threadsafe_function_ex/simple.js | 59 --- test/threadsafe_function_ex/test/basic.cc | 432 ++++++++++++++++++ test/threadsafe_function_ex/test/basic.js | 188 ++++++++ test/threadsafe_function_ex/test/example.cc | 257 +++++++++++ test/threadsafe_function_ex/test/example.js | 44 ++ .../{ => test}/threadsafe.cc | 1 + .../{ => test}/threadsafe.js | 19 +- 19 files changed, 982 insertions(+), 727 deletions(-) create mode 100644 test/threadsafe_function_ex/README.md delete mode 100644 test/threadsafe_function_ex/call.cc delete mode 100644 test/threadsafe_function_ex/call.js delete mode 100644 test/threadsafe_function_ex/context.cc delete mode 100644 test/threadsafe_function_ex/context.js delete mode 100644 test/threadsafe_function_ex/example.cc delete mode 100644 test/threadsafe_function_ex/example.js create mode 100644 test/threadsafe_function_ex/index.js delete mode 100644 test/threadsafe_function_ex/simple.cc delete mode 100644 test/threadsafe_function_ex/simple.js create mode 100644 test/threadsafe_function_ex/test/basic.cc create mode 100644 test/threadsafe_function_ex/test/basic.js create mode 100644 test/threadsafe_function_ex/test/example.cc create mode 100644 test/threadsafe_function_ex/test/example.js rename test/threadsafe_function_ex/{ => test}/threadsafe.cc (99%) rename test/threadsafe_function_ex/{ => test}/threadsafe.js (92%) diff --git a/test/binding.cc b/test/binding.cc index fd2ab85b0..d471831d6 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -48,10 +48,8 @@ Object InitThreadSafeFunctionPtr(Env env); Object InitThreadSafeFunctionSum(Env env); Object InitThreadSafeFunctionUnref(Env env); Object InitThreadSafeFunction(Env env); -Object InitThreadSafeFunctionExCall(Env env); -Object InitThreadSafeFunctionExContext(Env env); +Object InitThreadSafeFunctionExBasic(Env env); Object InitThreadSafeFunctionExExample(Env env); -Object InitThreadSafeFunctionExSimple(Env env); Object InitThreadSafeFunctionExThreadSafe(Env env); #endif Object InitTypedArray(Env env); @@ -110,10 +108,8 @@ Object Init(Env env, Object exports) { exports.Set("threadsafe_function_sum", InitThreadSafeFunctionSum(env)); exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env)); exports.Set("threadsafe_function", InitThreadSafeFunction(env)); - exports.Set("threadsafe_function_ex_call", InitThreadSafeFunctionExCall(env)); - exports.Set("threadsafe_function_ex_context", InitThreadSafeFunctionExContext(env)); + exports.Set("threadsafe_function_ex_basic", InitThreadSafeFunctionExBasic(env)); exports.Set("threadsafe_function_ex_example", InitThreadSafeFunctionExExample(env)); - exports.Set("threadsafe_function_ex_simple", InitThreadSafeFunctionExSimple(env)); exports.Set("threadsafe_function_ex_threadsafe", InitThreadSafeFunctionExThreadSafe(env)); #endif exports.Set("typedarray", InitTypedArray(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 288ad14d5..923cb2f55 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -35,10 +35,8 @@ 'object/set_property.cc', 'promise.cc', 'run_script.cc', - 'threadsafe_function_ex/call.cc', - 'threadsafe_function_ex/context.cc', + 'threadsafe_function_ex/basic.cc', 'threadsafe_function_ex/example.cc', - 'threadsafe_function_ex/simple.cc', 'threadsafe_function_ex/threadsafe.cc', 'threadsafe_function/threadsafe_function_ctx.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', diff --git a/test/index.js b/test/index.js index bc8470a61..a719c8d31 100644 --- a/test/index.js +++ b/test/index.js @@ -42,11 +42,7 @@ let testModules = [ 'object/set_property', 'promise', 'run_script', - 'threadsafe_function_ex/call', - 'threadsafe_function_ex/context', - 'threadsafe_function_ex/example', - 'threadsafe_function_ex/simple', - 'threadsafe_function_ex/threadsafe', + 'threadsafe_function_ex', 'threadsafe_function/threadsafe_function_ctx', 'threadsafe_function/threadsafe_function_existing_tsfn', 'threadsafe_function/threadsafe_function_ptr', @@ -79,10 +75,8 @@ if (napiVersion < 4) { testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_sum'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_unref'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); - testModules.splice(testModules.indexOf('threadsafe_function_ex/call'), 1); - testModules.splice(testModules.indexOf('threadsafe_function_ex/context'), 1); + testModules.splice(testModules.indexOf('threadsafe_function_ex/basic'), 1); testModules.splice(testModules.indexOf('threadsafe_function_ex/example'), 1); - testModules.splice(testModules.indexOf('threadsafe_function_ex/simple'), 1); testModules.splice(testModules.indexOf('threadsafe_function_ex/threadsafe'), 1); } @@ -96,35 +90,41 @@ if (napiVersion < 6) { testModules.splice(testModules.indexOf('addon_data'), 1); } -if (typeof global.gc === 'function') { - console.log(`Testing with N-API Version '${napiVersion}'.`); +async function run() { + if (typeof global.gc === 'function') { + console.log(`Testing with N-API Version '${napiVersion}'.`); - console.log('Starting test suite\n'); + console.log('Starting test suite\n'); - // Requiring each module runs tests in the module. - testModules.forEach(name => { - console.log(`Running test '${name}'`); - require('./' + name); - }); + // Requiring each module runs tests in the module. + testModules.forEach(name => { + console.log(`Running test '${name}'`); + require('./' + name); + }); - console.log('\nAll tests passed!'); -} else { - // Construct the correct (version-dependent) command-line args. - let args = ['--expose-gc', '--no-concurrent-array-buffer-freeing']; - if (majorNodeVersion >= 14) { - args.push('--no-concurrent-array-buffer-sweeping'); - } - args.push(__filename); + console.log('\nAll tests passed!'); + } else { + // Construct the correct (version-dependent) command-line args. + let args = ['--expose-gc', '--no-concurrent-array-buffer-freeing']; + if (majorNodeVersion >= 14) { + args.push('--no-concurrent-array-buffer-sweeping'); + } + args.push(__filename); - const child = require('./napi_child').spawnSync(process.argv[0], args, { - stdio: 'inherit', - }); + const child = require('./napi_child').spawnSync(process.argv[0], args, { + stdio: 'inherit', + }); - if (child.signal) { - console.error(`Tests aborted with ${child.signal}`); - process.exitCode = 1; - } else { - process.exitCode = child.status; + if (child.signal) { + console.error(`Tests aborted with ${child.signal}`); + process.exitCode = 1; + } else { + process.exitCode = child.status; + } + process.exit(process.exitCode); } - process.exit(process.exitCode); } + +run() + .catch(e => (console.error(e), process.exit(1))); + diff --git a/test/threadsafe_function_ex/README.md b/test/threadsafe_function_ex/README.md new file mode 100644 index 000000000..139838ae5 --- /dev/null +++ b/test/threadsafe_function_ex/README.md @@ -0,0 +1,5 @@ +# Napi::ThreadSafeFunctionEx tests + +|Spec|Test|Native|Node|Description| +|----|---|---|---|---| +|call \ No newline at end of file diff --git a/test/threadsafe_function_ex/call.cc b/test/threadsafe_function_ex/call.cc deleted file mode 100644 index 7d799a93d..000000000 --- a/test/threadsafe_function_ex/call.cc +++ /dev/null @@ -1,88 +0,0 @@ -#include "napi.h" - -#if (NAPI_VERSION > 3) - -using namespace Napi; - -namespace { - -// Context of our TSFN. -using TSFNContext = void; - -// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall -struct TSFNData { - Reference data; - Promise::Deferred deferred; -}; - -// CallJs callback function -static void CallJs(Napi::Env env, Napi::Function jsCallback, - TSFNContext * /*context*/, TSFNData *data) { - if (!(env == nullptr || jsCallback == nullptr)) { - if (data != nullptr) { - jsCallback.Call(env.Undefined(), {data->data.Value()}); - data->deferred.Resolve(data->data.Value()); - } - } - if (data != nullptr) { - delete data; - } -} - -// Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; - -// A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public ObjectWrap { -public: - static Object Init(Napi::Env env, Object exports); - TSFNWrap(const CallbackInfo &info); - - Napi::Value DoCall(const CallbackInfo &info) { - Napi::Env env = info.Env(); - TSFNData *data = - new TSFNData{Napi::Reference(Persistent(info[0])), - Promise::Deferred::New(env)}; - _tsfn.NonBlockingCall(data); - return data->deferred.Promise(); - }; - - Napi::Value Release(const CallbackInfo &) { - _tsfn.Release(); - return _deferred.Promise(); - }; - -private: - TSFN _tsfn; - Promise::Deferred _deferred; -}; - -Object TSFNWrap::Init(Napi::Env env, Object exports) { - Function func = DefineClass(env, "TSFNWrap", - {InstanceMethod("doCall", &TSFNWrap::DoCall), - InstanceMethod("release", &TSFNWrap::Release)}); - - exports.Set("TSFNWrap", func); - return exports; -} - -TSFNWrap::TSFNWrap(const CallbackInfo &info) - : ObjectWrap(info), - _deferred(Promise::Deferred::New(info.Env())) { - Napi::Env env = info.Env(); - Function callback = info[0].As(); - - _tsfn = TSFN::New(env, // napi_env env, - callback, // const Function& callback, - "Test", // ResourceString resourceName, - 0, // size_t maxQueueSize, - 1 // size_t initialThreadCount, - ); -} -} // namespace - -Object InitThreadSafeFunctionExCall(Env env) { - return TSFNWrap::Init(env, Object::New(env)); -} - -#endif diff --git a/test/threadsafe_function_ex/call.js b/test/threadsafe_function_ex/call.js deleted file mode 100644 index 344369370..000000000 --- a/test/threadsafe_function_ex/call.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; - -module.exports = Promise.all([ - test(require(`../build/${buildType}/binding.node`)), - test(require(`../build/${buildType}/binding_noexcept.node`)) -]); - -/** - * This test ensures the data sent to the NonBlockingCall and the data received - * in the JavaScript callback are the same. - * - Creates a contexted threadsafe function with callback. - * - Makes one call, and waits for call to complete. - * - The callback forwards the item's data to the given JavaScript function in - * the test. - * - Asserts the data is the same. - */ -async function test(binding) { - const data = {}; - const tsfn = new binding.threadsafe_function_ex_call.TSFNWrap(tsfnData => { - assert(data === tsfnData, "Data in and out of tsfn call do not equal"); - }); - await tsfn.doCall(data); - await tsfn.release(); -} diff --git a/test/threadsafe_function_ex/context.cc b/test/threadsafe_function_ex/context.cc deleted file mode 100644 index 1170eb913..000000000 --- a/test/threadsafe_function_ex/context.cc +++ /dev/null @@ -1,101 +0,0 @@ -#include "napi.h" - -#if (NAPI_VERSION > 3) - -using namespace Napi; - -namespace { - -// Context of our TSFN. -using TSFNContext = Reference; - -// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall -using TSFNData = Promise::Deferred; - -// CallJs callback function -static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, - TSFNContext *context, TSFNData *data) { - if (env != nullptr) { - if (data != nullptr) { - data->Resolve(context->Value()); - } - } - if (data != nullptr) { - delete data; - } -} - -// Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; - -// A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public ObjectWrap { -public: - static Object Init(Napi::Env env, Object exports); - TSFNWrap(const CallbackInfo &info); - - Napi::Value GetContextByCall(const CallbackInfo &info) { - Napi::Env env = info.Env(); - auto *callData = new TSFNData(env); - _tsfn.NonBlockingCall(callData); - return callData->Promise(); - }; - - Napi::Value GetContextFromTsfn(const CallbackInfo &) { - return _tsfn.GetContext()->Value(); - }; - - Napi::Value Release(const CallbackInfo &) { - _tsfn.Release(); - return _deferred.Promise(); - }; - -private: - TSFN _tsfn; - Promise::Deferred _deferred; -}; - -Object TSFNWrap::Init(Napi::Env env, Object exports) { - Function func = DefineClass( - env, "TSFNWrap", - {InstanceMethod("getContextByCall", &TSFNWrap::GetContextByCall), - InstanceMethod("getContextFromTsfn", &TSFNWrap::GetContextFromTsfn), - InstanceMethod("release", &TSFNWrap::Release)}); - - exports.Set("TSFNWrap", func); - return exports; -} - -TSFNWrap::TSFNWrap(const CallbackInfo &info) - : ObjectWrap(info), - _deferred(Promise::Deferred::New(info.Env())) { - Napi::Env env = info.Env(); - - TSFNContext *context = new TSFNContext(Persistent(info[0])); - - _tsfn = TSFN::New( - info.Env(), // napi_env env, - Function::New( - env, - [](const CallbackInfo & /*info*/) {}), // const Function& callback, - Value(), // const Object& resource, - "Test", // ResourceString resourceName, - 1, // size_t maxQueueSize, - 1, // size_t initialThreadCount, - context, // ContextType* context, - - [this](Napi::Env env, void *, - TSFNContext *ctx) { // Finalizer finalizeCallback, - _deferred.Resolve(env.Undefined()); - delete ctx; - }, - static_cast(nullptr) // FinalizerDataType* data, - ); -} -} // namespace - -Object InitThreadSafeFunctionExContext(Env env) { - return TSFNWrap::Init(env, Object::New(env)); -} - -#endif diff --git a/test/threadsafe_function_ex/context.js b/test/threadsafe_function_ex/context.js deleted file mode 100644 index 95e3b8914..000000000 --- a/test/threadsafe_function_ex/context.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; - -module.exports = Promise.all([ - test(require(`../build/${buildType}/binding.node`)), - test(require(`../build/${buildType}/binding_noexcept.node`)) -]); - -/** - * The context provided to the threadsafe function's constructor is accessible - * on both the threadsafe function's callback as well the threadsafe function - * itself. This test ensures the context across all three are the same. - * - Creates a contexted threadsafe function with callback. - * - The callback forwards the item's data to the given JavaScript function in - * the test. - * - Makes one call, and waits for call to complete. - * - Asserts the contexts are the same. - */ -async function test(binding) { - const ctx = {}; - const tsfn = new binding.threadsafe_function_ex_context.TSFNWrap(ctx); - assert(ctx === await tsfn.getContextByCall(), "getContextByCall context not equal"); - assert(ctx === tsfn.getContextFromTsfn(), "getContextFromTsfn context not equal"); - await tsfn.release(); -} diff --git a/test/threadsafe_function_ex/example.cc b/test/threadsafe_function_ex/example.cc deleted file mode 100644 index 1445ae688..000000000 --- a/test/threadsafe_function_ex/example.cc +++ /dev/null @@ -1,103 +0,0 @@ -/** - * This test is programmatically represents the example shown in - * `doc/threadsafe_function_ex.md` - */ - -#include "napi.h" - -#if (NAPI_VERSION > 3) - -using namespace Napi; - -namespace { - -// Context of our TSFN. -struct Context {}; - -// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall -using DataType = int; - -// Callback function -static void Callback(Napi::Env env, Napi::Function jsCallback, Context *context, - DataType *data) { - // Check that the threadsafe function has not been finalized. Node calls this - // callback for items remaining on the queue once finalization has completed. - if (!(env == nullptr || jsCallback == nullptr)) { - } - if (data != nullptr) { - delete data; - } -} - -// Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; - -// A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public ObjectWrap { - -public: - static Object Init(Napi::Env env, Object exports); - TSFNWrap(const CallbackInfo &info); - -private: - Napi::Value Start(const CallbackInfo &info); - Napi::Value Release(const CallbackInfo &info); -}; - -/** - * @brief Initialize `TSFNWrap` on the environment. - * - * @param env - * @param exports - * @return Object - */ -Object TSFNWrap::Init(Napi::Env env, Object exports) { - Function func = DefineClass(env, "TSFNWrap", - {InstanceMethod("start", &TSFNWrap::Start), - InstanceMethod("release", &TSFNWrap::Release)}); - - exports.Set("TSFNWrap", func); - return exports; -} - -/** - * @brief Construct a new TSFNWrap::TSFNWrap object - * - * @param info - */ -TSFNWrap::TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) {} -} // namespace - -/** - * @brief Instance method `TSFNWrap#start` - * - * @param info - * @return undefined - */ -Napi::Value TSFNWrap::Start(const CallbackInfo &info) { - Napi::Env env = info.Env(); - return env.Undefined(); -}; - -/** - * @brief Instance method `TSFNWrap#release` - * - * @param info - * @return undefined - */ -Napi::Value TSFNWrap::Release(const CallbackInfo &info) { - Napi::Env env = info.Env(); - return env.Undefined(); -}; - -/** - * @brief Module initialization function - * - * @param env - * @return Object - */ -Object InitThreadSafeFunctionExExample(Env env) { - return TSFNWrap::Init(env, Object::New(env)); -} - -#endif diff --git a/test/threadsafe_function_ex/example.js b/test/threadsafe_function_ex/example.js deleted file mode 100644 index 836120029..000000000 --- a/test/threadsafe_function_ex/example.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -/** - * This test is programmatically represents the example shown in - * `doc/threadsafe_function_ex.md` - */ - -const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; - -module.exports = Promise.all([ - test(require(`../build/${buildType}/binding.node`)), - test(require(`../build/${buildType}/binding_noexcept.node`)) -]); - -async function test(binding) { - const tsfn = new binding.threadsafe_function_ex_example.TSFNWrap(); - await tsfn.start(); - await tsfn.release(); -} diff --git a/test/threadsafe_function_ex/index.js b/test/threadsafe_function_ex/index.js new file mode 100644 index 000000000..a3cfc5c87 --- /dev/null +++ b/test/threadsafe_function_ex/index.js @@ -0,0 +1,5 @@ +module.exports = (async () => { + await require('./test/threadsafe') + await require('./test/basic'); + await require('./test/example'); +})(); \ No newline at end of file diff --git a/test/threadsafe_function_ex/simple.cc b/test/threadsafe_function_ex/simple.cc deleted file mode 100644 index fb62f756b..000000000 --- a/test/threadsafe_function_ex/simple.cc +++ /dev/null @@ -1,255 +0,0 @@ -#include "napi.h" - -#if (NAPI_VERSION > 3) - -using namespace Napi; - -namespace simple { - -// Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx<>; - -// A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public ObjectWrap { -public: - static Object Init(Napi::Env env, Object exports); - TSFNWrap(const CallbackInfo &info); - - Napi::Value Release(const CallbackInfo &) { - _tsfn.Release(); - return _deferred.Promise(); - }; - - Napi::Value Call(const CallbackInfo &info) { - _tsfn.NonBlockingCall(); - return info.Env().Undefined(); - }; - -private: - TSFN _tsfn; - Promise::Deferred _deferred; -}; - -Object TSFNWrap::Init(Napi::Env env, Object exports) { - Function func = DefineClass(env, "TSFNWrap", - {InstanceMethod("call", &TSFNWrap::Call), - InstanceMethod("release", &TSFNWrap::Release)}); - - exports.Set("TSFNWrap", func); - return exports; -} - -TSFNWrap::TSFNWrap(const CallbackInfo &info) - : ObjectWrap(info), - _deferred(Promise::Deferred::New(info.Env())) { - - auto env = info.Env(); -#if NAPI_VERSION == 4 - // A threadsafe function on N-API 4 still requires a callback function. - _tsfn = - TSFN::New(env, // napi_env env, - TSFN::DefaultFunctionFactory( - env), // N-API 5+: nullptr; else: const Function& callback, - "Test", // ResourceString resourceName, - 1, // size_t maxQueueSize, - 1 // size_t initialThreadCount - ); -#else - _tsfn = TSFN::New(env, // napi_env env, - "Test", // ResourceString resourceName, - 1, // size_t maxQueueSize, - 1 // size_t initialThreadCount - ); -#endif -} -} // namespace simple - -namespace existing { - -struct DataType { - Promise::Deferred deferred; - bool reject; -}; - -// CallJs callback function provided to `napi_create_threadsafe_function`. It is -// _NOT_ used by `Napi::ThreadSafeFunctionEx<>`. -static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, - void *data) { - DataType *casted = static_cast(data); - if (env != nullptr) { - if (data != nullptr) { - napi_value undefined; - napi_status status = napi_get_undefined(env, &undefined); - NAPI_THROW_IF_FAILED(env, status); - if (casted->reject) { - casted->deferred.Reject(undefined); - } else { - casted->deferred.Resolve(undefined); - } - } - } - if (casted != nullptr) { - delete casted; - } -} - -// Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; - -// A JS-accessible wrap that holds a TSFN. -class ExistingTSFNWrap : public ObjectWrap { -public: - static Object Init(Napi::Env env, Object exports); - ExistingTSFNWrap(const CallbackInfo &info); - - Napi::Value Release(const CallbackInfo &) { - _tsfn.Release(); - return _deferred.Promise(); - }; - - Napi::Value Call(const CallbackInfo &info) { - auto *data = - new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; - _tsfn.NonBlockingCall(data); - return data->deferred.Promise(); - }; - -private: - TSFN _tsfn; - Promise::Deferred _deferred; -}; - -Object ExistingTSFNWrap::Init(Napi::Env env, Object exports) { - Function func = - DefineClass(env, "ExistingTSFNWrap", - {InstanceMethod("call", &ExistingTSFNWrap::Call), - InstanceMethod("release", &ExistingTSFNWrap::Release)}); - - exports.Set("ExistingTSFNWrap", func); - return exports; -} - -ExistingTSFNWrap::ExistingTSFNWrap(const CallbackInfo &info) - : ObjectWrap(info), - _deferred(Promise::Deferred::New(info.Env())) { - - auto env = info.Env(); -#if NAPI_VERSION == 4 - napi_threadsafe_function napi_tsfn; - auto status = napi_create_threadsafe_function( - info.Env(), TSFN::DefaultFunctionFactory(env), nullptr, - String::From(info.Env(), "Test"), 0, 1, nullptr, nullptr, nullptr, CallJs, - &napi_tsfn); - if (status != napi_ok) { - NAPI_THROW_IF_FAILED(env, status); - } - // A threadsafe function on N-API 4 still requires a callback function. - _tsfn = TSFN(napi_tsfn); -#else - napi_threadsafe_function napi_tsfn; - auto status = napi_create_threadsafe_function( - info.Env(), nullptr, nullptr, String::From(info.Env(), "Test"), 0, 1, - nullptr, nullptr, nullptr, CallJs, &napi_tsfn); - if (status != napi_ok) { - NAPI_THROW_IF_FAILED(env, status); - } - _tsfn = TSFN(napi_tsfn); -#endif -} -} // namespace existing - -namespace empty { -#if NAPI_VERSION > 4 - -using Context = void; - -struct DataType { - Promise::Deferred deferred; - bool reject; -}; - -// CallJs callback function -static void CallJs(Napi::Env env, Function jsCallback, Context * /*context*/, - DataType *data) { - if (env != nullptr) { - if (data != nullptr) { - if (data->reject) { - data->deferred.Reject(env.Undefined()); - } else { - data->deferred.Resolve(env.Undefined()); - } - } - } - if (data != nullptr) { - delete data; - } -} - -// Full type of our ThreadSafeFunctionEx -using EmptyTSFN = ThreadSafeFunctionEx; - -// A JS-accessible wrap that holds a TSFN. -class EmptyTSFNWrap : public ObjectWrap { -public: - static Object Init(Napi::Env env, Object exports); - EmptyTSFNWrap(const CallbackInfo &info); - - Napi::Value Release(const CallbackInfo &) { - _tsfn.Release(); - return _deferred.Promise(); - }; - - Napi::Value Call(const CallbackInfo &info) { - if (info.Length() == 0 || !info[0].IsBoolean()) { - NAPI_THROW( - Napi::TypeError::New(info.Env(), "Expected argument 0 to be boolean"), - Value()); - } - - auto *data = - new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; - _tsfn.NonBlockingCall(data); - return data->deferred.Promise(); - }; - -private: - EmptyTSFN _tsfn; - Promise::Deferred _deferred; -}; - -Object EmptyTSFNWrap::Init(Napi::Env env, Object exports) { - Function func = - DefineClass(env, "EmptyTSFNWrap", - {InstanceMethod("call", &EmptyTSFNWrap::Call), - InstanceMethod("release", &EmptyTSFNWrap::Release)}); - - exports.Set("EmptyTSFNWrap", func); - return exports; -} - -EmptyTSFNWrap::EmptyTSFNWrap(const CallbackInfo &info) - : ObjectWrap(info), - _deferred(Promise::Deferred::New(info.Env())) { - - auto env = info.Env(); - _tsfn = EmptyTSFN::New(env, // napi_env env, - "Test", // ResourceString resourceName, - 0, // size_t maxQueueSize, - 1 // size_t initialThreadCount - ); -} -#endif -} // namespace empty -Object InitThreadSafeFunctionExSimple(Env env) { - -#if NAPI_VERSION > 4 - return empty::EmptyTSFNWrap::Init( - env, existing::ExistingTSFNWrap::Init( - env, simple::TSFNWrap::Init(env, Object::New(env)))); -#else - return existing::ExistingTSFNWrap::Init( - env, simple::TSFNWrap::Init(env, Object::New(env))); -#endif -} - -#endif diff --git a/test/threadsafe_function_ex/simple.js b/test/threadsafe_function_ex/simple.js deleted file mode 100644 index b3a6b0cf5..000000000 --- a/test/threadsafe_function_ex/simple.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; - -module.exports = Promise.all([ - test(require(`../build/${buildType}/binding.node`)), - test(require(`../build/${buildType}/binding_noexcept.node`)) -].reduce((p, c) => p.concat(c)), []); - -function test(binding) { - return [ - // testSimple(binding), - testEmpty(binding) - ]; -} - -/** - * A simple, fire-and-forget test. - * - Creates a threadsafe function with no context or callback. - * - The node-addon-api 'no callback' feature is implemented by passing either a - * no-op `Function` on N-API 4 or `std::nullptr` on N-API 5+ to the underlying - * `napi_create_threadsafe_function` call. - * - Makes one call, releases, then waits for finalization. - * - Inherently ignores the state of the item once it has been added to the - * queue. Since there are no callbacks or context, it is impossible to capture - * the state. - */ -async function testSimple(binding) { - const tsfn = new binding.threadsafe_function_ex_simple.TSFNWrap(); - tsfn.call(); - await tsfn.release(); -} - -/** - * **ONLY ON N-API 5+**. The optional JavaScript function callback feature is - * not available in N-API <= 4. - * - Creates a threadsafe function with no JavaScript context or callback. - * - Makes two calls, expecting the first to resolve and the second to reject. - * - Waits for Node to process the items on the queue prior releasing the - * threadsafe function. - */ -async function testEmpty(binding) { - const { EmptyTSFNWrap } = binding.threadsafe_function_ex_simple; - - if (typeof EmptyTSFNWrap === 'function') { - const tsfn = new binding.threadsafe_function_ex_simple.EmptyTSFNWrap(); - await tsfn.call(false /* reject */); - let caught = false; - try { - await tsfn.call(true /* reject */); - } catch (ex) { - caught = true; - } - - assert.ok(caught, 'The promise rejection was not caught'); - await tsfn.release(); - } -} diff --git a/test/threadsafe_function_ex/test/basic.cc b/test/threadsafe_function_ex/test/basic.cc new file mode 100644 index 000000000..49c9cd0ac --- /dev/null +++ b/test/threadsafe_function_ex/test/basic.cc @@ -0,0 +1,432 @@ +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace call { + +// Context of our TSFN. +using Context = void; + +// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall +struct DataType { + Reference data; + Promise::Deferred deferred; +}; + +// CallJs callback function +static void CallJs(Napi::Env env, Napi::Function jsCallback, + Context * /*context*/, DataType *data) { + if (!(env == nullptr || jsCallback == nullptr)) { + if (data != nullptr) { + jsCallback.Call(env.Undefined(), {data->data.Value()}); + data->deferred.Resolve(data->data.Value()); + } + } + if (data != nullptr) { + delete data; + } +} + +// Full type of our ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx; + +// A JS-accessible wrap that holds a TSFN. +class TSFNWrap : public ObjectWrap { +public: + static void Init(Napi::Env env, Object exports, const std::string &ns) { + Function func = + DefineClass(env, "TSFNCall", + {InstanceMethod("call", &TSFNWrap::Call), + InstanceMethod("release", &TSFNWrap::Release)}); + + auto locals(Object::New(env)); + exports.Set(ns, locals); + locals.Set("TSFNWrap", func); + } + + TSFNWrap(const CallbackInfo &info) + : ObjectWrap(info), + _deferred(Promise::Deferred::New(info.Env())) { + Napi::Env env = info.Env(); + Function callback = info[0].As(); + + _tsfn = TSFN::New(env, // napi_env env, + callback, // const Function& callback, + "Test", // ResourceString resourceName, + 0, // size_t maxQueueSize, + 1 // size_t initialThreadCount, + ); + } + Napi::Value Call(const CallbackInfo &info) { + Napi::Env env = info.Env(); + DataType *data = + new DataType{Napi::Reference(Persistent(info[0])), + Promise::Deferred::New(env)}; + _tsfn.NonBlockingCall(data); + return data->deferred.Promise(); + }; + + Napi::Value Release(const CallbackInfo &) { + _tsfn.Release(); + return _deferred.Promise(); + }; + +private: + TSFN _tsfn; + Promise::Deferred _deferred; +}; + +} // namespace call + +namespace context { + +// Context of our TSFN. +using Context = Reference; + +// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall +using DataType = Promise::Deferred; + +// CallJs callback function +static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, + Context *context, DataType *data) { + if (env != nullptr) { + if (data != nullptr) { + data->Resolve(context->Value()); + } + } + if (data != nullptr) { + delete data; + } +} + +// Full type of our ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx; + +// A JS-accessible wrap that holds a TSFN. +class TSFNWrap : public ObjectWrap { +public: + static void Init(Napi::Env env, Object exports, const char *ns) { + Function func = DefineClass( + env, "TSFNWrap", + {InstanceMethod("getContextByCall", &TSFNWrap::GetContextByCall), + InstanceMethod("getContextFromTsfn", &TSFNWrap::GetContextFromTsfn), + InstanceMethod("release", &TSFNWrap::Release)}); + + auto locals(Object::New(env)); + exports.Set(ns, locals); + locals.Set("TSFNWrap", func); + } + + TSFNWrap(const CallbackInfo &info) + : ObjectWrap(info), + _deferred(Promise::Deferred::New(info.Env())) { + Napi::Env env = info.Env(); + + Context *context = new Context(Persistent(info[0])); + + _tsfn = TSFN::New( + info.Env(), // napi_env env, + Function::New( + env, + [](const CallbackInfo & /*info*/) {}), // const Function& callback, + Value(), // const Object& resource, + "Test", // ResourceString resourceName, + 1, // size_t maxQueueSize, + 1, // size_t initialThreadCount, + context, // ContextType* context, + + [this](Napi::Env env, void *, + Context *ctx) { // Finalizer finalizeCallback, + _deferred.Resolve(env.Undefined()); + delete ctx; + }, + static_cast(nullptr) // FinalizerDataType* data, + ); + } + + Napi::Value GetContextByCall(const CallbackInfo &info) { + Napi::Env env = info.Env(); + auto *callData = new DataType(env); + _tsfn.NonBlockingCall(callData); + return callData->Promise(); + }; + + Napi::Value GetContextFromTsfn(const CallbackInfo &) { + return _tsfn.GetContext()->Value(); + }; + + Napi::Value Release(const CallbackInfo &) { + _tsfn.Release(); + return _deferred.Promise(); + }; + +private: + TSFN _tsfn; + Promise::Deferred _deferred; +}; +} // namespace context + +namespace empty { +#if NAPI_VERSION > 4 + +using Context = void; + +struct DataType { + Promise::Deferred deferred; + bool reject; +}; + +// CallJs callback function +static void CallJs(Napi::Env env, Function jsCallback, Context * /*context*/, + DataType *data) { + if (env != nullptr) { + if (data != nullptr) { + if (data->reject) { + data->deferred.Reject(env.Undefined()); + } else { + data->deferred.Resolve(env.Undefined()); + } + } + } + if (data != nullptr) { + delete data; + } +} + +// Full type of our ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx; + +// A JS-accessible wrap that holds a TSFN. +class TSFNWrap : public ObjectWrap { +public: + static void Init(Napi::Env env, Object exports, const std::string &ns) { + Function func = + DefineClass(env, "TSFNWrap", + {InstanceMethod("call", &TSFNWrap::Call), + InstanceMethod("release", &TSFNWrap::Release)}); + + auto locals(Object::New(env)); + exports.Set(ns, locals); + locals.Set("TSFNWrap", func); + } + + TSFNWrap(const CallbackInfo &info) + : ObjectWrap(info), + _deferred(Promise::Deferred::New(info.Env())) { + + auto env = info.Env(); + _tsfn = TSFN::New(env, // napi_env env, + "Test", // ResourceString resourceName, + 0, // size_t maxQueueSize, + 1 // size_t initialThreadCount + ); + } + Napi::Value Release(const CallbackInfo &) { + _tsfn.Release(); + return _deferred.Promise(); + }; + + Napi::Value Call(const CallbackInfo &info) { + if (info.Length() == 0 || !info[0].IsBoolean()) { + NAPI_THROW( + Napi::TypeError::New(info.Env(), "Expected argument 0 to be boolean"), + Value()); + } + + auto *data = + new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; + _tsfn.NonBlockingCall(data); + return data->deferred.Promise(); + }; + +private: + TSFN _tsfn; + Promise::Deferred _deferred; +}; + +#endif +} // namespace empty + +namespace existing { + +struct DataType { + Promise::Deferred deferred; + bool reject; +}; + +// CallJs callback function provided to `napi_create_threadsafe_function`. It is +// _NOT_ used by `Napi::ThreadSafeFunctionEx<>`. +static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, + void *data) { + DataType *casted = static_cast(data); + if (env != nullptr) { + if (data != nullptr) { + napi_value undefined; + napi_status status = napi_get_undefined(env, &undefined); + NAPI_THROW_IF_FAILED(env, status); + if (casted->reject) { + casted->deferred.Reject(undefined); + } else { + casted->deferred.Resolve(undefined); + } + } + } + if (casted != nullptr) { + delete casted; + } +} + +// Full type of our ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx; + +// A JS-accessible wrap that holds a TSFN. +class TSFNWrap : public ObjectWrap { +public: + static void Init(Napi::Env env, Object exports, const std::string &ns) { + Function func = + DefineClass(env, "TSFNWrap", + {InstanceMethod("call", &TSFNWrap::Call), + InstanceMethod("release", &TSFNWrap::Release)}); + auto locals(Object::New(env)); + exports.Set(ns, locals); + locals.Set("TSFNWrap", func); + } + + TSFNWrap(const CallbackInfo &info) + : ObjectWrap(info), + _deferred(Promise::Deferred::New(info.Env())) { + + auto env = info.Env(); +#if NAPI_VERSION == 4 + napi_threadsafe_function napi_tsfn; + auto status = napi_create_threadsafe_function( + info.Env(), TSFN::DefaultFunctionFactory(env), nullptr, + String::From(info.Env(), "Test"), 0, 1, nullptr, nullptr, nullptr, + CallJs, &napi_tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED(env, status); + } + // A threadsafe function on N-API 4 still requires a callback function. + _tsfn = TSFN(napi_tsfn); +#else + napi_threadsafe_function napi_tsfn; + auto status = napi_create_threadsafe_function( + info.Env(), nullptr, nullptr, String::From(info.Env(), "Test"), 0, 1, + nullptr, nullptr, nullptr, CallJs, &napi_tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED(env, status); + } + _tsfn = TSFN(napi_tsfn); +#endif + } + + Napi::Value Release(const CallbackInfo &) { + _tsfn.Release(); + return _deferred.Promise(); + }; + + Napi::Value Call(const CallbackInfo &info) { + auto *data = + new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; + _tsfn.NonBlockingCall(data); + return data->deferred.Promise(); + }; + +private: + TSFN _tsfn; + Promise::Deferred _deferred; +}; + +} // namespace existing +namespace simple { + +// Full type of our ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx<>; + +// A JS-accessible wrap that holds a TSFN. +class TSFNWrap : public ObjectWrap { +public: + static void Init(Napi::Env env, Object exports, const std::string &ns) { + Function func = + DefineClass(env, "TSFNSimple", + {InstanceMethod("call", &TSFNWrap::Call), + InstanceMethod("release", &TSFNWrap::Release)}); + + auto locals(Object::New(env)); + exports.Set(ns, locals); + locals.Set("TSFNWrap", func); + } + + TSFNWrap(const CallbackInfo &info) + : ObjectWrap(info) { + + auto env = info.Env(); +#if NAPI_VERSION == 4 + // A threadsafe function on N-API 4 still requires a callback function. + _tsfn = TSFN::New( + env, // napi_env env, + TSFN::DefaultFunctionFactory( + env), // N-API 5+: nullptr; else: const Function& callback, + "Test", // ResourceString resourceName, + 1, // size_t maxQueueSize, + 1 // size_t initialThreadCount + ); +#else + _tsfn = TSFN::New(env, // napi_env env, + "Test", // ResourceString resourceName, + 1, // size_t maxQueueSize, + 1 // size_t initialThreadCount + ); +#endif + } + + // Since this test spec has no CALLBACK, CONTEXT, or FINALIZER. We have no way + // to know when the underlying ThreadSafeFunction has been finalized. + Napi::Value Release(const CallbackInfo &info) { + _tsfn.Release(); + return info.Env().Undefined(); + }; + + Napi::Value Call(const CallbackInfo &info) { + _tsfn.NonBlockingCall(); + return info.Env().Undefined(); + }; + +private: + TSFN _tsfn; +}; + +} // namespace simple + +Object InitThreadSafeFunctionExBasic(Env env) { + +// A list of v4+ enables spec namespaces. +#define V4_EXPORTS(V) \ + V(simple) \ + V(existing) \ + V(call) \ + V(context) + +// A list of v5+ enables spec namespaces. +#define V5_EXPORTS(V) V(empty) + +#if NAPI_VERSION == 4 +#define EXPORTS(V) V4_EXPORTS(V) +#else +#define EXPORTS(V) \ + V4_EXPORTS(V) \ + V5_EXPORTS(V) +#endif + + Object exports(Object::New(env)); + +#define V(modname) modname::TSFNWrap::Init(env, exports, #modname); + EXPORTS(V) +#undef V + + return exports; +} + +#endif diff --git a/test/threadsafe_function_ex/test/basic.js b/test/threadsafe_function_ex/test/basic.js new file mode 100644 index 000000000..a3f9769e4 --- /dev/null +++ b/test/threadsafe_function_ex/test/basic.js @@ -0,0 +1,188 @@ +// @ts-check +'use strict'; +const assert = require('assert'); +const buildType = process.config.target_defaults.default_configuration; + +// If `true`, this module will re-throw any error caught, allowing the caller to +// handle. +const SHOW_OUTPUT = true; + +const print = (isError, newLine, ...what) => { + if (SHOW_OUTPUT) { + let target, method; + target = newLine ? console : process[isError ? 'stderr' : 'stdout']; + method = target === console ? (isError ? 'error' : 'log') : 'write'; + if (isError) { + method + + } + return target[method].apply(target, what); + } +} + +/** @returns {void} */ +const log = (...what) => print(false, true, ...what); + +/** @returns {void} */ +const error = (...what) => print(true, true, ...what); + +/** @returns {Promise} */ +const write = (...what) => print(false, false, ...what); + +/** @returns {Promise} */ +// const rewind = () => print(false, false, `\x1b[K`); +const rewind = () => print(false, false, `\x1b[1A`); + +const pad = (what, targetLength = 20, padString = ' ', padLeft) => { + const padder = (pad, str) => { + if (typeof str === 'undefined') + return pad; + if (padLeft) { + return (pad + str).slice(-pad.length); + } else { + return (str + pad).substring(0, pad.length); + } + }; + return padder(padString.repeat(targetLength), String(what)); +} + +/** + * Test runner helper class. Each static method's name corresponds to the + * namespace the test as defined in the native addon. Each test specifics are + * documented on the individual method. The async test handler runs + * synchronously in the series of all tests so the test **MUST** wait on the + * finalizer. Otherwise, the test runner will assume the test completed. + */ +class TestRunner { + + static async run(isNoExcept) { + const binding = require(`../../build/${buildType}/binding${isNoExcept ? '_noexcept' : ''}.node`); + const runner = new this(); + // Errors thrown are caught by caller, and re-thrown if `BUBBLE_ERRORS` is + // `true. + const cmdlineTests = process.argv.length > 2 ? process.argv.slice(2) : null; + for (const nsName of Object.getOwnPropertyNames(this.prototype)) { + if (nsName !== 'constructor') { + const ns = binding.threadsafe_function_ex_basic[nsName]; + let state; + const setState = (...newState) => { state = newState }; + const toLine = (state) => { + const [label, time, isNoExcept, nsName] = state; + const except = () => pad(isNoExcept ? '[noexcept]' : '', 12); + const timeStr = () => time == null ? '...' : `${time}${typeof time === 'number' ? 'ms' : ''}`; + return `${pad(nsName, 10)} ${except()}| ${pad(timeStr(), 8)}| ${pad(label, 15)}`; + }; + const stateLine = () => toLine(state); + if (ns && (cmdlineTests == null || cmdlineTests.indexOf(nsName) > -1)) { + setState('Running test', null, isNoExcept, nsName); + log(stateLine()); + + const start = Date.now(); + await runner[nsName](ns); + await new Promise(resolve => setTimeout(resolve, 50)); + rewind(); + setState('Finished test', Date.now() - start, isNoExcept, nsName); + log(stateLine()); + } else { + setState('Skipping test', '-', isNoExcept, nsName); + debugger; + log(stateLine()); + } + } + } + } + /** + * This test ensures the data sent to the NonBlockingCall and the data + * received in the JavaScript callback are the same. + * - Creates a contexted threadsafe function with callback. + * - Makes one call, and waits for call to complete. + * - The callback forwards the item's data to the given JavaScript function in + * the test. + * - Asserts the data is the same. + */ + async call({ TSFNWrap }) { + const data = {}; + const tsfn = new TSFNWrap(tsfnData => { + assert(data === tsfnData, "Data in and out of tsfn call do not equal"); + }); + await tsfn.call(data); + await tsfn.release(); + } + + /** + * The context provided to the threadsafe function's constructor is accessible + * on both the threadsafe function's callback as well the threadsafe function + * itself. This test ensures the context across all three are the same. + * - Creates a contexted threadsafe function with callback. + * - The callback forwards the item's data to the given JavaScript function in + * the test. + * - Makes one call, and waits for call to complete. + * - Asserts the contexts are the same. + */ + async context({ TSFNWrap }) { + const ctx = {}; + const tsfn = new TSFNWrap(ctx); + assert(ctx === await tsfn.getContextByCall(), "getContextByCall context not equal"); + assert(ctx === tsfn.getContextFromTsfn(), "getContextFromTsfn context not equal"); + await tsfn.release(); + } + + /** + * **ONLY ON N-API 5+**. The optional JavaScript function callback feature is + * not available in N-API <= 4. + * - Creates a threadsafe function with no JavaScript context or callback. + * - Makes two calls, waiting for each, and expecting the first to resolve + * and the second to reject. + * - Waits for Node to process the items on the queue prior releasing the + * threadsafe function. + */ + async empty({ TSFNWrap }) { + debugger; + if (typeof TSFNWrap === 'function') { + const tsfn = new TSFNWrap(); + await tsfn.call(false /* reject */); + let caught = false; + try { + await tsfn.call(true /* reject */); + } catch (ex) { + caught = true; + } + + assert.ok(caught, 'The promise rejection was not caught'); + await tsfn.release(); + } + } + + /** + * A `ThreadSafeFunctionEx<>` can be constructed with default type arguments. + * - Creates a threadsafe function with no context or callback. + * - The node-addon-api 'no callback' feature is implemented by passing either + * a no-op `Function` on N-API 4 or `std::nullptr` on N-API 5+ to the + * underlying `napi_create_threadsafe_function` call. + * - Makes one call, releases, then waits for finalization. + * - Inherently ignores the state of the item once it has been added to the + * queue. Since there are no callbacks or context, it is impossible to + * capture the state. + */ + async simple({ TSFNWrap }) { + const tsfn = new TSFNWrap(); + tsfn.call(); + await tsfn.release(); + } + +} + +async function run() { + await TestRunner.run(false); + await TestRunner.run(true); +} + + +module.exports = run() + .then(() => { log(`Finished executing tests in .${__filename.replace(process.cwd(), '')}`); }) + .catch((e) => { + // if (require.main !== module) { throw e; } + console.error(`Test failed!`, e); + process.exit(1); + }); + diff --git a/test/threadsafe_function_ex/test/example.cc b/test/threadsafe_function_ex/test/example.cc new file mode 100644 index 000000000..5d336b13e --- /dev/null +++ b/test/threadsafe_function_ex/test/example.cc @@ -0,0 +1,257 @@ +#undef NAPI_CPP_EXCEPTIONS +#define NAPI_DISABLE_CPP_EXCEPTIONS + +/** + * This test is programmatically represents the example shown in + * `doc/threadsafe_function_ex.md` + */ + +#if (NAPI_VERSION > 3) + +#include "napi.h" +#include +#include +static constexpr size_t DEFAULT_THREAD_COUNT = 10; +static constexpr int32_t DEFAULT_CALL_COUNT = 2; + +/** + * @brief Macro used specifically to support the dual CI test / documentation + * example setup. Exceptions are always thrown as JavaScript exceptions when + * running in example mode. + * + */ +#define TSFN_THROW(tsfnWrap, e, ...) \ + if (tsfnWrap->cppExceptions) { \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ + } while (0); \ + } else { \ + NAPI_THROW(e, __VA_ARGS__); \ + } + +using namespace Napi; + +namespace { + +// Context of our TSFN. +struct Context { + int32_t threadId; +}; + +// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall +using DataType = int; + +// Callback function +static void Callback(Napi::Env env, Napi::Function jsCallback, Context *context, + DataType *data) { + // Check that the threadsafe function has not been finalized. Node calls this + // callback for items remaining on the queue once finalization has completed. + if (!(env == nullptr || jsCallback == nullptr)) { + } + if (data != nullptr) { + delete data; + } +} + +// Full type of our ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx; + +struct FinalizerDataType { + std::vector threads; +}; + +// A JS-accessible wrap that holds a TSFN. +class TSFNWrap : public ObjectWrap { + +public: + static Object Init(Napi::Env env, Object exports); + TSFNWrap(const CallbackInfo &info); + + // When running as an example, we want exceptions to always go to JavaScript, + // allowing the user to try/catch errors from the addon. + bool cppExceptions; + +private: + Napi::Value Start(const CallbackInfo &info); + Napi::Value Release(const CallbackInfo &info); + + // Instantiated by `Start`; resolved on finalize of tsfn. + Promise::Deferred _deferred; + + // Reference to our TSFN + TSFN _tsfn; + + // Object.prototype.toString reference for use with error messages + FunctionReference _toString; +}; + +/** + * @brief Initialize `TSFNWrap` on the environment. + * + * @param env + * @param exports + * @return Object + */ +Object TSFNWrap::Init(Napi::Env env, Object exports) { + Function func = DefineClass(env, "TSFNWrap", + {InstanceMethod("start", &TSFNWrap::Start), + InstanceMethod("release", &TSFNWrap::Release)}); + + exports.Set("TSFNWrap", func); + return exports; +} + +static void threadEntry(size_t threadId, TSFN tsfn, int32_t callCount) { + using namespace std::chrono_literals; + for (int32_t i = 0; i < callCount; ++i) { + tsfn.NonBlockingCall(new int); + std::this_thread::sleep_for(50ms * threadId); + } + tsfn.Release(); +} + +/** + * @brief Construct a new TSFNWrap object on the main thread. If any arguments + * are passed, exceptions in the addon will always be thrown JavaScript + * exceptions, allowing the user to try/catch errors from the addon. + * + * @param info + */ +TSFNWrap::TSFNWrap(const CallbackInfo &info) + : ObjectWrap(info), + _deferred(Promise::Deferred::New(info.Env())) { + auto env = info.Env(); + _toString = Napi::Persistent(env.Global() + .Get("Object") + .ToObject() + .Get("prototype") + .ToObject() + .Get("toString") + .As()); + cppExceptions = true; + info.Length() > 0; +} +} // namespace + +/** + * @brief Instance method `TSFNWrap#start` + * + * @param info + * @return undefined + */ +Napi::Value TSFNWrap::Start(const CallbackInfo &info) { + Napi::Env env = info.Env(); + + // Creates a list to hold how many times each thread should make a call. + std::vector callCounts; + + // The JS-provided callback to execute for each call (if provided) + Function callback; + + if (info.Length() > 0 && info[0].IsObject()) { + auto arg0 = info[0].ToObject(); + if (arg0.Has("threads")) { + Napi::Value threads = arg0.Get("threads"); + if (threads.IsArray()) { + Napi::Array threadsArray = threads.As(); + for (auto i = 0U; i < threadsArray.Length(); ++i) { + Napi::Value elem = threadsArray.Get(i); + if (elem.IsNumber()) { + callCounts.push_back(elem.As().Int32Value()); + } else { + // TSFN_THROW(this, + // Napi::TypeError::New(Env(), + // "Invalid arguments"), + // Object()); + + // ThrowAsJavaScriptException + Napi::TypeError::New(Env(), "Invalid arguments") + .ThrowAsJavaScriptException(); + return env.Undefined(); + + // if (this->cppExceptions) { + // do { + // (Napi::TypeError::New(Env(), "Invalid arguments")) + // .ThrowAsJavaScriptException(); + // return Object(); + // } while (0); + // } else { + // NAPI_THROW(Napi::TypeError::New(Env(), "Invalid arguments"), + // Object()); + // } + } + } + } else if (threads.IsNumber()) { + auto threadCount = threads.As().Int32Value(); + for (int32_t i = 0; i < threadCount; ++i) { + callCounts.push_back(DEFAULT_CALL_COUNT); + } + } else { + TSFN_THROW(this, Napi::TypeError::New(Env(), "Invalid arguments"), + Number()); + } + } + + if (arg0.Has("callback")) { + auto cb = arg0.Get("callback"); + if (cb.IsFunction()) { + callback = cb.As(); + } else { + TSFN_THROW(this, + Napi::TypeError::New(Env(), "Callback is not a function"), + Number()); + } + } + } + + // Apply default arguments + if (callCounts.size() == 0) { + for (size_t i = 0; i < DEFAULT_THREAD_COUNT; ++i) { + callCounts.push_back(DEFAULT_CALL_COUNT); + } + } + + // const auto threadCount = callCounts.size(); + // FinalizerDataType *finalizerData = new FinalizerDataType(); + // // TSFN::New(info.Env(), info[0].As(), Object::New(info.Env()), + // // "Test", tsfnInfo.maxQueueSize, 2, &tsfnInfo, JoinTheThreads, + + // threads); _tsfn = TSFN::New(env, // napi_env env, + // callback, // const Function& callback, + // "Test", // ResourceString resourceName, + // 0, // size_t maxQueueSize, + // threadCount // size_t initialThreadCount, + // ); + // for (int32_t threadId = 0; threadId < threadCount; ++threadId) { + // // static void threadEntry(size_t threadId, TSFN tsfn, int32_t callCount) + // { + + // finalizerData->threads.push_back( + // std::thread(threadEntry, threadId, _tsfn, callCounts[threadId])); + // } + return env.Undefined(); +}; + +/** + * @brief Instance method `TSFNWrap#release` + * + * @param info + * @return undefined + */ +Napi::Value TSFNWrap::Release(const CallbackInfo &info) { + Napi::Env env = info.Env(); + return env.Undefined(); +}; + +/** + * @brief Module initialization function + * + * @param env + * @return Object + */ +Object InitThreadSafeFunctionExExample(Env env) { + return TSFNWrap::Init(env, Object::New(env)); +} + +#endif diff --git a/test/threadsafe_function_ex/test/example.js b/test/threadsafe_function_ex/test/example.js new file mode 100644 index 000000000..3aaf0c736 --- /dev/null +++ b/test/threadsafe_function_ex/test/example.js @@ -0,0 +1,44 @@ +'use strict'; + +/** + * This test is programmatically represents the example shown in + * `doc/threadsafe_function_ex.md` + */ + +const assert = require('assert'); +const buildType = 'Debug'; process.config.target_defaults.default_configuration; + +const isCI = require.main !== module; +const print = (isError, ...what) => isCI ? () => {} : console[isError ? 'error' : 'log'].apply(console.log, what); +const log = (...what) => print(false, ...what); +const error = (...what) => print(true, ...what); + +module.exports = Promise.all([ + // test(require(`../build/${buildType}/binding_noexcept.node`)), + isCI ? undefined : test(require(`../build/${buildType}/binding_noexcept.node`)) +]).catch(e => { + console.error('Error', e); +}); + +async function test(binding) { + try { + const tsfn = new binding.threadsafe_function_ex_example.TSFNWrap(true); + await tsfn.start({ + threads: ['f',5,5,5], + callback: ()=>{} + }); + await tsfn.release(); +} catch (e) { + error(e); +} +} + + +if (!isCI) { + console.log(module.exports); + module.exports.then(() => { + log('tea'); + }).catch((e) => { + error('Error!', e); + }); +} diff --git a/test/threadsafe_function_ex/threadsafe.cc b/test/threadsafe_function_ex/test/threadsafe.cc similarity index 99% rename from test/threadsafe_function_ex/threadsafe.cc rename to test/threadsafe_function_ex/test/threadsafe.cc index cf3eddca8..f12e7e49b 100644 --- a/test/threadsafe_function_ex/threadsafe.cc +++ b/test/threadsafe_function_ex/test/threadsafe.cc @@ -1,6 +1,7 @@ #include #include #include "napi.h" +#include #if (NAPI_VERSION > 3) diff --git a/test/threadsafe_function_ex/threadsafe.js b/test/threadsafe_function_ex/test/threadsafe.js similarity index 92% rename from test/threadsafe_function_ex/threadsafe.js rename to test/threadsafe_function_ex/test/threadsafe.js index f9789db58..6ceceb92c 100644 --- a/test/threadsafe_function_ex/threadsafe.js +++ b/test/threadsafe_function_ex/test/threadsafe.js @@ -2,10 +2,19 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -const common = require('../common'); - -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +const common = require('../../common'); + +module.exports = run() + .then(() => { console.log(`Finished executing tests in .${__filename.replace(process.cwd(),'')}`); }) + .catch((e) => { + console.error(`Test failed!`, e); + process.exit(1); + }); + +async function run() { + await test(require(`../../build/${buildType}/binding.node`)); + await test(require(`../../build/${buildType}/binding_noexcept.node`)); +} /** * This spec replicates the non-`Ex` multi-threaded spec using the `Ex` API. @@ -46,7 +55,7 @@ function test(binding) { }); } - new Promise(function testWithoutJSMarshaller(resolve) { + return new Promise(function testWithoutJSMarshaller(resolve) { let callCount = 0; binding.threadsafe_function_ex_threadsafe.startThreadNoNative(function testCallback() { callCount++; From d2bcc03d67fc6587b2547d95018f10e1e61b54a2 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 14 Jun 2020 17:41:57 +0200 Subject: [PATCH 209/696] test: napi v4,v5 all tests pass --- napi-inl.h | 6 +- napi.h | 7 +- test/threadsafe_function_ex/index.js | 16 +- test/threadsafe_function_ex/test/basic.cc | 190 +++++++++++------- test/threadsafe_function_ex/test/basic.js | 135 ++----------- test/threadsafe_function_ex/test/example.cc | 3 - .../threadsafe_function_ex/test/threadsafe.js | 2 +- .../threadsafe_function_ex/util/TestRunner.js | 166 +++++++++++++++ 8 files changed, 328 insertions(+), 197 deletions(-) create mode 100644 test/threadsafe_function_ex/util/TestRunner.js diff --git a/napi-inl.h b/napi-inl.h index 05e61d2e5..c5e262555 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4551,7 +4551,7 @@ ThreadSafeFunctionEx::New( return tsfn; } -// static, with Callback [x] Resource [x] Finalizer [missing] +// static, with Callback [passed] Resource [passed] Finalizer [missing] template template @@ -4574,7 +4574,7 @@ ThreadSafeFunctionEx::New( return tsfn; } -// static, with Callback [x] Resource [missing ] Finalizer [x] +// static, with Callback [passed] Resource [missing] Finalizer [passed] template template ::New( return tsfn; } -// static, with: Callback [x] Resource [x] Finalizer [x] +// static, with: Callback [passed] Resource [passed] Finalizer [passed] template template 3) - template class ThreadSafeFunctionEx { @@ -2058,7 +2060,8 @@ namespace Napi { // This API may only be called from the main thread. // Helper function that returns nullptr if running N-API 5+, otherwise a // non-empty, no-op Function. This provides the ability to specify at - // compile-time a callback parameter to `New` that safely does no action. + // compile-time a callback parameter to `New` that safely does no action + // when targeting _any_ N-API version. static DefaultFunctionType DefaultFunctionFactory(Napi::Env env); #if NAPI_VERSION > 4 diff --git a/test/threadsafe_function_ex/index.js b/test/threadsafe_function_ex/index.js index a3cfc5c87..9e7778d8f 100644 --- a/test/threadsafe_function_ex/index.js +++ b/test/threadsafe_function_ex/index.js @@ -1,5 +1,13 @@ +const tests = [ + // 'threadsafe', + 'basic', + 'example' +]; + +// Threadsafe tests must run synchronously. If two threaded-tests are running +// and one fails, Node may exit while `std::thread`s are running. module.exports = (async () => { - await require('./test/threadsafe') - await require('./test/basic'); - await require('./test/example'); -})(); \ No newline at end of file + for (const test of tests) { + await require(`./test/${test}`); + } +})(); diff --git a/test/threadsafe_function_ex/test/basic.cc b/test/threadsafe_function_ex/test/basic.cc index 49c9cd0ac..94594dfe7 100644 --- a/test/threadsafe_function_ex/test/basic.cc +++ b/test/threadsafe_function_ex/test/basic.cc @@ -7,17 +7,17 @@ using namespace Napi; namespace call { // Context of our TSFN. -using Context = void; +using Context = std::nullptr_t; // Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall -struct DataType { +struct Data { Reference data; Promise::Deferred deferred; }; // CallJs callback function static void CallJs(Napi::Env env, Napi::Function jsCallback, - Context * /*context*/, DataType *data) { + Context * /*context*/, Data *data) { if (!(env == nullptr || jsCallback == nullptr)) { if (data != nullptr) { jsCallback.Call(env.Undefined(), {data->data.Value()}); @@ -30,7 +30,7 @@ static void CallJs(Napi::Env env, Napi::Function jsCallback, } // Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; +using TSFN = ThreadSafeFunctionEx; // A JS-accessible wrap that holds a TSFN. class TSFNWrap : public ObjectWrap { @@ -46,36 +46,46 @@ class TSFNWrap : public ObjectWrap { locals.Set("TSFNWrap", func); } - TSFNWrap(const CallbackInfo &info) - : ObjectWrap(info), - _deferred(Promise::Deferred::New(info.Env())) { + TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { Napi::Env env = info.Env(); Function callback = info[0].As(); - _tsfn = TSFN::New(env, // napi_env env, callback, // const Function& callback, "Test", // ResourceString resourceName, 0, // size_t maxQueueSize, - 1 // size_t initialThreadCount, - ); + 1, // size_t initialThreadCount, + nullptr, + [this](Napi::Env env, void *, + Context *ctx) { // Finalizer finalizeCallback, + if (_deferred) { + _deferred->Resolve(Boolean::New(env, true)); + _deferred.release(); + } + }); } + Napi::Value Call(const CallbackInfo &info) { Napi::Env env = info.Env(); - DataType *data = - new DataType{Napi::Reference(Persistent(info[0])), - Promise::Deferred::New(env)}; + Data *data = new Data{Napi::Reference(Persistent(info[0])), + Promise::Deferred::New(env)}; _tsfn.NonBlockingCall(data); return data->deferred.Promise(); }; - Napi::Value Release(const CallbackInfo &) { + Napi::Value Release(const CallbackInfo &info) { + if (_deferred) { + return _deferred->Promise(); + } + + auto env = info.Env(); + _deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); _tsfn.Release(); - return _deferred.Promise(); + return _deferred->Promise(); }; private: TSFN _tsfn; - Promise::Deferred _deferred; + std::unique_ptr _deferred; }; } // namespace call @@ -86,11 +96,11 @@ namespace context { using Context = Reference; // Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall -using DataType = Promise::Deferred; +using Data = Promise::Deferred; // CallJs callback function static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, - Context *context, DataType *data) { + Context *context, Data *data) { if (env != nullptr) { if (data != nullptr) { data->Resolve(context->Value()); @@ -102,7 +112,7 @@ static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, } // Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; +using TSFN = ThreadSafeFunctionEx; // A JS-accessible wrap that holds a TSFN. class TSFNWrap : public ObjectWrap { @@ -119,36 +129,32 @@ class TSFNWrap : public ObjectWrap { locals.Set("TSFNWrap", func); } - TSFNWrap(const CallbackInfo &info) - : ObjectWrap(info), - _deferred(Promise::Deferred::New(info.Env())) { + TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { Napi::Env env = info.Env(); Context *context = new Context(Persistent(info[0])); - _tsfn = TSFN::New( - info.Env(), // napi_env env, - Function::New( - env, - [](const CallbackInfo & /*info*/) {}), // const Function& callback, - Value(), // const Object& resource, - "Test", // ResourceString resourceName, - 1, // size_t maxQueueSize, - 1, // size_t initialThreadCount, - context, // ContextType* context, - - [this](Napi::Env env, void *, - Context *ctx) { // Finalizer finalizeCallback, - _deferred.Resolve(env.Undefined()); - delete ctx; - }, - static_cast(nullptr) // FinalizerDataType* data, - ); + _tsfn = TSFN::New(info.Env(), // napi_env env, + Function::New(env, + [](const CallbackInfo & /*info*/) { + }), // const Function& callback, + Value(), // const Object& resource, + "Test", // ResourceString resourceName, + 0, // size_t maxQueueSize, + 1, // size_t initialThreadCount, + context, // Context* context, + [this](Napi::Env env, void *, + Context *ctx) { // Finalizer finalizeCallback, + if (_deferred) { + _deferred->Resolve(Boolean::New(env, true)); + _deferred.release(); + } + }); } Napi::Value GetContextByCall(const CallbackInfo &info) { Napi::Env env = info.Env(); - auto *callData = new DataType(env); + auto *callData = new Data(env); _tsfn.NonBlockingCall(callData); return callData->Promise(); }; @@ -157,14 +163,19 @@ class TSFNWrap : public ObjectWrap { return _tsfn.GetContext()->Value(); }; - Napi::Value Release(const CallbackInfo &) { + Napi::Value Release(const CallbackInfo &info) { + if (_deferred) { + return _deferred->Promise(); + } + auto env = info.Env(); + _deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); _tsfn.Release(); - return _deferred.Promise(); + return _deferred->Promise(); }; private: TSFN _tsfn; - Promise::Deferred _deferred; + std::unique_ptr _deferred; }; } // namespace context @@ -173,14 +184,14 @@ namespace empty { using Context = void; -struct DataType { +struct Data { Promise::Deferred deferred; bool reject; }; // CallJs callback function static void CallJs(Napi::Env env, Function jsCallback, Context * /*context*/, - DataType *data) { + Data *data) { if (env != nullptr) { if (data != nullptr) { if (data->reject) { @@ -196,7 +207,7 @@ static void CallJs(Napi::Env env, Function jsCallback, Context * /*context*/, } // Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; +using TSFN = ThreadSafeFunctionEx; // A JS-accessible wrap that holds a TSFN. class TSFNWrap : public ObjectWrap { @@ -212,20 +223,34 @@ class TSFNWrap : public ObjectWrap { locals.Set("TSFNWrap", func); } - TSFNWrap(const CallbackInfo &info) - : ObjectWrap(info), - _deferred(Promise::Deferred::New(info.Env())) { + TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { auto env = info.Env(); _tsfn = TSFN::New(env, // napi_env env, "Test", // ResourceString resourceName, 0, // size_t maxQueueSize, - 1 // size_t initialThreadCount - ); + 1, // size_t initialThreadCount, + nullptr, + [this](Napi::Env env, void *, + Context *ctx) { // Finalizer finalizeCallback, + if (_deferred) { + _deferred->Resolve(Boolean::New(env, true)); + _deferred.release(); + } + }); } - Napi::Value Release(const CallbackInfo &) { + + Napi::Value Release(const CallbackInfo &info) { + // Since this is actually a SINGLE-THREADED test, we don't have to worry + // about race conditions on accessing `_deferred`. + if (_deferred) { + return _deferred->Promise(); + } + + auto env = info.Env(); + _deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); _tsfn.Release(); - return _deferred.Promise(); + return _deferred->Promise(); }; Napi::Value Call(const CallbackInfo &info) { @@ -236,14 +261,14 @@ class TSFNWrap : public ObjectWrap { } auto *data = - new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; + new Data{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; _tsfn.NonBlockingCall(data); return data->deferred.Promise(); }; private: TSFN _tsfn; - Promise::Deferred _deferred; + std::unique_ptr _deferred; }; #endif @@ -251,7 +276,7 @@ class TSFNWrap : public ObjectWrap { namespace existing { -struct DataType { +struct Data { Promise::Deferred deferred; bool reject; }; @@ -260,7 +285,7 @@ struct DataType { // _NOT_ used by `Napi::ThreadSafeFunctionEx<>`. static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, void *data) { - DataType *casted = static_cast(data); + Data *casted = static_cast(data); if (env != nullptr) { if (data != nullptr) { napi_value undefined; @@ -278,8 +303,16 @@ static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, } } +// This test creates a native napi_threadsafe_function itself, whose `context` +// parameter is the `TSFNWrap` object itself. We forward-declare, so we can use +// it as an argument inside `ThreadSafeFunctionEx<>`. This also allows us to +// statically get the correct type when using `tsfn.GetContext()`. The converse +// is true: if the Context type does _not_ match that provided to the underlying +// napi_create_threadsafe_function, then the static type will be incorrect. +class TSFNWrap; + // Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; +using TSFN = ThreadSafeFunctionEx; // A JS-accessible wrap that holds a TSFN. class TSFNWrap : public ObjectWrap { @@ -294,9 +327,7 @@ class TSFNWrap : public ObjectWrap { locals.Set("TSFNWrap", func); } - TSFNWrap(const CallbackInfo &info) - : ObjectWrap(info), - _deferred(Promise::Deferred::New(info.Env())) { + TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { auto env = info.Env(); #if NAPI_VERSION == 4 @@ -314,7 +345,7 @@ class TSFNWrap : public ObjectWrap { napi_threadsafe_function napi_tsfn; auto status = napi_create_threadsafe_function( info.Env(), nullptr, nullptr, String::From(info.Env(), "Test"), 0, 1, - nullptr, nullptr, nullptr, CallJs, &napi_tsfn); + nullptr, Finalizer, this, CallJs, &napi_tsfn); if (status != napi_ok) { NAPI_THROW_IF_FAILED(env, status); } @@ -322,21 +353,39 @@ class TSFNWrap : public ObjectWrap { #endif } - Napi::Value Release(const CallbackInfo &) { + Napi::Value Release(const CallbackInfo &info) { + if (_deferred) { + return _deferred->Promise(); + } + auto env = info.Env(); + _deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); _tsfn.Release(); - return _deferred.Promise(); + return _deferred->Promise(); }; Napi::Value Call(const CallbackInfo &info) { auto *data = - new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; + new Data{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; _tsfn.NonBlockingCall(data); return data->deferred.Promise(); }; private: TSFN _tsfn; - Promise::Deferred _deferred; + std::unique_ptr _deferred; + + static void Finalizer(napi_env env, void * /*data*/, void *ctx) { + TSFNWrap *tsfn = static_cast(ctx); + tsfn->Finalizer(env); + } + + void Finalizer(napi_env e) { + if (_deferred) { + _deferred->Resolve(Boolean::New(e, true)); + _deferred.release(); + } + // Finalizer finalizeCallback, + } }; } // namespace existing @@ -359,8 +408,7 @@ class TSFNWrap : public ObjectWrap { locals.Set("TSFNWrap", func); } - TSFNWrap(const CallbackInfo &info) - : ObjectWrap(info) { + TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { auto env = info.Env(); #if NAPI_VERSION == 4 @@ -386,7 +434,7 @@ class TSFNWrap : public ObjectWrap { // to know when the underlying ThreadSafeFunction has been finalized. Napi::Value Release(const CallbackInfo &info) { _tsfn.Release(); - return info.Env().Undefined(); + return String::New(info.Env(), "TSFN may not have finalized."); }; Napi::Value Call(const CallbackInfo &info) { @@ -404,9 +452,9 @@ Object InitThreadSafeFunctionExBasic(Env env) { // A list of v4+ enables spec namespaces. #define V4_EXPORTS(V) \ + V(call) \ V(simple) \ V(existing) \ - V(call) \ V(context) // A list of v5+ enables spec namespaces. diff --git a/test/threadsafe_function_ex/test/basic.js b/test/threadsafe_function_ex/test/basic.js index a3f9769e4..598246b30 100644 --- a/test/threadsafe_function_ex/test/basic.js +++ b/test/threadsafe_function_ex/test/basic.js @@ -1,96 +1,14 @@ // @ts-check 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; -// If `true`, this module will re-throw any error caught, allowing the caller to -// handle. -const SHOW_OUTPUT = true; - -const print = (isError, newLine, ...what) => { - if (SHOW_OUTPUT) { - let target, method; - target = newLine ? console : process[isError ? 'stderr' : 'stdout']; - method = target === console ? (isError ? 'error' : 'log') : 'write'; - if (isError) { - method - - } - return target[method].apply(target, what); - } -} - -/** @returns {void} */ -const log = (...what) => print(false, true, ...what); - -/** @returns {void} */ -const error = (...what) => print(true, true, ...what); - -/** @returns {Promise} */ -const write = (...what) => print(false, false, ...what); - -/** @returns {Promise} */ -// const rewind = () => print(false, false, `\x1b[K`); -const rewind = () => print(false, false, `\x1b[1A`); - -const pad = (what, targetLength = 20, padString = ' ', padLeft) => { - const padder = (pad, str) => { - if (typeof str === 'undefined') - return pad; - if (padLeft) { - return (pad + str).slice(-pad.length); - } else { - return (str + pad).substring(0, pad.length); - } - }; - return padder(padString.repeat(targetLength), String(what)); -} +const { TestRunner } = require('../util/TestRunner'); /** - * Test runner helper class. Each static method's name corresponds to the - * namespace the test as defined in the native addon. Each test specifics are - * documented on the individual method. The async test handler runs - * synchronously in the series of all tests so the test **MUST** wait on the - * finalizer. Otherwise, the test runner will assume the test completed. + * A "basic" test spec. This spec does NOT use threads, and is primarily used to + * verify the API. */ -class TestRunner { - - static async run(isNoExcept) { - const binding = require(`../../build/${buildType}/binding${isNoExcept ? '_noexcept' : ''}.node`); - const runner = new this(); - // Errors thrown are caught by caller, and re-thrown if `BUBBLE_ERRORS` is - // `true. - const cmdlineTests = process.argv.length > 2 ? process.argv.slice(2) : null; - for (const nsName of Object.getOwnPropertyNames(this.prototype)) { - if (nsName !== 'constructor') { - const ns = binding.threadsafe_function_ex_basic[nsName]; - let state; - const setState = (...newState) => { state = newState }; - const toLine = (state) => { - const [label, time, isNoExcept, nsName] = state; - const except = () => pad(isNoExcept ? '[noexcept]' : '', 12); - const timeStr = () => time == null ? '...' : `${time}${typeof time === 'number' ? 'ms' : ''}`; - return `${pad(nsName, 10)} ${except()}| ${pad(timeStr(), 8)}| ${pad(label, 15)}`; - }; - const stateLine = () => toLine(state); - if (ns && (cmdlineTests == null || cmdlineTests.indexOf(nsName) > -1)) { - setState('Running test', null, isNoExcept, nsName); - log(stateLine()); - - const start = Date.now(); - await runner[nsName](ns); - await new Promise(resolve => setTimeout(resolve, 50)); - rewind(); - setState('Finished test', Date.now() - start, isNoExcept, nsName); - log(stateLine()); - } else { - setState('Skipping test', '-', isNoExcept, nsName); - debugger; - log(stateLine()); - } - } - } - } +class BasicTest extends TestRunner { /** * This test ensures the data sent to the NonBlockingCall and the data * received in the JavaScript callback are the same. @@ -106,35 +24,38 @@ class TestRunner { assert(data === tsfnData, "Data in and out of tsfn call do not equal"); }); await tsfn.call(data); - await tsfn.release(); + return await tsfn.release(); } /** * The context provided to the threadsafe function's constructor is accessible - * on both the threadsafe function's callback as well the threadsafe function - * itself. This test ensures the context across all three are the same. + * on both (A) the threadsafe function's callback as well as (B) the + * threadsafe function itself. This test ensures the context across all three + * are the same. * - Creates a contexted threadsafe function with callback. * - The callback forwards the item's data to the given JavaScript function in * the test. - * - Makes one call, and waits for call to complete. - * - Asserts the contexts are the same. + * - Asserts the contexts are the same as the context passed during threadsafe + * function construction in two places: + * - (A) Makes one call, and waits for call to complete. + * - (B) Asserts that the context returns from the API's `GetContext()` */ async context({ TSFNWrap }) { const ctx = {}; const tsfn = new TSFNWrap(ctx); assert(ctx === await tsfn.getContextByCall(), "getContextByCall context not equal"); assert(ctx === tsfn.getContextFromTsfn(), "getContextFromTsfn context not equal"); - await tsfn.release(); + return await tsfn.release(); } /** * **ONLY ON N-API 5+**. The optional JavaScript function callback feature is - * not available in N-API <= 4. + * not available in N-API <= 4. This test creates uses a threadsafe function + * that handles all of its JavaScript processing on the callJs instead of the + * callback. * - Creates a threadsafe function with no JavaScript context or callback. * - Makes two calls, waiting for each, and expecting the first to resolve * and the second to reject. - * - Waits for Node to process the items on the queue prior releasing the - * threadsafe function. */ async empty({ TSFNWrap }) { debugger; @@ -149,13 +70,14 @@ class TestRunner { } assert.ok(caught, 'The promise rejection was not caught'); - await tsfn.release(); + return await tsfn.release(); } + return true; } /** - * A `ThreadSafeFunctionEx<>` can be constructed with default type arguments. - * - Creates a threadsafe function with no context or callback. + * A `ThreadSafeFunctionEx<>` can be constructed with no type arguments. + * - Creates a threadsafe function with no context or callback or callJs. * - The node-addon-api 'no callback' feature is implemented by passing either * a no-op `Function` on N-API 4 or `std::nullptr` on N-API 5+ to the * underlying `napi_create_threadsafe_function` call. @@ -167,22 +89,9 @@ class TestRunner { async simple({ TSFNWrap }) { const tsfn = new TSFNWrap(); tsfn.call(); - await tsfn.release(); + return await tsfn.release(); } } -async function run() { - await TestRunner.run(false); - await TestRunner.run(true); -} - - -module.exports = run() - .then(() => { log(`Finished executing tests in .${__filename.replace(process.cwd(), '')}`); }) - .catch((e) => { - // if (require.main !== module) { throw e; } - console.error(`Test failed!`, e); - process.exit(1); - }); - +module.exports = new BasicTest('threadsafe_function_ex_basic', __filename).start(); diff --git a/test/threadsafe_function_ex/test/example.cc b/test/threadsafe_function_ex/test/example.cc index 5d336b13e..2da92104a 100644 --- a/test/threadsafe_function_ex/test/example.cc +++ b/test/threadsafe_function_ex/test/example.cc @@ -1,6 +1,3 @@ -#undef NAPI_CPP_EXCEPTIONS -#define NAPI_DISABLE_CPP_EXCEPTIONS - /** * This test is programmatically represents the example shown in * `doc/threadsafe_function_ex.md` diff --git a/test/threadsafe_function_ex/test/threadsafe.js b/test/threadsafe_function_ex/test/threadsafe.js index 6ceceb92c..2d807b040 100644 --- a/test/threadsafe_function_ex/test/threadsafe.js +++ b/test/threadsafe_function_ex/test/threadsafe.js @@ -5,13 +5,13 @@ const assert = require('assert'); const common = require('../../common'); module.exports = run() - .then(() => { console.log(`Finished executing tests in .${__filename.replace(process.cwd(),'')}`); }) .catch((e) => { console.error(`Test failed!`, e); process.exit(1); }); async function run() { + console.log(`Running tests in .${__filename.replace(process.cwd(),'')}`); await test(require(`../../build/${buildType}/binding.node`)); await test(require(`../../build/${buildType}/binding_noexcept.node`)); } diff --git a/test/threadsafe_function_ex/util/TestRunner.js b/test/threadsafe_function_ex/util/TestRunner.js new file mode 100644 index 000000000..b7e99c1b4 --- /dev/null +++ b/test/threadsafe_function_ex/util/TestRunner.js @@ -0,0 +1,166 @@ +// @ts-check +'use strict'; +const assert = require('assert'); +const { basename, extname } = require('path'); +const buildType = process.config.target_defaults.default_configuration; + +// If you pass certain test names as argv, run those only. +const cmdlineTests = process.argv.length > 2 ? process.argv.slice(2) : null; + +const pad = (what, targetLength = 20, padString = ' ', padLeft) => { + const padder = (pad, str) => { + if (typeof str === 'undefined') + return pad; + if (padLeft) { + return (pad + str).slice(-pad.length); + } else { + return (str + pad).substring(0, pad.length); + } + }; + return padder(padString.repeat(targetLength), String(what)); +} + +/** + * Test runner helper class. Each static method's name corresponds to the + * namespace the test as defined in the native addon. Each test specifics are + * documented on the individual method. The async test handler runs + * synchronously in the series of all tests so the test **MUST** wait on the + * finalizer. Otherwise, the test runner will assume the test completed. + */ +class TestRunner { + + /** + * If `true`, always show results as interactive. See constructor for more + * information. + */ + static SHOW_OUTPUT = false; + + /** + * @param {string} bindingKey The key to use when accessing the binding. + * @param {string} filename Name of file that the current TestRunner instance + * is being constructed. This determines how to log to console: + * - When the test is running as the current module, output is shown on both + * start and stop of test in an 'interactive' styling. + * - Otherwise, the output is more of a CI-like styling. + */ + constructor(bindingKey, filename) { + this.bindingKey = bindingKey; + this.filename = filename; + this.interactive = TestRunner.SHOW_OUTPUT || filename === require.main.filename; + this.specName = `${this.bindingKey}/${basename(this.filename, extname(this.filename))}`; + } + + async start() { + try { + this.log(`Running tests in .${this.filename.replace(process.cwd(), '')}:\n`); + // Run tests in both except and noexcept + await this.run(false); + await this.run(true); + } catch (ex) { + console.error(`Test failed!`, ex); + process.exit(1); + } + } + + /** + * @param {boolean} isNoExcept If true, use the 'noexcept' binding. + */ + async run(isNoExcept) { + const binding = require(`../../build/${buildType}/binding${isNoExcept ? '_noexcept' : ''}.node`); + const { bindingKey } = this; + const spec = binding[bindingKey]; + const runner = this; + + // If we can't find the key in the binding, error. + if (!spec) { + throw new Error(`Could not find '${bindingKey}' in binding.`); + } + + // A 'test' is defined as any function on the prototype of this object. + for (const nsName of Object.getOwnPropertyNames(Object.getPrototypeOf(this))) { + if (nsName !== 'constructor') { + const ns = spec[nsName]; + + // Interactive mode prints start and end messages + if (this.interactive) { + + + + /** @typedef {[string, string | null | number, boolean, string, any]} State [label, time, isNoExcept, nsName, returnValue] */ + + /** @type {State} */ + let state = [undefined, undefined, undefined, undefined, undefined] + + const stateLine = () => { + const [label, time, isNoExcept, nsName, returnValue] = state; + const except = () => pad(isNoExcept ? '[noexcept]' : '', 12); + const timeStr = () => time == null ? '...' : `${time}${typeof time === 'number' ? 'ms' : ''}`; + return `${pad(nsName, 10)} ${except()}| ${pad(timeStr(), 8)}| ${pad(label, 15)}${returnValue === undefined ? '' : `(return: ${returnValue})`}`; + }; + + /** + * @param {string} label + * @param {string | number} time + * @param {boolean} isNoExcept + * @param {string} nsName + * @param {any} returnValue + */ + const setState = (label, time, isNoExcept, nsName, returnValue) => { + if (state[1] === null) { + // Move to last line + this.print(false, `\x1b[1A`); + } + state = [label, time, isNoExcept, nsName, returnValue]; + this.log(stateLine()); + }; + + const runTest = (cmdlineTests == null || cmdlineTests.indexOf(nsName) > -1); + + if (ns && typeof runner[nsName] === 'function' && runTest) { + setState('Running test', null, isNoExcept, nsName, undefined); + const start = Date.now(); + const returnValue = await runner[nsName](ns); + await this.dummy(); + setState('Finished test', Date.now() - start, isNoExcept, nsName, returnValue); + } else { + setState('Skipping test', '-', isNoExcept, nsName, undefined); + } + } else { + console.log(`Running test '${this.specName}/${nsName}' ${isNoExcept ? '[noexcept]' : ''}`); + await runner[nsName](ns); + await this.dummy(); + } + } + } + } + + dummy() { return new Promise(resolve => setTimeout(resolve, 50)); } + + /** + * Print to console only when using interactive mode. + * + * @param {boolean} newLine If true, end with a new line. + * @param {any[]} what What to print + */ + print(newLine, ...what) { + if (this.interactive) { + let target, method; + target = newLine ? console : process.stdout; + method = target === console ? 'log' : 'write'; + return target[method].apply(target, what); + } + } + + /** + * Log to console only when using interactive mode. + * @param {string[]} what + */ + log(...what) { + this.print(true, ...what); + } + +} + +module.exports = { + TestRunner +}; From 6b7a7d05f2a70d3f168eee5313292b1b31984acb Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 14 Jun 2020 20:27:44 +0200 Subject: [PATCH 210/696] test: consolidate duplicated code --- test/binding.gyp | 6 +- test/threadsafe_function_ex/index.js | 2 +- test/threadsafe_function_ex/test/basic.cc | 304 +++++++++------------- test/threadsafe_function_ex/test/basic.js | 4 +- test/threadsafe_function_ex/util/util.h | 62 +++++ 5 files changed, 184 insertions(+), 194 deletions(-) create mode 100644 test/threadsafe_function_ex/util/util.h diff --git a/test/binding.gyp b/test/binding.gyp index 923cb2f55..8bfa59e3e 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -35,9 +35,9 @@ 'object/set_property.cc', 'promise.cc', 'run_script.cc', - 'threadsafe_function_ex/basic.cc', - 'threadsafe_function_ex/example.cc', - 'threadsafe_function_ex/threadsafe.cc', + 'threadsafe_function_ex/test/basic.cc', + 'threadsafe_function_ex/test/example.cc', + 'threadsafe_function_ex/test/threadsafe.cc', 'threadsafe_function/threadsafe_function_ctx.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', diff --git a/test/threadsafe_function_ex/index.js b/test/threadsafe_function_ex/index.js index 9e7778d8f..917d1b060 100644 --- a/test/threadsafe_function_ex/index.js +++ b/test/threadsafe_function_ex/index.js @@ -1,5 +1,5 @@ const tests = [ - // 'threadsafe', + 'threadsafe', 'basic', 'example' ]; diff --git a/test/threadsafe_function_ex/test/basic.cc b/test/threadsafe_function_ex/test/basic.cc index 94594dfe7..a0c1fd178 100644 --- a/test/threadsafe_function_ex/test/basic.cc +++ b/test/threadsafe_function_ex/test/basic.cc @@ -1,4 +1,6 @@ +#include #include "napi.h" +#include "../util/util.h" #if (NAPI_VERSION > 3) @@ -6,10 +8,10 @@ using namespace Napi; namespace call { -// Context of our TSFN. +// Context of the TSFN. using Context = std::nullptr_t; -// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall +// Data passed (as pointer) to [Non]BlockingCall struct Data { Reference data; Promise::Deferred deferred; @@ -29,39 +31,31 @@ static void CallJs(Napi::Env env, Napi::Function jsCallback, } } -// Full type of our ThreadSafeFunctionEx +// Full type of the ThreadSafeFunctionEx using TSFN = ThreadSafeFunctionEx; -// A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public ObjectWrap { +class TSFNWrap; +using base = tsfnutil::TSFNWrapBase; + +// A JS-accessible wrap that holds the TSFN. +class TSFNWrap : public base { public: - static void Init(Napi::Env env, Object exports, const std::string &ns) { - Function func = - DefineClass(env, "TSFNCall", - {InstanceMethod("call", &TSFNWrap::Call), - InstanceMethod("release", &TSFNWrap::Release)}); - - auto locals(Object::New(env)); - exports.Set(ns, locals); - locals.Set("TSFNWrap", func); + TSFNWrap(const CallbackInfo &info) : base(info) { + Napi::Env env = info.Env(); + _tsfn = TSFN::New(env, // napi_env env, + info[0].As(), // const Function& callback, + "Test", // ResourceString resourceName, + 0, // size_t maxQueueSize, + 1, // size_t initialThreadCount, + nullptr, // ContextType* context + base::Finalizer, // Finalizer finalizer + &_deferred // FinalizerDataType data + ); } - TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { - Napi::Env env = info.Env(); - Function callback = info[0].As(); - _tsfn = TSFN::New(env, // napi_env env, - callback, // const Function& callback, - "Test", // ResourceString resourceName, - 0, // size_t maxQueueSize, - 1, // size_t initialThreadCount, - nullptr, - [this](Napi::Env env, void *, - Context *ctx) { // Finalizer finalizeCallback, - if (_deferred) { - _deferred->Resolve(Boolean::New(env, true)); - _deferred.release(); - } - }); + static std::array, 2> InstanceMethods() { + return {InstanceMethod("release", &TSFNWrap::Release), + InstanceMethod("call", &TSFNWrap::Call)}; } Napi::Value Call(const CallbackInfo &info) { @@ -72,30 +66,16 @@ class TSFNWrap : public ObjectWrap { return data->deferred.Promise(); }; - Napi::Value Release(const CallbackInfo &info) { - if (_deferred) { - return _deferred->Promise(); - } - - auto env = info.Env(); - _deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); - _tsfn.Release(); - return _deferred->Promise(); - }; - -private: - TSFN _tsfn; - std::unique_ptr _deferred; }; } // namespace call namespace context { -// Context of our TSFN. +// Context of the TSFN. using Context = Reference; -// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall +// Data passed (as pointer) to [Non]BlockingCall using Data = Promise::Deferred; // CallJs callback function @@ -111,79 +91,58 @@ static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, } } -// Full type of our ThreadSafeFunctionEx +// Full type of the ThreadSafeFunctionEx using TSFN = ThreadSafeFunctionEx; -// A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public ObjectWrap { -public: - static void Init(Napi::Env env, Object exports, const char *ns) { - Function func = DefineClass( - env, "TSFNWrap", - {InstanceMethod("getContextByCall", &TSFNWrap::GetContextByCall), - InstanceMethod("getContextFromTsfn", &TSFNWrap::GetContextFromTsfn), - InstanceMethod("release", &TSFNWrap::Release)}); - - auto locals(Object::New(env)); - exports.Set(ns, locals); - locals.Set("TSFNWrap", func); - } +class TSFNWrap; +using base = tsfnutil::TSFNWrapBase; - TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { +// A JS-accessible wrap that holds the TSFN. +class TSFNWrap : public base { +public: + TSFNWrap(const CallbackInfo &info) : base(info) { Napi::Env env = info.Env(); Context *context = new Context(Persistent(info[0])); - _tsfn = TSFN::New(info.Env(), // napi_env env, - Function::New(env, - [](const CallbackInfo & /*info*/) { - }), // const Function& callback, - Value(), // const Object& resource, - "Test", // ResourceString resourceName, - 0, // size_t maxQueueSize, - 1, // size_t initialThreadCount, - context, // Context* context, - [this](Napi::Env env, void *, - Context *ctx) { // Finalizer finalizeCallback, - if (_deferred) { - _deferred->Resolve(Boolean::New(env, true)); - _deferred.release(); - } - }); + _tsfn = TSFN::New( + env, // napi_env env, + TSFN::DefaultFunctionFactory(env), // const Function& callback, + Value(), // const Object& resource, + "Test", // ResourceString resourceName, + 0, // size_t maxQueueSize, + 1, // size_t initialThreadCount, + context, // Context* context, + base::Finalizer, // Finalizer finalizer + &_deferred // FinalizerDataType data + ); + } + + static std::array, 3> InstanceMethods() { + return {InstanceMethod("call", &TSFNWrap::Call), + InstanceMethod("getContext", &TSFNWrap::GetContext), + InstanceMethod("release", &TSFNWrap::Release)}; } - Napi::Value GetContextByCall(const CallbackInfo &info) { - Napi::Env env = info.Env(); - auto *callData = new Data(env); + Napi::Value Call(const CallbackInfo &info) { + auto *callData = new Data(info.Env()); _tsfn.NonBlockingCall(callData); return callData->Promise(); }; - Napi::Value GetContextFromTsfn(const CallbackInfo &) { + Napi::Value GetContext(const CallbackInfo &) { return _tsfn.GetContext()->Value(); }; - - Napi::Value Release(const CallbackInfo &info) { - if (_deferred) { - return _deferred->Promise(); - } - auto env = info.Env(); - _deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); - _tsfn.Release(); - return _deferred->Promise(); - }; - -private: - TSFN _tsfn; - std::unique_ptr _deferred; }; } // namespace context namespace empty { #if NAPI_VERSION > 4 -using Context = void; +// Context of the TSFN. +using Context = std::nullptr_t; +// Data passed (as pointer) to [Non]BlockingCall struct Data { Promise::Deferred deferred; bool reject; @@ -206,52 +165,32 @@ static void CallJs(Napi::Env env, Function jsCallback, Context * /*context*/, } } -// Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; +// Full type of the ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx; -// A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public ObjectWrap { -public: - static void Init(Napi::Env env, Object exports, const std::string &ns) { - Function func = - DefineClass(env, "TSFNWrap", - {InstanceMethod("call", &TSFNWrap::Call), - InstanceMethod("release", &TSFNWrap::Release)}); - - auto locals(Object::New(env)); - exports.Set(ns, locals); - locals.Set("TSFNWrap", func); - } +class TSFNWrap; +using base = tsfnutil::TSFNWrapBase; - TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { +// A JS-accessible wrap that holds the TSFN. +class TSFNWrap : public base { +public: + TSFNWrap(const CallbackInfo &info) : base(info) { auto env = info.Env(); - _tsfn = TSFN::New(env, // napi_env env, - "Test", // ResourceString resourceName, - 0, // size_t maxQueueSize, - 1, // size_t initialThreadCount, - nullptr, - [this](Napi::Env env, void *, - Context *ctx) { // Finalizer finalizeCallback, - if (_deferred) { - _deferred->Resolve(Boolean::New(env, true)); - _deferred.release(); - } - }); + _tsfn = TSFN::New(env, // napi_env env, + "Test", // ResourceString resourceName, + 0, // size_t maxQueueSize, + 1, // size_t initialThreadCount, + nullptr, // ContextType* context + base::Finalizer, // Finalizer finalizer + &_deferred // FinalizerDataType data + ); } - Napi::Value Release(const CallbackInfo &info) { - // Since this is actually a SINGLE-THREADED test, we don't have to worry - // about race conditions on accessing `_deferred`. - if (_deferred) { - return _deferred->Promise(); - } - - auto env = info.Env(); - _deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); - _tsfn.Release(); - return _deferred->Promise(); - }; + static std::array, 2> InstanceMethods() { + return {InstanceMethod("release", &TSFNWrap::Release), + InstanceMethod("call", &TSFNWrap::Call)}; + } Napi::Value Call(const CallbackInfo &info) { if (info.Length() == 0 || !info[0].IsBoolean()) { @@ -265,10 +204,6 @@ class TSFNWrap : public ObjectWrap { _tsfn.NonBlockingCall(data); return data->deferred.Promise(); }; - -private: - TSFN _tsfn; - std::unique_ptr _deferred; }; #endif @@ -276,13 +211,15 @@ class TSFNWrap : public ObjectWrap { namespace existing { +// Data passed (as pointer) to [Non]BlockingCall struct Data { Promise::Deferred deferred; bool reject; }; // CallJs callback function provided to `napi_create_threadsafe_function`. It is -// _NOT_ used by `Napi::ThreadSafeFunctionEx<>`. +// _NOT_ used by `Napi::ThreadSafeFunctionEx<>`, which is why these arguments +// are napi_*. static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, void *data) { Data *casted = static_cast(data); @@ -304,34 +241,32 @@ static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, } // This test creates a native napi_threadsafe_function itself, whose `context` -// parameter is the `TSFNWrap` object itself. We forward-declare, so we can use +// parameter is the `TSFNWrap` object. We forward-declare, so we can use // it as an argument inside `ThreadSafeFunctionEx<>`. This also allows us to // statically get the correct type when using `tsfn.GetContext()`. The converse // is true: if the Context type does _not_ match that provided to the underlying // napi_create_threadsafe_function, then the static type will be incorrect. class TSFNWrap; +// Context of the TSFN. +using Context = TSFNWrap; + // Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; +using TSFN = ThreadSafeFunctionEx; +using base = tsfnutil::TSFNWrapBase; // A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public ObjectWrap { +class TSFNWrap : public base { public: - static void Init(Napi::Env env, Object exports, const std::string &ns) { - Function func = - DefineClass(env, "TSFNWrap", - {InstanceMethod("call", &TSFNWrap::Call), - InstanceMethod("release", &TSFNWrap::Release)}); - auto locals(Object::New(env)); - exports.Set(ns, locals); - locals.Set("TSFNWrap", func); - } - - TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { + TSFNWrap(const CallbackInfo &info) : base(info) { auto env = info.Env(); #if NAPI_VERSION == 4 napi_threadsafe_function napi_tsfn; + + // A threadsafe function on N-API 4 still requires a callback function, so + // this uses the `DefaultFunctionFactory` helper method to return a no-op + // Function. auto status = napi_create_threadsafe_function( info.Env(), TSFN::DefaultFunctionFactory(env), nullptr, String::From(info.Env(), "Test"), 0, 1, nullptr, nullptr, nullptr, @@ -339,10 +274,12 @@ class TSFNWrap : public ObjectWrap { if (status != napi_ok) { NAPI_THROW_IF_FAILED(env, status); } - // A threadsafe function on N-API 4 still requires a callback function. _tsfn = TSFN(napi_tsfn); #else napi_threadsafe_function napi_tsfn; + + // A threadsafe function may be `nullptr` on N-API 5+ as long as a `CallJS` + // is present. auto status = napi_create_threadsafe_function( info.Env(), nullptr, nullptr, String::From(info.Env(), "Test"), 0, 1, nullptr, Finalizer, this, CallJs, &napi_tsfn); @@ -353,15 +290,10 @@ class TSFNWrap : public ObjectWrap { #endif } - Napi::Value Release(const CallbackInfo &info) { - if (_deferred) { - return _deferred->Promise(); - } - auto env = info.Env(); - _deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); - _tsfn.Release(); - return _deferred->Promise(); - }; + static std::array, 2> InstanceMethods() { + return {InstanceMethod("release", &TSFNWrap::Release), + InstanceMethod("call", &TSFNWrap::Call)}; + } Napi::Value Call(const CallbackInfo &info) { auto *data = @@ -371,44 +303,38 @@ class TSFNWrap : public ObjectWrap { }; private: - TSFN _tsfn; - std::unique_ptr _deferred; - + // This test uses a custom napi (NOT node-addon-api) TSFN finalizer. static void Finalizer(napi_env env, void * /*data*/, void *ctx) { TSFNWrap *tsfn = static_cast(ctx); tsfn->Finalizer(env); } + // Clean up the TSFNWrap by resolving the promise. void Finalizer(napi_env e) { if (_deferred) { _deferred->Resolve(Boolean::New(e, true)); _deferred.release(); } - // Finalizer finalizeCallback, } }; } // namespace existing namespace simple { -// Full type of our ThreadSafeFunctionEx +using Context = std::nullptr_t; + +// Full type of our ThreadSafeFunctionEx. We don't specify the `Context` here +// (even though the _default_ for the type argument is `std::nullptr_t`) to +// demonstrate construction with no type arguments. using TSFN = ThreadSafeFunctionEx<>; +class TSFNWrap; +using base = tsfnutil::TSFNWrapBase; + // A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public ObjectWrap { +class TSFNWrap : public base { public: - static void Init(Napi::Env env, Object exports, const std::string &ns) { - Function func = - DefineClass(env, "TSFNSimple", - {InstanceMethod("call", &TSFNWrap::Call), - InstanceMethod("release", &TSFNWrap::Release)}); - - auto locals(Object::New(env)); - exports.Set(ns, locals); - locals.Set("TSFNWrap", func); - } - - TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { + TSFNWrap(const CallbackInfo &info) : base(info) { auto env = info.Env(); #if NAPI_VERSION == 4 @@ -430,6 +356,11 @@ class TSFNWrap : public ObjectWrap { #endif } + static std::array, 2> InstanceMethods() { + return {InstanceMethod("release", &TSFNWrap::Release), + InstanceMethod("call", &TSFNWrap::Call)}; + } + // Since this test spec has no CALLBACK, CONTEXT, or FINALIZER. We have no way // to know when the underlying ThreadSafeFunction has been finalized. Napi::Value Release(const CallbackInfo &info) { @@ -441,16 +372,13 @@ class TSFNWrap : public ObjectWrap { _tsfn.NonBlockingCall(); return info.Env().Undefined(); }; - -private: - TSFN _tsfn; }; } // namespace simple Object InitThreadSafeFunctionExBasic(Env env) { -// A list of v4+ enables spec namespaces. +// A list of v4+ enabled spec namespaces. #define V4_EXPORTS(V) \ V(call) \ V(simple) \ diff --git a/test/threadsafe_function_ex/test/basic.js b/test/threadsafe_function_ex/test/basic.js index 598246b30..bcb1cdc8a 100644 --- a/test/threadsafe_function_ex/test/basic.js +++ b/test/threadsafe_function_ex/test/basic.js @@ -43,8 +43,8 @@ class BasicTest extends TestRunner { async context({ TSFNWrap }) { const ctx = {}; const tsfn = new TSFNWrap(ctx); - assert(ctx === await tsfn.getContextByCall(), "getContextByCall context not equal"); - assert(ctx === tsfn.getContextFromTsfn(), "getContextFromTsfn context not equal"); + assert(ctx === await tsfn.call(), "getContextByCall context not equal"); + assert(ctx === tsfn.getContext(), "getContextFromTsfn context not equal"); return await tsfn.release(); } diff --git a/test/threadsafe_function_ex/util/util.h b/test/threadsafe_function_ex/util/util.h new file mode 100644 index 000000000..36472d81a --- /dev/null +++ b/test/threadsafe_function_ex/util/util.h @@ -0,0 +1,62 @@ +#include +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace tsfnutil { +template +class TSFNWrapBase : public ObjectWrap { +public: + + + static void Init(Napi::Env env, Object exports, const std::string &ns) { + // Get methods defined by child + auto methods(TSFNWrapImpl::InstanceMethods()); + + // Create a vector, since DefineClass doesn't accept arrays. + std::vector> methodsVec(methods.begin(), methods.end()); + + auto locals(Object::New(env)); + locals.Set("TSFNWrap", ObjectWrap::DefineClass(env, "TSFNWrap", methodsVec)); + exports.Set(ns, locals); + } + + // Release the TSFN. Returns a Promise that is resolved in the TSFN's + // finalizer. + // NOTE: the 'simple' test overrides this method, because it has no finalizer. + Napi::Value Release(const CallbackInfo &info) { + if (_deferred) { + return _deferred->Promise(); + } + + auto env = info.Env(); + _deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); + + _tsfn.Release(); + return _deferred->Promise(); + }; + + // TSFN finalizer. Resolves the Promise returned by `Release()` above. + static void Finalizer(Napi::Env env, + std::unique_ptr *deferred, + Context *ctx) { + if (deferred->get()) { + (*deferred)->Resolve(Boolean::New(env, true)); + deferred->release(); + } + } + + + TSFNWrapBase(const CallbackInfo &callbackInfo) + : ObjectWrap(callbackInfo) {} + +protected: + TSFN _tsfn; + std::unique_ptr _deferred; +}; + +} // namespace tsfnutil + +#endif From 7467a3f26f3ef2100af81cac28e58b1c3d9b2e3a Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 14 Jun 2020 21:00:00 +0200 Subject: [PATCH 211/696] test: basic example, standardize identifier names --- test/threadsafe_function_ex/test/basic.cc | 68 ++--- test/threadsafe_function_ex/test/example.cc | 279 ++++---------------- test/threadsafe_function_ex/test/example.js | 48 +--- 3 files changed, 100 insertions(+), 295 deletions(-) diff --git a/test/threadsafe_function_ex/test/basic.cc b/test/threadsafe_function_ex/test/basic.cc index a0c1fd178..c26ca0738 100644 --- a/test/threadsafe_function_ex/test/basic.cc +++ b/test/threadsafe_function_ex/test/basic.cc @@ -9,17 +9,17 @@ using namespace Napi; namespace call { // Context of the TSFN. -using Context = std::nullptr_t; +using ContextType = std::nullptr_t; // Data passed (as pointer) to [Non]BlockingCall -struct Data { +struct DataType { Reference data; Promise::Deferred deferred; }; // CallJs callback function static void CallJs(Napi::Env env, Napi::Function jsCallback, - Context * /*context*/, Data *data) { + ContextType * /*context*/, DataType *data) { if (!(env == nullptr || jsCallback == nullptr)) { if (data != nullptr) { jsCallback.Call(env.Undefined(), {data->data.Value()}); @@ -32,10 +32,10 @@ static void CallJs(Napi::Env env, Napi::Function jsCallback, } // Full type of the ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; +using TSFN = ThreadSafeFunctionEx; class TSFNWrap; -using base = tsfnutil::TSFNWrapBase; +using base = tsfnutil::TSFNWrapBase; // A JS-accessible wrap that holds the TSFN. class TSFNWrap : public base { @@ -49,7 +49,7 @@ class TSFNWrap : public base { 1, // size_t initialThreadCount, nullptr, // ContextType* context base::Finalizer, // Finalizer finalizer - &_deferred // FinalizerDataType data + &_deferred // FinalizerDataType* data ); } @@ -60,7 +60,7 @@ class TSFNWrap : public base { Napi::Value Call(const CallbackInfo &info) { Napi::Env env = info.Env(); - Data *data = new Data{Napi::Reference(Persistent(info[0])), + DataType *data = new DataType{Napi::Reference(Persistent(info[0])), Promise::Deferred::New(env)}; _tsfn.NonBlockingCall(data); return data->deferred.Promise(); @@ -73,14 +73,14 @@ class TSFNWrap : public base { namespace context { // Context of the TSFN. -using Context = Reference; +using ContextType = Reference; // Data passed (as pointer) to [Non]BlockingCall -using Data = Promise::Deferred; +using DataType = Promise::Deferred; // CallJs callback function static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, - Context *context, Data *data) { + ContextType *context, DataType *data) { if (env != nullptr) { if (data != nullptr) { data->Resolve(context->Value()); @@ -92,10 +92,10 @@ static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, } // Full type of the ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; +using TSFN = ThreadSafeFunctionEx; class TSFNWrap; -using base = tsfnutil::TSFNWrapBase; +using base = tsfnutil::TSFNWrapBase; // A JS-accessible wrap that holds the TSFN. class TSFNWrap : public base { @@ -103,7 +103,7 @@ class TSFNWrap : public base { TSFNWrap(const CallbackInfo &info) : base(info) { Napi::Env env = info.Env(); - Context *context = new Context(Persistent(info[0])); + ContextType *context = new ContextType(Persistent(info[0])); _tsfn = TSFN::New( env, // napi_env env, @@ -112,9 +112,9 @@ class TSFNWrap : public base { "Test", // ResourceString resourceName, 0, // size_t maxQueueSize, 1, // size_t initialThreadCount, - context, // Context* context, + context, // ContextType* context, base::Finalizer, // Finalizer finalizer - &_deferred // FinalizerDataType data + &_deferred // FinalizerDataType* data ); } @@ -125,7 +125,7 @@ class TSFNWrap : public base { } Napi::Value Call(const CallbackInfo &info) { - auto *callData = new Data(info.Env()); + auto *callData = new DataType(info.Env()); _tsfn.NonBlockingCall(callData); return callData->Promise(); }; @@ -140,17 +140,17 @@ namespace empty { #if NAPI_VERSION > 4 // Context of the TSFN. -using Context = std::nullptr_t; +using ContextType = std::nullptr_t; // Data passed (as pointer) to [Non]BlockingCall -struct Data { +struct DataType { Promise::Deferred deferred; bool reject; }; // CallJs callback function -static void CallJs(Napi::Env env, Function jsCallback, Context * /*context*/, - Data *data) { +static void CallJs(Napi::Env env, Function jsCallback, ContextType * /*context*/, + DataType *data) { if (env != nullptr) { if (data != nullptr) { if (data->reject) { @@ -166,10 +166,10 @@ static void CallJs(Napi::Env env, Function jsCallback, Context * /*context*/, } // Full type of the ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; +using TSFN = ThreadSafeFunctionEx; class TSFNWrap; -using base = tsfnutil::TSFNWrapBase; +using base = tsfnutil::TSFNWrapBase; // A JS-accessible wrap that holds the TSFN. class TSFNWrap : public base { @@ -183,7 +183,7 @@ class TSFNWrap : public base { 1, // size_t initialThreadCount, nullptr, // ContextType* context base::Finalizer, // Finalizer finalizer - &_deferred // FinalizerDataType data + &_deferred // FinalizerDataType* data ); } @@ -200,7 +200,7 @@ class TSFNWrap : public base { } auto *data = - new Data{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; + new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; _tsfn.NonBlockingCall(data); return data->deferred.Promise(); }; @@ -212,7 +212,7 @@ class TSFNWrap : public base { namespace existing { // Data passed (as pointer) to [Non]BlockingCall -struct Data { +struct DataType { Promise::Deferred deferred; bool reject; }; @@ -222,7 +222,7 @@ struct Data { // are napi_*. static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, void *data) { - Data *casted = static_cast(data); + DataType *casted = static_cast(data); if (env != nullptr) { if (data != nullptr) { napi_value undefined; @@ -244,16 +244,16 @@ static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, // parameter is the `TSFNWrap` object. We forward-declare, so we can use // it as an argument inside `ThreadSafeFunctionEx<>`. This also allows us to // statically get the correct type when using `tsfn.GetContext()`. The converse -// is true: if the Context type does _not_ match that provided to the underlying +// is true: if the ContextType does _not_ match that provided to the underlying // napi_create_threadsafe_function, then the static type will be incorrect. class TSFNWrap; // Context of the TSFN. -using Context = TSFNWrap; +using ContextType = TSFNWrap; // Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; -using base = tsfnutil::TSFNWrapBase; +using TSFN = ThreadSafeFunctionEx; +using base = tsfnutil::TSFNWrapBase; // A JS-accessible wrap that holds a TSFN. class TSFNWrap : public base { @@ -297,7 +297,7 @@ class TSFNWrap : public base { Napi::Value Call(const CallbackInfo &info) { auto *data = - new Data{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; + new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; _tsfn.NonBlockingCall(data); return data->deferred.Promise(); }; @@ -321,15 +321,15 @@ class TSFNWrap : public base { } // namespace existing namespace simple { -using Context = std::nullptr_t; +using ContextType = std::nullptr_t; -// Full type of our ThreadSafeFunctionEx. We don't specify the `Context` here +// Full type of our ThreadSafeFunctionEx. We don't specify the `ContextType` here // (even though the _default_ for the type argument is `std::nullptr_t`) to // demonstrate construction with no type arguments. using TSFN = ThreadSafeFunctionEx<>; class TSFNWrap; -using base = tsfnutil::TSFNWrapBase; +using base = tsfnutil::TSFNWrapBase; // A JS-accessible wrap that holds a TSFN. class TSFNWrap : public base { diff --git a/test/threadsafe_function_ex/test/example.cc b/test/threadsafe_function_ex/test/example.cc index 2da92104a..a20d9ecae 100644 --- a/test/threadsafe_function_ex/test/example.cc +++ b/test/threadsafe_function_ex/test/example.cc @@ -1,254 +1,83 @@ -/** - * This test is programmatically represents the example shown in - * `doc/threadsafe_function_ex.md` - */ +#include +#include "napi.h" +#include "../util/util.h" #if (NAPI_VERSION > 3) -#include "napi.h" -#include -#include -static constexpr size_t DEFAULT_THREAD_COUNT = 10; -static constexpr int32_t DEFAULT_CALL_COUNT = 2; - -/** - * @brief Macro used specifically to support the dual CI test / documentation - * example setup. Exceptions are always thrown as JavaScript exceptions when - * running in example mode. - * - */ -#define TSFN_THROW(tsfnWrap, e, ...) \ - if (tsfnWrap->cppExceptions) { \ - do { \ - (e).ThrowAsJavaScriptException(); \ - return __VA_ARGS__; \ - } while (0); \ - } else { \ - NAPI_THROW(e, __VA_ARGS__); \ - } - using namespace Napi; -namespace { +namespace example { -// Context of our TSFN. -struct Context { - int32_t threadId; -}; +// Context of the TSFN. +using Context = Reference; -// Data passed (as pointer) to ThreadSafeFunctionEx::[Non]BlockingCall -using DataType = int; +// Data passed (as pointer) to [Non]BlockingCall +using DataType = Promise::Deferred; -// Callback function -static void Callback(Napi::Env env, Napi::Function jsCallback, Context *context, - DataType *data) { - // Check that the threadsafe function has not been finalized. Node calls this - // callback for items remaining on the queue once finalization has completed. - if (!(env == nullptr || jsCallback == nullptr)) { +// CallJs callback function +static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, + Context *context, DataType *data) { + if (env != nullptr) { + if (data != nullptr) { + data->Resolve(context->Value()); + } } if (data != nullptr) { delete data; } } -// Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; - -struct FinalizerDataType { - std::vector threads; -}; +// Full type of the ThreadSafeFunctionEx +using TSFN = ThreadSafeFunctionEx; -// A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public ObjectWrap { +class TSFNWrap; +using base = tsfnutil::TSFNWrapBase; +// A JS-accessible wrap that holds the TSFN. +class TSFNWrap : public base { public: - static Object Init(Napi::Env env, Object exports); - TSFNWrap(const CallbackInfo &info); - - // When running as an example, we want exceptions to always go to JavaScript, - // allowing the user to try/catch errors from the addon. - bool cppExceptions; - -private: - Napi::Value Start(const CallbackInfo &info); - Napi::Value Release(const CallbackInfo &info); - - // Instantiated by `Start`; resolved on finalize of tsfn. - Promise::Deferred _deferred; - - // Reference to our TSFN - TSFN _tsfn; - - // Object.prototype.toString reference for use with error messages - FunctionReference _toString; -}; - -/** - * @brief Initialize `TSFNWrap` on the environment. - * - * @param env - * @param exports - * @return Object - */ -Object TSFNWrap::Init(Napi::Env env, Object exports) { - Function func = DefineClass(env, "TSFNWrap", - {InstanceMethod("start", &TSFNWrap::Start), - InstanceMethod("release", &TSFNWrap::Release)}); - - exports.Set("TSFNWrap", func); - return exports; -} - -static void threadEntry(size_t threadId, TSFN tsfn, int32_t callCount) { - using namespace std::chrono_literals; - for (int32_t i = 0; i < callCount; ++i) { - tsfn.NonBlockingCall(new int); - std::this_thread::sleep_for(50ms * threadId); + TSFNWrap(const CallbackInfo &info) : base(info) { + Napi::Env env = info.Env(); + + Context *context = new Context(Persistent(info[0])); + + _tsfn = TSFN::New( + env, // napi_env env, + TSFN::DefaultFunctionFactory(env), // const Function& callback, + Value(), // const Object& resource, + "Test", // ResourceString resourceName, + 0, // size_t maxQueueSize, + 1, // size_t initialThreadCount, + context, // Context* context, + base::Finalizer, // Finalizer finalizer + &_deferred // FinalizerDataType data + ); } - tsfn.Release(); -} - -/** - * @brief Construct a new TSFNWrap object on the main thread. If any arguments - * are passed, exceptions in the addon will always be thrown JavaScript - * exceptions, allowing the user to try/catch errors from the addon. - * - * @param info - */ -TSFNWrap::TSFNWrap(const CallbackInfo &info) - : ObjectWrap(info), - _deferred(Promise::Deferred::New(info.Env())) { - auto env = info.Env(); - _toString = Napi::Persistent(env.Global() - .Get("Object") - .ToObject() - .Get("prototype") - .ToObject() - .Get("toString") - .As()); - cppExceptions = true; - info.Length() > 0; -} -} // namespace - -/** - * @brief Instance method `TSFNWrap#start` - * - * @param info - * @return undefined - */ -Napi::Value TSFNWrap::Start(const CallbackInfo &info) { - Napi::Env env = info.Env(); - - // Creates a list to hold how many times each thread should make a call. - std::vector callCounts; - - // The JS-provided callback to execute for each call (if provided) - Function callback; - - if (info.Length() > 0 && info[0].IsObject()) { - auto arg0 = info[0].ToObject(); - if (arg0.Has("threads")) { - Napi::Value threads = arg0.Get("threads"); - if (threads.IsArray()) { - Napi::Array threadsArray = threads.As(); - for (auto i = 0U; i < threadsArray.Length(); ++i) { - Napi::Value elem = threadsArray.Get(i); - if (elem.IsNumber()) { - callCounts.push_back(elem.As().Int32Value()); - } else { - // TSFN_THROW(this, - // Napi::TypeError::New(Env(), - // "Invalid arguments"), - // Object()); - - // ThrowAsJavaScriptException - Napi::TypeError::New(Env(), "Invalid arguments") - .ThrowAsJavaScriptException(); - return env.Undefined(); - - // if (this->cppExceptions) { - // do { - // (Napi::TypeError::New(Env(), "Invalid arguments")) - // .ThrowAsJavaScriptException(); - // return Object(); - // } while (0); - // } else { - // NAPI_THROW(Napi::TypeError::New(Env(), "Invalid arguments"), - // Object()); - // } - } - } - } else if (threads.IsNumber()) { - auto threadCount = threads.As().Int32Value(); - for (int32_t i = 0; i < threadCount; ++i) { - callCounts.push_back(DEFAULT_CALL_COUNT); - } - } else { - TSFN_THROW(this, Napi::TypeError::New(Env(), "Invalid arguments"), - Number()); - } - } - if (arg0.Has("callback")) { - auto cb = arg0.Get("callback"); - if (cb.IsFunction()) { - callback = cb.As(); - } else { - TSFN_THROW(this, - Napi::TypeError::New(Env(), "Callback is not a function"), - Number()); - } - } + static std::array, 3> InstanceMethods() { + return {InstanceMethod("call", &TSFNWrap::Call), + InstanceMethod("getContext", &TSFNWrap::GetContext), + InstanceMethod("release", &TSFNWrap::Release)}; } - // Apply default arguments - if (callCounts.size() == 0) { - for (size_t i = 0; i < DEFAULT_THREAD_COUNT; ++i) { - callCounts.push_back(DEFAULT_CALL_COUNT); - } - } + Napi::Value Call(const CallbackInfo &info) { + auto *callData = new DataType(info.Env()); + _tsfn.NonBlockingCall(callData); + return callData->Promise(); + }; - // const auto threadCount = callCounts.size(); - // FinalizerDataType *finalizerData = new FinalizerDataType(); - // // TSFN::New(info.Env(), info[0].As(), Object::New(info.Env()), - // // "Test", tsfnInfo.maxQueueSize, 2, &tsfnInfo, JoinTheThreads, - - // threads); _tsfn = TSFN::New(env, // napi_env env, - // callback, // const Function& callback, - // "Test", // ResourceString resourceName, - // 0, // size_t maxQueueSize, - // threadCount // size_t initialThreadCount, - // ); - // for (int32_t threadId = 0; threadId < threadCount; ++threadId) { - // // static void threadEntry(size_t threadId, TSFN tsfn, int32_t callCount) - // { - - // finalizerData->threads.push_back( - // std::thread(threadEntry, threadId, _tsfn, callCounts[threadId])); - // } - return env.Undefined(); + Napi::Value GetContext(const CallbackInfo &) { + return _tsfn.GetContext()->Value(); + }; }; +} // namespace context -/** - * @brief Instance method `TSFNWrap#release` - * - * @param info - * @return undefined - */ -Napi::Value TSFNWrap::Release(const CallbackInfo &info) { - Napi::Env env = info.Env(); - return env.Undefined(); -}; -/** - * @brief Module initialization function - * - * @param env - * @return Object - */ Object InitThreadSafeFunctionExExample(Env env) { - return TSFNWrap::Init(env, Object::New(env)); + auto exports(Object::New(env)); + example::TSFNWrap::Init(env, exports, "example"); + return exports; } + #endif diff --git a/test/threadsafe_function_ex/test/example.js b/test/threadsafe_function_ex/test/example.js index 3aaf0c736..323777a7c 100644 --- a/test/threadsafe_function_ex/test/example.js +++ b/test/threadsafe_function_ex/test/example.js @@ -1,44 +1,20 @@ +// @ts-check 'use strict'; - -/** - * This test is programmatically represents the example shown in - * `doc/threadsafe_function_ex.md` - */ - const assert = require('assert'); -const buildType = 'Debug'; process.config.target_defaults.default_configuration; -const isCI = require.main !== module; -const print = (isError, ...what) => isCI ? () => {} : console[isError ? 'error' : 'log'].apply(console.log, what); -const log = (...what) => print(false, ...what); -const error = (...what) => print(true, ...what); +const { TestRunner } = require('../util/TestRunner'); -module.exports = Promise.all([ - // test(require(`../build/${buildType}/binding_noexcept.node`)), - isCI ? undefined : test(require(`../build/${buildType}/binding_noexcept.node`)) -]).catch(e => { - console.error('Error', e); -}); -async function test(binding) { - try { - const tsfn = new binding.threadsafe_function_ex_example.TSFNWrap(true); - await tsfn.start({ - threads: ['f',5,5,5], - callback: ()=>{} - }); - await tsfn.release(); -} catch (e) { - error(e); -} -} +class ExampleTest extends TestRunner { + async example({ TSFNWrap }) { + const ctx = {}; + const tsfn = new TSFNWrap(ctx); + assert(ctx === await tsfn.call(), "getContextByCall context not equal"); + assert(ctx === tsfn.getContext(), "getContextFromTsfn context not equal"); + return await tsfn.release(); + } -if (!isCI) { - console.log(module.exports); - module.exports.then(() => { - log('tea'); - }).catch((e) => { - error('Error!', e); - }); } + +module.exports = new ExampleTest('threadsafe_function_ex_example', __filename).start(); From a01c3c865276b2bee298141892a1f16d5c0bad19 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 14 Jun 2020 21:27:17 +0200 Subject: [PATCH 212/696] test: refactor the the 'empty' tsfnex test It should actually check for empty jsCallback --- test/threadsafe_function_ex/test/basic.cc | 34 +++++++++-------------- test/threadsafe_function_ex/test/basic.js | 13 ++------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/test/threadsafe_function_ex/test/basic.cc b/test/threadsafe_function_ex/test/basic.cc index c26ca0738..aa471dc91 100644 --- a/test/threadsafe_function_ex/test/basic.cc +++ b/test/threadsafe_function_ex/test/basic.cc @@ -1,6 +1,6 @@ -#include -#include "napi.h" #include "../util/util.h" +#include "napi.h" +#include #if (NAPI_VERSION > 3) @@ -60,12 +60,12 @@ class TSFNWrap : public base { Napi::Value Call(const CallbackInfo &info) { Napi::Env env = info.Env(); - DataType *data = new DataType{Napi::Reference(Persistent(info[0])), - Promise::Deferred::New(env)}; + DataType *data = + new DataType{Napi::Reference(Persistent(info[0])), + Promise::Deferred::New(env)}; _tsfn.NonBlockingCall(data); return data->deferred.Promise(); }; - }; } // namespace call @@ -145,18 +145,17 @@ using ContextType = std::nullptr_t; // Data passed (as pointer) to [Non]BlockingCall struct DataType { Promise::Deferred deferred; - bool reject; }; // CallJs callback function -static void CallJs(Napi::Env env, Function jsCallback, ContextType * /*context*/, - DataType *data) { +static void CallJs(Napi::Env env, Function jsCallback, + ContextType * /*context*/, DataType *data) { if (env != nullptr) { if (data != nullptr) { - if (data->reject) { - data->deferred.Reject(env.Undefined()); + if (jsCallback.IsEmpty()) { + data->deferred.Resolve(Boolean::New(env, true)); } else { - data->deferred.Resolve(env.Undefined()); + data->deferred.Reject(String::New(env, "jsCallback is not empty")); } } } @@ -193,14 +192,7 @@ class TSFNWrap : public base { } Napi::Value Call(const CallbackInfo &info) { - if (info.Length() == 0 || !info[0].IsBoolean()) { - NAPI_THROW( - Napi::TypeError::New(info.Env(), "Expected argument 0 to be boolean"), - Value()); - } - - auto *data = - new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; + auto data = new DataType{Promise::Deferred::New(info.Env())}; _tsfn.NonBlockingCall(data); return data->deferred.Promise(); }; @@ -323,8 +315,8 @@ namespace simple { using ContextType = std::nullptr_t; -// Full type of our ThreadSafeFunctionEx. We don't specify the `ContextType` here -// (even though the _default_ for the type argument is `std::nullptr_t`) to +// Full type of our ThreadSafeFunctionEx. We don't specify the `ContextType` +// here (even though the _default_ for the type argument is `std::nullptr_t`) to // demonstrate construction with no type arguments. using TSFN = ThreadSafeFunctionEx<>; diff --git a/test/threadsafe_function_ex/test/basic.js b/test/threadsafe_function_ex/test/basic.js index bcb1cdc8a..432856ddf 100644 --- a/test/threadsafe_function_ex/test/basic.js +++ b/test/threadsafe_function_ex/test/basic.js @@ -54,22 +54,13 @@ class BasicTest extends TestRunner { * that handles all of its JavaScript processing on the callJs instead of the * callback. * - Creates a threadsafe function with no JavaScript context or callback. - * - Makes two calls, waiting for each, and expecting the first to resolve - * and the second to reject. + * - Makes one call, waiting for completion. The internal `CallJs` resolves the call if jsCallback is empty, otherwise rejects. */ async empty({ TSFNWrap }) { debugger; if (typeof TSFNWrap === 'function') { const tsfn = new TSFNWrap(); - await tsfn.call(false /* reject */); - let caught = false; - try { - await tsfn.call(true /* reject */); - } catch (ex) { - caught = true; - } - - assert.ok(caught, 'The promise rejection was not caught'); + await tsfn.call(); return await tsfn.release(); } return true; From b0e7817f28bec975ea435937ba50d79a16037f5a Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 14 Jun 2020 22:46:12 +0200 Subject: [PATCH 213/696] basic multi-threading --- test/threadsafe_function_ex/test/example.cc | 270 ++++++++++++++++++-- test/threadsafe_function_ex/test/example.js | 6 +- 2 files changed, 252 insertions(+), 24 deletions(-) diff --git a/test/threadsafe_function_ex/test/example.cc b/test/threadsafe_function_ex/test/example.cc index a20d9ecae..b97f0e826 100644 --- a/test/threadsafe_function_ex/test/example.cc +++ b/test/threadsafe_function_ex/test/example.cc @@ -1,6 +1,37 @@ -#include -#include "napi.h" #include "../util/util.h" +#include "napi.h" +#include +#include +#include +#include +#include + +static constexpr auto DEFAULT_THREAD_COUNT = 10U; +static constexpr auto DEFAULT_CALL_COUNT = 2; + + + static struct { + bool logCall = true; // Uses JS console.log to output when the TSFN is + // processing the NonBlockingCall(). + bool logThread = false; // Uses native std::cout to output when the thread's + // NonBlockingCall() request has finished. + } DefaultOptions; // Options from Start() + +/** + * @brief Macro used specifically to support the dual CI test / documentation + * example setup. Exceptions are always thrown as JavaScript exceptions when + * running in example mode. + * + */ +#define TSFN_THROW(tsfnWrap, e, ...) \ + if (tsfnWrap->cppExceptions) { \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ + } while (0); \ + } else { \ + NAPI_THROW(e, __VA_ARGS__); \ + } #if (NAPI_VERSION > 3) @@ -8,18 +39,26 @@ using namespace Napi; namespace example { +class TSFNWrap; + // Context of the TSFN. -using Context = Reference; +using Context = TSFNWrap; + +using CompletionHandler = std::function; // Data passed (as pointer) to [Non]BlockingCall -using DataType = Promise::Deferred; +struct DataType { + // Promise::Deferred; + // CompletionHandler handler; + std::future deferred; +}; // CallJs callback function static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, Context *context, DataType *data) { if (env != nullptr) { if (data != nullptr) { - data->Resolve(context->Value()); + // data->Resolve(context->Value()); } } if (data != nullptr) { @@ -30,16 +69,168 @@ static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, // Full type of the ThreadSafeFunctionEx using TSFN = ThreadSafeFunctionEx; -class TSFNWrap; +struct FinalizerDataType { + std::vector threads; + std::unique_ptr deferred; + // struct { + // bool logCall = true; // Uses JS console.log to output when the TSFN is + // // processing the NonBlockingCall(). + // bool logThread = false; // Uses native std::cout to output when the thread's + // // NonBlockingCall() request has finished. + // } options; // Options from Start() +}; + +static void threadEntry(size_t threadId, TSFN tsfn, uint32_t callCount, + bool logThread) { + using namespace std::chrono_literals; + for (auto i = 0U; i < callCount; ++i) { + // auto callData = new DataType(); + // tsfn.NonBlockingCall(callData); + // auto result = callData->deferred.get(); + // if (logThread) { + // std::cout << "Thread " << threadId << " got result " << result << "\n"; + // } + + // std::this_thread::sleep_for(50ms * threadId); + } + std::cout << "Thread " << threadId << "finished\n"; + tsfn.Release(); +} + using base = tsfnutil::TSFNWrapBase; // A JS-accessible wrap that holds the TSFN. class TSFNWrap : public base { public: TSFNWrap(const CallbackInfo &info) : base(info) { + if (info.Length() > 0 && info[0].IsObject()) { + auto arg0 = info[0].ToObject(); + if (arg0.Has("cppExceptions")) { + auto cppExceptions = arg0.Get("cppExceptions"); + if (cppExceptions.IsBoolean()) { + cppExceptions = cppExceptions.As(); + } else { + // We explicitly use the addon's except/noexcept settings here, since + // we don't have a valid setting. + Napi::TypeError::New(Env(), "cppExceptions is not a boolean") + .ThrowAsJavaScriptException(); + } + } + } + } + ~TSFNWrap() { + for (auto& thread : finalizerData->threads) { + if (thread.joinable()) { + thread.join(); + } + } + } + + static std::array, 3> InstanceMethods() { + return {InstanceMethod("call", &TSFNWrap::Call), + InstanceMethod("start", &TSFNWrap::Start), + InstanceMethod("release", &TSFNWrap::Release)}; + } + + bool cppExceptions = false; + std::shared_ptr finalizerData; + + Napi::Value Start(const CallbackInfo &info) { Napi::Env env = info.Env(); - Context *context = new Context(Persistent(info[0])); + if (_tsfn) { + TSFN_THROW(this, Napi::Error::New(Env(), "TSFN already exists."), + Value()); + } + + // Creates a list to hold how many times each thread should make a call. + std::vector callCounts; + + // The JS-provided callback to execute for each call (if provided) + Function callback; + + // std::unique_ptr finalizerData = + // std::make_unique(); + + // finalizerData = std::shared_ptr(FinalizerDataType{ std::vector() , Promise::Deferred::New(env) }); + + finalizerData = std::make_shared(); + + + bool logThread = DefaultOptions.logThread; + bool logCall = DefaultOptions.logCall; + + if (info.Length() > 0 && info[0].IsObject()) { + auto arg0 = info[0].ToObject(); + if (arg0.Has("threads")) { + Napi::Value threads = arg0.Get("threads"); + if (threads.IsArray()) { + Napi::Array threadsArray = threads.As(); + for (auto i = 0U; i < threadsArray.Length(); ++i) { + Napi::Value elem = threadsArray.Get(i); + if (elem.IsNumber()) { + callCounts.push_back(elem.As().Int32Value()); + } else { + TSFN_THROW(this, Napi::TypeError::New(Env(), "Invalid arguments"), + Value()); + } + } + } else if (threads.IsNumber()) { + auto threadCount = threads.As().Int32Value(); + for (auto i = 0; i < threadCount; ++i) { + callCounts.push_back(DEFAULT_CALL_COUNT); + } + } else { + TSFN_THROW(this, Napi::TypeError::New(Env(), "Invalid arguments"), + Value()); + } + } + + if (arg0.Has("callback")) { + auto cb = arg0.Get("callback"); + if (cb.IsFunction()) { + callback = cb.As(); + } else { + TSFN_THROW(this, + Napi::TypeError::New(Env(), "Callback is not a function"), + Value()); + } + } + + if (arg0.Has("logCall")) { + auto logCallOption = arg0.Get("logCall"); + if (logCallOption.IsBoolean()) { + logCall = logCallOption.As(); + } else { + TSFN_THROW(this, + Napi::TypeError::New(Env(), "logCall is not a boolean"), + Value()); + } + } + + if (arg0.Has("logThread")) { + auto logThreadOption = arg0.Get("logThread"); + if (logThreadOption.IsBoolean()) { + logThread = logThreadOption.As(); + } else { + TSFN_THROW(this, + Napi::TypeError::New(Env(), "logThread is not a boolean"), + Value()); + } + } + } + + + // Apply default arguments + if (callCounts.size() == 0) { + for (auto i = 0U; i < DEFAULT_THREAD_COUNT; ++i) { + callCounts.push_back(DEFAULT_CALL_COUNT); + } + } + + const auto threadCount = callCounts.size(); + + auto *finalizerDataPtr = new std::shared_ptr(finalizerData); _tsfn = TSFN::New( env, // napi_env env, @@ -47,31 +238,67 @@ class TSFNWrap : public base { Value(), // const Object& resource, "Test", // ResourceString resourceName, 0, // size_t maxQueueSize, - 1, // size_t initialThreadCount, - context, // Context* context, - base::Finalizer, // Finalizer finalizer - &_deferred // FinalizerDataType data + threadCount + 1, // size_t initialThreadCount, +1 for Node thread + this, // Context* context, + Finalizer, // Finalizer finalizer + finalizerDataPtr // FinalizerDataType* data ); - } - static std::array, 3> InstanceMethods() { - return {InstanceMethod("call", &TSFNWrap::Call), - InstanceMethod("getContext", &TSFNWrap::GetContext), - InstanceMethod("release", &TSFNWrap::Release)}; + for (auto threadId = 0U; threadId < threadCount; ++threadId) { + finalizerData->threads.push_back( + std::thread(threadEntry, threadId, _tsfn, callCounts[threadId], + logThread)); + } + + + return String::New(env, "started"); + }; + + // TSFN finalizer. Resolves the Promise returned by `Release()` above. + static void Finalizer(Napi::Env env, std::shared_ptr *finalizeData, + Context *ctx) { + // for (auto thread : finalizeData->threads) { + + for (auto &thread : (*finalizeData)->threads) { + std::cout << "Finalizer joining thread\n"; + if (thread.joinable()) { + thread.join(); + } + } + + delete finalizeData; + + // } + // if (deferred->get()) { + // (*deferred)->Resolve(Boolean::New(env, true)); + // deferred->release(); + + // } } + Napi::Value Release(const CallbackInfo &info) { + if (finalizerData->deferred) { + return finalizerData->deferred->Promise(); + } + // return finalizerData->deferred.Promise(); + auto env = info.Env(); + finalizerData->deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); + _tsfn.Release(); + return finalizerData->deferred->Promise(); + }; + Napi::Value Call(const CallbackInfo &info) { - auto *callData = new DataType(info.Env()); - _tsfn.NonBlockingCall(callData); - return callData->Promise(); + // auto *callData = new DataType(info.Env()); + // _tsfn.NonBlockingCall(callData); + // return callData->Promise(); + return info.Env().Undefined(); }; Napi::Value GetContext(const CallbackInfo &) { return _tsfn.GetContext()->Value(); }; }; -} // namespace context - +} // namespace example Object InitThreadSafeFunctionExExample(Env env) { auto exports(Object::New(env)); @@ -79,5 +306,4 @@ Object InitThreadSafeFunctionExExample(Env env) { return exports; } - #endif diff --git a/test/threadsafe_function_ex/test/example.js b/test/threadsafe_function_ex/test/example.js index 323777a7c..3328371be 100644 --- a/test/threadsafe_function_ex/test/example.js +++ b/test/threadsafe_function_ex/test/example.js @@ -9,9 +9,11 @@ class ExampleTest extends TestRunner { async example({ TSFNWrap }) { const ctx = {}; + console.log("starting"); const tsfn = new TSFNWrap(ctx); - assert(ctx === await tsfn.call(), "getContextByCall context not equal"); - assert(ctx === tsfn.getContext(), "getContextFromTsfn context not equal"); + console.log("tsfn is", tsfn); + console.log("start is", tsfn.start()); + console.log(); return await tsfn.release(); } From 44adeea1bd66ce4ea8876506d34d3602b4eed7e3 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Mon, 15 Jun 2020 00:02:09 +0200 Subject: [PATCH 214/696] test: wip with example --- test/threadsafe_function_ex/test/example.cc | 146 ++++++++---------- test/threadsafe_function_ex/test/example.js | 15 +- .../threadsafe_function_ex/util/TestRunner.js | 3 +- 3 files changed, 79 insertions(+), 85 deletions(-) diff --git a/test/threadsafe_function_ex/test/example.cc b/test/threadsafe_function_ex/test/example.cc index b97f0e826..a7c401aaf 100644 --- a/test/threadsafe_function_ex/test/example.cc +++ b/test/threadsafe_function_ex/test/example.cc @@ -9,13 +9,12 @@ static constexpr auto DEFAULT_THREAD_COUNT = 10U; static constexpr auto DEFAULT_CALL_COUNT = 2; - - static struct { - bool logCall = true; // Uses JS console.log to output when the TSFN is - // processing the NonBlockingCall(). - bool logThread = false; // Uses native std::cout to output when the thread's - // NonBlockingCall() request has finished. - } DefaultOptions; // Options from Start() +static struct { + bool logCall = true; // Uses JS console.log to output when the TSFN is + // processing the NonBlockingCall(). + bool logThread = false; // Uses native std::cout to output when the thread's + // NonBlockingCall() request has finished. +} DefaultOptions; // Options from Start() /** * @brief Macro used specifically to support the dual CI test / documentation @@ -44,59 +43,26 @@ class TSFNWrap; // Context of the TSFN. using Context = TSFNWrap; -using CompletionHandler = std::function; - // Data passed (as pointer) to [Non]BlockingCall -struct DataType { - // Promise::Deferred; - // CompletionHandler handler; - std::future deferred; -}; +using DataType = std::unique_ptr>; // CallJs callback function static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, Context *context, DataType *data) { - if (env != nullptr) { - if (data != nullptr) { - // data->Resolve(context->Value()); - } - } if (data != nullptr) { - delete data; + if (env != nullptr) { + (*data)->set_value(clock()); + } else { + (*data)->set_exception(std::make_exception_ptr( + std::runtime_error("TSFN has been finalized."))); + } } + // We do NOT delete data as it is a unique_ptr held by the calling thread. } // Full type of the ThreadSafeFunctionEx using TSFN = ThreadSafeFunctionEx; -struct FinalizerDataType { - std::vector threads; - std::unique_ptr deferred; - // struct { - // bool logCall = true; // Uses JS console.log to output when the TSFN is - // // processing the NonBlockingCall(). - // bool logThread = false; // Uses native std::cout to output when the thread's - // // NonBlockingCall() request has finished. - // } options; // Options from Start() -}; - -static void threadEntry(size_t threadId, TSFN tsfn, uint32_t callCount, - bool logThread) { - using namespace std::chrono_literals; - for (auto i = 0U; i < callCount; ++i) { - // auto callData = new DataType(); - // tsfn.NonBlockingCall(callData); - // auto result = callData->deferred.get(); - // if (logThread) { - // std::cout << "Thread " << threadId << " got result " << result << "\n"; - // } - - // std::this_thread::sleep_for(50ms * threadId); - } - std::cout << "Thread " << threadId << "finished\n"; - tsfn.Release(); -} - using base = tsfnutil::TSFNWrapBase; // A JS-accessible wrap that holds the TSFN. @@ -119,13 +85,40 @@ class TSFNWrap : public base { } } ~TSFNWrap() { - for (auto& thread : finalizerData->threads) { + for (auto &thread : finalizerData->threads) { if (thread.joinable()) { thread.join(); } } } + struct FinalizerDataType { + std::vector threads; + std::unique_ptr deferred; + }; + + // The finalizer data is shared, because we want to join the threads if our + // TSFNWrap object gets garbage-collected and there are still active threads. + using SharedFinalizerDataType = std::shared_ptr; + + static void threadEntry(size_t threadId, TSFN tsfn, uint32_t callCount, + bool logThread) { + using namespace std::chrono_literals; + for (auto i = 0U; i < callCount; ++i) { + auto promise = std::make_unique>(); + tsfn.NonBlockingCall(&promise); + auto future = promise->get_future(); + auto result = future.get(); + if (logThread) { + std::cout << "Thread " << threadId << " got result " << result << "\n"; + } + } + if (logThread) { + std::cout << "Thread " << threadId << "finished\n"; + } + tsfn.Release(); + } + static std::array, 3> InstanceMethods() { return {InstanceMethod("call", &TSFNWrap::Call), InstanceMethod("start", &TSFNWrap::Start), @@ -133,6 +126,7 @@ class TSFNWrap : public base { } bool cppExceptions = false; + bool logThread; std::shared_ptr finalizerData; Napi::Value Start(const CallbackInfo &info) { @@ -149,15 +143,9 @@ class TSFNWrap : public base { // The JS-provided callback to execute for each call (if provided) Function callback; - // std::unique_ptr finalizerData = - // std::make_unique(); - - // finalizerData = std::shared_ptr(FinalizerDataType{ std::vector() , Promise::Deferred::New(env) }); - finalizerData = std::make_shared(); - - bool logThread = DefaultOptions.logThread; + logThread = DefaultOptions.logThread; bool logCall = DefaultOptions.logCall; if (info.Length() > 0 && info[0].IsObject()) { @@ -220,7 +208,6 @@ class TSFNWrap : public base { } } - // Apply default arguments if (callCounts.size() == 0) { for (auto i = 0U; i < DEFAULT_THREAD_COUNT; ++i) { @@ -230,7 +217,7 @@ class TSFNWrap : public base { const auto threadCount = callCounts.size(); - auto *finalizerDataPtr = new std::shared_ptr(finalizerData); + auto *finalizerDataPtr = new SharedFinalizerDataType(finalizerData); _tsfn = TSFN::New( env, // napi_env env, @@ -238,42 +225,40 @@ class TSFNWrap : public base { Value(), // const Object& resource, "Test", // ResourceString resourceName, 0, // size_t maxQueueSize, - threadCount + 1, // size_t initialThreadCount, +1 for Node thread - this, // Context* context, - Finalizer, // Finalizer finalizer + threadCount + 1, // size_t initialThreadCount, +1 for Node thread + this, // Context* context, + Finalizer, // Finalizer finalizer finalizerDataPtr // FinalizerDataType* data ); for (auto threadId = 0U; threadId < threadCount; ++threadId) { - finalizerData->threads.push_back( - std::thread(threadEntry, threadId, _tsfn, callCounts[threadId], - logThread)); + finalizerData->threads.push_back(std::thread( + threadEntry, threadId, _tsfn, callCounts[threadId], logThread)); } - return String::New(env, "started"); }; - // TSFN finalizer. Resolves the Promise returned by `Release()` above. - static void Finalizer(Napi::Env env, std::shared_ptr *finalizeData, + // TSFN finalizer. Joins the threads and resolves the Promise returned by + // `Release()` above. + static void Finalizer(Napi::Env env, SharedFinalizerDataType *finalizeData, Context *ctx) { - // for (auto thread : finalizeData->threads) { + if (ctx->logThread) { + std::cout << "Finalizer joining threads\n"; + } for (auto &thread : (*finalizeData)->threads) { - std::cout << "Finalizer joining thread\n"; if (thread.joinable()) { thread.join(); } } + ctx->clearTSFN(); + if (ctx->logThread) { + std::cout << "Finished\n"; + } + (*finalizeData)->deferred->Resolve(Boolean::New(env, true)); delete finalizeData; - - // } - // if (deferred->get()) { - // (*deferred)->Resolve(Boolean::New(env, true)); - // deferred->release(); - - // } } Napi::Value Release(const CallbackInfo &info) { @@ -282,21 +267,24 @@ class TSFNWrap : public base { } // return finalizerData->deferred.Promise(); auto env = info.Env(); - finalizerData->deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); + finalizerData->deferred.reset( + new Promise::Deferred(Promise::Deferred::New(env))); _tsfn.Release(); return finalizerData->deferred->Promise(); }; Napi::Value Call(const CallbackInfo &info) { // auto *callData = new DataType(info.Env()); - // _tsfn.NonBlockingCall(callData); - // return callData->Promise(); + // _tsfn.NonBlockingCall(callData); return callData->Promise(); return info.Env().Undefined(); }; Napi::Value GetContext(const CallbackInfo &) { return _tsfn.GetContext()->Value(); }; + + // This does not run on the node thread. + void clearTSFN() { _tsfn = TSFN(); } }; } // namespace example diff --git a/test/threadsafe_function_ex/test/example.js b/test/threadsafe_function_ex/test/example.js index 3328371be..eadea4f9e 100644 --- a/test/threadsafe_function_ex/test/example.js +++ b/test/threadsafe_function_ex/test/example.js @@ -9,12 +9,17 @@ class ExampleTest extends TestRunner { async example({ TSFNWrap }) { const ctx = {}; - console.log("starting"); const tsfn = new TSFNWrap(ctx); - console.log("tsfn is", tsfn); - console.log("start is", tsfn.start()); - console.log(); - return await tsfn.release(); + const run = async (i) => { + const result = tsfn.start({threads:[1]}); + await result; + return await tsfn.release(); + }; + const results = [ await run(1) ]; + results.push( await run(2) ); + return results; + // return await Promise.all( [ run(1), run(2) ] ); + // await run(2); } } diff --git a/test/threadsafe_function_ex/util/TestRunner.js b/test/threadsafe_function_ex/util/TestRunner.js index b7e99c1b4..6a7957a5c 100644 --- a/test/threadsafe_function_ex/util/TestRunner.js +++ b/test/threadsafe_function_ex/util/TestRunner.js @@ -56,6 +56,7 @@ class TestRunner { // Run tests in both except and noexcept await this.run(false); await this.run(true); + console.log("ALL DONE"); } catch (ex) { console.error(`Test failed!`, ex); process.exit(1); @@ -95,7 +96,7 @@ class TestRunner { const [label, time, isNoExcept, nsName, returnValue] = state; const except = () => pad(isNoExcept ? '[noexcept]' : '', 12); const timeStr = () => time == null ? '...' : `${time}${typeof time === 'number' ? 'ms' : ''}`; - return `${pad(nsName, 10)} ${except()}| ${pad(timeStr(), 8)}| ${pad(label, 15)}${returnValue === undefined ? '' : `(return: ${returnValue})`}`; + return `${pad(nsName, 10)} ${except()}| ${pad(timeStr(), 8)}| ${pad(label, 15)}${returnValue === undefined ? '' : `(return: ${JSON.stringify(returnValue)})`}`; }; /** From c20685be7a7678d9092f82bb7a058f3b5fb398b7 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Mon, 15 Jun 2020 01:22:24 +0200 Subject: [PATCH 215/696] test: wip with example test --- test/threadsafe_function_ex/test/example.cc | 96 ++++++++++++------- test/threadsafe_function_ex/test/example.js | 37 ++++--- .../threadsafe_function_ex/util/TestRunner.js | 1 - 3 files changed, 86 insertions(+), 48 deletions(-) diff --git a/test/threadsafe_function_ex/test/example.cc b/test/threadsafe_function_ex/test/example.cc index a7c401aaf..b47d684ad 100644 --- a/test/threadsafe_function_ex/test/example.cc +++ b/test/threadsafe_function_ex/test/example.cc @@ -43,17 +43,22 @@ class TSFNWrap; // Context of the TSFN. using Context = TSFNWrap; -// Data passed (as pointer) to [Non]BlockingCall -using DataType = std::unique_ptr>; +struct Data { + // Data passed (as pointer) to [Non]BlockingCall + std::promise promise; + uint32_t base; +}; +using DataType = std::unique_ptr; // CallJs callback function static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, - Context *context, DataType *data) { - if (data != nullptr) { + Context *context, DataType *dataPtr) { + if (dataPtr != nullptr) { + auto &data = *dataPtr; if (env != nullptr) { - (*data)->set_value(clock()); + data->promise.set_value(data->base * data->base); } else { - (*data)->set_exception(std::make_exception_ptr( + data->promise.set_exception(std::make_exception_ptr( std::runtime_error("TSFN has been finalized."))); } } @@ -92,42 +97,54 @@ class TSFNWrap : public base { } } - struct FinalizerDataType { + struct FinalizerData { std::vector threads; std::unique_ptr deferred; }; // The finalizer data is shared, because we want to join the threads if our // TSFNWrap object gets garbage-collected and there are still active threads. - using SharedFinalizerDataType = std::shared_ptr; + using FinalizerDataType = std::shared_ptr; +#define THREADLOG(X) if (context->logThread) {\ +std::cout << X;\ +} static void threadEntry(size_t threadId, TSFN tsfn, uint32_t callCount, - bool logThread) { + Context *context) { using namespace std::chrono_literals; + + THREADLOG("Thread " << threadId << " starting...\n") + for (auto i = 0U; i < callCount; ++i) { - auto promise = std::make_unique>(); - tsfn.NonBlockingCall(&promise); - auto future = promise->get_future(); + auto data = std::make_unique(); + data->base = threadId + 1; + THREADLOG("Thread " << threadId << " making call, base = " << data->base << "\n") + + tsfn.NonBlockingCall(&data); + auto future = data->promise.get_future(); auto result = future.get(); - if (logThread) { - std::cout << "Thread " << threadId << " got result " << result << "\n"; - } - } - if (logThread) { - std::cout << "Thread " << threadId << "finished\n"; + context->callSucceeded(result); + THREADLOG("Thread " << threadId << " got result: " << result << "\n") } + + THREADLOG("Thread " << threadId << " finished.\n\n") tsfn.Release(); } +#undef THREADLOG - static std::array, 3> InstanceMethods() { - return {InstanceMethod("call", &TSFNWrap::Call), + static std::array, 4> InstanceMethods() { + return {InstanceMethod("getContext", &TSFNWrap::GetContext), InstanceMethod("start", &TSFNWrap::Start), + InstanceMethod("callCount", &TSFNWrap::CallCount), InstanceMethod("release", &TSFNWrap::Release)}; } bool cppExceptions = false; bool logThread; - std::shared_ptr finalizerData; + std::atomic_uint succeededCalls; + std::atomic_int aggregate; + + FinalizerDataType finalizerData; Napi::Value Start(const CallbackInfo &info) { Napi::Env env = info.Env(); @@ -143,7 +160,7 @@ class TSFNWrap : public base { // The JS-provided callback to execute for each call (if provided) Function callback; - finalizerData = std::make_shared(); + finalizerData = std::make_shared(); logThread = DefaultOptions.logThread; bool logCall = DefaultOptions.logCall; @@ -217,8 +234,10 @@ class TSFNWrap : public base { const auto threadCount = callCounts.size(); - auto *finalizerDataPtr = new SharedFinalizerDataType(finalizerData); + auto *finalizerDataPtr = new FinalizerDataType(finalizerData); + succeededCalls = 0; + aggregate = 0; _tsfn = TSFN::New( env, // napi_env env, TSFN::DefaultFunctionFactory(env), // const Function& callback, @@ -232,8 +251,8 @@ class TSFNWrap : public base { ); for (auto threadId = 0U; threadId < threadCount; ++threadId) { - finalizerData->threads.push_back(std::thread( - threadEntry, threadId, _tsfn, callCounts[threadId], logThread)); + finalizerData->threads.push_back(std::thread(threadEntry, threadId, _tsfn, + callCounts[threadId], this)); } return String::New(env, "started"); @@ -241,7 +260,7 @@ class TSFNWrap : public base { // TSFN finalizer. Joins the threads and resolves the Promise returned by // `Release()` above. - static void Finalizer(Napi::Env env, SharedFinalizerDataType *finalizeData, + static void Finalizer(Napi::Env env, FinalizerDataType *finalizeData, Context *ctx) { if (ctx->logThread) { @@ -254,7 +273,7 @@ class TSFNWrap : public base { } ctx->clearTSFN(); if (ctx->logThread) { - std::cout << "Finished\n"; + std::cout << "Finished finalizing threads.\n"; } (*finalizeData)->deferred->Resolve(Boolean::New(env, true)); @@ -265,26 +284,33 @@ class TSFNWrap : public base { if (finalizerData->deferred) { return finalizerData->deferred->Promise(); } - // return finalizerData->deferred.Promise(); - auto env = info.Env(); finalizerData->deferred.reset( - new Promise::Deferred(Promise::Deferred::New(env))); + new Promise::Deferred(Promise::Deferred::New(info.Env()))); _tsfn.Release(); return finalizerData->deferred->Promise(); }; - Napi::Value Call(const CallbackInfo &info) { - // auto *callData = new DataType(info.Env()); - // _tsfn.NonBlockingCall(callData); return callData->Promise(); - return info.Env().Undefined(); + Napi::Value CallCount(const CallbackInfo &info) { + Napi::Env env(info.Env()); + + auto results = Array::New(env, 2); + results.Set("0", Number::New(env, succeededCalls)); + results.Set("1", Number::New(env, aggregate)); + return results; }; Napi::Value GetContext(const CallbackInfo &) { return _tsfn.GetContext()->Value(); }; - // This does not run on the node thread. + // This method does not run on the Node thread. void clearTSFN() { _tsfn = TSFN(); } + + // This method does not run on the Node thread. + void callSucceeded(int result) { + succeededCalls++; + aggregate += result; + } }; } // namespace example diff --git a/test/threadsafe_function_ex/test/example.js b/test/threadsafe_function_ex/test/example.js index eadea4f9e..53b6783cf 100644 --- a/test/threadsafe_function_ex/test/example.js +++ b/test/threadsafe_function_ex/test/example.js @@ -8,18 +8,31 @@ const { TestRunner } = require('../util/TestRunner'); class ExampleTest extends TestRunner { async example({ TSFNWrap }) { - const ctx = {}; - const tsfn = new TSFNWrap(ctx); - const run = async (i) => { - const result = tsfn.start({threads:[1]}); - await result; - return await tsfn.release(); - }; - const results = [ await run(1) ]; - results.push( await run(2) ); - return results; - // return await Promise.all( [ run(1), run(2) ] ); - // await run(2); + const tsfn = new TSFNWrap(); + + const threads = [1]; //, 2, 2, 5, 12]; + const started = await tsfn.start({ threads, logThread: true }); + + /** + * Calculate the expected results. + */ + const expected = threads.reduce((p, threadCallCount, threadId) => ( + ++threadId, + p[0] += threadCallCount, + p[1] += threadCallCount * threadId ** 2, + p + ), [0, 0]); + + if (started) { + const released = await tsfn.release(); + const [callCountActual, aggregateActual] = tsfn.callCount(); + const [callCountExpected, aggregateExpected] = expected; + assert(callCountActual == callCountExpected, `The number of calls do not match: actual = ${callCountActual}, expected = ${callCountExpected}`); + assert(aggregateActual == aggregateExpected, `The aggregate of calls do not match: actual = ${aggregateActual}, expected = ${aggregateExpected}`); + return expected; + } else { + throw new Error('The TSFN failed to start'); + } } } diff --git a/test/threadsafe_function_ex/util/TestRunner.js b/test/threadsafe_function_ex/util/TestRunner.js index 6a7957a5c..e01b42480 100644 --- a/test/threadsafe_function_ex/util/TestRunner.js +++ b/test/threadsafe_function_ex/util/TestRunner.js @@ -56,7 +56,6 @@ class TestRunner { // Run tests in both except and noexcept await this.run(false); await this.run(true); - console.log("ALL DONE"); } catch (ex) { console.error(`Test failed!`, ex); process.exit(1); From 3b24e7431a53b8091c9b597f24a222392f2a8728 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Mon, 15 Jun 2020 03:21:30 +0200 Subject: [PATCH 216/696] src: consolidate duplicated tsfnex code --- napi-inl.h | 119 ++++++----------------------------------------------- napi.h | 50 +++------------------- 2 files changed, 18 insertions(+), 151 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index c5e262555..959cffa36 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -213,6 +213,14 @@ static inline CallJsWrapper(napi_env env, napi_value jsCallback, void * /*contex Function(env, jsCallback).Call(0, nullptr); } } + +template +typename ThreadSafeFunctionEx<>::DefaultFunctionType +DefaultCallbackWrapper( + napi_env env, CallbackType cb) { + return ThreadSafeFunctionEx<>::DefaultFunctionFactory(env); +} + #endif template @@ -4342,28 +4350,6 @@ ThreadSafeFunctionEx::New( return tsfn; } -// static, with Callback [nullptr] Resource [missing] Finalizer [missing] -template -template -inline ThreadSafeFunctionEx -ThreadSafeFunctionEx::New( - napi_env env, std::nullptr_t callback, ResourceString resourceName, size_t maxQueueSize, - size_t initialThreadCount, ContextType *context) { - ThreadSafeFunctionEx tsfn; - - napi_status status = napi_create_threadsafe_function( - env, nullptr, nullptr, String::From(env, resourceName), maxQueueSize, - initialThreadCount, nullptr, nullptr, context, - CallJsInternal, &tsfn._tsfn); - if (status != napi_ok) { - NAPI_THROW_IF_FAILED(env, status, - ThreadSafeFunctionEx()); - } - - return tsfn; -} - // static, with Callback [missing] Resource [passed] Finalizer [missing] template @@ -4386,28 +4372,6 @@ ThreadSafeFunctionEx::New( return tsfn; } -// static, with Callback [nullptr] Resource [passed] Finalizer [missing] -template -template -inline ThreadSafeFunctionEx -ThreadSafeFunctionEx::New( - napi_env env, std::nullptr_t callback, const Object &resource, ResourceString resourceName, - size_t maxQueueSize, size_t initialThreadCount, ContextType *context) { - ThreadSafeFunctionEx tsfn; - - napi_status status = napi_create_threadsafe_function( - env, nullptr, resource, String::From(env, resourceName), maxQueueSize, - initialThreadCount, nullptr, nullptr, context, CallJsInternal, - &tsfn._tsfn); - if (status != napi_ok) { - NAPI_THROW_IF_FAILED(env, status, - ThreadSafeFunctionEx()); - } - - return tsfn; -} - // static, with Callback [missing] Resource [missing] Finalizer [passed] template @@ -4438,36 +4402,6 @@ ThreadSafeFunctionEx::New( return tsfn; } -// static, with Callback [nullptr] Resource [missing] Finalizer [passed] -template -template -inline ThreadSafeFunctionEx -ThreadSafeFunctionEx::New( - napi_env env, std::nullptr_t callback, ResourceString resourceName, size_t maxQueueSize, - size_t initialThreadCount, ContextType *context, Finalizer finalizeCallback, - FinalizerDataType *data) { - ThreadSafeFunctionEx tsfn; - - auto *finalizeData = new details::ThreadSafeFinalize( - {data, finalizeCallback}); - napi_status status = napi_create_threadsafe_function( - env, nullptr, nullptr, String::From(env, resourceName), maxQueueSize, - initialThreadCount, finalizeData, - details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext, - context, CallJsInternal, &tsfn._tsfn); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, - ThreadSafeFunctionEx()); - } - - return tsfn; -} - // static, with Callback [missing] Resource [passed] Finalizer [passed] template @@ -4497,36 +4431,6 @@ ThreadSafeFunctionEx::New( return tsfn; } - -// static, with Callback [nullptr] Resource [passed] Finalizer [passed] -template -template -inline ThreadSafeFunctionEx -ThreadSafeFunctionEx::New( - napi_env env, std::nullptr_t callback, const Object &resource, ResourceString resourceName, - size_t maxQueueSize, size_t initialThreadCount, ContextType *context, - Finalizer finalizeCallback, FinalizerDataType *data) { - ThreadSafeFunctionEx tsfn; - - auto *finalizeData = new details::ThreadSafeFinalize( - {data, finalizeCallback}); - napi_status status = napi_create_threadsafe_function( - env, nullptr, resource, String::From(env, resourceName), maxQueueSize, - initialThreadCount, finalizeData, - details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext, - context, CallJsInternal, &tsfn._tsfn); - if (status != napi_ok) { - delete finalizeData; - NAPI_THROW_IF_FAILED(env, status, - ThreadSafeFunctionEx()); - } - - return tsfn; -} #endif // static, with Callback [passed] Resource [missing] Finalizer [missing] @@ -4607,11 +4511,11 @@ ThreadSafeFunctionEx::New( // static, with: Callback [passed] Resource [passed] Finalizer [passed] template -template inline ThreadSafeFunctionEx ThreadSafeFunctionEx::New( - napi_env env, const Function &callback, const Object &resource, + napi_env env, CallbackType callback, const Object &resource, ResourceString resourceName, size_t maxQueueSize, size_t initialThreadCount, ContextType *context, Finalizer finalizeCallback, FinalizerDataType *data) { ThreadSafeFunctionEx tsfn; @@ -4620,7 +4524,7 @@ ThreadSafeFunctionEx::New( FinalizerDataType>( {data, finalizeCallback}); napi_status status = napi_create_threadsafe_function( - env, callback, resource, String::From(env, resourceName), maxQueueSize, + env, details::DefaultCallbackWrapper(env, callback), resource, String::From(env, resourceName), maxQueueSize, initialThreadCount, finalizeData, details::ThreadSafeFinalize:: FinalizeFinalizeWrapperWithDataAndContext, @@ -4743,6 +4647,7 @@ ThreadSafeFunctionEx::DefaultFunctionFactory( #endif } + //////////////////////////////////////////////////////////////////////////////// // ThreadSafeFunction class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 40e0d9359..1fc40bdda 100644 --- a/napi.h +++ b/napi.h @@ -2049,14 +2049,13 @@ namespace Napi { void (*CallJs)(Napi::Env, Napi::Function, ContextType *, DataType *) = nullptr> class ThreadSafeFunctionEx { - private: + + public: #if NAPI_VERSION > 4 using DefaultFunctionType = std::nullptr_t; #else using DefaultFunctionType = const Napi::Function; #endif - - public: // This API may only be called from the main thread. // Helper function that returns nullptr if running N-API 5+, otherwise a // non-empty, no-op Function. This provides the ability to specify at @@ -2064,6 +2063,7 @@ namespace Napi { // when targeting _any_ N-API version. static DefaultFunctionType DefaultFunctionFactory(Napi::Env env); + #if NAPI_VERSION > 4 // This API may only be called from the main thread. // Creates a new threadsafe function with: @@ -2073,14 +2073,6 @@ namespace Napi { New(napi_env env, ResourceString resourceName, size_t maxQueueSize, size_t initialThreadCount, ContextType *context = nullptr); - // This API may only be called from the main thread. - // Callback [nullptr] Resource [missing] Finalizer [missing] - template - static ThreadSafeFunctionEx - New(napi_env env, std::nullptr_t callback, ResourceString resourceName, - size_t maxQueueSize, size_t initialThreadCount, - ContextType *context = nullptr); - // This API may only be called from the main thread. // Creates a new threadsafe function with: // Callback [missing] Resource [passed] Finalizer [missing] @@ -2090,15 +2082,6 @@ namespace Napi { size_t maxQueueSize, size_t initialThreadCount, ContextType *context = nullptr); - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [nullptr] Resource [passed] Finalizer [missing] - template - static ThreadSafeFunctionEx - New(napi_env env, std::nullptr_t callback, const Object &resource, - ResourceString resourceName, size_t maxQueueSize, - size_t initialThreadCount, ContextType *context = nullptr); - // This API may only be called from the main thread. // Creates a new threadsafe function with: // Callback [missing] Resource [missing] Finalizer [passed] @@ -2109,16 +2092,6 @@ namespace Napi { size_t initialThreadCount, ContextType *context, Finalizer finalizeCallback, FinalizerDataType *data = nullptr); - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [nullptr] Resource [missing] Finalizer [passed] - template - static ThreadSafeFunctionEx - New(napi_env env, std::nullptr_t callback, ResourceString resourceName, - size_t maxQueueSize, size_t initialThreadCount, ContextType *context, - Finalizer finalizeCallback, FinalizerDataType *data = nullptr); - // This API may only be called from the main thread. // Creates a new threadsafe function with: // Callback [missing] Resource [passed] Finalizer [passed] @@ -2128,17 +2101,6 @@ namespace Napi { New(napi_env env, const Object &resource, ResourceString resourceName, size_t maxQueueSize, size_t initialThreadCount, ContextType *context, Finalizer finalizeCallback, FinalizerDataType *data = nullptr); - - // This API may only be called from the main thread. - // Creates a new threadsafe function with: - // Callback [nullptr] Resource [passed] Finalizer [passed] - template - static ThreadSafeFunctionEx - New(napi_env env, std::nullptr_t callback, const Object &resource, - ResourceString resourceName, size_t maxQueueSize, - size_t initialThreadCount, ContextType *context, - Finalizer finalizeCallback, FinalizerDataType *data = nullptr); #endif // This API may only be called from the main thread. @@ -2172,10 +2134,10 @@ namespace Napi { // This API may only be called from the main thread. // Creates a new threadsafe function with: // Callback [passed] Resource [passed] Finalizer [passed] - template + template static ThreadSafeFunctionEx - New(napi_env env, const Function &callback, const Object &resource, + New(napi_env env, CallbackType callback, const Object &resource, ResourceString resourceName, size_t maxQueueSize, size_t initialThreadCount, ContextType *context, Finalizer finalizeCallback, FinalizerDataType *data = nullptr); From a7a535284e4d2f067faa2d50ce99cc5924fb3323 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Mon, 15 Jun 2020 07:47:17 +0200 Subject: [PATCH 217/696] test: v4,v5+ tests pass --- napi-inl.h | 70 +++++++++++++++---- napi.h | 15 ++-- test/threadsafe_function_ex/test/basic.cc | 62 ++++++++-------- test/threadsafe_function_ex/test/example.cc | 62 +++++++++++----- test/threadsafe_function_ex/test/example.js | 32 ++++++--- .../threadsafe_function_ex/test/threadsafe.cc | 1 - test/threadsafe_function_ex/util/util.h | 2 +- 7 files changed, 165 insertions(+), 79 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 959cffa36..c200f971e 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -214,13 +214,27 @@ static inline CallJsWrapper(napi_env env, napi_value jsCallback, void * /*contex } } -template -typename ThreadSafeFunctionEx<>::DefaultFunctionType -DefaultCallbackWrapper( - napi_env env, CallbackType cb) { - return ThreadSafeFunctionEx<>::DefaultFunctionFactory(env); +#if NAPI_VERSION > 4 + +template +napi_value DefaultCallbackWrapper(napi_env /*env*/, std::nullptr_t /*cb*/) { + return nullptr; +} + +template +napi_value DefaultCallbackWrapper(napi_env /*env*/, Napi::Function cb) { + return cb; } +#else +template +napi_value DefaultCallbackWrapper(napi_env env, Napi::Function cb) { + if (cb.IsEmpty()) { + return TSFN::EmptyFunctionFactory(env); + } + return cb; +} +#endif #endif template @@ -4524,8 +4538,9 @@ ThreadSafeFunctionEx::New( FinalizerDataType>( {data, finalizeCallback}); napi_status status = napi_create_threadsafe_function( - env, details::DefaultCallbackWrapper(env, callback), resource, String::From(env, resourceName), maxQueueSize, - initialThreadCount, finalizeData, + env, details::DefaultCallbackWrapper>(env, callback), resource, + String::From(env, resourceName), maxQueueSize, initialThreadCount, + finalizeData, details::ThreadSafeFinalize:: FinalizeFinalizeWrapperWithDataAndContext, context, CallJsInternal, &tsfn._tsfn); @@ -4634,19 +4649,48 @@ void ThreadSafeFunctionEx::CallJsInternal( env, jsCallback, context, data); } +#if NAPI_VERSION == 4 // static template -typename ThreadSafeFunctionEx::DefaultFunctionType -ThreadSafeFunctionEx::DefaultFunctionFactory( +Napi::Function +ThreadSafeFunctionEx::EmptyFunctionFactory( Napi::Env env) { -#if NAPI_VERSION > 4 - return nullptr; + return Napi::Function::New(env, [](const CallbackInfo &cb) {}); +} + +// static +template +Napi::Function +ThreadSafeFunctionEx::FunctionOrEmpty( + Napi::Env env, Napi::Function &callback) { + if (callback.IsEmpty()) { + return EmptyFunctionFactory(env); + } + return callback; +} + #else - return Function::New(env, [](const CallbackInfo &cb) {}); -#endif +// static +template +std::nullptr_t +ThreadSafeFunctionEx::EmptyFunctionFactory( + Napi::Env /*env*/) { + return nullptr; } +// static +template +Napi::Function +ThreadSafeFunctionEx::FunctionOrEmpty( + Napi::Env /*env*/, Napi::Function &callback) { + return callback; +} + +#endif //////////////////////////////////////////////////////////////////////////////// // ThreadSafeFunction class diff --git a/napi.h b/napi.h index 1fc40bdda..1644632f6 100644 --- a/napi.h +++ b/napi.h @@ -2051,17 +2051,20 @@ namespace Napi { class ThreadSafeFunctionEx { public: -#if NAPI_VERSION > 4 - using DefaultFunctionType = std::nullptr_t; -#else - using DefaultFunctionType = const Napi::Function; -#endif + // This API may only be called from the main thread. // Helper function that returns nullptr if running N-API 5+, otherwise a // non-empty, no-op Function. This provides the ability to specify at // compile-time a callback parameter to `New` that safely does no action // when targeting _any_ N-API version. - static DefaultFunctionType DefaultFunctionFactory(Napi::Env env); +#if NAPI_VERSION > 4 + static std::nullptr_t EmptyFunctionFactory(Napi::Env env); +#else + static Napi::Function EmptyFunctionFactory(Napi::Env env); +#endif + static Napi::Function FunctionOrEmpty(Napi::Env env, Napi::Function& callback); + + #if NAPI_VERSION > 4 diff --git a/test/threadsafe_function_ex/test/basic.cc b/test/threadsafe_function_ex/test/basic.cc index aa471dc91..e39b1f7b7 100644 --- a/test/threadsafe_function_ex/test/basic.cc +++ b/test/threadsafe_function_ex/test/basic.cc @@ -54,8 +54,8 @@ class TSFNWrap : public base { } static std::array, 2> InstanceMethods() { - return {InstanceMethod("release", &TSFNWrap::Release), - InstanceMethod("call", &TSFNWrap::Call)}; + return {{InstanceMethod("release", &TSFNWrap::Release), + InstanceMethod("call", &TSFNWrap::Call)}}; } Napi::Value Call(const CallbackInfo &info) { @@ -105,23 +105,23 @@ class TSFNWrap : public base { ContextType *context = new ContextType(Persistent(info[0])); - _tsfn = TSFN::New( - env, // napi_env env, - TSFN::DefaultFunctionFactory(env), // const Function& callback, - Value(), // const Object& resource, - "Test", // ResourceString resourceName, - 0, // size_t maxQueueSize, - 1, // size_t initialThreadCount, - context, // ContextType* context, - base::Finalizer, // Finalizer finalizer - &_deferred // FinalizerDataType* data - ); + _tsfn = + TSFN::New(env, // napi_env env, + TSFN::EmptyFunctionFactory(env), // const Function& callback, + Value(), // const Object& resource, + "Test", // ResourceString resourceName, + 0, // size_t maxQueueSize, + 1, // size_t initialThreadCount, + context, // ContextType* context, + base::Finalizer, // Finalizer finalizer + &_deferred // FinalizerDataType* data + ); } static std::array, 3> InstanceMethods() { - return {InstanceMethod("call", &TSFNWrap::Call), - InstanceMethod("getContext", &TSFNWrap::GetContext), - InstanceMethod("release", &TSFNWrap::Release)}; + return {{InstanceMethod("call", &TSFNWrap::Call), + InstanceMethod("getContext", &TSFNWrap::GetContext), + InstanceMethod("release", &TSFNWrap::Release)}}; } Napi::Value Call(const CallbackInfo &info) { @@ -187,8 +187,8 @@ class TSFNWrap : public base { } static std::array, 2> InstanceMethods() { - return {InstanceMethod("release", &TSFNWrap::Release), - InstanceMethod("call", &TSFNWrap::Call)}; + return {{InstanceMethod("release", &TSFNWrap::Release), + InstanceMethod("call", &TSFNWrap::Call)}}; } Napi::Value Call(const CallbackInfo &info) { @@ -212,14 +212,17 @@ struct DataType { // CallJs callback function provided to `napi_create_threadsafe_function`. It is // _NOT_ used by `Napi::ThreadSafeFunctionEx<>`, which is why these arguments // are napi_*. -static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, +static void CallJs(napi_env env, napi_value /*jsCallback*/, void * /*context*/, void *data) { DataType *casted = static_cast(data); if (env != nullptr) { if (data != nullptr) { napi_value undefined; napi_status status = napi_get_undefined(env, &undefined); - NAPI_THROW_IF_FAILED(env, status); + if (status != napi_ok) { + NAPI_THROW_VOID( + Error::New(env, "Could not get undefined from environment")); + } if (casted->reject) { casted->deferred.Reject(undefined); } else { @@ -257,14 +260,14 @@ class TSFNWrap : public base { napi_threadsafe_function napi_tsfn; // A threadsafe function on N-API 4 still requires a callback function, so - // this uses the `DefaultFunctionFactory` helper method to return a no-op + // this uses the `EmptyFunctionFactory` helper method to return a no-op // Function. auto status = napi_create_threadsafe_function( - info.Env(), TSFN::DefaultFunctionFactory(env), nullptr, + info.Env(), TSFN::EmptyFunctionFactory(env), nullptr, String::From(info.Env(), "Test"), 0, 1, nullptr, nullptr, nullptr, CallJs, &napi_tsfn); if (status != napi_ok) { - NAPI_THROW_IF_FAILED(env, status); + NAPI_THROW_VOID(Error::New(env, "Could not create TSFN.")); } _tsfn = TSFN(napi_tsfn); #else @@ -276,15 +279,16 @@ class TSFNWrap : public base { info.Env(), nullptr, nullptr, String::From(info.Env(), "Test"), 0, 1, nullptr, Finalizer, this, CallJs, &napi_tsfn); if (status != napi_ok) { - NAPI_THROW_IF_FAILED(env, status); + NAPI_THROW_VOID( + Error::New(env, "Could not get undefined from environment")); } _tsfn = TSFN(napi_tsfn); #endif } static std::array, 2> InstanceMethods() { - return {InstanceMethod("release", &TSFNWrap::Release), - InstanceMethod("call", &TSFNWrap::Call)}; + return {{InstanceMethod("release", &TSFNWrap::Release), + InstanceMethod("call", &TSFNWrap::Call)}}; } Napi::Value Call(const CallbackInfo &info) { @@ -333,7 +337,7 @@ class TSFNWrap : public base { // A threadsafe function on N-API 4 still requires a callback function. _tsfn = TSFN::New( env, // napi_env env, - TSFN::DefaultFunctionFactory( + TSFN::EmptyFunctionFactory( env), // N-API 5+: nullptr; else: const Function& callback, "Test", // ResourceString resourceName, 1, // size_t maxQueueSize, @@ -349,8 +353,8 @@ class TSFNWrap : public base { } static std::array, 2> InstanceMethods() { - return {InstanceMethod("release", &TSFNWrap::Release), - InstanceMethod("call", &TSFNWrap::Call)}; + return {{InstanceMethod("release", &TSFNWrap::Release), + InstanceMethod("call", &TSFNWrap::Call)}}; } // Since this test spec has no CALLBACK, CONTEXT, or FINALIZER. We have no way diff --git a/test/threadsafe_function_ex/test/example.cc b/test/threadsafe_function_ex/test/example.cc index b47d684ad..83293667c 100644 --- a/test/threadsafe_function_ex/test/example.cc +++ b/test/threadsafe_function_ex/test/example.cc @@ -43,20 +43,40 @@ class TSFNWrap; // Context of the TSFN. using Context = TSFNWrap; +// Data passed (as pointer) to [Non]BlockingCall struct Data { - // Data passed (as pointer) to [Non]BlockingCall std::promise promise; + uint32_t threadId; + bool logCall; uint32_t base; }; using DataType = std::unique_ptr; // CallJs callback function -static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, - Context *context, DataType *dataPtr) { +static void CallJs(Napi::Env env, Napi::Function jsCallback, + Context * /*context*/, DataType *dataPtr) { if (dataPtr != nullptr) { auto &data = *dataPtr; if (env != nullptr) { - data->promise.set_value(data->base * data->base); + auto calculated = data->base * data->base; + if (!jsCallback.IsEmpty()) { + auto value = jsCallback.Call({Number::New(env, data->threadId), Number::New(env, calculated)}); + if (env.IsExceptionPending()) { + const auto &error = env.GetAndClearPendingException(); + data->promise.set_exception( + std::make_exception_ptr(std::runtime_error(error.Message()))); + } else if (value.IsNumber()) { + calculated = value.ToNumber(); + } + } + if (data->logCall) { + std::string message("Thread " + std::to_string(data->threadId) + + " CallJs resolving std::promise"); + auto console = env.Global().Get("console").As(); + console.Get("log").As().Call(console, + {String::New(env, message)}); + } + data->promise.set_value(calculated); } else { data->promise.set_exception(std::make_exception_ptr( std::runtime_error("TSFN has been finalized."))); @@ -106,9 +126,10 @@ class TSFNWrap : public base { // TSFNWrap object gets garbage-collected and there are still active threads. using FinalizerDataType = std::shared_ptr; -#define THREADLOG(X) if (context->logThread) {\ -std::cout << X;\ -} +#define THREADLOG(X) \ + if (context->logThread) { \ + std::cout << X; \ + } static void threadEntry(size_t threadId, TSFN tsfn, uint32_t callCount, Context *context) { using namespace std::chrono_literals; @@ -118,7 +139,10 @@ std::cout << X;\ for (auto i = 0U; i < callCount; ++i) { auto data = std::make_unique(); data->base = threadId + 1; - THREADLOG("Thread " << threadId << " making call, base = " << data->base << "\n") + data->threadId = threadId; + data->logCall = context->logCall; + THREADLOG("Thread " << threadId << " making call, base = " << data->base + << "\n") tsfn.NonBlockingCall(&data); auto future = data->promise.get_future(); @@ -133,14 +157,15 @@ std::cout << X;\ #undef THREADLOG static std::array, 4> InstanceMethods() { - return {InstanceMethod("getContext", &TSFNWrap::GetContext), - InstanceMethod("start", &TSFNWrap::Start), - InstanceMethod("callCount", &TSFNWrap::CallCount), - InstanceMethod("release", &TSFNWrap::Release)}; + return {{InstanceMethod("getContext", &TSFNWrap::GetContext), + InstanceMethod("start", &TSFNWrap::Start), + InstanceMethod("callCount", &TSFNWrap::CallCount), + InstanceMethod("release", &TSFNWrap::Release)}}; } bool cppExceptions = false; bool logThread; + bool logCall; std::atomic_uint succeededCalls; std::atomic_int aggregate; @@ -163,7 +188,6 @@ std::cout << X;\ finalizerData = std::make_shared(); logThread = DefaultOptions.logThread; - bool logCall = DefaultOptions.logCall; if (info.Length() > 0 && info[0].IsObject()) { auto arg0 = info[0].ToObject(); @@ -239,11 +263,11 @@ std::cout << X;\ succeededCalls = 0; aggregate = 0; _tsfn = TSFN::New( - env, // napi_env env, - TSFN::DefaultFunctionFactory(env), // const Function& callback, - Value(), // const Object& resource, - "Test", // ResourceString resourceName, - 0, // size_t maxQueueSize, + env, // napi_env env, + TSFN::FunctionOrEmpty(env, callback), // const Function& callback, + Value(), // const Object& resource, + "Test", // ResourceString resourceName, + 0, // size_t maxQueueSize, threadCount + 1, // size_t initialThreadCount, +1 for Node thread this, // Context* context, Finalizer, // Finalizer finalizer @@ -255,7 +279,7 @@ std::cout << X;\ callCounts[threadId], this)); } - return String::New(env, "started"); + return Number::New(env, threadCount); }; // TSFN finalizer. Joins the threads and resolves the Promise returned by diff --git a/test/threadsafe_function_ex/test/example.js b/test/threadsafe_function_ex/test/example.js index 53b6783cf..d47cba236 100644 --- a/test/threadsafe_function_ex/test/example.js +++ b/test/threadsafe_function_ex/test/example.js @@ -10,26 +10,38 @@ class ExampleTest extends TestRunner { async example({ TSFNWrap }) { const tsfn = new TSFNWrap(); - const threads = [1]; //, 2, 2, 5, 12]; - const started = await tsfn.start({ threads, logThread: true }); + const threads = [1, 2, 3, 4, 5]; + let callAggregate = 0; + const startedActual = await tsfn.start({ + threads, + logThread: false, + logCall: false, + + callback: (_ /*threadId*/, valueFromCallJs) => { + callAggregate += valueFromCallJs; + } + }); /** * Calculate the expected results. */ const expected = threads.reduce((p, threadCallCount, threadId) => ( ++threadId, - p[0] += threadCallCount, - p[1] += threadCallCount * threadId ** 2, + ++p.threadCount, + p.callCount += threadCallCount, + p.aggregate += threadCallCount * threadId ** 2, p - ), [0, 0]); + ), { threadCount: 0, callCount: 0, aggregate: 0 }); - if (started) { + if (typeof startedActual === 'number') { const released = await tsfn.release(); const [callCountActual, aggregateActual] = tsfn.callCount(); - const [callCountExpected, aggregateExpected] = expected; - assert(callCountActual == callCountExpected, `The number of calls do not match: actual = ${callCountActual}, expected = ${callCountExpected}`); - assert(aggregateActual == aggregateExpected, `The aggregate of calls do not match: actual = ${aggregateActual}, expected = ${aggregateExpected}`); - return expected; + const { threadCount, callCount, aggregate } = expected; + assert(startedActual === threadCount, `The number of threads started do not match: actual = ${startedActual}, expected = ${threadCount}`) + assert(callCountActual === callCount, `The number of calls do not match: actual = ${callCountActual}, expected = ${callCount}`); + assert(aggregateActual === aggregate, `The aggregate of calls do not match: actual = ${aggregateActual}, expected = ${aggregate}`); + assert(aggregate === callAggregate, `The number aggregated by the JavaScript callback and the thread calculated aggregate do not match: actual ${aggregate}, expected = ${callAggregate}`) + return { released, ...expected, callAggregate }; } else { throw new Error('The TSFN failed to start'); } diff --git a/test/threadsafe_function_ex/test/threadsafe.cc b/test/threadsafe_function_ex/test/threadsafe.cc index f12e7e49b..cf3eddca8 100644 --- a/test/threadsafe_function_ex/test/threadsafe.cc +++ b/test/threadsafe_function_ex/test/threadsafe.cc @@ -1,7 +1,6 @@ #include #include #include "napi.h" -#include #if (NAPI_VERSION > 3) diff --git a/test/threadsafe_function_ex/util/util.h b/test/threadsafe_function_ex/util/util.h index 36472d81a..6f202601b 100644 --- a/test/threadsafe_function_ex/util/util.h +++ b/test/threadsafe_function_ex/util/util.h @@ -41,7 +41,7 @@ class TSFNWrapBase : public ObjectWrap { // TSFN finalizer. Resolves the Promise returned by `Release()` above. static void Finalizer(Napi::Env env, std::unique_ptr *deferred, - Context *ctx) { + Context * /*ctx*/) { if (deferred->get()) { (*deferred)->Resolve(Boolean::New(env, true)); deferred->release(); From d092a324ce65ab7308cd53c91c7aa614b95ad934 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Wed, 17 Jun 2020 20:28:10 +0200 Subject: [PATCH 218/696] doc: wip with tsfn documentation --- README.md | 8 +- doc/threadsafe.md | 123 +++++++++++++ doc/threadsafe_function.md | 51 ++---- doc/threadsafe_function_ex.md | 318 ++++++++++++++++++++++++++++++++++ 4 files changed, 456 insertions(+), 44 deletions(-) create mode 100644 doc/threadsafe.md create mode 100644 doc/threadsafe_function_ex.md diff --git a/README.md b/README.md index 206c7f108..c88104623 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ to ideas specified in the **ECMA262 Language Specification**. -node-addon-api is based on [N-API](https://nodejs.org/api/n-api.html) and supports using different N-API versions. -This allows addons built with it to run with Node.js versions which support the targeted N-API version. +node-addon-api is based on [N-API](https://nodejs.org/api/n-api.html) and supports using different N-API versions. +This allows addons built with it to run with Node.js versions which support the targeted N-API version. **However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. @@ -116,7 +116,9 @@ The following is the documentation for node-addon-api. - [AsyncWorker](doc/async_worker.md) - [AsyncContext](doc/async_context.md) - [AsyncWorker Variants](doc/async_worker_variants.md) - - [Thread-safe Functions](doc/threadsafe_function.md) + - [Thread-safe Functions](doc/threadsafe.md) + - [ThreadSafeFunction](doc/threadsafe_function.md) + - [ThreadSafeFunctionEx](doc/threadsafe_function_ex.md) - [Promises](doc/promises.md) - [Version management](doc/version_management.md) diff --git a/doc/threadsafe.md b/doc/threadsafe.md new file mode 100644 index 000000000..70eb296ba --- /dev/null +++ b/doc/threadsafe.md @@ -0,0 +1,123 @@ +# Thread-safe Functions + +JavaScript functions can normally only be called from a native addon's main +thread. If an addon creates additional threads, then node-addon-api functions +that require a `Napi::Env`, `Napi::Value`, or `Napi::Reference` must not be +called from those threads. + +When an addon has additional threads and JavaScript functions need to be invoked +based on the processing completed by those threads, those threads must +communicate with the addon's main thread so that the main thread can invoke the +JavaScript function on their behalf. The thread-safe function APIs provide an +easy way to do this. These APIs provide two types -- +[`Napi::ThreadSafeFunction`](threadsafe_function.md) and +[`Napi::ThreadSafeFunctionEx`](threadsafe_function_ex.md) -- as well as APIs to +create, destroy, and call objects of this type. The differences between the two +are subtle and are [highlighted below](#implementation-differences). Regardless +of which type you choose, the API between the two are similar. + +`Napi::ThreadSafeFunction[Ex]::New()` creates a persistent reference that holds +a JavaScript function which can be called from multiple threads. The calls +happen asynchronously. This means that values with which the JavaScript callback +is to be called will be placed in a queue, and, for each value in the queue, a +call will eventually be made to the JavaScript function. + +`Napi::ThreadSafeFunction[Ex]` objects are destroyed when every thread which +uses the object has called `Release()` or has received a return status of +`napi_closing` in response to a call to `BlockingCall()` or `NonBlockingCall()`. +The queue is emptied before the `Napi::ThreadSafeFunction[Ex]` is destroyed. It +is important that `Release()` be the last API call made in conjunction with a +given `Napi::ThreadSafeFunction[Ex]`, because after the call completes, there is +no guarantee that the `Napi::ThreadSafeFunction[Ex]` is still allocated. For the +same reason it is also important that no more use be made of a thread-safe +function after receiving a return value of `napi_closing` in response to a call +to `BlockingCall()` or `NonBlockingCall()`. Data associated with the +`Napi::ThreadSafeFunction[Ex]` can be freed in its `Finalizer` callback which +was passed to `ThreadSafeFunction[Ex]::New()`. + +Once the number of threads making use of a `Napi::ThreadSafeFunction[Ex]` +reaches zero, no further threads can start making use of it by calling +`Acquire()`. In fact, all subsequent API calls associated with it, except +`Release()`, will return an error value of `napi_closing`. + +## Implementation Differences + +The choice between `Napi::ThreadSafeFunction` and `Napi::ThreadSafeFunctionEx` +depends largely on how you plan to execute your native C++ code (the "callback") +on the Node thread. + +### [`Napi::ThreadSafeFunction`](threadsafe_function.md) + +This API is designed without N-API 5 native support for [optional JavaScript + function callback feature](https://github.com/nodejs/node/commit/53297e66cb). + `::New` methods that do not have a `Function` parameter will construct a + _new_, no-op `Function` on the environment to pass to the underlying N-API + call. + +This API has some dynamic functionality, in that: +- The `[Non]BlockingCall()` methods provide a `Napi::Function` parameter as the + callback to run when processing the data item on the main thread -- the + `CallJs` callback. Since the callback is a parameter, it can be changed for + every call. +- Different C++ data types may be passed with each call of `[Non]BlockingCall()` + to match the specific data type as specified in the `CallJs` callback. + +However, this functionality comes with some **additional overhead** and +situational **memory leaks**: +- The API acts as a "middle-man" between the underlying + `napi_threadsafe_function`, and dynamically constructs a wrapper for your + callback on the heap for every call to `[Non]BlockingCall()`. +- In acting in this "middle-man" fashion, the API will call the underlying "make + call" N-API method on this packaged item. If the API has determined the + threadsafe function is no longer accessible (eg. all threads have Released yet + there are still items on the queue), **the callback passed to + [Non]BlockingCall will not execute**. This means it is impossible to perform + clean-up for calls that never execute their `CallJs` callback. **This may lead + to memory leaks** if you are dynamically allocating memory. +- The `CallJs` does not receive the threadsafe function's context as a + parameter. In order for the callback to access the context, it must have a + reference to either (1) the context directly, or (2) the threadsafe function + to call `GetContext()`. Furthermore, the `GetContext()` method is not + _type-safe_, as the method returns an object that can be "any-casted", instead + of having a static type. + +### [`Napi::ThreadSafeFunctionEx`](threadsafe_function_ex.md) + +The `ThreadSafeFunctionEx` class is a new implementation to address the +drawbacks listed above. The API is designed with N-API 5's support of an +optional function callback. The API will correctly allow developers to pass +`std::nullptr` instead of a `const Function&` for the callback function +specified in `::New`. It also provides helper APIs to _target_ N-API 4 and +construct a no-op `Function` **or** to target N-API 5 and "construct" an +`std::nullptr` callback. This allows a single codebase to use the same APIs, +with just a switch of the `NAPI_VERSION` compile-time constant. + +The removal of the dynamic call functionality has the additional side effects: +- The API does _not_ act as a "middle-man" compared to the non-`Ex`. Once Node + finalizes the threadsafe function, the `CallJs` callback will execute with an + empty `Napi::Env` for any remaining items on the queue. This provides the the + ability to handle any necessary clean up of the item's data. +- The callback _does_ receive the context as a parameter, so a call to + `GetContext()` is _not_ necessary. This context type is specified as the + **first type argument** specified to `::New`, ensuring type safety. +- The `New()` constructor accepts the `CallJs` callback as the **second type + argument**. The callback must be statically defined for the API to access it. + This affords the ability to statically pass the context as the correct type + across all methods. +- Only one C++ data type may be specified to every call to `[Non]BlockingCall()` + -- the **third type argument** specified to `::New`. Any "dynamic call data" + must be implemented by the user. + + +### Usage Suggestions + +In summary, it may be best to use `Napi::ThreadSafeFunctionEx` if: + +- static, compile-time support for targeting N-API 4 or 5+ with an optional + JavaScript callback feature is desired; +- the callback can have `static` storage class and will not change across calls + to `[Non]BlockingCall()`; +- cleanup of items' data is required (eg. deleting dynamically-allocated data + that is created at the caller level). + +Otherwise, `Napi::ThreadSafeFunction` may be a better choice. diff --git a/doc/threadsafe_function.md b/doc/threadsafe_function.md index 2bd8b67c9..0b3202929 100644 --- a/doc/threadsafe_function.md +++ b/doc/threadsafe_function.md @@ -1,41 +1,10 @@ # ThreadSafeFunction -JavaScript functions can normally only be called from a native addon's main -thread. If an addon creates additional threads, then node-addon-api functions -that require a `Napi::Env`, `Napi::Value`, or `Napi::Reference` must not be -called from those threads. - -When an addon has additional threads and JavaScript functions need to be invoked -based on the processing completed by those threads, those threads must -communicate with the addon's main thread so that the main thread can invoke the -JavaScript function on their behalf. The thread-safe function APIs provide an -easy way to do this. - -These APIs provide the type `Napi::ThreadSafeFunction` as well as APIs to -create, destroy, and call objects of this type. -`Napi::ThreadSafeFunction::New()` creates a persistent reference that holds a -JavaScript function which can be called from multiple threads. The calls happen -asynchronously. This means that values with which the JavaScript callback is to -be called will be placed in a queue, and, for each value in the queue, a call -will eventually be made to the JavaScript function. - -`Napi::ThreadSafeFunction` objects are destroyed when every thread which uses -the object has called `Release()` or has received a return status of -`napi_closing` in response to a call to `BlockingCall()` or `NonBlockingCall()`. -The queue is emptied before the `Napi::ThreadSafeFunction` is destroyed. It is -important that `Release()` be the last API call made in conjunction with a given -`Napi::ThreadSafeFunction`, because after the call completes, there is no -guarantee that the `Napi::ThreadSafeFunction` is still allocated. For the same -reason it is also important that no more use be made of a thread-safe function -after receiving a return value of `napi_closing` in response to a call to -`BlockingCall()` or `NonBlockingCall()`. Data associated with the -`Napi::ThreadSafeFunction` can be freed in its `Finalizer` callback which was -passed to `ThreadSafeFunction::New()`. - -Once the number of threads making use of a `Napi::ThreadSafeFunction` reaches -zero, no further threads can start making use of it by calling `Acquire()`. In -fact, all subsequent API calls associated with it, except `Release()`, will -return an error value of `napi_closing`. +The `Napi::ThreadSafeFunction` type provides APIs for threads to communicate +with the addon's main thread to invoke JavaScript functions on their behalf. +Documentation can be found for an [overview of the API](threadsafe.md), as well +as [differences between the two thread-safe function +APIs](threadsafe.md#implementation-differences). ## Methods @@ -93,6 +62,7 @@ New(napi_env env, - `initialThreadCount`: The initial number of threads, including the main thread, which will be making use of this function. - `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`. + Can be retreived via `GetContext()`. - `[optional] finalizeCallback`: Function to call when the `ThreadSafeFunction` is being destroyed. This callback will be invoked on the main thread when the thread-safe function is about to be destroyed. It receives the context and the @@ -102,7 +72,6 @@ New(napi_env env, there be no threads left using the thread-safe function after the finalize callback completes. Must implement `void operator()(Env env, DataType* data, Context* hint)`, skipping `data` or `hint` if they are not provided. - Can be retreived via `GetContext()`. - `[optional] data`: Data to be passed to `finalizeCallback`. Returns a non-empty `Napi::ThreadSafeFunction` instance. @@ -110,7 +79,7 @@ Returns a non-empty `Napi::ThreadSafeFunction` instance. ### Acquire Add a thread to this thread-safe function object, indicating that a new thread -will start making use of the thread-safe function. +will start making use of the thread-safe function. ```cpp napi_status Napi::ThreadSafeFunction::Acquire() @@ -118,7 +87,7 @@ napi_status Napi::ThreadSafeFunction::Acquire() Returns one of: - `napi_ok`: The thread has successfully acquired the thread-safe function -for its use. +for its use. - `napi_closing`: The thread-safe function has been marked as closing via a previous call to `Abort()`. @@ -258,10 +227,10 @@ Value Start( const CallbackInfo& info ) // Create a native thread nativeThread = std::thread( [count] { auto callback = []( Napi::Env env, Function jsCallback, int* value ) { - // Transform native data into JS data, passing it to the provided + // Transform native data into JS data, passing it to the provided // `jsCallback` -- the TSFN's JavaScript function. jsCallback.Call( {Number::New( env, *value )} ); - + // We're finished with the data. delete value; }; diff --git a/doc/threadsafe_function_ex.md b/doc/threadsafe_function_ex.md new file mode 100644 index 000000000..2657dd752 --- /dev/null +++ b/doc/threadsafe_function_ex.md @@ -0,0 +1,318 @@ +# TODO +- Document new N-API 5+ only methods +- Continue with examples + +# ThreadSafeFunctionEx + +The `Napi::ThreadSafeFunctionEx` type provides APIs for threads to communicate +with the addon's main thread to invoke JavaScript functions on their behalf. The +type is a three-argument templated class, each argument representing the type +of: +- `ContextType = std::nullptr_t`: The threadsafe function's context. By default, + a TSFN has no context. +- `DataType = void*`: The data to use in the native callback. By default, a TSFN + can accept *any* data type. +- `Callback = void*(Napi::Env, Napi::Function, ContextType*, DataType*)`: The + callback to run for each item added to the queue. + +Documentation can be found for an [overview of the API](threadsafe.md), as well +as [differences between the two thread-safe function +APIs](threadsafe.md#implementation-differences). + +## Methods + +### Constructor + +Creates a new empty instance of `Napi::ThreadSafeFunctionEx`. + +```cpp +Napi::Function::ThreadSafeFunctionEx::ThreadSafeFunctionEx(); +``` + +### Constructor + +Creates a new instance of the `Napi::ThreadSafeFunctionEx` object. + +```cpp +Napi::ThreadSafeFunctionEx::ThreadSafeFunctionEx(napi_threadsafe_function tsfn); +``` + +- `tsfn`: The `napi_threadsafe_function` which is a handle for an existing + thread-safe function. + +Returns a non-empty `Napi::ThreadSafeFunctionEx` instance. + +### New + +Creates a new instance of the `Napi::ThreadSafeFunctionEx` object. + +```cpp +New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); +``` + +- `env`: The `napi_env` environment in which to construct the + `Napi::ThreadSafeFunction` object. +- `callback`: The `Function` to call from another thread. +- `resource`: An object associated with the async work that will be passed to + possible async_hooks init hooks. +- `resourceName`: A JavaScript string to provide an identifier for the kind of + resource that is being provided for diagnostic information exposed by the + async_hooks API. +- `maxQueueSize`: Maximum size of the queue. `0` for no limit. +- `initialThreadCount`: The initial number of threads, including the main + thread, which will be making use of this function. +- `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`. + Can be retreived via `GetContext()`. + +Returns a non-empty `Napi::ThreadSafeFunction` instance. + +### New + +Creates a new instance of the `Napi::ThreadSafeFunctionEx` object with a +finalizer that runs when the object is being destroyed. + +```cpp +New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); +``` + +- `env`: The `napi_env` environment in which to construct the + `Napi::ThreadSafeFunction` object. +- `callback`: The `Function` to call from another thread. +- `resource`: An object associated with the async work that will be passed to + possible async_hooks init hooks. +- `resourceName`: A JavaScript string to provide an identifier for the kind of + resource that is being provided for diagnostic information exposed by the + async_hooks API. +- `maxQueueSize`: Maximum size of the queue. `0` for no limit. +- `initialThreadCount`: The initial number of threads, including the main + thread, which will be making use of this function. +- `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`. + Can be retreived via `GetContext()`. +- `finalizeCallback`: Function to call when the `ThreadSafeFunctionEx` is being + destroyed. This callback will be invoked on the main thread when the + thread-safe function is about to be destroyed. It receives the context and the + finalize data given during construction (if given), and provides an + opportunity for cleaning up after the threads e.g. by calling + `uv_thread_join()`. It is important that, aside from the main loop thread, + there be no threads left using the thread-safe function after the finalize + callback completes. Must implement `void operator()(Env env, DataType* data, + ContextType* hint)`. +- `[optional] data`: Data to be passed to `finalizeCallback`. + +Returns a non-empty `Napi::ThreadSafeFunctionEx` instance. + +### Acquire + +Add a thread to this thread-safe function object, indicating that a new thread +will start making use of the thread-safe function. + +```cpp +napi_status Napi::ThreadSafeFunctionEx::Acquire() +``` + +Returns one of: +- `napi_ok`: The thread has successfully acquired the thread-safe function for + its use. +- `napi_closing`: The thread-safe function has been marked as closing via a + previous call to `Abort()`. + +### Release + +Indicate that an existing thread will stop making use of the thread-safe +function. A thread should call this API when it stops making use of this +thread-safe function. Using any thread-safe APIs after having called this API +has undefined results in the current thread, as it may have been destroyed. + +```cpp +napi_status Napi::ThreadSafeFunctionEx::Release() +``` + +Returns one of: +- `napi_ok`: The thread-safe function has been successfully released. +- `napi_invalid_arg`: The thread-safe function's thread-count is zero. +- `napi_generic_failure`: A generic error occurred when attemping to release the + thread-safe function. + +### Abort + +"Abort" the thread-safe function. This will cause all subsequent APIs associated +with the thread-safe function except `Release()` to return `napi_closing` even +before its reference count reaches zero. In particular, `BlockingCall` and +`NonBlockingCall()` will return `napi_closing`, thus informing the threads that +it is no longer possible to make asynchronous calls to the thread-safe function. +This can be used as a criterion for terminating the thread. Upon receiving a +return value of `napi_closing` from a thread-safe function call a thread must +make no further use of the thread-safe function because it is no longer +guaranteed to be allocated. + +```cpp +napi_status Napi::ThreadSafeFunctionEx::Abort() +``` + +Returns one of: +- `napi_ok`: The thread-safe function has been successfully aborted. +- `napi_invalid_arg`: The thread-safe function's thread-count is zero. +- `napi_generic_failure`: A generic error occurred when attemping to abort the + thread-safe function. + +### BlockingCall / NonBlockingCall + +Calls the Javascript function in either a blocking or non-blocking fashion. +- `BlockingCall()`: the API blocks until space becomes available in the queue. + Will never block if the thread-safe function was created with a maximum queue + size of `0`. +- `NonBlockingCall()`: will return `napi_queue_full` if the queue was full, + preventing data from being successfully added to the queue. + +```cpp +napi_status Napi::ThreadSafeFunctionEx::BlockingCall(DataType* data = nullptr) const + +napi_status Napi::ThreadSafeFunctionEx::NonBlockingCall(DataType* data = nullptr) const +``` + +- `[optional] data`: Data to pass to the callback which was passed to + `ThreadSafeFunctionEx::New()`. +- `[optional] callback`: C++ function that is invoked on the main thread. The + callback receives the `ThreadSafeFunction`'s JavaScript callback function to + call as an `Napi::Function` in its parameters and the `DataType*` data pointer + (if provided). Must implement `void operator()(Napi::Env env, Function + jsCallback, DataType* data)`, skipping `data` if not provided. It is not + necessary to call into JavaScript via `MakeCallback()` because N-API runs + `callback` in a context appropriate for callbacks. + +Returns one of: +- `napi_ok`: The call was successfully added to the queue. +- `napi_queue_full`: The queue was full when trying to call in a non-blocking + method. +- `napi_closing`: The thread-safe function is aborted and cannot accept more + calls. +- `napi_invalid_arg`: The thread-safe function is closed. +- `napi_generic_failure`: A generic error occurred when attemping to add to the + queue. + +## Example + +```cpp +#include +#include +#include + +using namespace Napi; + +std::thread nativeThread; + +struct ContextType { + int threadId; +}; + +using DataType = int; + +using ThreadSafeFunctionEx = tsfn; + +Value Start( const CallbackInfo& info ) +{ + Napi::Env env = info.Env(); + + if ( info.Length() < 2 ) + { + throw TypeError::New( env, "Expected two arguments" ); + } + else if ( !info[0].IsFunction() ) + { + throw TypeError::New( env, "Expected first arg to be function" ); + } + else if ( !info[1].IsNumber() ) + { + throw TypeError::New( env, "Expected second arg to be number" ); + } + + int count = info[1].As().Int32Value(); + + // Create a ThreadSafeFunction + tsfn = ThreadSafeFunction::New( + env, + info[0].As(), // JavaScript function called asynchronously + "Resource Name", // Name + 0, // Unlimited queue + 1, // Only one thread will use this initially + []( Napi::Env ) { // Finalizer used to clean threads up + nativeThread.join(); + } ); + + // Create a native thread + nativeThread = std::thread( [count] { + auto callback = []( Napi::Env env, Function jsCallback, int* value ) { + // Transform native data into JS data, passing it to the provided + // `jsCallback` -- the TSFN's JavaScript function. + jsCallback.Call( {Number::New( env, *value )} ); + + // We're finished with the data. + delete value; + }; + + for ( int i = 0; i < count; i++ ) + { + // Create new data + int* value = new int( clock() ); + + // Perform a blocking call + napi_status status = tsfn.BlockingCall( value, callback ); + if ( status != napi_ok ) + { + // Handle error + break; + } + + std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); + } + + // Release the thread-safe function + tsfn.Release(); + } ); + + return Boolean::New(env, true); +} + +Napi::Object Init( Napi::Env env, Object exports ) +{ + exports.Set( "start", Function::New( env, Start ) ); + return exports; +} + +NODE_API_MODULE( clock, Init ) +``` + +The above code can be used from JavaScript as follows: + +```js +const { start } = require('bindings')('clock'); + +start(function () { + console.log("JavaScript callback called with arguments", Array.from(arguments)); +}, 5); +``` + +When executed, the output will show the value of `clock()` five times at one +second intervals: + +``` +JavaScript callback called with arguments [ 84745 ] +JavaScript callback called with arguments [ 103211 ] +JavaScript callback called with arguments [ 104516 ] +JavaScript callback called with arguments [ 105104 ] +JavaScript callback called with arguments [ 105691 ] +``` From bd2c5ec502edecab89c7ecf47fa8dc47a617a8f2 Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Wed, 24 Jun 2020 23:16:48 +0200 Subject: [PATCH 219/696] Fixes issue 745. (#748) --- doc/bigint.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/bigint.md b/doc/bigint.md index 92607e7fd..33ada43b3 100644 --- a/doc/bigint.md +++ b/doc/bigint.md @@ -79,7 +79,7 @@ Returns the number of words needed to store this `BigInt` value. ### ToWords ```cpp -void Napi::BigInt::ToWords(size_t* word_count, int* sign_bit, uint64_t* words); +void Napi::BigInt::ToWords(int* sign_bit, size_t* word_count, uint64_t* words); ``` - `[out] sign_bit`: Integer representing if the JavaScript `BigInt` is positive From 48f6762bf634a424ec681371927d7e4626824500 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sat, 30 May 2020 11:48:13 -0500 Subject: [PATCH 220/696] src: add __wasm32__ guards --- napi-inl.h | 8 ++++---- napi.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 649be98ac..99ed3e3d3 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -137,7 +137,7 @@ struct FinalizeData { Hint* hint; }; -#if (NAPI_VERSION > 3) +#if (NAPI_VERSION > 3 && !defined(__wasm32__)) template , typename FinalizerDataType=void> @@ -196,7 +196,7 @@ struct ThreadSafeFinalize { FinalizerDataType* data; Finalizer callback; }; -#endif +#endif // NAPI_VERSION > 3 && !defined(__wasm32__) template struct AccessorCallbackData { @@ -4302,7 +4302,7 @@ inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { } } -#if (NAPI_VERSION > 3) +#if (NAPI_VERSION > 3 && !defined(__wasm32__)) //////////////////////////////////////////////////////////////////////////////// // ThreadSafeFunction class //////////////////////////////////////////////////////////////////////////////// @@ -4969,7 +4969,7 @@ template inline void AsyncProgressQueueWorker::ExecutionProgress::Send(const T* data, size_t count) const { _worker->SendProgress_(data, count); } -#endif +#endif // NAPI_VERSION > 3 && !defined(__wasm32__) //////////////////////////////////////////////////////////////////////////////// // Memory Management class diff --git a/napi.h b/napi.h index de4d82f36..6c80efc26 100644 --- a/napi.h +++ b/napi.h @@ -2042,7 +2042,7 @@ namespace Napi { bool _suppress_destruct; }; - #if (NAPI_VERSION > 3) + #if (NAPI_VERSION > 3 && !defined(__wasm32__)) class ThreadSafeFunction { public: // This API may only be called from the main thread. @@ -2405,7 +2405,7 @@ namespace Napi { void Signal() const; void SendProgress_(const T* data, size_t count); }; - #endif + #endif // NAPI_VERSION > 3 && !defined(__wasm32__) // Memory management. class MemoryManagement { From ef16dfb4a2f7efbb2bd5704a517b383c98c5a3c7 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Mon, 29 Jun 2020 12:49:04 -0700 Subject: [PATCH 221/696] doc: update ObjectWrap example * Remove the global static reference to the constructor * Use `Napi::Env::SetInstanceData` to store the constructor, and * Add a static method that uses `Napi::FunctionReference::New` to create a new instance of the class by retrieving the constructor using `Napi::Env::GetInstanceData` and using `Napi::FunctionReference::New` to create the new instance. Fixes: https://github.com/nodejs/node-addon-api/issues/711 PR-URL: https://github.com/nodejs/node-addon-api/pull/754 Reviewed-By: Nicola Del Gobbo Reviewed-By: Anna Henningsen --- doc/object_wrap.md | 48 +++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/doc/object_wrap.md b/doc/object_wrap.md index cdf7ce1e2..0843092df 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -22,49 +22,57 @@ your C++ class methods. class Example : public Napi::ObjectWrap { public: static Napi::Object Init(Napi::Env env, Napi::Object exports); - Example(const Napi::CallbackInfo &info); + Example(const Napi::CallbackInfo& info); + static Napi::Value CreateNewItem(const Napi::CallbackInfo& info); private: - static Napi::FunctionReference constructor; double _value; - Napi::Value GetValue(const Napi::CallbackInfo &info); - Napi::Value SetValue(const Napi::CallbackInfo &info); + Napi::Value GetValue(const Napi::CallbackInfo& info); + Napi::Value SetValue(const Napi::CallbackInfo& info); }; Napi::Object Example::Init(Napi::Env env, Napi::Object exports) { // This method is used to hook the accessor and method callbacks Napi::Function func = DefineClass(env, "Example", { InstanceMethod<&Example::GetValue>("GetValue"), - InstanceMethod<&Example::SetValue>("SetValue") + InstanceMethod<&Example::SetValue>("SetValue"), + StaticMethod<&Example::CreateNewItem>("CreateNewItem"), }); + Napi::FunctionReference* constructor = new Napi::FunctionReference(); + // Create a peristent reference to the class constructor. This will allow // a function called on a class prototype and a function // called on instance of a class to be distinguished from each other. - constructor = Napi::Persistent(func); - // Call the SuppressDestruct() method on the static data prevent the calling - // to this destructor to reset the reference when the environment is no longer - // available. - constructor.SuppressDestruct(); + *constructor = Napi::Persistent(func); exports.Set("Example", func); + + // Store the constructor as the add-on instance data. This will allow this + // add-on to support multiple instances of itself running on multiple worker + // threads, as well as multiple instances of itself running in different + // contexts on the same thread. + // + // By default, the value set on the environment here will be destroyed when + // the add-on is unloaded using the `delete` operator, but it is also + // possible to supply a custom deleter. + env.SetInstanceData(constructor); + return exports; } -Example::Example(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) { +Example::Example(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { Napi::Env env = info.Env(); // ... Napi::Number value = info[0].As(); this->_value = value.DoubleValue(); } -Napi::FunctionReference Example::constructor; - -Napi::Value Example::GetValue(const Napi::CallbackInfo &info){ +Napi::Value Example::GetValue(const Napi::CallbackInfo& info){ Napi::Env env = info.Env(); return Napi::Number::New(env, this->_value); } -Napi::Value Example::SetValue(const Napi::CallbackInfo &info){ +Napi::Value Example::SetValue(const Napi::CallbackInfo& info){ Napi::Env env = info.Env(); // ... Napi::Number value = info[0].As(); @@ -78,6 +86,16 @@ Napi::Object Init (Napi::Env env, Napi::Object exports) { return exports; } +// Create a new item using the constructor stored during Init. +Napi::Value Example::CreateNewItem(const Napi::CallbackInfo& info) { + // Retrieve the instance data we stored during `Init()`. We only stored the + // constructor there, so we retrieve it here to create a new instance of the + // JS class the constructor represents. + Napi::FunctionReference* constructor = + info.Env().GetInstanceData(); + return constructor->New({ Napi::Number::New(info.Env(), 42) }); +} + // Register and initialize native add-on NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) ``` From 40c79263421d21d92199cbbef0533f42a434d4f4 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Sat, 4 Jul 2020 11:37:40 +0100 Subject: [PATCH 222/696] build: ensure paths with spaces can be used Ensure include path is relative to process working directory (PWD) This allows the use of parent paths that contain whitespace, plus keeps the approach consistent with that used by nan. (The previous approach of adding double quotes did not work as intended due to node-gyp removing these on the way through.) PR-URL: https://github.com/nodejs/node-addon-api/pull/757 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 393fa348e..75b96e0ed 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,10 @@ const path = require('path'); +const include = path.relative('.', __dirname); + module.exports = { - include: `"${__dirname}"`, - gyp: path.join(__dirname, 'node_api.gyp:nothing'), + include: include, + gyp: path.join(include, 'node_api.gyp:nothing'), isNodeApiBuiltin: true, needsFlag: false }; From 61c98463939630bb52438bb8ea1fb1fdf651af1c Mon Sep 17 00:00:00 2001 From: NickNaso Date: Mon, 13 Jul 2020 14:58:26 +0200 Subject: [PATCH 223/696] Prepare release 3.0.1. --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ README.md | 6 +++--- package.json | 18 +++++++++++++++++- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7775216d1..e2ef3d432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # node-addon-api Changelog +## 2020-07-13 Version 3.0.1, @NickNaso + +### Notable changes: + +#### API + +- Fixed the usage of `Napi::Reference` with `Napi::TypedArray`. +- Fixed `Napi::ObjectWrap` inheritance. + +#### Documentation + +- Updated the example for `Napi::ObjectWrap`. +- Added documentation for instance data APIs. +- Some minor corrections all over the documentation. + +#### TEST + +- Fixed test for `Napi::ArrayBuffer` and `Napi::Buffer`. +- Some minor corrections all over the test suite. + +### Commits + +* [[`40c7926342`](https://github.com/nodejs/node-addon-api/commit/40c7926342)] - **build**: ensure paths with spaces can be used (Lovell Fuller) [#757](https://github.com/nodejs/node-addon-api/pull/757) +* [[`ef16dfb4a2`](https://github.com/nodejs/node-addon-api/commit/ef16dfb4a2)] - **doc**: update ObjectWrap example (Gabriel Schulhof) [#754](https://github.com/nodejs/node-addon-api/pull/754) +* [[`48f6762bf6`](https://github.com/nodejs/node-addon-api/commit/48f6762bf6)] - **src**: add \_\_wasm32\_\_ guards (Gus Caplan) +* [[`bd2c5ec502`](https://github.com/nodejs/node-addon-api/commit/bd2c5ec502)] - Fixes issue 745. (#748) (Nicola Del Gobbo) +* [[`4c01af2d87`](https://github.com/nodejs/node-addon-api/commit/4c01af2d87)] - Fix typo in CHANGELOG (#715) (Kasumi Hanazuki) +* [[`36e1af96d5`](https://github.com/nodejs/node-addon-api/commit/36e1af96d5)] - **src**: fix use of Reference with typed arrays (Michael Dawson) [#726](https://github.com/nodejs/node-addon-api/pull/726) +* [[`d463f02bc7`](https://github.com/nodejs/node-addon-api/commit/d463f02bc7)] - **src**: fix testEnumerables on ObjectWrap (Ferdinand Holzer) [#736](https://github.com/nodejs/node-addon-api/pull/736) +* [[`ba7ad37d44`](https://github.com/nodejs/node-addon-api/commit/ba7ad37d44)] - **src**: fix ObjectWrap inheritance (David Halls) [#732](https://github.com/nodejs/node-addon-api/pull/732) +* [[`31504c862b`](https://github.com/nodejs/node-addon-api/commit/31504c862b)] - **doc**: fix minor typo in object\_wrap.md (#741) (Daniel Bevenius) [#741](https://github.com/nodejs/node-addon-api/pull/741) +* [[`beccf2145d`](https://github.com/nodejs/node-addon-api/commit/beccf2145d)] - **test**: fix up delays for array buffer test (Michael Dawson) [#737](https://github.com/nodejs/node-addon-api/pull/737) +* [[`45cb1d9748`](https://github.com/nodejs/node-addon-api/commit/45cb1d9748)] - Correct AsyncProgressWorker link in README (#716) (Jeroen Janssen) +* [[`381c0da60c`](https://github.com/nodejs/node-addon-api/commit/381c0da60c)] - **doc**: add instance data APIs (Gabriel Schulhof) [#708](https://github.com/nodejs/node-addon-api/pull/708) + ## 2020-04-30 Version 3.0.0, @NickNaso ### Notable changes: diff --git a/README.md b/README.md index 206c7f108..dfc7c953e 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ to ideas specified in the **ECMA262 Language Specification**. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 3.0.0** +## **Current version: 3.0.1** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) @@ -55,8 +55,8 @@ to ideas specified in the **ECMA262 Language Specification**. -node-addon-api is based on [N-API](https://nodejs.org/api/n-api.html) and supports using different N-API versions. -This allows addons built with it to run with Node.js versions which support the targeted N-API version. +node-addon-api is based on [N-API](https://nodejs.org/api/n-api.html) and supports using different N-API versions. +This allows addons built with it to run with Node.js versions which support the targeted N-API version. **However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. diff --git a/package.json b/package.json index 4cc4b149c..b357f07b3 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,10 @@ "name": "Cory Mickelson", "url": "https://github.com/corymickelson" }, + { + "name": "Daniel Bevenius", + "url": "https://github.com/danbev" + }, { "name": "David Halls", "url": "https://github.com/davedoesdev" @@ -107,6 +111,10 @@ "name": "Jason Ginchereau", "url": "https://github.com/jasongin" }, + { + "name": "Jeroen Janssen", + "url": "https://github.com/japj" + }, { "name": "Jim Schlight", "url": "https://github.com/jschlight" @@ -119,6 +127,10 @@ "name": "joshgarde", "url": "https://github.com/joshgarde" }, + { + "name": "Kasumi Hanazuki", + "url": "https://github.com/hanazuki" + }, { "name": "Kelvin", "url": "https://github.com/kelvinhammond" @@ -139,6 +151,10 @@ "name": "legendecas", "url": "https://github.com/legendecas" }, + { + "name": "Lovell Fuller", + "url": "https://github.com/lovell" + }, { "name": "Luciano Martorella", "url": "https://github.com/lmartorella" @@ -272,5 +288,5 @@ "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile" }, - "version": "3.0.0" + "version": "3.0.1" } From 75dd4221240bbca2a9bce01498db93d1050cffec Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 21 Jun 2020 15:27:39 +0200 Subject: [PATCH 224/696] doc,test: finish TSFNEx --- doc/threadsafe.md | 8 +- doc/threadsafe_function_ex.md | 241 +++---- test/binding.gyp | 7 +- test/threadsafe_function_ex/test/basic.cc | 93 ++- test/threadsafe_function_ex/test/basic.js | 79 +- test/threadsafe_function_ex/test/example.cc | 681 +++++++++++++----- test/threadsafe_function_ex/test/example.js | 334 ++++++++- .../threadsafe_function_ex/util/TestRunner.js | 32 +- 8 files changed, 1064 insertions(+), 411 deletions(-) diff --git a/doc/threadsafe.md b/doc/threadsafe.md index 70eb296ba..86f63906b 100644 --- a/doc/threadsafe.md +++ b/doc/threadsafe.md @@ -69,14 +69,14 @@ situational **memory leaks**: callback on the heap for every call to `[Non]BlockingCall()`. - In acting in this "middle-man" fashion, the API will call the underlying "make call" N-API method on this packaged item. If the API has determined the - threadsafe function is no longer accessible (eg. all threads have Released yet + thread-safe function is no longer accessible (eg. all threads have released yet there are still items on the queue), **the callback passed to [Non]BlockingCall will not execute**. This means it is impossible to perform clean-up for calls that never execute their `CallJs` callback. **This may lead to memory leaks** if you are dynamically allocating memory. -- The `CallJs` does not receive the threadsafe function's context as a +- The `CallJs` does not receive the thread-safe function's context as a parameter. In order for the callback to access the context, it must have a - reference to either (1) the context directly, or (2) the threadsafe function + reference to either (1) the context directly, or (2) the thread-safe function to call `GetContext()`. Furthermore, the `GetContext()` method is not _type-safe_, as the method returns an object that can be "any-casted", instead of having a static type. @@ -94,7 +94,7 @@ with just a switch of the `NAPI_VERSION` compile-time constant. The removal of the dynamic call functionality has the additional side effects: - The API does _not_ act as a "middle-man" compared to the non-`Ex`. Once Node - finalizes the threadsafe function, the `CallJs` callback will execute with an + finalizes the thread-safe function, the `CallJs` callback will execute with an empty `Napi::Env` for any remaining items on the queue. This provides the the ability to handle any necessary clean up of the item's data. - The callback _does_ receive the context as a parameter, so a call to diff --git a/doc/threadsafe_function_ex.md b/doc/threadsafe_function_ex.md index 2657dd752..15188a96c 100644 --- a/doc/threadsafe_function_ex.md +++ b/doc/threadsafe_function_ex.md @@ -1,19 +1,17 @@ -# TODO -- Document new N-API 5+ only methods -- Continue with examples - # ThreadSafeFunctionEx The `Napi::ThreadSafeFunctionEx` type provides APIs for threads to communicate with the addon's main thread to invoke JavaScript functions on their behalf. The type is a three-argument templated class, each argument representing the type of: -- `ContextType = std::nullptr_t`: The threadsafe function's context. By default, +- `ContextType = std::nullptr_t`: The thread-safe function's context. By default, a TSFN has no context. - `DataType = void*`: The data to use in the native callback. By default, a TSFN can accept *any* data type. -- `Callback = void*(Napi::Env, Napi::Function, ContextType*, DataType*)`: The - callback to run for each item added to the queue. +- `Callback = void*(Napi::Env, Napi::Function jsCallback, ContextType*, + DataType*)`: The callback to run for each item added to the queue. If no + `Callback` is given, the API will call the function `jsCallback` with no + arguments. Documentation can be found for an [overview of the API](threadsafe.md), as well as [differences between the two thread-safe function @@ -40,46 +38,20 @@ Napi::ThreadSafeFunctionEx::ThreadSafeFunctionE - `tsfn`: The `napi_threadsafe_function` which is a handle for an existing thread-safe function. -Returns a non-empty `Napi::ThreadSafeFunctionEx` instance. - -### New - -Creates a new instance of the `Napi::ThreadSafeFunctionEx` object. - -```cpp -New(napi_env env, - const Function& callback, - const Object& resource, - ResourceString resourceName, - size_t maxQueueSize, - size_t initialThreadCount, - ContextType* context = nullptr); -``` - -- `env`: The `napi_env` environment in which to construct the - `Napi::ThreadSafeFunction` object. -- `callback`: The `Function` to call from another thread. -- `resource`: An object associated with the async work that will be passed to - possible async_hooks init hooks. -- `resourceName`: A JavaScript string to provide an identifier for the kind of - resource that is being provided for diagnostic information exposed by the - async_hooks API. -- `maxQueueSize`: Maximum size of the queue. `0` for no limit. -- `initialThreadCount`: The initial number of threads, including the main - thread, which will be making use of this function. -- `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`. - Can be retreived via `GetContext()`. - -Returns a non-empty `Napi::ThreadSafeFunction` instance. +Returns a non-empty `Napi::ThreadSafeFunctionEx` instance. To ensure the API +statically handles the correct return type for `GetContext()` and +`[Non]BlockingCall()`, pass the proper type arguments to +`Napi::ThreadSafeFunctionEx`. ### New -Creates a new instance of the `Napi::ThreadSafeFunctionEx` object with a -finalizer that runs when the object is being destroyed. +Creates a new instance of the `Napi::ThreadSafeFunctionEx` object. The `New` +function has several overloads for the various optional parameters: skip the +optional parameter for that specific overload. ```cpp New(napi_env env, - const Function& callback, + CallbackType callback, const Object& resource, ResourceString resourceName, size_t maxQueueSize, @@ -91,9 +63,9 @@ New(napi_env env, - `env`: The `napi_env` environment in which to construct the `Napi::ThreadSafeFunction` object. -- `callback`: The `Function` to call from another thread. -- `resource`: An object associated with the async work that will be passed to - possible async_hooks init hooks. +- `[optional] callback`: The `Function` to call from another thread. +- `[optional] resource`: An object associated with the async work that will be + passed to possible async_hooks init hooks. - `resourceName`: A JavaScript string to provide an identifier for the kind of resource that is being provided for diagnostic information exposed by the async_hooks API. @@ -102,19 +74,31 @@ New(napi_env env, thread, which will be making use of this function. - `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`. Can be retreived via `GetContext()`. -- `finalizeCallback`: Function to call when the `ThreadSafeFunctionEx` is being - destroyed. This callback will be invoked on the main thread when the - thread-safe function is about to be destroyed. It receives the context and the - finalize data given during construction (if given), and provides an - opportunity for cleaning up after the threads e.g. by calling - `uv_thread_join()`. It is important that, aside from the main loop thread, - there be no threads left using the thread-safe function after the finalize - callback completes. Must implement `void operator()(Env env, DataType* data, - ContextType* hint)`. +- `[optional] finalizeCallback`: Function to call when the + `ThreadSafeFunctionEx` is being destroyed. This callback will be invoked on + the main thread when the thread-safe function is about to be destroyed. It + receives the context and the finalize data given during construction (if + given), and provides an opportunity for cleaning up after the threads e.g. by + calling `uv_thread_join()`. It is important that, aside from the main loop + thread, there be no threads left using the thread-safe function after the + finalize callback completes. Must implement `void operator()(Env env, + DataType* data, ContextType* hint)`. - `[optional] data`: Data to be passed to `finalizeCallback`. Returns a non-empty `Napi::ThreadSafeFunctionEx` instance. +Depending on the targetted `NAPI_VERSION`, the API has different implementations +for `CallbackType callback`. + +When targetting version 4, `CallbackType` is: +- `const Function&` +- skipped, in which case the API creates a new no-op `Function` + +When targetting version 5+, `CallbackType` is: +- `const Function&` +- `std::nullptr_t` +- skipped, in which case the API passes `std::nullptr` + ### Acquire Add a thread to this thread-safe function object, indicating that a new thread @@ -206,113 +190,54 @@ Returns one of: ## Example -```cpp -#include -#include -#include - -using namespace Napi; - -std::thread nativeThread; - -struct ContextType { - int threadId; -}; - -using DataType = int; - -using ThreadSafeFunctionEx = tsfn; - -Value Start( const CallbackInfo& info ) -{ - Napi::Env env = info.Env(); - - if ( info.Length() < 2 ) - { - throw TypeError::New( env, "Expected two arguments" ); - } - else if ( !info[0].IsFunction() ) - { - throw TypeError::New( env, "Expected first arg to be function" ); - } - else if ( !info[1].IsNumber() ) - { - throw TypeError::New( env, "Expected second arg to be number" ); - } - - int count = info[1].As().Int32Value(); - - // Create a ThreadSafeFunction - tsfn = ThreadSafeFunction::New( - env, - info[0].As(), // JavaScript function called asynchronously - "Resource Name", // Name - 0, // Unlimited queue - 1, // Only one thread will use this initially - []( Napi::Env ) { // Finalizer used to clean threads up - nativeThread.join(); - } ); - - // Create a native thread - nativeThread = std::thread( [count] { - auto callback = []( Napi::Env env, Function jsCallback, int* value ) { - // Transform native data into JS data, passing it to the provided - // `jsCallback` -- the TSFN's JavaScript function. - jsCallback.Call( {Number::New( env, *value )} ); - - // We're finished with the data. - delete value; - }; - - for ( int i = 0; i < count; i++ ) - { - // Create new data - int* value = new int( clock() ); - - // Perform a blocking call - napi_status status = tsfn.BlockingCall( value, callback ); - if ( status != napi_ok ) - { - // Handle error - break; - } - - std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); - } - - // Release the thread-safe function - tsfn.Release(); - } ); - - return Boolean::New(env, true); -} - -Napi::Object Init( Napi::Env env, Object exports ) -{ - exports.Set( "start", Function::New( env, Start ) ); - return exports; -} - -NODE_API_MODULE( clock, Init ) -``` - -The above code can be used from JavaScript as follows: +For an in-line documented example, please see the ThreadSafeFunctionEx CI tests hosted here. +- [test/threadsafe_function_ex/test/example.js](../test/threadsafe_function_ex/test/example.js) +- [test/threadsafe_function_ex/test/example.cc](../test/threadsafe_function_ex/test/example.cc) -```js -const { start } = require('bindings')('clock'); - -start(function () { - console.log("JavaScript callback called with arguments", Array.from(arguments)); -}, 5); -``` +The example will create multiple set of threads. Each thread calls into +JavaScript with a numeric `base` value (deterministically calculated by the +thread id), with Node returning either a `number` or `Promise` that +resolves to `base * base`. -When executed, the output will show the value of `clock()` five times at one -second intervals: +From the root of the `node-addon-api` repository: ``` -JavaScript callback called with arguments [ 84745 ] -JavaScript callback called with arguments [ 103211 ] -JavaScript callback called with arguments [ 104516 ] -JavaScript callback called with arguments [ 105104 ] -JavaScript callback called with arguments [ 105691 ] +Usage: node ./test/threadsafe_function_ex/test/example.js [options] + + -c, --calls The number of calls each thread should make (number[]). + -a, --acquire [factor] Acquire a new set of `factor` call threads, using the + same `calls` definition. + -d, --call-delay The delay on callback resolution that each thread should + have (number[]). This is achieved via a delayed Promise + resolution in the JavaScript callback provided to the + TSFN. Using large delays here will cause all threads to + bottle-neck. + -D, --thread-delay The delay that each thread should have prior to making a + call (number[]). Using large delays here will cause the + individual thread to bottle-neck. + -l, --log-call Display console.log-based logging messages. + -L, --log-thread Display std::cout-based logging messages. + -n, --no-callback Do not use a JavaScript callback. + -e, --callback-error [thread[.call]] Cause an error to occur in the JavaScript callback for + the given thread's call (if provided; first thread's + first call otherwise). + + When not provided: + - defaults to [1,2,3,4,5] + - [factor] defaults to 1 + - defaults to [400,200,100,50,0] + - defaults to [400,200,100,50,0] + + +Examples: + + -c [1,2,3] -l -L + + Creates three threads that makes one, two, and three calls each, respectively. + + -c [5,5] -d [5000,5000] -D [0,0] -l -L + + Creates two threads that make five calls each. In this scenario, the threads will be + blocked primarily on waiting for the callback to resolve, as each thread's call takes + 5000 milliseconds. ``` diff --git a/test/binding.gyp b/test/binding.gyp index 8bfa59e3e..3a7ab89c9 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -2,6 +2,9 @@ 'target_defaults': { 'includes': ['../common.gypi'], 'sources': [ + 'threadsafe_function_ex/test/basic.cc', + 'threadsafe_function_ex/test/example.cc', + 'threadsafe_function_ex/test/threadsafe.cc', 'addon_data.cc', 'arraybuffer.cc', 'asynccontext.cc', @@ -35,9 +38,7 @@ 'object/set_property.cc', 'promise.cc', 'run_script.cc', - 'threadsafe_function_ex/test/basic.cc', - 'threadsafe_function_ex/test/example.cc', - 'threadsafe_function_ex/test/threadsafe.cc', + 'threadsafe_function/threadsafe_function_ctx.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', diff --git a/test/threadsafe_function_ex/test/basic.cc b/test/threadsafe_function_ex/test/basic.cc index e39b1f7b7..9c9b5e636 100644 --- a/test/threadsafe_function_ex/test/basic.cc +++ b/test/threadsafe_function_ex/test/basic.cc @@ -212,21 +212,20 @@ struct DataType { // CallJs callback function provided to `napi_create_threadsafe_function`. It is // _NOT_ used by `Napi::ThreadSafeFunctionEx<>`, which is why these arguments // are napi_*. -static void CallJs(napi_env env, napi_value /*jsCallback*/, void * /*context*/, +static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, void *data) { DataType *casted = static_cast(data); if (env != nullptr) { + if (jsCallback != nullptr) { + Function(env, jsCallback).Call(0, nullptr); + } if (data != nullptr) { - napi_value undefined; - napi_status status = napi_get_undefined(env, &undefined); - if (status != napi_ok) { - NAPI_THROW_VOID( - Error::New(env, "Could not get undefined from environment")); - } if (casted->reject) { - casted->deferred.Reject(undefined); + casted->deferred.Reject( + String::New(env, "The CallJs has rejected the promise")); } else { - casted->deferred.Resolve(undefined); + casted->deferred.Resolve( + String::New(env, "The CallJs has resolved the promise")); } } } @@ -256,34 +255,24 @@ class TSFNWrap : public base { TSFNWrap(const CallbackInfo &info) : base(info) { auto env = info.Env(); -#if NAPI_VERSION == 4 - napi_threadsafe_function napi_tsfn; - // A threadsafe function on N-API 4 still requires a callback function, so - // this uses the `EmptyFunctionFactory` helper method to return a no-op - // Function. - auto status = napi_create_threadsafe_function( - info.Env(), TSFN::EmptyFunctionFactory(env), nullptr, - String::From(info.Env(), "Test"), 0, 1, nullptr, nullptr, nullptr, - CallJs, &napi_tsfn); - if (status != napi_ok) { - NAPI_THROW_VOID(Error::New(env, "Could not create TSFN.")); + if (info.Length() < 1 || !info[0].IsFunction()) { + NAPI_THROW_VOID(Napi::TypeError::New( + env, "Invalid arguments: Expected arg0 = function")); } - _tsfn = TSFN(napi_tsfn); -#else + napi_threadsafe_function napi_tsfn; - // A threadsafe function may be `nullptr` on N-API 5+ as long as a `CallJS` - // is present. + // A threadsafe function on N-API 4 still requires a callback function, so + // this uses the `EmptyFunctionFactory` helper method to return a no-op + // Function on N-API 5+. auto status = napi_create_threadsafe_function( - info.Env(), nullptr, nullptr, String::From(info.Env(), "Test"), 0, 1, + info.Env(), info[0], nullptr, String::From(info.Env(), "Test"), 0, 1, nullptr, Finalizer, this, CallJs, &napi_tsfn); if (status != napi_ok) { - NAPI_THROW_VOID( - Error::New(env, "Could not get undefined from environment")); + NAPI_THROW_VOID(Error::New(env, "Could not create TSFN.")); } _tsfn = TSFN(napi_tsfn); -#endif } static std::array, 2> InstanceMethods() { @@ -292,10 +281,50 @@ class TSFNWrap : public base { } Napi::Value Call(const CallbackInfo &info) { - auto *data = - new DataType{Promise::Deferred::New(info.Env()), info[0].ToBoolean()}; - _tsfn.NonBlockingCall(data); - return data->deferred.Promise(); + Napi::Env env = info.Env(); + if (info.Length() < 1) { + NAPI_THROW(Napi::TypeError::New( + env, "Invalid arguments: Expected arg0 = number [0,5]"), + Value()); + } + auto arg0 = info[0]; + if (!arg0.IsNumber()) { + NAPI_THROW(Napi::TypeError::New( + env, "Invalid arguments: Expected arg0 = number [0,5]"), + Value()); + } + auto mode = info[0].ToNumber().Int32Value(); + switch (mode) { + // Use node-addon-api to send a call that either resolves or rejects the + // promise in the data. + case 0: + case 1: { + auto *data = new DataType{Promise::Deferred::New(env), mode == 1}; + _tsfn.NonBlockingCall(data); + return data->deferred.Promise(); + } + // Use node-addon-api to send a call with no data + case 2: { + _tsfn.NonBlockingCall(); + return Boolean::New(env, true); + } + // Use napi to send a call that either resolves or rejects the promise in + // the data. + case 3: + case 4: { + auto *data = new DataType{Promise::Deferred::New(env), mode == 4}; + napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); + return data->deferred.Promise(); + } + // Use napi to send a call with no data + case 5: { + napi_call_threadsafe_function(_tsfn, nullptr, napi_tsfn_nonblocking); + return Boolean::New(env, true); + } + } + NAPI_THROW(Napi::TypeError::New( + env, "Invalid arguments: Expected arg0 = number [0,5]"), + Value()); }; private: diff --git a/test/threadsafe_function_ex/test/basic.js b/test/threadsafe_function_ex/test/basic.js index 432856ddf..f5bfc678b 100644 --- a/test/threadsafe_function_ex/test/basic.js +++ b/test/threadsafe_function_ex/test/basic.js @@ -37,7 +37,7 @@ class BasicTest extends TestRunner { * the test. * - Asserts the contexts are the same as the context passed during threadsafe * function construction in two places: - * - (A) Makes one call, and waits for call to complete. + * - (A) Makes one call, and waits for call to complete. * - (B) Asserts that the context returns from the API's `GetContext()` */ async context({ TSFNWrap }) { @@ -54,10 +54,10 @@ class BasicTest extends TestRunner { * that handles all of its JavaScript processing on the callJs instead of the * callback. * - Creates a threadsafe function with no JavaScript context or callback. - * - Makes one call, waiting for completion. The internal `CallJs` resolves the call if jsCallback is empty, otherwise rejects. + * - Makes one call, waiting for completion. The internal `CallJs` resolves + * the call if jsCallback is empty, otherwise rejects. */ async empty({ TSFNWrap }) { - debugger; if (typeof TSFNWrap === 'function') { const tsfn = new TSFNWrap(); await tsfn.call(); @@ -66,6 +66,79 @@ class BasicTest extends TestRunner { return true; } + /** + * A `ThreadSafeFunctionEx` can be constructed with an existing + * napi_threadsafe_function. + * - Creates a native napi_threadsafe_function with no context, using the + * jsCallback passed from this test. + * - Makes six calls: + * - Use node-addon-api's `NonBlockingCall` *OR* napi's + * `napi_call_threadsafe_function` _cross_ + * - With data that resolves *OR rejects on CallJs + * - With no data that rejects on CallJs + * - Releases the TSFN. + */ + async existing({ TSFNWrap }) { + + /** + * Called by the TSFN's jsCallback below. + * @type {function|undefined} + */ + let currentCallback = undefined; + + const tsfn = new TSFNWrap(function () { + if (typeof currentCallback === 'function') { + currentCallback.apply(undefined, arguments); + } + }); + /** + * The input argument to `tsfn.call()`: 0-2: + * ThreadSafeFunctionEx.NonBlockingCall(data) with... + * - 0: data, resolve promise in CallJs + * - 1: data, reject promise in CallJs + * - 2: data = nullptr 3-5: napi_call_threadsafe_function(data, + * napi_tsfn_nonblocking) with... + * - 3: data, resolve promise in CallJs + * - 4: data, reject promise in CallJs + * - 5: data = nullptr + * @type {[0,1,2,3,4,5]} + */ + const input = [0, 1, 2, 3, 4, 5]; + + let caught = false; + + while (input.length) { + // Perform a call that resolves + await tsfn.call(input.shift()); + + // Perform a call that rejects + caught = false; + try { + await tsfn.call(input.shift()); + } catch (e) { + caught = true; + } finally { + assert(caught, "The rejection was not caught"); + } + + // Perform a call with no data + caught = false; + await new Promise((resolve, reject) => { + currentCallback = () => { + resolve(); + reject = undefined; + }; + tsfn.call(input.shift()); + setTimeout(() => { + if (reject) { + reject(new Error("tsfn.call() timed out")); + } + }, 0); + }); + } + return await tsfn.release(); + } + /** * A `ThreadSafeFunctionEx<>` can be constructed with no type arguments. * - Creates a threadsafe function with no context or callback or callJs. diff --git a/test/threadsafe_function_ex/test/example.cc b/test/threadsafe_function_ex/test/example.cc index 83293667c..b0063a5cd 100644 --- a/test/threadsafe_function_ex/test/example.cc +++ b/test/threadsafe_function_ex/test/example.cc @@ -3,11 +3,18 @@ #include #include #include +#include #include #include -static constexpr auto DEFAULT_THREAD_COUNT = 10U; -static constexpr auto DEFAULT_CALL_COUNT = 2; +using ThreadExitHandler = void (*)(size_t threadId); + +struct ThreadOptions { + size_t threadId; + int calls; + int callDelay; + int threadDelay; +}; static struct { bool logCall = true; // Uses JS console.log to output when the TSFN is @@ -16,22 +23,6 @@ static struct { // NonBlockingCall() request has finished. } DefaultOptions; // Options from Start() -/** - * @brief Macro used specifically to support the dual CI test / documentation - * example setup. Exceptions are always thrown as JavaScript exceptions when - * running in example mode. - * - */ -#define TSFN_THROW(tsfnWrap, e, ...) \ - if (tsfnWrap->cppExceptions) { \ - do { \ - (e).ThrowAsJavaScriptException(); \ - return __VA_ARGS__; \ - } while (0); \ - } else { \ - NAPI_THROW(e, __VA_ARGS__); \ - } - #if (NAPI_VERSION > 3) using namespace Napi; @@ -43,186 +34,298 @@ class TSFNWrap; // Context of the TSFN. using Context = TSFNWrap; -// Data passed (as pointer) to [Non]BlockingCall +// Data returned to a thread when it requests a TSFN call. This example uses +// promises to synchronize between threads. Since this example needs to be built +// with exceptions both enabled and disabled, we will always use a static +// "positive" result, and dynamically at run-time determine if it failed. +// Otherwise, we could use std::promise.set_exception to handle errors. +struct CallJsResult { + int result; + bool isFinalized; + std::string error; +}; + +// The structure of data we send to `CallJs` struct Data { - std::promise promise; + std::promise promise; uint32_t threadId; bool logCall; - uint32_t base; + int callDelay; + uint32_t base; // The "input" data, which CallJs will calculate `base * base` + int callId; // The call id (unique to the thread) }; -using DataType = std::unique_ptr; -// CallJs callback function +// Data passed (as pointer) to [Non]BlockingCall is shared among multiple +// threads (native thread and Node thread). +using DataType = std::shared_ptr; + +// When providing the `CallJs` result back to the thread, we pass information +// about where and how the result came (for logging). +enum ResultLocation { + NUMBER, // The callback returned a number + PROMISE, // The callback returned a Promise that resolved to a number + DEFAULT // There was no callback provided to TSFN::New +}; + +// CallJs callback function, used to transform the native C data to JS data. static void CallJs(Napi::Env env, Napi::Function jsCallback, Context * /*context*/, DataType *dataPtr) { + // If we have data if (dataPtr != nullptr) { - auto &data = *dataPtr; + std::weak_ptr weakData(*dataPtr); + // Create concrete reference to our DataType. + auto &data(*dataPtr); + + // The success handler ran by the following `CallJs` function. + auto handleResult = [=](Napi::Env env, int calculated, + ResultLocation location) { + // auto &data(*dataPtr); + if (auto data = + weakData + .lock()) { // Has to be copied into a shared_ptr before usage + // std::cout << *data << "\n"; + if (data->logCall) { + std::string message( + "[Thread " + std::to_string(data->threadId) + + "] [CallJs ] [Call " + std::to_string(data->callId) + + "] Receive answer: result = " + std::to_string(calculated) + + (location == ResultLocation::NUMBER + ? " (as number)" + : location == ResultLocation::PROMISE ? " (as Promise)" + : " (as default)")); + + auto console = env.Global().Get("console").As(); + console.Get("log").As().Call(console, + {String::New(env, message)}); + } + // Resolve the `std::promise` awaited on in the child thread. + data->promise.set_value(CallJsResult{calculated, false, ""}); + // Free the data. + // delete dataPtr; + } + }; + + // The error handler ran by the following `CallJs` function. + + auto handleError = [=](const std::string &what) { + if (auto data = + weakData + .lock()) { // Has to be copied into a shared_ptr before usage + // Resolve the `std::promise` awaited on in the child thread with an + // "errored success" value. Instead of erroring at the thread level, + // this could also return the default result. + data->promise.set_value(CallJsResult{0, false, what}); + } + + // // Free the data. + // delete dataPtr; + }; + if (env != nullptr) { - auto calculated = data->base * data->base; + // If the callback was provided at construction time via TSFN::New if (!jsCallback.IsEmpty()) { - auto value = jsCallback.Call({Number::New(env, data->threadId), Number::New(env, calculated)}); + // Call the callback + auto value = jsCallback.Call( + {Number::New(env, data->threadId), Number::New(env, data->callId), + Number::New(env, data->logCall), Number::New(env, data->base), + Number::New(env, data->callDelay)}); + + // Check if the callback failed if (env.IsExceptionPending()) { const auto &error = env.GetAndClearPendingException(); - data->promise.set_exception( - std::make_exception_ptr(std::runtime_error(error.Message()))); - } else if (value.IsNumber()) { - calculated = value.ToNumber(); + handleError(error.Message()); + } + + // Check for an immediate number result + else if (value.IsNumber()) { + handleResult(env, value.ToNumber(), ResultLocation::NUMBER); + } + + // Check for a Promise result + else if (value.IsPromise()) { + + // Construct the Promise.then and Promise.catch handlers. These could + // also be a statically-defined `Function`s. + + // Promise.then handler. + auto promiseHandlerThen = Function::New(env, [=](const CallbackInfo + &info) { + // Check for Promise result + if (info.Length() < 1 || !info[0].IsNumber()) { + handleError( + "Expected callback Promise resolution to be of type number"); + } else { + auto result = info[0].ToNumber().Int32Value(); + handleResult(info.Env(), result, ResultLocation::PROMISE); + } + }); + + // Promise.catch handler. + auto promiseHandlerCatch = + Function::New(env, [&](const CallbackInfo &info) { + if (info.Length() < 1 || !info[0].IsObject()) { + handleError("Unknown error in callback handler"); + } else { + auto errorAsValue(info[0] + .As() + .Get("toString") + .As() + .Call(info[0], {})); + handleError(errorAsValue.ToString()); + } + }); + + // Execute the JavaScript equivalent of `promise.then.call(promise, + // promiseHandlerThen).catch.call(promise, promiseHandlerCatch);` + value.As() + .Get("then") + .As() + .Call(value, {promiseHandlerThen}) + .As() + .Get("catch") + .As() + .Call(value, {promiseHandlerCatch}); + } + // When using N-API 4, the callback is a valid no-op Function that + // returns `undefined`. This also allows the callback itself to return + // `undefined` to take the default result. + else if (value.IsUndefined()) { + handleResult(env, data->base * data->base, ResultLocation::DEFAULT); + } else { + handleError("Expected callback return to be of type number " + "| Promise"); } } - if (data->logCall) { - std::string message("Thread " + std::to_string(data->threadId) + - " CallJs resolving std::promise"); - auto console = env.Global().Get("console").As(); - console.Get("log").As().Call(console, - {String::New(env, message)}); + + // If no callback provided, handle with default result that the callback + // would have provided. + else { + handleResult(env, data->base * data->base, ResultLocation::DEFAULT); } - data->promise.set_value(calculated); - } else { - data->promise.set_exception(std::make_exception_ptr( - std::runtime_error("TSFN has been finalized."))); + } + // If `env` is nullptr, then all threads have called finished their usage of + // the TSFN (either by calling `Release` or making a call and receiving + // `napi_closing`). In this scenario, it is not allowed to call into + // JavaScript, as the TSFN has been finalized. + else { + handleError("The TSFN has been finalized."); } } - // We do NOT delete data as it is a unique_ptr held by the calling thread. } // Full type of the ThreadSafeFunctionEx using TSFN = ThreadSafeFunctionEx; - using base = tsfnutil::TSFNWrapBase; // A JS-accessible wrap that holds the TSFN. class TSFNWrap : public base { public: - TSFNWrap(const CallbackInfo &info) : base(info) { - if (info.Length() > 0 && info[0].IsObject()) { - auto arg0 = info[0].ToObject(); - if (arg0.Has("cppExceptions")) { - auto cppExceptions = arg0.Get("cppExceptions"); - if (cppExceptions.IsBoolean()) { - cppExceptions = cppExceptions.As(); - } else { - // We explicitly use the addon's except/noexcept settings here, since - // we don't have a valid setting. - Napi::TypeError::New(Env(), "cppExceptions is not a boolean") - .ThrowAsJavaScriptException(); - } - } - } - } + TSFNWrap(const CallbackInfo &info) : base(info) {} + ~TSFNWrap() { - for (auto &thread : finalizerData->threads) { + for (auto &thread : finalizerData.threads) { + // The TSFNWrap destructor runs when our ObjectWrap'd instance is + // garbage-collected. This should never happen with proper usage of + // `await` on `tsfn.release()`! if (thread.joinable()) { thread.join(); } } } - struct FinalizerData { - std::vector threads; - std::unique_ptr deferred; - }; - - // The finalizer data is shared, because we want to join the threads if our - // TSFNWrap object gets garbage-collected and there are still active threads. - using FinalizerDataType = std::shared_ptr; - -#define THREADLOG(X) \ - if (context->logThread) { \ - std::cout << X; \ - } - static void threadEntry(size_t threadId, TSFN tsfn, uint32_t callCount, - Context *context) { - using namespace std::chrono_literals; - - THREADLOG("Thread " << threadId << " starting...\n") - - for (auto i = 0U; i < callCount; ++i) { - auto data = std::make_unique(); - data->base = threadId + 1; - data->threadId = threadId; - data->logCall = context->logCall; - THREADLOG("Thread " << threadId << " making call, base = " << data->base - << "\n") - - tsfn.NonBlockingCall(&data); - auto future = data->promise.get_future(); - auto result = future.get(); - context->callSucceeded(result); - THREADLOG("Thread " << threadId << " got result: " << result << "\n") - } - - THREADLOG("Thread " << threadId << " finished.\n\n") - tsfn.Release(); - } -#undef THREADLOG - - static std::array, 4> InstanceMethods() { + static std::array, 5> InstanceMethods() { return {{InstanceMethod("getContext", &TSFNWrap::GetContext), InstanceMethod("start", &TSFNWrap::Start), + InstanceMethod("acquire", &TSFNWrap::Acquire), InstanceMethod("callCount", &TSFNWrap::CallCount), InstanceMethod("release", &TSFNWrap::Release)}}; } - bool cppExceptions = false; - bool logThread; - bool logCall; + bool logThread = DefaultOptions.logThread; + bool logCall = DefaultOptions.logCall; + bool hasEmptyCallback; std::atomic_uint succeededCalls; std::atomic_int aggregate; - FinalizerDataType finalizerData; + // The structure of the data send to the finalizer. + struct FinalizerDataType { + std::vector threads; + std::vector outstandingCalls; + std::mutex + callMutex; // To protect multi-threaded accesses to `outstandingCalls` + std::unique_ptr deferred; + } finalizerData; + + // Used for logging. + std::mutex logMutex; Napi::Value Start(const CallbackInfo &info) { Napi::Env env = info.Env(); if (_tsfn) { - TSFN_THROW(this, Napi::Error::New(Env(), "TSFN already exists."), - Value()); + NAPI_THROW(Napi::Error::New(Env(), "TSFN already exists."), Value()); } // Creates a list to hold how many times each thread should make a call. - std::vector callCounts; + std::vector callCounts; // The JS-provided callback to execute for each call (if provided) Function callback; - finalizerData = std::make_shared(); - - logThread = DefaultOptions.logThread; - if (info.Length() > 0 && info[0].IsObject()) { auto arg0 = info[0].ToObject(); + + if (arg0.Has("callback")) { + auto cb = arg0.Get("callback"); + if (cb.IsUndefined()) { + // An empty callback option will create a valid no-op function on + // N-API 4 or leave `callback` as `std::nullptr` on N-API 5+. + callback = TSFN::FunctionOrEmpty(env, callback); + } else if (cb.IsFunction()) { + callback = cb.As(); + } else { + NAPI_THROW(Napi::TypeError::New( + Env(), "Invalid arguments: callback is not a " + "function. See StartOptions definition."), + Value()); + } + } + + hasEmptyCallback = callback.IsEmpty(); + + // Ensure proper parameters and add to our list of threads. if (arg0.Has("threads")) { Napi::Value threads = arg0.Get("threads"); if (threads.IsArray()) { Napi::Array threadsArray = threads.As(); for (auto i = 0U; i < threadsArray.Length(); ++i) { Napi::Value elem = threadsArray.Get(i); - if (elem.IsNumber()) { - callCounts.push_back(elem.As().Int32Value()); + if (elem.IsObject()) { + Object o = elem.ToObject(); + if (!(o.Has("calls") && o.Has("callDelay") && + o.Has("threadDelay"))) { + NAPI_THROW(Napi::TypeError::New( + Env(), "Invalid arguments. See " + "StartOptions.threads definition."), + Value()); + } + callCounts.push_back(ThreadOptions{ + callCounts.size(), o.Get("calls").ToNumber(), + hasEmptyCallback ? -1 : o.Get("callDelay").ToNumber(), + o.Get("threadDelay").ToNumber()}); } else { - TSFN_THROW(this, Napi::TypeError::New(Env(), "Invalid arguments"), + NAPI_THROW(Napi::TypeError::New( + Env(), "Invalid arguments. See " + "StartOptions.threads definition."), Value()); } } - } else if (threads.IsNumber()) { - auto threadCount = threads.As().Int32Value(); - for (auto i = 0; i < threadCount; ++i) { - callCounts.push_back(DEFAULT_CALL_COUNT); - } - } else { - TSFN_THROW(this, Napi::TypeError::New(Env(), "Invalid arguments"), - Value()); - } - } - - if (arg0.Has("callback")) { - auto cb = arg0.Get("callback"); - if (cb.IsFunction()) { - callback = cb.As(); } else { - TSFN_THROW(this, - Napi::TypeError::New(Env(), "Callback is not a function"), - Value()); + NAPI_THROW( + Napi::TypeError::New( + Env(), + "Invalid arguments. See StartOptions.threads definition."), + Value()); } } @@ -231,8 +334,9 @@ class TSFNWrap : public base { if (logCallOption.IsBoolean()) { logCall = logCallOption.As(); } else { - TSFN_THROW(this, - Napi::TypeError::New(Env(), "logCall is not a boolean"), + NAPI_THROW(Napi::TypeError::New( + Env(), "Invalid arguments: logCall is not a boolean. " + "See StartOptions definition."), Value()); } } @@ -242,24 +346,16 @@ class TSFNWrap : public base { if (logThreadOption.IsBoolean()) { logThread = logThreadOption.As(); } else { - TSFN_THROW(this, - Napi::TypeError::New(Env(), "logThread is not a boolean"), + NAPI_THROW(Napi::TypeError::New( + Env(), "Invalid arguments: logThread is not a " + "boolean. See StartOptions definition."), Value()); } } } - // Apply default arguments - if (callCounts.size() == 0) { - for (auto i = 0U; i < DEFAULT_THREAD_COUNT; ++i) { - callCounts.push_back(DEFAULT_CALL_COUNT); - } - } - const auto threadCount = callCounts.size(); - auto *finalizerDataPtr = new FinalizerDataType(finalizerData); - succeededCalls = 0; aggregate = 0; _tsfn = TSFN::New( @@ -271,49 +367,121 @@ class TSFNWrap : public base { threadCount + 1, // size_t initialThreadCount, +1 for Node thread this, // Context* context, Finalizer, // Finalizer finalizer - finalizerDataPtr // FinalizerDataType* data + &finalizerData // FinalizerDataType* data ); - for (auto threadId = 0U; threadId < threadCount; ++threadId) { - finalizerData->threads.push_back(std::thread(threadEntry, threadId, _tsfn, - callCounts[threadId], this)); + if (logThread) { + std::cout << "[Starting] Starting example with options: {\n[Starting] " + "Log Call = " + << (logCall ? "true" : "false") + << ",\n[Starting] Log Thread = " + << (logThread ? "true" : "false") + << ",\n[Starting] Callback = " + << (hasEmptyCallback ? "[empty]" : "function") + << ",\n[Starting] Threads = [\n"; + for (auto &threadOption : callCounts) { + std::cout << "[Starting] " << threadOption.threadId + << " -> { Calls: " << threadOption.calls << ", Call Delay: " + << (threadOption.callDelay == -1 + ? "[no callback]" + : std::to_string(threadOption.callDelay)) + << ", Thread Delay: " << threadOption.threadDelay << " },\n"; + } + std::cout << "[Starting] ]\n[Starting] }\n"; + } + + // for (auto threadId = 0U; threadId < threadCount; ++threadId) { + for (auto &threadOption : callCounts) { + finalizerData.threads.push_back( + std::thread(threadEntry, _tsfn, threadOption, this)); } return Number::New(env, threadCount); }; - // TSFN finalizer. Joins the threads and resolves the Promise returned by - // `Release()` above. - static void Finalizer(Napi::Env env, FinalizerDataType *finalizeData, - Context *ctx) { + Napi::Value Acquire(const CallbackInfo &info) { + Napi::Env env = info.Env(); - if (ctx->logThread) { - std::cout << "Finalizer joining threads\n"; + if (!_tsfn) { + NAPI_THROW(Napi::Error::New(Env(), "TSFN does not exist."), Value()); } - for (auto &thread : (*finalizeData)->threads) { - if (thread.joinable()) { - thread.join(); + + // Creates a list to hold how many times each thread should make a call. + std::vector callCounts; + if (info.Length() > 0 && info[0].IsArray()) { + Napi::Array threadsArray = info[0].As(); + for (auto i = 0U; i < threadsArray.Length(); ++i) { + Napi::Value elem = threadsArray.Get(i); + if (elem.IsObject()) { + Object o = elem.ToObject(); + if (!(o.Has("calls") && o.Has("callDelay") && o.Has("threadDelay"))) { + NAPI_THROW(Napi::TypeError::New(Env(), + "Invalid arguments. See " + "StartOptions.threads definition."), + Value()); + } + callCounts.push_back(ThreadOptions{ + callCounts.size() + finalizerData.threads.size(), + o.Get("calls").ToNumber(), + hasEmptyCallback ? -1 : o.Get("callDelay").ToNumber(), + o.Get("threadDelay").ToNumber()}); + } else { + NAPI_THROW(Napi::TypeError::New(Env(), + "Invalid arguments. See " + "StartOptions.threads definition."), + Value()); + } } + } else { + NAPI_THROW( + Napi::TypeError::New( + Env(), "Invalid arguments. See StartOptions.threads definition."), + Value()); } - ctx->clearTSFN(); - if (ctx->logThread) { - std::cout << "Finished finalizing threads.\n"; + + if (logThread) { + for (auto &threadOption : callCounts) { + std::cout << "[Acquire ] " << threadOption.threadId + << " -> { Calls: " << threadOption.calls << ", Call Delay: " + << (threadOption.callDelay == -1 + ? "[no callback]" + : std::to_string(threadOption.callDelay)) + << ", Thread Delay: " << threadOption.threadDelay << " },\n"; + } + std::cout << "[Acquire ] ]\n[Acquire ] }\n"; } - (*finalizeData)->deferred->Resolve(Boolean::New(env, true)); - delete finalizeData; + auto started = 0U; + + for (auto &threadOption : callCounts) { + // The `Acquire` call may be called from any thread, but we do it here to + // avoid a race condition where the thread starts but the TSFN has been + // finalized. + auto status = _tsfn.Acquire(); + if (status == napi_ok) { + finalizerData.threads.push_back( + std::thread(threadEntry, _tsfn, threadOption, this)); + ++started; + } + } + return Number::New(env, started); } + + // Release the TSFN from the Node thread. This will return a `Promise` that + // resolves in the Finalizer. Napi::Value Release(const CallbackInfo &info) { - if (finalizerData->deferred) { - return finalizerData->deferred->Promise(); + if (finalizerData.deferred) { + return finalizerData.deferred->Promise(); } - finalizerData->deferred.reset( + finalizerData.deferred.reset( new Promise::Deferred(Promise::Deferred::New(info.Env()))); _tsfn.Release(); - return finalizerData->deferred->Promise(); + return finalizerData.deferred->Promise(); }; + // Returns an array corresponding to the amount of succeeded calls and the sum + // aggregate. Napi::Value CallCount(const CallbackInfo &info) { Napi::Env env(info.Env()); @@ -323,17 +491,196 @@ class TSFNWrap : public base { return results; }; + // Returns the TSFN's context. Napi::Value GetContext(const CallbackInfo &) { return _tsfn.GetContext()->Value(); }; + // The thread entry point. It receives as arguments the TSFN, the call + // options, and the context. + static void threadEntry(TSFN tsfn, ThreadOptions options, Context *context) { +#define THREADLOG(X) \ + if (context->logThread) { \ + std::lock_guard lock(context->logMutex); \ + std::cout << "[Thread " << threadId << "] [Native ] " \ + << (data->callId == -1 \ + ? "" \ + : "[Call " + std::to_string(data->callId) + "] ") \ + << X; \ + } + +#define THREADLOG_MAIN(X) \ + if (context->logThread) { \ + std::lock_guard lock(context->logMutex); \ + std::cout << "[Thread " << threadId << "] [Native ] " << X; \ + } + using namespace std::chrono_literals; + auto threadId = options.threadId; + + // To help with simultaneous threads using the logging mechanism, we'll + // delay at thread start. + std::this_thread::sleep_for(threadId * 10ms); + THREADLOG_MAIN("Thread " << threadId << " started.\n") + + enum ThreadState { + // Starting stating. + Running, + + // When all requests have been completed. + Release, + + // If a `NonBlockingCall` results in a Promise but while waiting + // for the resolution, the TSFN is finalized. + AlreadyFinalized, + + // If a `NonBlockingCall` receiving `napi_closing`, we do *NOT* `Release` + // it. + Closing + } state = ThreadState::Running; + + for (auto i = 0; state == Running; ++i) { + + if (i >= options.calls) { + state = Release; + break; + } + + DataType data(context->makeNewCall(threadId, i, context->logCall, + options.callDelay)); + + if (options.threadDelay > 0 && i > 0) { + THREADLOG("Delay for " << options.threadDelay + << "ms before next call\n") + std::this_thread::sleep_for(options.threadDelay * 1ms); + } + + THREADLOG("Performing call request: base = " << data->base << "\n") + + auto status = tsfn.NonBlockingCall(&data); + + if (status == napi_ok) { + auto future = data->promise.get_future(); + auto result = future.get(); + if (result.error.length() == 0) { + context->callSucceeded(data, result.result); + THREADLOG("Receive answer: result = " << result.result << "\n") + continue; + } else if (result.isFinalized) { + THREADLOG("Application Error: The TSFN has been finalized.\n") + // If the Finalizer has canceled this request, we do not call + // `Release()`. + state = AlreadyFinalized; + } + } else if (status == napi_closing) { + // A thread **MUST NOT** call `Abort()` or `Release()` if we receive an + // `napi_closing` call. + THREADLOG("N-API Error: The thread-safe function is aborted and " + "cannot accept more calls.\n") + state = Closing; + } else if (status == napi_queue_full) { + // The example will finish this thread's use of the TSFN if it is full. + THREADLOG("N-API Error: The queue was full when trying to call in a " + "non-blocking method.\n") + state = Release; + } else if (status == napi_invalid_arg) { + THREADLOG("N-API Error: The thread-safe function is closed.\n") + state = AlreadyFinalized; + } else { + THREADLOG("N-API Error: A generic error occurred when attemping to " + "add to the queue.\n") + state = AlreadyFinalized; + } + context->callFailed(data); + } + + THREADLOG_MAIN("Thread " << threadId << " finished. State: " + << (state == Closing ? "Closing" + : state == AlreadyFinalized + ? "Already Finalized" + : "Release") + << "\n") + + if (state == Release) { + tsfn.Release(); + } +#undef THREADLOG +#undef THREADLOG_MAIN + } + + // TSFN finalizer. Joins the threads and resolves the Promise returned by + // `Release()` above. + static void Finalizer(Napi::Env env, FinalizerDataType *finalizeDataPtr, + Context *ctx) { + + auto &finalizeData(*finalizeDataPtr); + auto outstanding = finalizeData.outstandingCalls.size(); + if (ctx->logThread) { + std::cout << "[Finalize] [Native ] Joining threads (" << outstanding + << " outstanding requests)...\n"; + } + if (outstanding > 0) { + for (auto &request : finalizeData.outstandingCalls) { + request->promise.set_value( + CallJsResult{-1, true, "The TSFN has been finalized."}); + } + } + for (auto &thread : finalizeData.threads) { + thread.join(); + } + + ctx->clearTSFN(); + if (ctx->logThread) { + std::cout << "[Finalize] [Native ] Threads joined.\n"; + } + + finalizeData.deferred->Resolve(Boolean::New(env, true)); + } + // This method does not run on the Node thread. void clearTSFN() { _tsfn = TSFN(); } // This method does not run on the Node thread. - void callSucceeded(int result) { + void callSucceeded(DataType data, int result) { + std::lock_guard lock(finalizerData.callMutex); succeededCalls++; aggregate += result; + + auto &calls = finalizerData.outstandingCalls; + auto it = std::find_if( + calls.begin(), calls.end(), + [&](std::shared_ptr const &p) { return p.get() == data.get(); }); + + if (it != calls.end()) { + calls.erase(it); + } + } + + // This method does not run on the Node thread. + void callFailed(DataType data) { + std::lock_guard lock(finalizerData.callMutex); + auto &calls = finalizerData.outstandingCalls; + auto it = + std::find_if(calls.begin(), calls.end(), + [&](std::shared_ptr const &p) { return p == data; }); + + if (it != calls.end()) { + calls.erase(it); + } + } + + DataType makeNewCall(size_t threadId, int callId, bool logCall, + int callDelay) { + // x + // auto &calls(finalizerData.outstandingCalls); + finalizerData.outstandingCalls.emplace_back(std::make_shared()); + auto data(finalizerData.outstandingCalls.back()); + data->threadId = threadId; + data->logCall = logCall; + data->callDelay = callDelay; + data->base = threadId + 1; + data->callId = callId; + + return data; } }; } // namespace example diff --git a/test/threadsafe_function_ex/test/example.js b/test/threadsafe_function_ex/test/example.js index d47cba236..2afa36de8 100644 --- a/test/threadsafe_function_ex/test/example.js +++ b/test/threadsafe_function_ex/test/example.js @@ -1,47 +1,337 @@ // @ts-check 'use strict'; const assert = require('assert'); - const { TestRunner } = require('../util/TestRunner'); +/** + * @typedef {(threadId: number, callId: number, logCall: boolean, value: number, callDelay: + * number)=>number|Promise} TSFNCallback + */ + +/** + * @typedef {Object} ThreadOptions + * @property {number} calls + * @property {number} callDelay + * @property {number} threadDelay + */ + +/** + * The options when starting the addon's TSFN. + * @typedef {Object} StartOptions + * @property {ThreadOptions[]} threads + * @property {boolean} [logCall] If `true`, log messages via `console.log`. + * @property {boolean} [logThread] If `true`, log messages via `std::cout`. + * @property {number} [acquireFactor] Acquire a new set of \`acquireFactor\` + * call threads. `NAPI_CPP_EXCEPTIONS`, allowing errors to be caught as + * exceptions. + * @property {TSFNCallback} callback The callback provided to the threadsafe + * function. + * @property {[number,number]} [callbackError] Tuple of `[threadId, callId]` to + * cause an error on +*/ + +/** + * Returns test options. + * @type {() => { options: StartOptions, calls: { aggregate: number } }} + */ +const getTestDetails = () => { + const TEST_CALLS = [1, 2, 3, 4, 5]; + const TEST_ACQUIRE = 1; + const TEST_CALL_DELAY = [400, 200, 100, 50, 0] + const TEST_THREAD_DELAY = TEST_CALL_DELAY.map(_ => _); + const TEST_LOG_CALL = false; + const TEST_LOG_THREAD = false; + const TEST_NO_CALLBACK = false; + + /** @type {[number, number] | undefined} [threadId, callId] */ + const TEST_CALLBACK_ERROR = undefined; + + // Set options as defaults + let testCalls = TEST_CALLS; + let testAcquire = TEST_ACQUIRE; + let testCallDelay = TEST_CALL_DELAY; + let testThreadDelay = TEST_THREAD_DELAY; + let testLogCall = TEST_LOG_CALL; + let testLogThread = TEST_LOG_THREAD; + let testNoCallback = TEST_NO_CALLBACK; + let testCallbackError = TEST_CALLBACK_ERROR; + + let args = process.argv.slice(2); + let arg; + + const showHelp = () => { + console.log( + ` +Usage: ${process.argv0} .${process.argv[1].replace(process.cwd(), '')} [options] + + -c, --calls The number of calls each thread should make (number[]). + -a, --acquire [factor] Acquire a new set of \`factor\` call threads, using the + same \`calls\` definition. + -d, --call-delay The delay on callback resolution that each thread should + have (number[]). This is achieved via a delayed Promise + resolution in the JavaScript callback provided to the + TSFN. Using large delays here will cause all threads to + bottle-neck. + -D, --thread-delay The delay that each thread should have prior to making a + call (number[]). Using large delays here will cause the + individual thread to bottle-neck. + -l, --log-call Display console.log-based logging messages. + -L, --log-thread Display std::cout-based logging messages. + -n, --no-callback Do not use a JavaScript callback. + -e, --callback-error [thread[.call]] Cause an error to occur in the JavaScript callback for + the given thread's call (if provided; first thread's + first call otherwise). + + When not provided: + - defaults to [${TEST_CALLS}] + - [factor] defaults to ${TEST_ACQUIRE} + - defaults to [${TEST_CALL_DELAY}] + - defaults to [${TEST_THREAD_DELAY}] + + +Examples: + + -c [1,2,3] -l -L + + Creates three threads that makes one, two, and three calls each, respectively. + + -c [5,5] -d [5000,5000] -D [0,0] -l -L + + Creates two threads that make five calls each. In this scenario, the threads will be + blocked primarily on waiting for the callback to resolve, as each thread's call takes + 5000 milliseconds. +` + ); + return undefined; + }; + + while ((arg = args.shift())) { + switch (arg) { + case "-h": + case "--help": + return showHelp(); + + case "--calls": + case "-c": + try { + testCalls = JSON.parse(args.shift()); + } catch (ex) { /* ignore */ } + break; + + case "--acquire": + case "-a": + testAcquire = parseInt(args[0]); + if (!isNaN(testAcquire)) { + args.shift(); + } else { + testAcquire = TEST_ACQUIRE; + } + break; + + case "--call-delay": + case "-d": + try { + testCallDelay = JSON.parse(args.shift()); + } catch (ex) { /* ignore */ } + break; + + case "--thread-delay": + case "-D": + try { + testThreadDelay = JSON.parse(args.shift()); + } catch (ex) { /* ignore */ } + break; + + case "--log-call": + case "-l": + testLogCall = true; + break; + + case "--log-thread": + case "-L": + testLogThread = true; + break; + + case "--no-callback": + case "-n": + testNoCallback = true; + break; + + case "-e": + case "--callback-error": + try { + if (!args[0].startsWith("-")) { + const split = args.shift().split(/\./); + testCallbackError = [parseInt(split[0], 10) || 0, parseInt(split[1], 10) || 0]; + } + } + catch (ex) { /*ignore*/ } + finally { + if (!testCallbackError) { + testCallbackError = [0, 0]; + } + } + break; + + default: + console.error("Unknown option:", arg); + return showHelp(); + } + } + + if (testCallbackError && testNoCallback) { + console.error("--error cannot be used in conjunction with --no-callback"); + return undefined; + } + + testCalls = Array.isArray(testCalls) ? testCalls : TEST_CALLS; + + const calls = { aggregate: testNoCallback ? null : 0 }; + + /** + * The JavaScript callback provided to our TSFN. + * @callback TSFNCallback + * @param {number} threadId Thread Id + * @param {number} callId Call Id + * @param {boolean} logCall If true, log messages to console regarding this + * call. + * @param {number} base The input as calculated from CallJs + * @param {number} callDelay If `> 0`, return a `Promise` that resolves with + * `value` after `callDelay` milliseconds. Otherwise, return a `number` + * whose value is `value`. + */ + + /** @type {undefined | TSFNCallback} */ + const callback = testNoCallback ? undefined : (threadId, callId, logCall, base, callDelay) => { + // Calculate the result value as `base * base`. + const value = base * base; + + // Add the value to our call aggregate + calls.aggregate += value; + + if (testCallbackError !== undefined && testCallbackError[0] === threadId && testCallbackError[1] === callId) { + return new Error(`Test throw error for ${threadId}.${callId}`); + } + + // If `callDelay > 0`, then return a Promise that resolves with `value` after + // `callDelay` milliseconds. + if (callDelay > 0) { + // Logging messages. + if (logCall) { + console.log(`[Thread ${threadId}] [Callback] [Call ${callId}] Receive request: base = ${base}, delay = ${callDelay}ms`); + } + + const start = Date.now(); + + return new Promise(resolve => setTimeout(() => { + // Logging messages. + if (logCall) { + console.log(`[Thread ${threadId}] [Callback] [Call ${callId}] Answer request: base = ${base}, value = ${value} after ${Date.now() - start}ms`); + } + resolve(value); + }, callDelay)); + } + + // Otherwise, return a `number` whose value is `value`. + else { + // Logging messages. + if (logCall) { + console.log(`[Thread ${threadId}] [Callback] [Call ${callId}] Receive, answer request: base = ${base}, value = ${value}`); + } + return value; + } + }; + + return { + options: { + // Construct `ThreadOption[] threads` from `number[] testCalls` + threads: testCalls.map((callCount, index) => ({ + calls: callCount, + callDelay: testCallDelay !== null && typeof testCallDelay[index] === 'number' ? testCallDelay[index] : 0, + threadDelay: testThreadDelay !== null && typeof testThreadDelay[index] === 'number' ? testThreadDelay[index] : 0, + })), + logCall: testLogCall, + logThread: testLogThread, + acquireFactor: testAcquire, + callback, + callbackError: testCallbackError + }, + calls + }; +} class ExampleTest extends TestRunner { async example({ TSFNWrap }) { + + /** + * @typedef {Object} TSFNWrap + * @property {(opts: StartOptions) => number} start Start the TSFN. Returns + * the number of threads started. + * @property {() => Promise} release Release the TSFN. + * @property {() => [number, number]} callCount Returns the call aggregates + * as counted by the TSFN: + * - `[0]`: The sum of the number of calls by each thread. + * - `[1]`: The sum of the `value`s returned by each call by each thread. + * @property {(threads: ThreadOptions[]) => number} acquire + */ + + /** @type {TSFNWrap} */ const tsfn = new TSFNWrap(); - const threads = [1, 2, 3, 4, 5]; - let callAggregate = 0; - const startedActual = await tsfn.start({ - threads, - logThread: false, - logCall: false, + const testDetails = getTestDetails(); + if (testDetails === undefined) { + throw new Error("No test details"); + } + const { options } = testDetails; + const { acquireFactor, threads, callback, callbackError } = options; - callback: (_ /*threadId*/, valueFromCallJs) => { - callAggregate += valueFromCallJs; - } - }); + /** + * Start the TSFN with the given options. This will create the TSFN with initial + * thread count of `threads.length + 1` (+1 due to the Node thread using the TSFN) + */ + const startedActual = tsfn.start(options); + + /** + * The initial + */ + const threadsPerSet = threads.length; /** - * Calculate the expected results. + * Calculate the expected results. Create a new list of thread options by + * concatinating `threads` by `acquireFactor` times. */ - const expected = threads.reduce((p, threadCallCount, threadId) => ( + const startedThreads = [...new Array(acquireFactor)].map(_ => threads).reduce((p, c) => p.concat(c), []); + + const expected = startedThreads.reduce((p, threadCallCount, threadId) => ( ++threadId, ++p.threadCount, - p.callCount += threadCallCount, - p.aggregate += threadCallCount * threadId ** 2, + p.callCount += threadCallCount.calls, + p.aggregate += threadCallCount.calls * threadId ** 2, + p.callbackAggregate = p.callbackAggregate === null ? null : p.aggregate, p - ), { threadCount: 0, callCount: 0, aggregate: 0 }); + ), { threadCount: 0, callCount: 0, aggregate: 0, callbackAggregate: callback ? 0 : null }); if (typeof startedActual === 'number') { + const { threadCount, callCount, aggregate, callbackAggregate } = expected; + assert(startedActual === threadsPerSet, `The number of threads when starting the TSFN do not match: actual = ${startedActual}, expected = ${threadsPerSet}`) + for (let i = 1; i < acquireFactor; ++i) { + const acquiredActual = tsfn.acquire(threads); + assert(acquiredActual === threadsPerSet, `The number of threads when acquiring a new set of threads do not match: actual = ${acquiredActual}, expected = ${threadsPerSet}`) + } const released = await tsfn.release(); const [callCountActual, aggregateActual] = tsfn.callCount(); - const { threadCount, callCount, aggregate } = expected; - assert(startedActual === threadCount, `The number of threads started do not match: actual = ${startedActual}, expected = ${threadCount}`) - assert(callCountActual === callCount, `The number of calls do not match: actual = ${callCountActual}, expected = ${callCount}`); - assert(aggregateActual === aggregate, `The aggregate of calls do not match: actual = ${aggregateActual}, expected = ${aggregate}`); - assert(aggregate === callAggregate, `The number aggregated by the JavaScript callback and the thread calculated aggregate do not match: actual ${aggregate}, expected = ${callAggregate}`) - return { released, ...expected, callAggregate }; + const { calls } = testDetails; + const { aggregate: actualCallAggregate } = calls; + if (!callbackError) { + assert(callCountActual === callCount, `The number of calls do not match: actual = ${callCountActual}, expected = ${callCount}`); + assert(aggregateActual === aggregate, `The aggregate of calls do not match: actual = ${aggregateActual}, expected = ${aggregate}`); + assert(actualCallAggregate === callbackAggregate, `The number aggregated by the JavaScript callback and the thread calculated aggregate do not match: actual ${actualCallAggregate}, expected = ${aggregate}`) + return { released, ...expected }; + } + // The test runner erases the last line, so write an empty line. + if (options.logCall) { console.log(); } + return released; } else { throw new Error('The TSFN failed to start'); } diff --git a/test/threadsafe_function_ex/util/TestRunner.js b/test/threadsafe_function_ex/util/TestRunner.js index e01b42480..8b425ab65 100644 --- a/test/threadsafe_function_ex/util/TestRunner.js +++ b/test/threadsafe_function_ex/util/TestRunner.js @@ -4,9 +4,6 @@ const assert = require('assert'); const { basename, extname } = require('path'); const buildType = process.config.target_defaults.default_configuration; -// If you pass certain test names as argv, run those only. -const cmdlineTests = process.argv.length > 2 ? process.argv.slice(2) : null; - const pad = (what, targetLength = 20, padString = ' ', padLeft) => { const padder = (pad, str) => { if (typeof str === 'undefined') @@ -20,6 +17,12 @@ const pad = (what, targetLength = 20, padString = ' ', padLeft) => { return padder(padString.repeat(targetLength), String(what)); } +/** + * If `true`, always show results as interactive. See constructor for more + * information. +*/ +const SHOW_OUTPUT = false; + /** * Test runner helper class. Each static method's name corresponds to the * namespace the test as defined in the native addon. Each test specifics are @@ -29,12 +32,6 @@ const pad = (what, targetLength = 20, padString = ' ', padLeft) => { */ class TestRunner { - /** - * If `true`, always show results as interactive. See constructor for more - * information. - */ - static SHOW_OUTPUT = false; - /** * @param {string} bindingKey The key to use when accessing the binding. * @param {string} filename Name of file that the current TestRunner instance @@ -46,7 +43,7 @@ class TestRunner { constructor(bindingKey, filename) { this.bindingKey = bindingKey; this.filename = filename; - this.interactive = TestRunner.SHOW_OUTPUT || filename === require.main.filename; + this.interactive = SHOW_OUTPUT || filename === require.main.filename; this.specName = `${this.bindingKey}/${basename(this.filename, extname(this.filename))}`; } @@ -84,8 +81,6 @@ class TestRunner { // Interactive mode prints start and end messages if (this.interactive) { - - /** @typedef {[string, string | null | number, boolean, string, any]} State [label, time, isNoExcept, nsName, returnValue] */ /** @type {State} */ @@ -114,31 +109,25 @@ class TestRunner { this.log(stateLine()); }; - const runTest = (cmdlineTests == null || cmdlineTests.indexOf(nsName) > -1); - - if (ns && typeof runner[nsName] === 'function' && runTest) { + if (ns && typeof runner[nsName] === 'function') { setState('Running test', null, isNoExcept, nsName, undefined); const start = Date.now(); const returnValue = await runner[nsName](ns); - await this.dummy(); setState('Finished test', Date.now() - start, isNoExcept, nsName, returnValue); } else { setState('Skipping test', '-', isNoExcept, nsName, undefined); } - } else { + } else if (ns) { console.log(`Running test '${this.specName}/${nsName}' ${isNoExcept ? '[noexcept]' : ''}`); await runner[nsName](ns); - await this.dummy(); } } } } - dummy() { return new Promise(resolve => setTimeout(resolve, 50)); } - /** * Print to console only when using interactive mode. - * + * * @param {boolean} newLine If true, end with a new line. * @param {any[]} what What to print */ @@ -158,7 +147,6 @@ class TestRunner { log(...what) { this.print(true, ...what); } - } module.exports = { From 6148fb4bcc30f2ab4c90b080488e787874c833b4 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Tue, 21 Jul 2020 22:24:21 +0100 Subject: [PATCH 225/696] Synchronise Node.js versions in Appveyor Windows CI with Travis (#768) --- appveyor.yml | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 439b91df0..3f08b6837 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,24 +1,13 @@ environment: # https://github.com/jasongin/nvs/blob/master/doc/CI.md NVS_VERSION: 1.4.2 - fast_finish: true matrix: - - NODEJS_VERSION: node/4 - - NODEJS_VERSION: node/6 - - NODEJS_VERSION: node/8 - - NODEJS_VERSION: node/9 - NODEJS_VERSION: node/10 - - NODEJS_VERSION: chakracore/8 - - NODEJS_VERSION: chakracore/10 + - NODEJS_VERSION: node/12 + - NODEJS_VERSION: node/14 - NODEJS_VERSION: nightly - - NODEJS_VERSION: chakracore-nightly - -matrix: - fast_finish: true - allow_failures: - - NODEJS_VERSION: nightly - - NODEJS_VERSION: chakracore-nightly +os: Visual Studio 2017 platform: - x86 - x64 From 5af645f64900044971787b092567998be784dcb5 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Fri, 19 Jun 2020 13:12:26 -0700 Subject: [PATCH 226/696] src: add Addon class * separate out instance-related APIs from `ObjectWrap` into a new class `InstanceWrap` which then becomes a base class for `ObjectWrap`. * Add `Addon` class as a subclass of `InstanceWrap`, reimplementing `Unwrap()` to retrieve the instance data using `GetInstanceData()` of `Napi::Env` instead of `napi_unwrap()`. * Add macros `NODE_API_ADDON()` and `NODE_API_NAMED_ADDON()` to load an add-on from its `Addon` subclass definition. Bindings created like this perform slightly worse than static ones in exchange for the benefit of having the context of a class instance as their C++ `this` object. This way, they avoid having to call `info.GetInstanceData()` in the bindings, which brings with it the risk that the wrong `ClassName` will end up in the template parameter thus resulting in a hard-to-track-down segfault. Static bindings can still be created and associated with the `exports` object and they can use `Napi::Env::GetInstanceData()` to retrieve the add-on instance. PR-URL: https://github.com/nodejs/node-addon-api/pull/749 Reviewed-By: Michael Dawson --- README.md | 1 + benchmark/function_args.cc | 64 +++ benchmark/function_args.js | 22 +- benchmark/property_descriptor.cc | 31 ++ benchmark/property_descriptor.js | 16 +- doc/addon.md | 518 ++++++++++++++++++ napi-inl.h | 870 +++++++++++++++++-------------- napi.h | 250 +++++---- package.json | 1 + test/addon.cc | 36 ++ test/addon.js | 12 + test/binding.cc | 2 + test/binding.gyp | 1 + test/index.js | 4 +- 14 files changed, 1312 insertions(+), 516 deletions(-) create mode 100644 doc/addon.md create mode 100644 test/addon.cc create mode 100644 test/addon.js diff --git a/README.md b/README.md index dfc7c953e..1f6e34e75 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ The oldest Node.js version supported by the current version of node-addon-api is The following is the documentation for node-addon-api. + - [Addon Structure](doc/addon.md) - [Basic Types](doc/basic_types.md) - [Array](doc/basic_types.md#array) - [Symbol](doc/symbol.md) diff --git a/benchmark/function_args.cc b/benchmark/function_args.cc index 7dcc7c781..54bdbe342 100644 --- a/benchmark/function_args.cc +++ b/benchmark/function_args.cc @@ -78,6 +78,66 @@ static void FourArgFunction(const Napi::CallbackInfo& info) { Napi::Value argv3 = info[3]; (void) argv3; } +#if NAPI_VERSION > 5 +class FunctionArgsBenchmark : public Napi::Addon { + public: + FunctionArgsBenchmark(Napi::Env env, Napi::Object exports) { + DefineAddon(exports, { + InstanceValue("addon", DefineProperties(Napi::Object::New(env), { + InstanceMethod("noArgFunction", &FunctionArgsBenchmark::NoArgFunction), + InstanceMethod("oneArgFunction", + &FunctionArgsBenchmark::OneArgFunction), + InstanceMethod("twoArgFunction", + &FunctionArgsBenchmark::TwoArgFunction), + InstanceMethod("threeArgFunction", + &FunctionArgsBenchmark::ThreeArgFunction), + InstanceMethod("fourArgFunction", + &FunctionArgsBenchmark::FourArgFunction), + }), napi_enumerable), + InstanceValue("addon_templated", + DefineProperties(Napi::Object::New(env), { + InstanceMethod<&FunctionArgsBenchmark::NoArgFunction>( + "noArgFunction"), + InstanceMethod<&FunctionArgsBenchmark::OneArgFunction>( + "oneArgFunction"), + InstanceMethod<&FunctionArgsBenchmark::TwoArgFunction>( + "twoArgFunction"), + InstanceMethod<&FunctionArgsBenchmark::ThreeArgFunction>( + "threeArgFunction"), + InstanceMethod<&FunctionArgsBenchmark::FourArgFunction>( + "fourArgFunction"), + }), napi_enumerable), + }); + } + private: + void NoArgFunction(const Napi::CallbackInfo& info) { + (void) info; + } + + void OneArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; + } + + void TwoArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; + Napi::Value argv1 = info[1]; (void) argv1; + } + + void ThreeArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; + Napi::Value argv1 = info[1]; (void) argv1; + Napi::Value argv2 = info[2]; (void) argv2; + } + + void FourArgFunction(const Napi::CallbackInfo& info) { + Napi::Value argv0 = info[0]; (void) argv0; + Napi::Value argv1 = info[1]; (void) argv1; + Napi::Value argv2 = info[2]; (void) argv2; + Napi::Value argv3 = info[3]; (void) argv3; + } +}; +#endif // NAPI_VERSION > 5 + static Napi::Object Init(Napi::Env env, Napi::Object exports) { napi_value no_arg_function, one_arg_function, two_arg_function, three_arg_function, four_arg_function; @@ -147,6 +207,10 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) { templated["fourArgFunction"] = Napi::Function::New(env); exports["templated"] = templated; +#if NAPI_VERSION > 5 + FunctionArgsBenchmark::Init(env, exports); +#endif // NAPI_VERSION > 5 + return exports; } diff --git a/benchmark/function_args.js b/benchmark/function_args.js index 3dee09a68..e7fb6636f 100644 --- a/benchmark/function_args.js +++ b/benchmark/function_args.js @@ -4,16 +4,22 @@ const addonName = path.basename(__filename, '.js'); [ addonName, addonName + '_noexcept' ] .forEach((addonName) => { - const rootAddon = require(`./build/Release/${addonName}`); + const rootAddon = require('bindings')({ + bindings: addonName, + module_root: __dirname + }); + delete rootAddon.path; const implems = Object.keys(rootAddon); + const maxNameLength = + implems.reduce((soFar, value) => Math.max(soFar, value.length), 0); const anObject = {}; - console.log(`${addonName}: `); + console.log(`\n${addonName}: `); console.log('no arguments:'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].noArgFunction; - return suite.add(implem, () => fn()); + return suite.add(implem.padStart(maxNameLength, ' '), () => fn()); }, new Benchmark.Suite) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -21,7 +27,7 @@ const addonName = path.basename(__filename, '.js'); console.log('one argument:'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].oneArgFunction; - return suite.add(implem, () => fn('x')); + return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x')); }, new Benchmark.Suite) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -29,7 +35,7 @@ const addonName = path.basename(__filename, '.js'); console.log('two arguments:'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].twoArgFunction; - return suite.add(implem, () => fn('x', 12)); + return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x', 12)); }, new Benchmark.Suite) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -37,7 +43,8 @@ const addonName = path.basename(__filename, '.js'); console.log('three arguments:'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].threeArgFunction; - return suite.add(implem, () => fn('x', 12, true)); + return suite.add(implem.padStart(maxNameLength, ' '), + () => fn('x', 12, true)); }, new Benchmark.Suite) .on('cycle', (event) => console.log(String(event.target))) .run(); @@ -45,7 +52,8 @@ const addonName = path.basename(__filename, '.js'); console.log('four arguments:'); implems.reduce((suite, implem) => { const fn = rootAddon[implem].fourArgFunction; - return suite.add(implem, () => fn('x', 12, true, anObject)); + return suite.add(implem.padStart(maxNameLength, ' '), + () => fn('x', 12, true, anObject)); }, new Benchmark.Suite) .on('cycle', (event) => console.log(String(event.target))) .run(); diff --git a/benchmark/property_descriptor.cc b/benchmark/property_descriptor.cc index e4e26e7c9..19803f595 100644 --- a/benchmark/property_descriptor.cc +++ b/benchmark/property_descriptor.cc @@ -26,6 +26,33 @@ static void Setter(const Napi::CallbackInfo& info) { (void) info[0]; } +#if NAPI_VERSION > 5 +class PropDescBenchmark : public Napi::Addon { + public: + PropDescBenchmark(Napi::Env, Napi::Object exports) { + DefineAddon(exports, { + InstanceAccessor("addon", + &PropDescBenchmark::Getter, + &PropDescBenchmark::Setter, + napi_enumerable), + InstanceAccessor<&PropDescBenchmark::Getter, + &PropDescBenchmark::Setter>("addon_templated", + napi_enumerable), + }); + } + + private: + Napi::Value Getter(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), 42); + } + + void Setter(const Napi::CallbackInfo& info, const Napi::Value& val) { + (void) info[0]; + (void) val; + } +}; +#endif // NAPI_VERSION > 5 + static Napi::Object Init(Napi::Env env, Napi::Object exports) { napi_status status; napi_property_descriptor core_prop = { @@ -54,6 +81,10 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) { Napi::PropertyDescriptor::Accessor("templated", napi_enumerable)); +#if NAPI_VERSION > 5 + PropDescBenchmark::Init(env, exports); +#endif // NAPI_VERSION > 5 + return exports; } diff --git a/benchmark/property_descriptor.js b/benchmark/property_descriptor.js index cab510601..848aaaf4a 100644 --- a/benchmark/property_descriptor.js +++ b/benchmark/property_descriptor.js @@ -4,17 +4,23 @@ const addonName = path.basename(__filename, '.js'); [ addonName, addonName + '_noexcept' ] .forEach((addonName) => { - const rootAddon = require(`./build/Release/${addonName}`); + const rootAddon = require('bindings')({ + bindings: addonName, + module_root: __dirname + }); + delete rootAddon.path; const getters = new Benchmark.Suite; const setters = new Benchmark.Suite; + const maxNameLength = Object.keys(rootAddon) + .reduce((soFar, value) => Math.max(soFar, value.length), 0); - console.log(`${addonName}: `); + console.log(`\n${addonName}: `); Object.keys(rootAddon).forEach((key) => { - getters.add(`${key} getter`, () => { + getters.add(`${key} getter`.padStart(maxNameLength + 7), () => { const x = rootAddon[key]; }); - setters.add(`${key} setter`, () => { + setters.add(`${key} setter`.padStart(maxNameLength + 7), () => { rootAddon[key] = 5; }) }); @@ -23,6 +29,8 @@ const addonName = path.basename(__filename, '.js'); .on('cycle', (event) => console.log(String(event.target))) .run(); + console.log(''); + setters .on('cycle', (event) => console.log(String(event.target))) .run(); diff --git a/doc/addon.md b/doc/addon.md new file mode 100644 index 000000000..b4c9c9d6b --- /dev/null +++ b/doc/addon.md @@ -0,0 +1,518 @@ +# Add-on Structure + +Creating add-ons that work correctly when loaded multiple times from the same +source package into multiple Node.js threads and/or multiple times into the same +Node.js thread requires that all global data they hold be associated with the +environment in which they run. It is not safe to store global data in static +variables because doing so does not take into account the fact that an add-on +may be loaded into multiple threads nor that an add-on may be loaded multiple +times into a single thread. + +The `Napi::Addon` class can be used to define an entire add-on. Instances of +`Napi::Addon` subclasses become instances of the add-on, stored safely by +Node.js on its various threads and into its various contexts. Thus, any data +stored in the instance variables of a `Napi::Addon` subclass instance are stored +safely by Node.js. Functions exposed to JavaScript using +`Napi::Addon::InstanceMethod` and/or `Napi::Addon::DefineAddon` are instance +methods of the `Napi::Addon` subclass and thus have access to data stored inside +the instance. + +`Napi::Addon::DefineProperties` may be used to attach `Napi::Addon` subclass +instance methods to objects other than the one that will be returned to Node.js +as the add-on instance. + +The `Napi::Addon` class can be used together with the `NODE_API_ADDON()` and +`NODE_API_NAMED_ADDON()` macros to define add-ons. + +## Example + +```cpp +#include + +class ExampleAddon : public Napi::Addon { + public: + ExampleAddon(Napi::Env env, Napi::Object exports) { + // In the constructor we declare the functions the add-on makes avaialable + // to JavaScript. + DefineAddon(exports, { + InstanceMethod("increment", &ExampleAddon::Increment), + + // We can also attach plain objects to `exports`, and instance methods as + // properties of those sub-objects. + InstanceValue("subObject", DefineProperties(Napi::Object::New(), { + InstanceMethod("decrement", &ExampleAddon::Decrement + })), napi_enumerable) + }); + } + private: + + // This method has access to the data stored in the environment because it is + // an instance method of `ExampleAddon` and because it was listed among the + // property descriptors passed to `DefineAddon()` in the constructor. + Napi::Value Increment(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), ++value); + } + + // This method has access to the data stored in the environment because it is + // an instance method of `ExampleAddon` and because it was exposed to + // JavaScript by calling `DefineProperties()` with the object onto which it is + // attached. + Napi::Value Decrement(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), --value); + } + + // Data stored in these variables is unique to each instance of the add-on. + uint32_t value = 42; +}; + +// The macro announces that instances of the class `ExampleAddon` will be +// created for each instance of the add-on that must be loaded into Node.js. +NODE_API_ADDON(ExampleAddon) +``` + +The above code can be used from JavaScript as follows: + +```js +'use strict' + +const exampleAddon = require('bindings')('example_addon'); +console.log(exampleAddon.increment()); // prints 43 +console.log(exampleAddon.increment()); // prints 44 +consnole.log(exampleAddon.subObject.decrement()); // prints 43 +``` + +When Node.js loads an instance of the add-on, a new instance of the class is +created. Its constructor receives the environment `Napi::Env env` and the +exports object `Napi::Object exports`. It can then use the method `DefineAddon` +to either attach methods, accessors, and/or values to the `exports` object or to +create its own `exports` object and attach methods, accessors, and/or values to +it. + +Functions created with `Napi::Function::New()`, accessors created with +`PropertyDescriptor::Accessor()`, and values can also be attached. If their +implementation requires the `ExampleAddon` instance, it can be retrieved from +the `Napi::Env env` with `GetInstanceData()`: + +```cpp +void ExampleBinding(const Napi::CallbackInfo& info) { + ExampleAddon* addon = info.Env().GetInstanceData(); +} +``` + +## Methods + +### Constructor + +Creates a new instance of the add-on. + +```cpp +Napi::Addon(Napi::Env env, Napi::Object exports); +``` + +- `[in] env`: The environment into which the add-on is being loaded. +- `[in] exports`: The exports object received from JavaScript. + +Typically, the constructor calls `DefineAddon()` to attach methods, accessors, +and/or values to `exports`. The constructor may also create a new object and +pass it to `DefineAddon()` as its first parameter if it wishes to replace the +`exports` object as provided by Node.js. + +### DefineAddon + +Defines an add-on instance with functions, accessors, and/or values. + +```cpp +void Napi::Addon::DefineAddon(Napi::Object exports, + const std::initializer_list& properties); +``` + +* `[in] exports`: The object to return to Node.js as an instance of the add-on. +* `[in] properties`: Initializer list of add-on property descriptors of the +methods, property accessors, and values that define the add-on. They will be +set on `exports`. +See: [`Class property and descriptor`](class_property_descriptor.md). + +### DefineProperties + +Defines function, accessor, and/or value properties on an object using add-on +instance methods. + +```cpp +Napi::Object +Napi::Addon::DefineProperties(Napi::Object object, + const std::initializer_list& properties); +``` + +* `[in] object`: The object that will receive the new properties. +* `[in] properties`: Initializer list of property descriptors of the methods, +property accessors, and values to attach to `object`. +See: [`Class property and descriptor`](class_property_descriptor.md). + +Returns `object`. + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +void MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +Napi::Value MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(Napi::Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: JavaScript symbol that represents the name of the method provided +by the add-on. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +void MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(Napi::Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: JavaScript symbol that represents the name of the method provided +by the add-on. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +Napi::Value MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +void MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +Napi::Value MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(Napi::Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance method for the class. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +void MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceMethod(Napi::Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance method for the class. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents a method provided +by the add-on. The method must be of the form + +```cpp +Napi::Value MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceAccessor + +Creates a property descriptor that represents an instance accessor property +provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceAccessor(const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] getter`: The native function to call when a get access to the property +is performed. +- `[in] setter`: The native function to call when a set access to the property +is performed. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the getter or the setter when it +is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents an instance accessor +property provided by the add-on. + +### InstanceAccessor + +Creates a property descriptor that represents an instance accessor property +provided by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceAccessor(Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance accessor. +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the getter or the setter when it +is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents an instance accessor +property provided by the add-on. + +### InstanceAccessor + +Creates a property descriptor that represents an instance accessor property +provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceAccessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by the add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the getter or the setter when it +is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents an instance accessor +property provided by the add-on. + +### InstanceAccessor + +Creates a property descriptor that represents an instance accessor property +provided by the add-on. + +```cpp +template +static Napi::PropertyDescriptor +Napi::Addon::InstanceAccessor(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance accessor. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the getter or the setter when it +is invoked. + +Returns a `Napi::PropertyDescriptor` object that represents an instance accessor +property provided by the add-on. + +### InstanceValue + +Creates property descriptor that represents an instance value property provided +by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceValue(const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the property. +- `[in] value`: The value that's retrieved by a get access of the property. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. + +Returns a `Napi::PropertyDescriptor` object that represents an instance value +property of an add-on. + +### InstanceValue + +Creates property descriptor that represents an instance value property provided +by the add-on. + +```cpp +static Napi::PropertyDescriptor +Napi::Addon::InstanceValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +name of the property. +- `[in] value`: The value that's retrieved by a get access of the property. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. + +Returns a `Napi::PropertyDescriptor` object that represents an instance value +property of an add-on. diff --git a/napi-inl.h b/napi-inl.h index 99ed3e3d3..a18ffcd59 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -238,6 +238,7 @@ struct AccessorCallbackData { // Module registration //////////////////////////////////////////////////////////////////////////////// +// Register an add-on based on an initializer function. #define NODE_API_MODULE(modname, regfunc) \ napi_value __napi_ ## regfunc(napi_env env, \ napi_value exports) { \ @@ -245,6 +246,20 @@ struct AccessorCallbackData { } \ NAPI_MODULE(modname, __napi_ ## regfunc) +// Register an add-on based on a subclass of `Addon` with a custom Node.js +// module name. +#define NODE_API_NAMED_ADDON(modname, classname) \ + static napi_value __napi_ ## classname(napi_env env, \ + napi_value exports) { \ + return Napi::RegisterModule(env, exports, &classname::Init); \ + } \ + NAPI_MODULE(modname, __napi_ ## classname) + +// Register an add-on based on a subclass of `Addon` with the Node.js module +// name given by node-gyp from the `target_name` in binding.gyp. +#define NODE_API_ADDON(classname) \ + NODE_API_NAMED_ADDON(NODE_GYP_MODULE_NAME, classname) + // Adapt the NAPI_MODULE registration function: // - Wrap the arguments in NAPI wrappers. // - Catch any NAPI errors and rethrow as JS exceptions. @@ -3143,419 +3158,553 @@ inline PropertyDescriptor::operator const napi_property_descriptor&() const { } //////////////////////////////////////////////////////////////////////////////// -// ObjectWrap class +// InstanceWrap class //////////////////////////////////////////////////////////////////////////////// template -inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { - napi_env env = callbackInfo.Env(); - napi_value wrapper = callbackInfo.This(); - napi_status status; - napi_ref ref; - T* instance = static_cast(this); - status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); - NAPI_THROW_IF_FAILED_VOID(env, status); - - Reference* instanceRef = instance; - *instanceRef = Reference(env, ref); -} - -template -inline ObjectWrap::~ObjectWrap() { - // If the JS object still exists at this point, remove the finalizer added - // through `napi_wrap()`. - if (!IsEmpty()) { - Object object = Value(); - // It is not valid to call `napi_remove_wrap()` with an empty `object`. - // This happens e.g. during garbage collection. - if (!object.IsEmpty() && _construction_failed) { - napi_remove_wrap(Env(), object, nullptr); - } - } -} - -template -inline T* ObjectWrap::Unwrap(Object wrapper) { - T* unwrapped; - napi_status status = napi_unwrap(wrapper.Env(), wrapper, reinterpret_cast(&unwrapped)); - NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); - return unwrapped; -} - -template -inline Function -ObjectWrap::DefineClass(Napi::Env env, - const char* utf8name, - const size_t props_count, - const napi_property_descriptor* descriptors, - void* data) { +inline void InstanceWrap::AttachPropData(napi_env env, + napi_value value, + const napi_property_descriptor* prop) { napi_status status; - std::vector props(props_count); - - // We copy the descriptors to a local array because before defining the class - // we must replace static method property descriptors with value property - // descriptors such that the value is a function-valued `napi_value` created - // with `CreateFunction()`. - // - // This replacement could be made for instance methods as well, but V8 aborts - // if we do that, because it expects methods defined on the prototype template - // to have `FunctionTemplate`s. - for (size_t index = 0; index < props_count; index++) { - props[index] = descriptors[index]; - napi_property_descriptor* prop = &props[index]; - if (prop->method == T::StaticMethodCallbackWrapper) { - status = CreateFunction(env, - utf8name, - prop->method, - static_cast(prop->data), - &(prop->value)); - NAPI_THROW_IF_FAILED(env, status, Function()); - prop->method = nullptr; - prop->data = nullptr; - } else if (prop->method == T::StaticVoidMethodCallbackWrapper) { - status = CreateFunction(env, - utf8name, - prop->method, - static_cast(prop->data), - &(prop->value)); - NAPI_THROW_IF_FAILED(env, status, Function()); - prop->method = nullptr; - prop->data = nullptr; - } - } - - napi_value value; - status = napi_define_class(env, - utf8name, - NAPI_AUTO_LENGTH, - T::ConstructorCallbackWrapper, - data, - props_count, - props.data(), - &value); - NAPI_THROW_IF_FAILED(env, status, Function()); - - // After defining the class we iterate once more over the property descriptors - // and attach the data associated with accessors and instance methods to the - // newly created JavaScript class. - for (size_t idx = 0; idx < props_count; idx++) { - const napi_property_descriptor* prop = &props[idx]; - - if (prop->getter == T::StaticGetterCallbackWrapper || - prop->setter == T::StaticSetterCallbackWrapper) { + if (prop->method != nullptr && !(prop->attributes & napi_static)) { + if (prop->method == T::InstanceVoidMethodCallbackWrapper) { status = Napi::details::AttachData(env, - value, - static_cast(prop->data)); - NAPI_THROW_IF_FAILED(env, status, Function()); + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); + } else if (prop->method == T::InstanceMethodCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); } else if (prop->getter == T::InstanceGetterCallbackWrapper || prop->setter == T::InstanceSetterCallbackWrapper) { status = Napi::details::AttachData(env, value, static_cast(prop->data)); - NAPI_THROW_IF_FAILED(env, status, Function()); - } else if (prop->method != nullptr && !(prop->attributes & napi_static)) { - if (prop->method == T::InstanceVoidMethodCallbackWrapper) { - status = Napi::details::AttachData(env, - value, - static_cast(prop->data)); - NAPI_THROW_IF_FAILED(env, status, Function()); - } else if (prop->method == T::InstanceMethodCallbackWrapper) { - status = Napi::details::AttachData(env, - value, - static_cast(prop->data)); - NAPI_THROW_IF_FAILED(env, status, Function()); - } + NAPI_THROW_IF_FAILED_VOID(env, status); } } - - return Function(env, value); -} - -template -inline Function ObjectWrap::DefineClass( - Napi::Env env, - const char* utf8name, - const std::initializer_list>& properties, - void* data) { - return DefineClass(env, - utf8name, - properties.size(), - reinterpret_cast(properties.begin()), - data); -} - -template -inline Function ObjectWrap::DefineClass( - Napi::Env env, - const char* utf8name, - const std::vector>& properties, - void* data) { - return DefineClass(env, - utf8name, - properties.size(), - reinterpret_cast(properties.data()), - data); } template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( const char* utf8name, - StaticVoidMethodCallback method, + InstanceVoidMethodCallback method, napi_property_attributes attributes, void* data) { - StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); + InstanceVoidMethodCallbackData* callbackData = + new InstanceVoidMethodCallbackData({ method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = T::StaticVoidMethodCallbackWrapper; + desc.method = T::InstanceVoidMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( const char* utf8name, - StaticMethodCallback method, + InstanceMethodCallback method, napi_property_attributes attributes, void* data) { - StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); + InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = T::StaticMethodCallbackWrapper; + desc.method = T::InstanceMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( Symbol name, - StaticVoidMethodCallback method, + InstanceVoidMethodCallback method, napi_property_attributes attributes, void* data) { - StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); + InstanceVoidMethodCallbackData* callbackData = + new InstanceVoidMethodCallbackData({ method, data}); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = T::StaticVoidMethodCallbackWrapper; + desc.method = T::InstanceVoidMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( Symbol name, - StaticMethodCallback method, + InstanceMethodCallback method, napi_property_attributes attributes, void* data) { - StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); + InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = T::StaticMethodCallbackWrapper; + desc.method = T::InstanceMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticVoidMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = &ObjectWrap::WrappedMethod; + desc.method = &InstanceWrap::WrappedMethod; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticVoidMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - Symbol name, +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.method = &ObjectWrap::WrappedMethod; + desc.utf8name = utf8name; + desc.method = &InstanceWrap::WrappedMethod; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( - const char* utf8name, +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.method = &ObjectWrap::WrappedMethod; + desc.name = name; + desc.method = &InstanceWrap::WrappedMethod; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::StaticMethod( +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = &ObjectWrap::WrappedMethod; + desc.method = &InstanceWrap::WrappedMethod; desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( const char* utf8name, - StaticGetterCallback getter, - StaticSetterCallback setter, + InstanceGetterCallback getter, + InstanceSetterCallback setter, napi_property_attributes attributes, void* data) { - StaticAccessorCallbackData* callbackData = - new StaticAccessorCallbackData({ getter, setter, data }); + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({ getter, setter, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( Symbol name, - StaticGetterCallback getter, - StaticSetterCallback setter, + InstanceGetterCallback getter, + InstanceSetterCallback setter, napi_property_attributes attributes, void* data) { - StaticAccessorCallbackData* callbackData = - new StaticAccessorCallbackData({ getter, setter, data }); + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({ getter, setter, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; desc.data = callbackData; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticGetterCallback getter, - typename ObjectWrap::StaticSetterCallback setter> -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( +template ::InstanceGetterCallback getter, + typename InstanceWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); - desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.getter = This::WrapGetter(This::GetterTag()); + desc.setter = This::WrapSetter(This::SetterTag()); desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -template ::StaticGetterCallback getter, - typename ObjectWrap::StaticSetterCallback setter> -inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( +template ::InstanceGetterCallback getter, + typename InstanceWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); - desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.getter = This::WrapGetter(This::GetterTag()); + desc.setter = This::WrapSetter(This::SetterTag()); desc.data = data; - desc.attributes = static_cast(attributes | napi_static); + desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( +inline ClassPropertyDescriptor InstanceWrap::InstanceValue( const char* utf8name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes, - void* data) { - InstanceVoidMethodCallbackData* callbackData = - new InstanceVoidMethodCallbackData({ method, data}); - + Napi::Value value, + napi_property_attributes attributes) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = T::InstanceVoidMethodCallbackWrapper; - desc.data = callbackData; + desc.value = value; desc.attributes = attributes; return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( - const char* utf8name, - InstanceMethodCallback method, - napi_property_attributes attributes, - void* data) { - InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); +inline ClassPropertyDescriptor InstanceWrap::InstanceValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = attributes; + return desc; +} + +template +inline napi_value InstanceWrap::InstanceVoidMethodCallbackWrapper( + napi_env env, + napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceVoidMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->callback; + (instance->*cb)(callbackInfo); + return nullptr; + }); +} + +template +inline napi_value InstanceWrap::InstanceMethodCallbackWrapper( + napi_env env, + napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->callback; + return (instance->*cb)(callbackInfo); + }); +} + +template +inline napi_value InstanceWrap::InstanceGetterCallbackWrapper( + napi_env env, + napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->getterCallback; + return (instance->*cb)(callbackInfo); + }); +} + +template +inline napi_value InstanceWrap::InstanceSetterCallbackWrapper( + napi_env env, + napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->setterCallback; + (instance->*cb)(callbackInfo, callbackInfo[0]); + return nullptr; + }); +} + +template +template ::InstanceVoidMethodCallback method> +inline napi_value InstanceWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + (instance->*method)(cbInfo); + return nullptr; + }); +} + +template +template ::InstanceMethodCallback method> +inline napi_value InstanceWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + return (instance->*method)(cbInfo); + }); +} + +template +template ::InstanceSetterCallback method> +inline napi_value InstanceWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + (instance->*method)(cbInfo, cbInfo[0]); + return nullptr; + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// ObjectWrap class +//////////////////////////////////////////////////////////////////////////////// + +template +inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { + napi_env env = callbackInfo.Env(); + napi_value wrapper = callbackInfo.This(); + napi_status status; + napi_ref ref; + T* instance = static_cast(this); + status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); + NAPI_THROW_IF_FAILED_VOID(env, status); + + Reference* instanceRef = instance; + *instanceRef = Reference(env, ref); +} + +template +inline ObjectWrap::~ObjectWrap() { + // If the JS object still exists at this point, remove the finalizer added + // through `napi_wrap()`. + if (!IsEmpty()) { + Object object = Value(); + // It is not valid to call `napi_remove_wrap()` with an empty `object`. + // This happens e.g. during garbage collection. + if (!object.IsEmpty() && _construction_failed) { + napi_remove_wrap(Env(), object, nullptr); + } + } +} + +template +inline T* ObjectWrap::Unwrap(Object wrapper) { + T* unwrapped; + napi_status status = napi_unwrap(wrapper.Env(), wrapper, reinterpret_cast(&unwrapped)); + NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); + return unwrapped; +} + +template +inline Function +ObjectWrap::DefineClass(Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* descriptors, + void* data) { + napi_status status; + std::vector props(props_count); + + // We copy the descriptors to a local array because before defining the class + // we must replace static method property descriptors with value property + // descriptors such that the value is a function-valued `napi_value` created + // with `CreateFunction()`. + // + // This replacement could be made for instance methods as well, but V8 aborts + // if we do that, because it expects methods defined on the prototype template + // to have `FunctionTemplate`s. + for (size_t index = 0; index < props_count; index++) { + props[index] = descriptors[index]; + napi_property_descriptor* prop = &props[index]; + if (prop->method == T::StaticMethodCallbackWrapper) { + status = CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } else if (prop->method == T::StaticVoidMethodCallbackWrapper) { + status = CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } + } + + napi_value value; + status = napi_define_class(env, + utf8name, + NAPI_AUTO_LENGTH, + T::ConstructorCallbackWrapper, + data, + props_count, + props.data(), + &value); + NAPI_THROW_IF_FAILED(env, status, Function()); + + // After defining the class we iterate once more over the property descriptors + // and attach the data associated with accessors and instance methods to the + // newly created JavaScript class. + for (size_t idx = 0; idx < props_count; idx++) { + const napi_property_descriptor* prop = &props[idx]; + + if (prop->getter == T::StaticGetterCallbackWrapper || + prop->setter == T::StaticSetterCallbackWrapper) { + status = Napi::details::AttachData(env, + value, + static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } else { + // InstanceWrap::AttachPropData is responsible for attaching the data + // of instance methods and accessors. + T::AttachPropData(env, value, prop); + } + } + + return Function(env, value); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const std::initializer_list>& properties, + void* data) { + return DefineClass(env, + utf8name, + properties.size(), + reinterpret_cast(properties.begin()), + data); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const std::vector>& properties, + void* data) { + return DefineClass(env, + utf8name, + properties.size(), + reinterpret_cast(properties.data()), + data); +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + StaticVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = T::InstanceMethodCallbackWrapper; + desc.method = T::StaticVoidMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + StaticMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::StaticMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( Symbol name, - InstanceVoidMethodCallback method, + StaticVoidMethodCallback method, napi_property_attributes attributes, void* data) { - InstanceVoidMethodCallbackData* callbackData = - new InstanceVoidMethodCallbackData({ method, data}); + StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = T::InstanceVoidMethodCallbackWrapper; + desc.method = T::StaticVoidMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( Symbol name, - InstanceMethodCallback method, + StaticMethodCallback method, napi_property_attributes attributes, void* data) { - InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data }); + StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = T::InstanceMethodCallbackWrapper; + desc.method = T::StaticMethodCallbackWrapper; desc.data = callbackData; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceVoidMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( const char* utf8name, napi_property_attributes attributes, void* data) { @@ -3563,41 +3712,41 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( desc.utf8name = utf8name; desc.method = &ObjectWrap::WrappedMethod; desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( - const char* utf8name, +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; + desc.name = name; desc.method = &ObjectWrap::WrappedMethod; desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceVoidMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( - Symbol name, +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; + desc.utf8name = utf8name; desc.method = &ObjectWrap::WrappedMethod; desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceMethodCallback method> -inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( Symbol name, napi_property_attributes attributes, void* data) { @@ -3605,77 +3754,77 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( desc.name = name; desc.method = &ObjectWrap::WrappedMethod; desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( const char* utf8name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, + StaticGetterCallback getter, + StaticSetterCallback setter, napi_property_attributes attributes, void* data) { - InstanceAccessorCallbackData* callbackData = - new InstanceAccessorCallbackData({ getter, setter, data }); + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({ getter, setter, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; desc.data = callbackData; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( Symbol name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, + StaticGetterCallback getter, + StaticSetterCallback setter, napi_property_attributes attributes, void* data) { - InstanceAccessorCallbackData* callbackData = - new InstanceAccessorCallbackData({ getter, setter, data }); + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({ getter, setter, data }); napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; - desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; desc.data = callbackData; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceGetterCallback getter, - typename ObjectWrap::InstanceSetterCallback setter> -inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( const char* utf8name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = This::WrapGetter(This::GetterTag()); - desc.setter = This::WrapSetter(This::SetterTag()); + desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } template -template ::InstanceGetterCallback getter, - typename ObjectWrap::InstanceSetterCallback setter> -inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( Symbol name, napi_property_attributes attributes, void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = This::WrapGetter(This::GetterTag()); - desc.setter = This::WrapSetter(This::SetterTag()); + desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); desc.data = data; - desc.attributes = attributes; + desc.attributes = static_cast(attributes | napi_static); return desc; } @@ -3699,30 +3848,6 @@ inline ClassPropertyDescriptor ObjectWrap::StaticValue(Symbol name, return desc; } -template -inline ClassPropertyDescriptor ObjectWrap::InstanceValue( - const char* utf8name, - Napi::Value value, - napi_property_attributes attributes) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.utf8name = utf8name; - desc.value = value; - desc.attributes = attributes; - return desc; -} - -template -inline ClassPropertyDescriptor ObjectWrap::InstanceValue( - Symbol name, - Napi::Value value, - napi_property_attributes attributes) { - napi_property_descriptor desc = napi_property_descriptor(); - desc.name = name; - desc.value = value; - desc.attributes = attributes; - return desc; -} - template inline void ObjectWrap::Finalize(Napi::Env /*env*/) {} @@ -3815,68 +3940,6 @@ inline napi_value ObjectWrap::StaticSetterCallbackWrapper( }); } -template -inline napi_value ObjectWrap::InstanceVoidMethodCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { - CallbackInfo callbackInfo(env, info); - InstanceVoidMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = Unwrap(callbackInfo.This().As()); - auto cb = callbackData->callback; - (instance->*cb)(callbackInfo); - return nullptr; - }); -} - -template -inline napi_value ObjectWrap::InstanceMethodCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { - CallbackInfo callbackInfo(env, info); - InstanceMethodCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = Unwrap(callbackInfo.This().As()); - auto cb = callbackData->callback; - return (instance->*cb)(callbackInfo); - }); -} - -template -inline napi_value ObjectWrap::InstanceGetterCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { - CallbackInfo callbackInfo(env, info); - InstanceAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = Unwrap(callbackInfo.This().As()); - auto cb = callbackData->getterCallback; - return (instance->*cb)(callbackInfo); - }); -} - -template -inline napi_value ObjectWrap::InstanceSetterCallbackWrapper( - napi_env env, - napi_callback_info info) { - return details::WrapCallback([&] { - CallbackInfo callbackInfo(env, info); - InstanceAccessorCallbackData* callbackData = - reinterpret_cast(callbackInfo.Data()); - callbackInfo.SetData(callbackData->data); - T* instance = Unwrap(callbackInfo.This().As()); - auto cb = callbackData->setterCallback; - (instance->*cb)(callbackInfo, callbackInfo[0]); - return nullptr; - }); -} - template inline void ObjectWrap::FinalizeCallback(napi_env env, void* data, void* /*hint*/) { T* instance = static_cast(data); @@ -3901,27 +3964,6 @@ inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info }); } -template -template ::InstanceVoidMethodCallback method> -inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { - const CallbackInfo cbInfo(env, info); - T* instance = Unwrap(cbInfo.This().As()); - (instance->*method)(cbInfo); - return nullptr; - }); -} - -template -template ::InstanceMethodCallback method> -inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { - const CallbackInfo cbInfo(env, info); - T* instance = Unwrap(cbInfo.This().As()); - return (instance->*method)(cbInfo); - }); -} - template template ::StaticSetterCallback method> inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { @@ -3932,17 +3974,6 @@ inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info }); } -template -template ::InstanceSetterCallback method> -inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { - const CallbackInfo cbInfo(env, info); - T* instance = Unwrap(cbInfo.This().As()); - (instance->*method)(cbInfo, cbInfo[0]); - return nullptr; - }); -} - //////////////////////////////////////////////////////////////////////////////// // HandleScope class //////////////////////////////////////////////////////////////////////////////// @@ -5000,6 +5031,49 @@ inline const napi_node_version* VersionManagement::GetNodeVersion(Env env) { return result; } +#if NAPI_VERSION > 5 +//////////////////////////////////////////////////////////////////////////////// +// Addon class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Object Addon::Init(Env env, Object exports) { + T* addon = new T(env, exports); + env.SetInstanceData(addon); + return addon->entry_point_; +} + +template +inline T* Addon::Unwrap(Object wrapper) { + return wrapper.Env().GetInstanceData(); +} + +template +inline void +Addon::DefineAddon(Object exports, + const std::initializer_list& props) { + DefineProperties(exports, props); + entry_point_ = exports; +} + +template +inline Napi::Object +Addon::DefineProperties(Object object, + const std::initializer_list& props) { + const napi_property_descriptor* properties = + reinterpret_cast(props.begin()); + size_t size = props.size(); + napi_status status = napi_define_properties(object.Env(), + object, + size, + properties); + NAPI_THROW_IF_FAILED(object.Env(), status, object); + for (size_t idx = 0; idx < size; idx++) + T::AttachPropData(object.Env(), object, &properties[idx]); + return object; +} +#endif // NAPI_VERSION > 5 + } // namespace Napi #endif // SRC_NAPI_INL_H_ diff --git a/napi.h b/napi.h index 6c80efc26..cf0ce51e7 100644 --- a/napi.h +++ b/napi.h @@ -1647,6 +1647,122 @@ namespace Napi { napi_property_descriptor _desc; }; + template + struct MethodCallbackData { + TCallback callback; + void* data; + }; + + template + struct AccessorCallbackData { + TGetterCallback getterCallback; + TSetterCallback setterCallback; + void* data; + }; + + template + class InstanceWrap { + public: + + typedef void (T::*InstanceVoidMethodCallback)(const CallbackInfo& info); + typedef Napi::Value (T::*InstanceMethodCallback)(const CallbackInfo& info); + typedef Napi::Value (T::*InstanceGetterCallback)(const CallbackInfo& info); + typedef void (T::*InstanceSetterCallback)(const CallbackInfo& info, const Napi::Value& value); + + typedef ClassPropertyDescriptor PropertyDescriptor; + + static PropertyDescriptor InstanceMethod(const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod(const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod(Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod(Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor(const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor(Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor(Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceValue(const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor InstanceValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + + protected: + static void AttachPropData(napi_env env, napi_value value, const napi_property_descriptor* prop); + + private: + using This = InstanceWrap; + + typedef MethodCallbackData InstanceVoidMethodCallbackData; + typedef MethodCallbackData InstanceMethodCallbackData; + typedef AccessorCallbackData InstanceAccessorCallbackData; + + static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, napi_callback_info info); + static napi_value InstanceMethodCallbackWrapper(napi_env env, napi_callback_info info); + static napi_value InstanceGetterCallbackWrapper(napi_env env, napi_callback_info info); + static napi_value InstanceSetterCallbackWrapper(napi_env env, napi_callback_info info); + + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + + template struct GetterTag {}; + template struct SetterTag {}; + + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + template + static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; + template + static napi_callback WrapGetter(GetterTag) noexcept { return &This::WrappedMethod; } + static napi_callback WrapGetter(GetterTag) noexcept { return nullptr; } + template + static napi_callback WrapSetter(SetterTag) noexcept { return &This::WrappedMethod; } + static napi_callback WrapSetter(SetterTag) noexcept { return nullptr; } + }; + /// Base class to be extended by C++ classes exposed to JavaScript; each C++ class instance gets /// "wrapped" by a JavaScript object that is managed by this class. /// @@ -1673,7 +1789,7 @@ namespace Napi { /// Napi::Value DoSomething(const Napi::CallbackInfo& info); /// } template - class ObjectWrap : public Reference { + class ObjectWrap : public InstanceWrap, public Reference { public: ObjectWrap(const CallbackInfo& callbackInfo); virtual ~ObjectWrap(); @@ -1685,10 +1801,6 @@ namespace Napi { typedef Napi::Value (*StaticMethodCallback)(const CallbackInfo& info); typedef Napi::Value (*StaticGetterCallback)(const CallbackInfo& info); typedef void (*StaticSetterCallback)(const CallbackInfo& info, const Napi::Value& value); - typedef void (T::*InstanceVoidMethodCallback)(const CallbackInfo& info); - typedef Napi::Value (T::*InstanceMethodCallback)(const CallbackInfo& info); - typedef Napi::Value (T::*InstanceGetterCallback)(const CallbackInfo& info); - typedef void (T::*InstanceSetterCallback)(const CallbackInfo& info, const Napi::Value& value); typedef ClassPropertyDescriptor PropertyDescriptor; @@ -1750,68 +1862,12 @@ namespace Napi { static PropertyDescriptor StaticAccessor(Symbol name, napi_property_attributes attributes = napi_default, void* data = nullptr); - static PropertyDescriptor InstanceMethod(const char* utf8name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod(const char* utf8name, - InstanceMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod(Symbol name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceMethod(Symbol name, - InstanceMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceMethod(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceAccessor(const char* utf8name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - static PropertyDescriptor InstanceAccessor(Symbol name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceAccessor(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); - template - static PropertyDescriptor InstanceAccessor(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); static PropertyDescriptor StaticValue(const char* utf8name, Napi::Value value, napi_property_attributes attributes = napi_default); static PropertyDescriptor StaticValue(Symbol name, Napi::Value value, napi_property_attributes attributes = napi_default); - static PropertyDescriptor InstanceValue(const char* utf8name, - Napi::Value value, - napi_property_attributes attributes = napi_default); - static PropertyDescriptor InstanceValue(Symbol name, - Napi::Value value, - napi_property_attributes attributes = napi_default); virtual void Finalize(Napi::Env env); private: @@ -1822,10 +1878,6 @@ namespace Napi { static napi_value StaticMethodCallbackWrapper(napi_env env, napi_callback_info info); static napi_value StaticGetterCallbackWrapper(napi_env env, napi_callback_info info); static napi_value StaticSetterCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value InstanceMethodCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value InstanceGetterCallbackWrapper(napi_env env, napi_callback_info info); - static napi_value InstanceSetterCallbackWrapper(napi_env env, napi_callback_info info); static void FinalizeCallback(napi_env env, void* data, void* hint); static Function DefineClass(Napi::Env env, const char* utf8name, @@ -1833,26 +1885,12 @@ namespace Napi { const napi_property_descriptor* props, void* data = nullptr); - template - struct MethodCallbackData { - TCallback callback; - void* data; - }; - typedef MethodCallbackData StaticVoidMethodCallbackData; - typedef MethodCallbackData StaticMethodCallbackData; - typedef MethodCallbackData InstanceVoidMethodCallbackData; - typedef MethodCallbackData InstanceMethodCallbackData; - - template - struct AccessorCallbackData { - TGetterCallback getterCallback; - TSetterCallback setterCallback; - void* data; - }; - typedef AccessorCallbackData - StaticAccessorCallbackData; - typedef AccessorCallbackData - InstanceAccessorCallbackData; + typedef MethodCallbackData StaticVoidMethodCallbackData; + typedef MethodCallbackData StaticMethodCallbackData; + + typedef AccessorCallbackData StaticAccessorCallbackData; template static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; @@ -1860,22 +1898,11 @@ namespace Napi { template static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - template static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - - template struct StaticGetterTag {}; - template struct StaticSetterTag {}; - template struct GetterTag {}; - template struct SetterTag {}; + template struct StaticGetterTag {}; + template struct StaticSetterTag {}; template static napi_callback WrapStaticGetter(StaticGetterTag) noexcept { return &This::WrappedMethod; } @@ -1885,14 +1912,6 @@ namespace Napi { static napi_callback WrapStaticSetter(StaticSetterTag) noexcept { return &This::WrappedMethod; } static napi_callback WrapStaticSetter(StaticSetterTag) noexcept { return nullptr; } - template - static napi_callback WrapGetter(GetterTag) noexcept { return &This::WrappedMethod; } - static napi_callback WrapGetter(GetterTag) noexcept { return nullptr; } - - template - static napi_callback WrapSetter(SetterTag) noexcept { return &This::WrappedMethod; } - static napi_callback WrapSetter(SetterTag) noexcept { return nullptr; } - bool _construction_failed = true; }; @@ -2420,6 +2439,25 @@ namespace Napi { static const napi_node_version* GetNodeVersion(Env env); }; +#if NAPI_VERSION > 5 + template + class Addon : public InstanceWrap { + public: + static inline Object Init(Env env, Object exports); + static T* Unwrap(Object wrapper); + + protected: + typedef ClassPropertyDescriptor AddonProp; + void DefineAddon(Object exports, + const std::initializer_list& props); + Napi::Object DefineProperties(Object object, + const std::initializer_list& props); + + private: + Object entry_point_; + }; +#endif // NAPI_VERSION > 5 + } // namespace Napi // Inline implementations of all the above class methods are included here. diff --git a/package.json b/package.json index b357f07b3..64a44f32a 100644 --- a/package.json +++ b/package.json @@ -252,6 +252,7 @@ "description": "Node.js API (N-API)", "devDependencies": { "benchmark": "^2.1.4", + "bindings": "^1.5.0", "safe-buffer": "^5.1.1" }, "directories": {}, diff --git a/test/addon.cc b/test/addon.cc new file mode 100644 index 000000000..9652f9aa4 --- /dev/null +++ b/test/addon.cc @@ -0,0 +1,36 @@ +#if (NAPI_VERSION > 5) +#include +#include "napi.h" + +namespace { + +class TestAddon : public Napi::Addon { + public: + inline TestAddon(Napi::Env env, Napi::Object exports) { + DefineAddon(exports, { + InstanceMethod("increment", &TestAddon::Increment), + InstanceValue("subObject", DefineProperties(Napi::Object::New(env), { + InstanceMethod("decrement", &TestAddon::Decrement) + })) + }); + } + + private: + Napi::Value Increment(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), ++value); + } + + Napi::Value Decrement(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), --value); + } + + uint32_t value = 42; +}; + +} // end of anonymous namespace + +Napi::Object InitAddon(Napi::Env env) { + return TestAddon::Init(env, Napi::Object::New(env)); +} + +#endif // (NAPI_VERSION > 5) diff --git a/test/addon.js b/test/addon.js new file mode 100644 index 000000000..54a5f666a --- /dev/null +++ b/test/addon.js @@ -0,0 +1,12 @@ +'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.addon.increment(), 43); + assert.strictEqual(binding.addon.increment(), 44); + assert.strictEqual(binding.addon.subObject.decrement(), 43); +} diff --git a/test/binding.cc b/test/binding.cc index 0eb22abbf..ebfe5e5db 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -3,6 +3,7 @@ using namespace Napi; #if (NAPI_VERSION > 5) +Object InitAddon(Env env); Object InitAddonData(Env env); #endif Object InitArrayBuffer(Env env); @@ -61,6 +62,7 @@ Object InitThunkingManual(Env env); Object Init(Env env, Object exports) { #if (NAPI_VERSION > 5) + exports.Set("addon", InitAddon(env)); exports.Set("addon_data", InitAddonData(env)); #endif exports.Set("arraybuffer", InitArrayBuffer(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 797d81139..288051633 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -2,6 +2,7 @@ 'target_defaults': { 'includes': ['../common.gypi'], 'sources': [ + 'addon.cc', 'addon_data.cc', 'arraybuffer.cc', 'asynccontext.cc', diff --git a/test/index.js b/test/index.js index e930eab5c..137cad238 100644 --- a/test/index.js +++ b/test/index.js @@ -8,6 +8,7 @@ process.config.target_defaults.default_configuration = // FIXME: We might need a way to load test modules automatically without // explicit declaration as follows. let testModules = [ + 'addon', 'addon_data', 'arraybuffer', 'asynccontext', @@ -83,9 +84,10 @@ if (napiVersion < 5) { } if (napiVersion < 6) { + testModules.splice(testModules.indexOf('addon'), 1); + testModules.splice(testModules.indexOf('addon_data'), 1); testModules.splice(testModules.indexOf('bigint'), 1); testModules.splice(testModules.indexOf('typedarray-bigint'), 1); - testModules.splice(testModules.indexOf('addon_data'), 1); } if (typeof global.gc === 'function') { From 0d84cf7be4b785b231f68cb2cd0f53c0b34dcd83 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Mon, 27 Jul 2020 18:07:20 +0200 Subject: [PATCH 227/696] Test with longer timeout --- test/threadsafe_function_ex/test/basic.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/threadsafe_function_ex/test/basic.js b/test/threadsafe_function_ex/test/basic.js index f5bfc678b..780116954 100644 --- a/test/threadsafe_function_ex/test/basic.js +++ b/test/threadsafe_function_ex/test/basic.js @@ -133,7 +133,7 @@ class BasicTest extends TestRunner { if (reject) { reject(new Error("tsfn.call() timed out")); } - }, 0); + }, 1000); }); } return await tsfn.release(); From 461e3640c6e3ca0eb429d5953c44d18ccb066bdc Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Sat, 25 Jul 2020 10:40:28 -0700 Subject: [PATCH 228/696] test: string tests together * Use promises and async/await to ensure that one test finishes before another one starts. This prevents errors thrown in one test from appearing to originate from another test. * Perform garbage collection consistently, via `testUtil.runGCTests()`, to ensure that it is performed correctly and that its results are awaited. PR-URL: https://github.com/nodejs/node-addon-api/pull/773 Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- test/addon_data.js | 47 ++++---- test/arraybuffer.js | 28 ++--- test/asyncprogressqueueworker.cc | 9 -- test/asyncprogressqueueworker.js | 81 ++++++-------- test/asyncprogressworker.js | 61 +++++----- test/asyncworker-nocallback.js | 17 ++- test/asyncworker-persistent.js | 2 +- test/asyncworker.js | 105 +++++++++++------- test/buffer.js | 6 +- test/external.js | 6 +- test/index.js | 11 +- test/object/finalizer.js | 25 +++-- test/objectreference.js | 6 +- test/objectwrap-removewrap.js | 31 ++++-- test/objectwrap.js | 43 ++++--- test/objectwrap_constructor_exception.js | 19 +++- test/promise.js | 10 +- test/reference.js | 12 +- test/run_script.js | 6 +- test/testUtil.js | 67 ++++++----- .../threadsafe_function.js | 6 +- .../threadsafe_function_ctx.js | 6 +- .../threadsafe_function_existing_tsfn.js | 6 +- .../threadsafe_function_sum.cc | 34 +++++- .../threadsafe_function_sum.js | 14 +-- .../threadsafe_function_unref.js | 46 ++++---- 26 files changed, 381 insertions(+), 323 deletions(-) diff --git a/test/addon_data.js b/test/addon_data.js index 0a3852696..571b23e72 100644 --- a/test/addon_data.js +++ b/test/addon_data.js @@ -5,29 +5,37 @@ const { spawn } = require('child_process'); const readline = require('readline'); const path = require('path'); -test(path.resolve(__dirname, `./build/${buildType}/binding.node`)); -test(path.resolve(__dirname, `./build/${buildType}/binding_noexcept.node`)); +module.exports = + test(path.resolve(__dirname, `./build/${buildType}/binding.node`)) + .then(() => + test(path.resolve(__dirname, + `./build/${buildType}/binding_noexcept.node`))); // Make sure the instance data finalizer is called at process exit. If the hint // is non-zero, it will be printed out by the child process. function testFinalizer(bindingName, hint, expected) { - bindingName = bindingName.split('\\').join('\\\\'); - const child = spawn(process.execPath, [ - '-e', - `require('${bindingName}').addon_data(${hint}).verbose = true;` - ]); - const actual = []; - readline - .createInterface({ input: child.stderr }) - .on('line', (line) => { - if (expected.indexOf(line) >= 0) { - actual.push(line); - } - }) - .on('close', () => assert.deepStrictEqual(expected, actual)); + return new Promise((resolve) => { + bindingName = bindingName.split('\\').join('\\\\'); + const child = spawn(process.execPath, [ + '-e', + `require('${bindingName}').addon_data(${hint}).verbose = true;` + ]); + const actual = []; + readline + .createInterface({ input: child.stderr }) + .on('line', (line) => { + if (expected.indexOf(line) >= 0) { + actual.push(line); + } + }) + .on('close', () => { + assert.deepStrictEqual(expected, actual); + resolve(); + }); + }); } -function test(bindingName) { +async function test(bindingName) { const binding = require(bindingName).addon_data(0); // Make sure it is possible to get/set instance data. @@ -37,6 +45,7 @@ function test(bindingName) { binding.verbose = false; assert.strictEqual(binding.verbose.verbose, false); - testFinalizer(bindingName, 0, ['addon_data: Addon::~Addon']); - testFinalizer(bindingName, 42, ['addon_data: Addon::~Addon', 'hint: 42']); + await testFinalizer(bindingName, 0, ['addon_data: Addon::~Addon']); + await testFinalizer(bindingName, 42, + ['addon_data: Addon::~Addon', 'hint: 42']); } diff --git a/test/arraybuffer.js b/test/arraybuffer.js index 4d0681ac3..38d35d1e7 100644 --- a/test/arraybuffer.js +++ b/test/arraybuffer.js @@ -3,11 +3,11 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const testUtil = require('./testUtil'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); function test(binding) { - testUtil.runGCTests([ + return testUtil.runGCTests([ 'Internal ArrayBuffer', () => { const test = binding.arraybuffer.createBuffer(); @@ -25,10 +25,8 @@ function test(binding) { assert.ok(test instanceof ArrayBuffer); assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); }, - () => { - global.gc(); - assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); - }, + + () => assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()), 'External ArrayBuffer with finalizer', () => { @@ -37,12 +35,8 @@ function test(binding) { assert.ok(test instanceof ArrayBuffer); assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); }, - () => { - global.gc(); - }, - () => { - assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()); - }, + + () => assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()), 'External ArrayBuffer with finalizer hint', () => { @@ -51,12 +45,8 @@ function test(binding) { assert.ok(test instanceof ArrayBuffer); assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); }, - () => { - global.gc(); - }, - () => { - assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()); - }, + + () => assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()), 'ArrayBuffer with constructor', () => { diff --git a/test/asyncprogressqueueworker.cc b/test/asyncprogressqueueworker.cc index b30863301..23c6707ff 100644 --- a/test/asyncprogressqueueworker.cc +++ b/test/asyncprogressqueueworker.cc @@ -37,14 +37,6 @@ class TestWorker : public AsyncProgressQueueWorker { worker->Queue(); } - static void CancelWork(const CallbackInfo& info) { - auto wrap = info[0].As>(); - auto worker = wrap.Data(); - // We cannot cancel a worker if it got started. So we have to do a quick cancel. - worker->Queue(); - worker->Cancel(); - } - protected: void Execute(const ExecutionProgress& progress) override { using namespace std::chrono_literals; @@ -89,7 +81,6 @@ Object InitAsyncProgressQueueWorker(Env env) { Object exports = Object::New(env); exports["createWork"] = Function::New(env, TestWorker::CreateWork); exports["queueWork"] = Function::New(env, TestWorker::QueueWork); - exports["cancelWork"] = Function::New(env, TestWorker::CancelWork); return exports; } diff --git a/test/asyncprogressqueueworker.js b/test/asyncprogressqueueworker.js index 6fa65520e..4bb525e9a 100644 --- a/test/asyncprogressqueueworker.js +++ b/test/asyncprogressqueueworker.js @@ -4,60 +4,45 @@ const common = require('./common') const assert = require('assert'); const os = require('os'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); -function test({ asyncprogressqueueworker }) { - success(asyncprogressqueueworker); - fail(asyncprogressqueueworker); - cancel(asyncprogressqueueworker); - return; +async function test({ asyncprogressqueueworker }) { + await success(asyncprogressqueueworker); + await fail(asyncprogressqueueworker); } function success(binding) { - const expected = [0, 1, 2, 3]; - const actual = []; - const worker = binding.createWork(expected.length, - common.mustCall((err) => { - if (err) { - assert.fail(err); - } - // All queued items shall be invoked before complete callback. - assert.deepEqual(actual, expected); - }), - common.mustCall((_progress) => { - actual.push(_progress); - }, expected.length) - ); - binding.queueWork(worker); + return new Promise((resolve, reject) => { + const expected = [0, 1, 2, 3]; + const actual = []; + const worker = binding.createWork(expected.length, + common.mustCall((err) => { + if (err) { + reject(err); + } else { + // All queued items shall be invoked before complete callback. + assert.deepEqual(actual, expected); + resolve(); + } + }), + common.mustCall((_progress) => { + actual.push(_progress); + }, expected.length) + ); + binding.queueWork(worker); + }); } function fail(binding) { - const worker = binding.createWork(-1, - common.mustCall((err) => { - assert.throws(() => { throw err }, /test error/) - }), - () => { - assert.fail('unexpected progress report'); - } - ); - binding.queueWork(worker); -} - -function cancel(binding) { - // make sure the work we are going to cancel will not be - // able to start by using all the threads in the pool. - for (let i = 0; i < os.cpus().length; ++i) { - const worker = binding.createWork(-1, () => {}, () => {}); + return new Promise((resolve, reject) => { + const worker = binding.createWork(-1, + common.mustCall((err) => { + assert.throws(() => { throw err }, /test error/); + resolve(); + }), + common.mustNotCall() + ); binding.queueWork(worker); - } - const worker = binding.createWork(-1, - () => { - assert.fail('unexpected callback'); - }, - () => { - assert.fail('unexpected progress report'); - } - ); - binding.cancelWork(worker); + }); } diff --git a/test/asyncprogressworker.js b/test/asyncprogressworker.js index 0aee9b7f8..b2896a6ca 100644 --- a/test/asyncprogressworker.js +++ b/test/asyncprogressworker.js @@ -3,40 +3,43 @@ const buildType = process.config.target_defaults.default_configuration; const common = require('./common') const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); -function test({ asyncprogressworker }) { - success(asyncprogressworker); - fail(asyncprogressworker); - return; +async function test({ asyncprogressworker }) { + await success(asyncprogressworker); + await fail(asyncprogressworker); } function success(binding) { - const expected = [0, 1, 2, 3]; - const actual = []; - binding.doWork(expected.length, - common.mustCall((err) => { - if (err) { - assert.fail(err); - } - }), - common.mustCall((_progress) => { - actual.push(_progress); - if (actual.length === expected.length) { - assert.deepEqual(actual, expected); - } - }, expected.length) - ); + return new Promise((resolve, reject) => { + const expected = [0, 1, 2, 3]; + const actual = []; + binding.doWork(expected.length, + common.mustCall((err) => { + if (err) { + reject(err); + } + }), + common.mustCall((_progress) => { + actual.push(_progress); + if (actual.length === expected.length) { + assert.deepEqual(actual, expected); + resolve(); + } + }, expected.length) + ); + }); } function fail(binding) { - binding.doWork(-1, - common.mustCall((err) => { - assert.throws(() => { throw err }, /test error/) - }), - () => { - assert.fail('unexpected progress report'); - } - ); + return new Promise((resolve) => { + binding.doWork(-1, + common.mustCall((err) => { + assert.throws(() => { throw err }, /test error/) + resolve(); + }), + common.mustNotCall() + ); + }); } diff --git a/test/asyncworker-nocallback.js b/test/asyncworker-nocallback.js index c873c5fbf..fa9c172c0 100644 --- a/test/asyncworker-nocallback.js +++ b/test/asyncworker-nocallback.js @@ -2,14 +2,13 @@ const buildType = process.config.target_defaults.default_configuration; const common = require('./common'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); -function test(binding) { - const resolving = binding.asyncworker.doWorkNoCallback(true, {}); - resolving.then(common.mustCall()).catch(common.mustNotCall()); +async function test(binding) { + await binding.asyncworker.doWorkNoCallback(true, {}) + .then(common.mustCall()).catch(common.mustNotCall()); - const rejecting = binding.asyncworker.doWorkNoCallback(false, {}); - rejecting.then(common.mustNotCall()).catch(common.mustCall()); - return; -} \ No newline at end of file + await binding.asyncworker.doWorkNoCallback(false, {}) + .then(common.mustNotCall()).catch(common.mustCall()); +} diff --git a/test/asyncworker-persistent.js b/test/asyncworker-persistent.js index 90806ebf9..d584086e7 100644 --- a/test/asyncworker-persistent.js +++ b/test/asyncworker-persistent.js @@ -21,7 +21,7 @@ function test(binding, succeed) { })); } -test(binding.persistentasyncworker, false) +module.exports = test(binding.persistentasyncworker, false) .then(() => test(binding.persistentasyncworker, true)) .then(() => test(noexceptBinding.persistentasyncworker, false)) .then(() => test(noexceptBinding.persistentasyncworker, true)); diff --git a/test/asyncworker.js b/test/asyncworker.js index 04415d522..0f008bb33 100644 --- a/test/asyncworker.js +++ b/test/asyncworker.js @@ -17,8 +17,8 @@ function checkAsyncHooks() { return false; } -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); function installAsyncHooksForTest() { return new Promise((resolve, reject) => { @@ -52,41 +52,54 @@ function installAsyncHooksForTest() { }); } -function test(binding) { +async function test(binding) { if (!checkAsyncHooks()) { - binding.asyncworker.doWork(true, {}, function (e) { - assert.strictEqual(typeof e, 'undefined'); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - }, 'test data'); + await new Promise((resolve) => { + binding.asyncworker.doWork(true, {}, function (e) { + assert.strictEqual(typeof e, 'undefined'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); - binding.asyncworker.doWork(false, {}, function (e) { - assert.ok(e instanceof Error); - assert.strictEqual(e.message, 'test error'); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - }, 'test data'); + await new Promise((resolve) => { + binding.asyncworker.doWork(false, {}, function (e) { + assert.ok(e instanceof Error); + assert.strictEqual(e.message, 'test error'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); + + await new Promise((resolve) => { + binding.asyncworker.doWorkWithResult(true, {}, function (succeed, succeedString) { + assert(arguments.length == 2); + assert(succeed); + assert(succeedString == "ok"); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); - binding.asyncworker.doWorkWithResult(true, {}, function (succeed, succeedString) { - assert(arguments.length == 2); - assert(succeed); - assert(succeedString == "ok"); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - }, 'test data'); return; } { const hooks = installAsyncHooksForTest(); const triggerAsyncId = async_hooks.executionAsyncId(); - binding.asyncworker.doWork(true, { foo: 'foo' }, function (e) { - assert.strictEqual(typeof e, 'undefined'); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - }, 'test data'); + await new Promise((resolve) => { + binding.asyncworker.doWork(true, { foo: 'foo' }, function (e) { + assert.strictEqual(typeof e, 'undefined'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); - hooks.then(actual => { + await hooks.then(actual => { assert.deepStrictEqual(actual, [ { eventName: 'init', type: 'TestResource', @@ -102,15 +115,19 @@ function test(binding) { { const hooks = installAsyncHooksForTest(); const triggerAsyncId = async_hooks.executionAsyncId(); - binding.asyncworker.doWorkWithResult(true, { foo: 'foo' }, function (succeed, succeedString) { - assert(arguments.length == 2); - assert(succeed); - assert(succeedString == "ok"); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - }, 'test data'); + await new Promise((resolve) => { + binding.asyncworker.doWorkWithResult(true, { foo: 'foo' }, + function (succeed, succeedString) { + assert(arguments.length == 2); + assert(succeed); + assert(succeedString == "ok"); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); - hooks.then(actual => { + await hooks.then(actual => { assert.deepStrictEqual(actual, [ { eventName: 'init', type: 'TestResource', @@ -126,15 +143,17 @@ function test(binding) { { const hooks = installAsyncHooksForTest(); const triggerAsyncId = async_hooks.executionAsyncId(); + await new Promise((resolve) => { + binding.asyncworker.doWork(false, { foo: 'foo' }, function (e) { + assert.ok(e instanceof Error); + assert.strictEqual(e.message, 'test error'); + assert.strictEqual(typeof this, 'object'); + assert.strictEqual(this.data, 'test data'); + resolve(); + }, 'test data'); + }); - binding.asyncworker.doWork(false, { foo: 'foo' }, function (e) { - assert.ok(e instanceof Error); - assert.strictEqual(e.message, 'test error'); - assert.strictEqual(typeof this, 'object'); - assert.strictEqual(this.data, 'test data'); - }, 'test data'); - - hooks.then(actual => { + await hooks.then(actual => { assert.deepStrictEqual(actual, [ { eventName: 'init', type: 'TestResource', diff --git a/test/buffer.js b/test/buffer.js index 9b94a9069..ff17d30e7 100644 --- a/test/buffer.js +++ b/test/buffer.js @@ -4,11 +4,11 @@ const assert = require('assert'); const testUtil = require('./testUtil'); const safeBuffer = require('safe-buffer'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); function test(binding) { - testUtil.runGCTests([ + return testUtil.runGCTests([ 'Internal Buffer', () => { const test = binding.buffer.createBuffer(); diff --git a/test/external.js b/test/external.js index e2067006a..85dbe702f 100644 --- a/test/external.js +++ b/test/external.js @@ -3,11 +3,11 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const testUtil = require('./testUtil'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); function test(binding) { - testUtil.runGCTests([ + return testUtil.runGCTests([ 'External without finalizer', () => { const test = binding.external.createExternal(); diff --git a/test/index.js b/test/index.js index 137cad238..86939af84 100644 --- a/test/index.js +++ b/test/index.js @@ -91,17 +91,22 @@ if (napiVersion < 6) { } if (typeof global.gc === 'function') { + (async function() { console.log(`Testing with N-API Version '${napiVersion}'.`); console.log('Starting test suite\n'); // Requiring each module runs tests in the module. - testModules.forEach(name => { + for (const name of testModules) { console.log(`Running test '${name}'`); - require('./' + name); - }); + await require('./' + name); + }; console.log('\nAll tests passed!'); + })().catch((error) => { + console.log(error); + process.exit(1); + }); } else { // Construct the correct (version-dependent) command-line args. let args = ['--expose-gc', '--no-concurrent-array-buffer-freeing']; diff --git a/test/object/finalizer.js b/test/object/finalizer.js index 26820a05a..312b2de6d 100644 --- a/test/object/finalizer.js +++ b/test/object/finalizer.js @@ -2,20 +2,29 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); +const testUtil = require('../testUtil'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`../build/${buildType}/binding.node`)) + .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); function createWeakRef(binding, bindingToTest) { return binding.object[bindingToTest]({}); } function test(binding) { - const obj1 = createWeakRef(binding, 'addFinalizer'); - global.gc(); - assert.deepStrictEqual(obj1, { finalizerCalled: true }); + let obj1; + let obj2; + return testUtil.runGCTests([ + 'addFinalizer', + () => { + obj1 = createWeakRef(binding, 'addFinalizer'); + }, + () => assert.deepStrictEqual(obj1, { finalizerCalled: true }), - const obj2 = createWeakRef(binding, 'addFinalizerWithHint'); - global.gc(); - assert.deepStrictEqual(obj2, { finalizerCalledWithCorrectHint: true }); + 'addFinalizerWithHint', + () => { + obj2 = createWeakRef(binding, 'addFinalizerWithHint'); + }, + () => assert.deepStrictEqual(obj2, { finalizerCalledWithCorrectHint: true }) + ]); } diff --git a/test/objectreference.js b/test/objectreference.js index 07de0bc77..55b95dba6 100644 --- a/test/objectreference.js +++ b/test/objectreference.js @@ -14,8 +14,8 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const testUtil = require('./testUtil'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); function test(binding) { function testCastedEqual(testToCompare) { @@ -29,7 +29,7 @@ function test(binding) { } } - testUtil.runGCTests([ + return testUtil.runGCTests([ 'Weak Casted Array', () => { binding.objectreference.setCastedObjects(); diff --git a/test/objectwrap-removewrap.js b/test/objectwrap-removewrap.js index 01c8b9169..560f61d46 100644 --- a/test/objectwrap-removewrap.js +++ b/test/objectwrap-removewrap.js @@ -8,18 +8,25 @@ if (process.argv[2] === 'child') { const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const { spawnSync } = require('child_process'); +const testUtil = require('./testUtil'); -const test = (bindingName) => { - const binding = require(bindingName); - const Test = binding.objectwrap_removewrap.Test; - const getDtorCalled = binding.objectwrap_removewrap.getDtorCalled; +function test(bindingName) { + return testUtil.runGCTests([ + 'objectwrap removewrap test', + () => { + const binding = require(bindingName); + const Test = binding.objectwrap_removewrap.Test; + const getDtorCalled = binding.objectwrap_removewrap.getDtorCalled; - assert.strictEqual(getDtorCalled(), 0); - assert.throws(() => { - new Test(); - }); - assert.strictEqual(getDtorCalled(), 1); - global.gc(); // Does not crash. + assert.strictEqual(getDtorCalled(), 0); + assert.throws(() => { + new Test(); + }); + assert.strictEqual(getDtorCalled(), 1); + }, + // Test that gc does not crash. + () => {} + ]); // Start a child process that creates a single wrapped instance to ensure that // it is properly freed at its exit. It must not segfault. @@ -31,5 +38,5 @@ const test = (bindingName) => { assert.strictEqual(child.status, 0); } -test(`./build/${buildType}/binding.node`); -test(`./build/${buildType}/binding_noexcept.node`); +module.exports = test(`./build/${buildType}/binding.node`) + .then(() => test(`./build/${buildType}/binding_noexcept.node`)); diff --git a/test/objectwrap.js b/test/objectwrap.js index 02abf60b9..3a4168359 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -1,8 +1,9 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); +const testUtil = require('./testUtil'); -const test = (binding) => { +async function test(binding) { const Test = binding.objectwrap.Test; const testValue = (obj, clazz) => { @@ -237,22 +238,20 @@ const test = (binding) => { } }; - const testFinalize = (clazz) => { - + async function testFinalize(clazz) { let finalizeCalled = false; - const finalizeCb = function(called) { - finalizeCalled = called; - }; - - //Scope Test instance so that it can be gc'd. - (function() { - new Test(finalizeCb); - })(); - - global.gc(); - - assert.strictEqual(finalizeCalled, true); - + await testUtil.runGCTests([ + 'test finalize', + () => { + const finalizeCb = function(called) { + finalizeCalled = called; + }; + + //Scope Test instance so that it can be gc'd. + (() => { new Test(finalizeCb); })(); + }, + () => assert.strictEqual(finalizeCalled, true) + ]); }; const testObj = (obj, clazz) => { @@ -265,22 +264,22 @@ const test = (binding) => { testConventions(obj, clazz); } - const testClass = (clazz) => { + async function testClass(clazz) { testStaticValue(clazz); testStaticAccessor(clazz); testStaticMethod(clazz); testStaticEnumerables(clazz); - testFinalize(clazz); + await testFinalize(clazz); }; // `Test` is needed for accessing exposed symbols testObj(new Test(), Test); - testClass(Test); + await testClass(Test); // Make sure the C++ object can be garbage collected without issues. - setImmediate(global.gc); + await testUtil.runGCTests(['one last gc', () => {}, () => {}]); } -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); diff --git a/test/objectwrap_constructor_exception.js b/test/objectwrap_constructor_exception.js index 8428c8034..02dff2c48 100644 --- a/test/objectwrap_constructor_exception.js +++ b/test/objectwrap_constructor_exception.js @@ -1,12 +1,19 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); +const testUtil = require('./testUtil'); -const test = (binding) => { - const { ConstructorExceptionTest } = binding.objectwrapConstructorException; - assert.throws(() => (new ConstructorExceptionTest()), /an exception/); - global.gc(); +function test(binding) { + return testUtil.runGCTests([ + 'objectwrap constructor exception', + () => { + const { ConstructorExceptionTest } = binding.objectwrapConstructorException; + assert.throws(() => (new ConstructorExceptionTest()), /an exception/); + }, + // Do on gc before returning. + () => {} + ]); } -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); diff --git a/test/promise.js b/test/promise.js index 4a04ab9a0..65544c648 100644 --- a/test/promise.js +++ b/test/promise.js @@ -3,17 +3,17 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const common = require('./common'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); -function test(binding) { +async function test(binding) { assert.strictEqual(binding.promise.isPromise({}), false); const resolving = binding.promise.resolvePromise('resolved'); - assert.strictEqual(binding.promise.isPromise(resolving), true); + await assert.strictEqual(binding.promise.isPromise(resolving), true); resolving.then(common.mustCall()).catch(common.mustNotCall()); const rejecting = binding.promise.rejectPromise('error'); - assert.strictEqual(binding.promise.isPromise(rejecting), true); + await assert.strictEqual(binding.promise.isPromise(rejecting), true); rejecting.then(common.mustNotCall()).catch(common.mustCall()); } diff --git a/test/reference.js b/test/reference.js index 3a59e850f..22ee8c842 100644 --- a/test/reference.js +++ b/test/reference.js @@ -5,11 +5,13 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const testUtil = require('./testUtil'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); function test(binding) { - binding.reference.createWeakArray(); - global.gc(); - assert.strictEqual(true, binding.reference.accessWeakArrayEmpty()); + return testUtil.runGCTests([ + 'test reference', + () => binding.reference.createWeakArray(), + () => assert.strictEqual(true, binding.reference.accessWeakArrayEmpty()) + ]); }; diff --git a/test/run_script.js b/test/run_script.js index 271eeb50c..ec36dcf51 100644 --- a/test/run_script.js +++ b/test/run_script.js @@ -3,11 +3,11 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const testUtil = require('./testUtil'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`./build/${buildType}/binding.node`)) + .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); function test(binding) { - testUtil.runGCTests([ + return testUtil.runGCTests([ 'Plain C string', () => { const sum = binding.run_script.plainString(); diff --git a/test/testUtil.js b/test/testUtil.js index dfd7fab26..b8777e781 100644 --- a/test/testUtil.js +++ b/test/testUtil.js @@ -1,38 +1,51 @@ // Run each test function in sequence, // with an async delay and GC call between each. -function tick(x, cb) { - function ontick() { - if (--x === 0) { - if (typeof cb === 'function') cb(); - } else { - setImmediate(ontick); - } - } - setImmediate(ontick); +function tick(x) { + return new Promise((resolve) => { + setImmediate(function ontick() { + if (--x === 0) { + resolve(); + } else { + setImmediate(ontick); + } + }); + }); }; -function runGCTests(tests, i, title) { - if (!i) { - i = 0; +async function runGCTests(tests) { + // Break up test list into a list of lists of the form + // [ [ 'test name', function() {}, ... ], ..., ]. + const testList = []; + let currentTest; + for (const item of tests) { + if (typeof item === 'string') { + currentTest = []; + testList.push(currentTest); + } + currentTest.push(item); } - if (tests[i]) { - if (typeof tests[i] === 'string') { - title = tests[i]; - runGCTests(tests, i + 1, title); - } else { - try { - tests[i](); - } catch (e) { - console.error('Test failed: ' + title); - throw e; + for (const test of testList) { + await (async function(test) { + let title; + for (let i = 0; i < test.length; i++) { + if (i === 0) { + title = test[i]; + } else { + try { + test[i](); + } catch (e) { + console.error('Test failed: ' + title); + throw e; + } + if (i < tests.length - 1) { + global.gc(); + await tick(10); + } + } } - setImmediate(() => { - global.gc(); - tick(10, runGCTests(tests, i + 1, title)); - }); - } + })(test); } } diff --git a/test/threadsafe_function/threadsafe_function.js b/test/threadsafe_function/threadsafe_function.js index 710c21212..a3690fcf3 100644 --- a/test/threadsafe_function/threadsafe_function.js +++ b/test/threadsafe_function/threadsafe_function.js @@ -4,8 +4,8 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const common = require('../common'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = test(require(`../build/${buildType}/binding.node`)) + .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); function test(binding) { const expectedArray = (function(arrayLength) { @@ -43,7 +43,7 @@ function test(binding) { }); } - new Promise(function testWithoutJSMarshaller(resolve) { + return new Promise(function testWithoutJSMarshaller(resolve) { let callCount = 0; binding.threadsafe_function.startThreadNoNative(function testCallback() { callCount++; diff --git a/test/threadsafe_function/threadsafe_function_ctx.js b/test/threadsafe_function/threadsafe_function_ctx.js index d091bbbdd..2651586a0 100644 --- a/test/threadsafe_function/threadsafe_function_ctx.js +++ b/test/threadsafe_function/threadsafe_function_ctx.js @@ -3,10 +3,8 @@ const assert = require('assert'); const buildType = process.config.target_defaults.default_configuration; -module.exports = Promise.all[ - test(require(`../build/${buildType}/binding.node`)), - test(require(`../build/${buildType}/binding_noexcept.node`)) -]; +module.exports = test(require(`../build/${buildType}/binding.node`)) + .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); async function test(binding) { const ctx = { }; diff --git a/test/threadsafe_function/threadsafe_function_existing_tsfn.js b/test/threadsafe_function/threadsafe_function_existing_tsfn.js index 8843decd1..d77f57ef1 100644 --- a/test/threadsafe_function/threadsafe_function_existing_tsfn.js +++ b/test/threadsafe_function/threadsafe_function_existing_tsfn.js @@ -4,10 +4,8 @@ const assert = require('assert'); const buildType = process.config.target_defaults.default_configuration; -module.exports = Promise.all([ - test(require(`../build/${buildType}/binding.node`)), - test(require(`../build/${buildType}/binding_noexcept.node`)) -]); +module.exports = test(require(`../build/${buildType}/binding.node`)) + .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); async function test(binding) { const testCall = binding.threadsafe_function_existing_tsfn.testCall; diff --git a/test/threadsafe_function/threadsafe_function_sum.cc b/test/threadsafe_function/threadsafe_function_sum.cc index eac248816..5134e5816 100644 --- a/test/threadsafe_function/threadsafe_function_sum.cc +++ b/test/threadsafe_function/threadsafe_function_sum.cc @@ -22,6 +22,10 @@ struct TestData { std::vector threads = {}; ThreadSafeFunction tsfn = ThreadSafeFunction(); + + // These variables are only accessed from the main thread. + bool mainWantsRelease = false; + size_t expected_calls = 0; }; void FinalizerCallback(Napi::Env env, TestData* finalizeData){ @@ -142,10 +146,27 @@ static Value TestDelayedTSFN(const CallbackInfo &info) { return testData->deferred.Promise(); } +void AcquireFinalizerCallback(Napi::Env env, + TestData* finalizeData, + TestData* context) { + (void) context; + for (size_t i = 0; i < finalizeData->threads.size(); ++i) { + finalizeData->threads[i].join(); + } + finalizeData->deferred.Resolve(Boolean::New(env, true)); + delete finalizeData; +} + void entryAcquire(ThreadSafeFunction tsfn, int threadId) { tsfn.Acquire(); + TestData* testData = tsfn.GetContext(); std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); tsfn.BlockingCall( [=](Napi::Env env, Function callback) { + // This lambda runs on the main thread so it's OK to access the variables + // `expected_calls` and `mainWantsRelease`. + testData->expected_calls--; + if (testData->expected_calls == 0 && testData->mainWantsRelease) + testData->tsfn.Release(); callback.Call( { Number::New(env, static_cast(threadId))}); }); tsfn.Release(); @@ -153,6 +174,11 @@ void entryAcquire(ThreadSafeFunction tsfn, int threadId) { static Value CreateThread(const CallbackInfo& info) { TestData* testData = static_cast(info.Data()); + // Counting expected calls like this only works because on the JS side this + // binding is called from a synchronous loop. This means the main loop has no + // chance to run the tsfn JS callback before we've counted how many threads + // the JS intends to create. + testData->expected_calls++; ThreadSafeFunction tsfn = testData->tsfn; int threadId = testData->threads.size(); // A copy of the ThreadSafeFunction will go to the thread entry point @@ -162,8 +188,7 @@ static Value CreateThread(const CallbackInfo& info) { static Value StopThreads(const CallbackInfo& info) { TestData* testData = static_cast(info.Data()); - ThreadSafeFunction tsfn = testData->tsfn; - tsfn.Release(); + testData->mainWantsRelease = true; return info.Env().Undefined(); } @@ -176,8 +201,9 @@ static Value TestAcquire(const CallbackInfo& info) { TestData *testData = new TestData(Promise::Deferred::New(info.Env())); testData->tsfn = ThreadSafeFunction::New( - env, cb, "Test", 0, 1, - std::function(FinalizerCallback), testData); + env, cb, "Test", 0, 1, testData, + std::function(AcquireFinalizerCallback), + testData); Object result = Object::New(env); result["createThread"] = Function::New( env, CreateThread, "createThread", testData); diff --git a/test/threadsafe_function/threadsafe_function_sum.js b/test/threadsafe_function/threadsafe_function_sum.js index 4323dabeb..738e31db2 100644 --- a/test/threadsafe_function/threadsafe_function_sum.js +++ b/test/threadsafe_function/threadsafe_function_sum.js @@ -29,10 +29,8 @@ const buildType = process.config.target_defaults.default_configuration; const THREAD_COUNT = 5; const EXPECTED_SUM = (THREAD_COUNT - 1) * (THREAD_COUNT) / 2; -module.exports = Promise.all([ - test(require(`../build/${buildType}/binding.node`)), - test(require(`../build/${buildType}/binding_noexcept.node`)) -]); +module.exports = test(require(`../build/${buildType}/binding.node`)) + .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); /** @param {number[]} N */ const sum = (N) => N.reduce((sum, n) => sum + n, 0); @@ -57,9 +55,7 @@ function test(binding) { assert.equal(sum(calls), EXPECTED_SUM); } - return Promise.all([ - check(binding.threadsafe_function_sum.testDelayedTSFN), - check(binding.threadsafe_function_sum.testWithTSFN), - checkAcquire() - ]); + return check(binding.threadsafe_function_sum.testDelayedTSFN) + .then(() => check(binding.threadsafe_function_sum.testWithTSFN)) + .then(() => checkAcquire()); } diff --git a/test/threadsafe_function/threadsafe_function_unref.js b/test/threadsafe_function/threadsafe_function_unref.js index 37ce56b4d..e8f0ee391 100644 --- a/test/threadsafe_function/threadsafe_function_unref.js +++ b/test/threadsafe_function/threadsafe_function_unref.js @@ -15,8 +15,8 @@ const isMainProcess = process.argv[1] != __filename; */ if (isMainProcess) { - test(`../build/${buildType}/binding.node`); - test(`../build/${buildType}/binding_noexcept.node`); + module.exports = test(`../build/${buildType}/binding.node`) + .then(() => test(`../build/${buildType}/binding_noexcept.node`)); } else { test(process.argv[2]); } @@ -24,27 +24,29 @@ if (isMainProcess) { function test(bindingFile) { if (isMainProcess) { // Main process - const child = require('../napi_child').spawn(process.argv[0], [ '--expose-gc', __filename, bindingFile ], { - stdio: 'inherit', + return new Promise((resolve, reject) => { + const child = require('../napi_child').spawn(process.argv[0], [ + '--expose-gc', __filename, bindingFile + ], { stdio: 'inherit' }); + + let timeout = setTimeout( function() { + child.kill(); + timeout = 0; + reject(new Error("Expected child to die")); + }, 5000); + + child.on("error", (err) => { + clearTimeout(timeout); + timeout = 0; + reject(new Error(err)); + }) + + child.on("close", (code) => { + if (timeout) clearTimeout(timeout); + assert.strictEqual(code, 0, "Expected return value 0"); + resolve(); + }); }); - - let timeout = setTimeout( function() { - child.kill(); - timeout = 0; - throw new Error("Expected child to die"); - }, 5000); - - child.on("error", (err) => { - clearTimeout(timeout); - timeout = 0; - throw new Error(err); - }) - - child.on("close", (code) => { - if (timeout) clearTimeout(timeout); - assert(!code, "Expected return value 0"); - }); - } else { // Child process const binding = require(bindingFile); From 4ce40d22a62d2c784b009179b74bfa1d0b5c810d Mon Sep 17 00:00:00 2001 From: Koki Nishihara Date: Sun, 26 Jul 2020 08:30:32 +0900 Subject: [PATCH 229/696] test: use assert.strictEqual() Fixes: https://github.com/nodejs/node-addon-api/issues/775 PR-URL: https://github.com/nodejs/node-addon-api/pull/777 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- .../threadsafe_function_existing_tsfn.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/threadsafe_function/threadsafe_function_existing_tsfn.js b/test/threadsafe_function/threadsafe_function_existing_tsfn.js index d77f57ef1..d5ab1854a 100644 --- a/test/threadsafe_function/threadsafe_function_existing_tsfn.js +++ b/test/threadsafe_function/threadsafe_function_existing_tsfn.js @@ -10,8 +10,8 @@ module.exports = test(require(`../build/${buildType}/binding.node`)) async function test(binding) { const testCall = binding.threadsafe_function_existing_tsfn.testCall; - assert(typeof await testCall({ blocking: true, data: true }) === "number"); - assert(typeof await testCall({ blocking: true, data: false }) === "undefined"); - assert(typeof await testCall({ blocking: false, data: true }) === "number"); - assert(typeof await testCall({ blocking: false, data: false }) === "undefined"); + assert.strictEqual(typeof await testCall({ blocking: true, data: true }), "number"); + assert.strictEqual(typeof await testCall({ blocking: true, data: false }), "undefined"); + assert.strictEqual(typeof await testCall({ blocking: false, data: true }), "number"); + assert.strictEqual(typeof await testCall({ blocking: false, data: false }), "undefined"); } From cec2c769417b476711ce5f5fe95e9e6cddb9a529 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Thu, 9 Jul 2020 11:00:27 -0700 Subject: [PATCH 230/696] src: wrap finalizer callback Make sure C++ exceptions thrown from a finalizer are converted into JS exceptions just as they are in regular callbacks. Signed-off-by: Gabriel Schulhof PR-URL: https://github.com/nodejs/node-addon-api/pull/762 Reviewed-By: Michael Dawson test: add finalizer exception test src: wrap finalizer callback --- napi-inl.h | 38 +++++++++++++++++++++++++++-------- test/external.cc | 15 ++++++++++++++ test/external.js | 52 +++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 94 insertions(+), 11 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index a18ffcd59..4f0636a08 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -82,6 +82,24 @@ inline napi_value WrapCallback(Callable callback) { #endif // NAPI_CPP_EXCEPTIONS } +// For use in JS to C++ void callback wrappers to catch any Napi::Error +// exceptions and rethrow them as JavaScript exceptions before returning from the +// callback. +template +inline void WrapVoidCallback(Callable callback) { +#ifdef NAPI_CPP_EXCEPTIONS + try { + callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + } +#else // NAPI_CPP_EXCEPTIONS + // When C++ exceptions are disabled, errors are immediately thrown as JS + // exceptions, so there is no need to catch and rethrow them here. + callback(); +#endif // NAPI_CPP_EXCEPTIONS +} + template struct CallbackData { static inline @@ -120,17 +138,21 @@ struct CallbackData { template struct FinalizeData { static inline - void Wrapper(napi_env env, void* data, void* finalizeHint) { - FinalizeData* finalizeData = static_cast(finalizeHint); - finalizeData->callback(Env(env), static_cast(data)); - delete finalizeData; + void Wrapper(napi_env env, void* data, void* finalizeHint) noexcept { + WrapVoidCallback([&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(Env(env), static_cast(data)); + delete finalizeData; + }); } static inline - void WrapperWithHint(napi_env env, void* data, void* finalizeHint) { - FinalizeData* finalizeData = static_cast(finalizeHint); - finalizeData->callback(Env(env), static_cast(data), finalizeData->hint); - delete finalizeData; + void WrapperWithHint(napi_env env, void* data, void* finalizeHint) noexcept { + WrapVoidCallback([&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(Env(env), static_cast(data), finalizeData->hint); + delete finalizeData; + }); } Finalizer callback; diff --git a/test/external.cc b/test/external.cc index 25dacc426..9c22dcbe8 100644 --- a/test/external.cc +++ b/test/external.cc @@ -51,6 +51,19 @@ Value GetFinalizeCount(const CallbackInfo& info) { return Number::New(info.Env(), finalizeCount); } +Value CreateExternalWithFinalizeException(const CallbackInfo& info) { + return External::New(info.Env(), new int(1), + [](Env env, int* data) { + Error error = Error::New(env, "Finalizer exception"); + delete data; +#ifdef NAPI_CPP_EXCEPTIONS + throw error; +#else + error.ThrowAsJavaScriptException(); +#endif + }); +} + } // end anonymous namespace Object InitExternal(Env env) { @@ -58,6 +71,8 @@ Object InitExternal(Env env) { exports["createExternal"] = Function::New(env, CreateExternal); exports["createExternalWithFinalize"] = Function::New(env, CreateExternalWithFinalize); + exports["createExternalWithFinalizeException"] = + Function::New(env, CreateExternalWithFinalizeException); exports["createExternalWithFinalizeHint"] = Function::New(env, CreateExternalWithFinalizeHint); exports["checkExternal"] = Function::New(env, CheckExternal); exports["getFinalizeCount"] = Function::New(env, GetFinalizeCount); diff --git a/test/external.js b/test/external.js index 85dbe702f..0443e3f55 100644 --- a/test/external.js +++ b/test/external.js @@ -1,12 +1,58 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); +const { spawnSync } = require('child_process'); const testUtil = require('./testUtil'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +if (process.argv.length === 3) { + let interval; + + // Running as the child process, hook up an `uncaughtException` handler to + // examine the error thrown by the finalizer. + process.on('uncaughtException', (error) => { + // TODO (gabrielschulhof): Use assert.matches() when we drop support for + // Node.js v10.x. + assert(!!error.message.match(/Finalizer exception/)); + if (interval) { + clearInterval(interval); + } + process.exit(0); + }); + + // Create an external whose finalizer throws. + (() => + require(process.argv[2]).external.createExternalWithFinalizeException())(); + + // gc until the external's finalizer throws or until we give up. Since the + // exception is thrown from a native `SetImmediate()` we cannot catch it + // anywhere except in the process' `uncaughtException` handler. + let maxGCTries = 10; + (function gcInterval() { + global.gc(); + if (!interval) { + interval = setInterval(gcInterval, 100); + } else if (--maxGCTries === 0) { + throw new Error('Timed out waiting for the gc to throw'); + process.exit(1); + } + })(); + + return; +} + +module.exports = test(require.resolve(`./build/${buildType}/binding.node`)) + .then(() => + test(require.resolve(`./build/${buildType}/binding_noexcept.node`))); + +function test(bindingPath) { + const binding = require(bindingPath); + + const child = spawnSync(process.execPath, [ + '--expose-gc', __filename, bindingPath + ], { stdio: 'inherit' }); + assert.strictEqual(child.status, 0); + assert.strictEqual(child.signal, null); -function test(binding) { return testUtil.runGCTests([ 'External without finalizer', () => { From 807fb27c4f24ebb5fc6237deb30e2424e13186af Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 9 Aug 2020 19:02:02 +0200 Subject: [PATCH 231/696] Apply suggestions from code review Co-authored-by: Gabriel Schulhof --- doc/threadsafe.md | 22 +++++++++++----------- doc/threadsafe_function.md | 2 +- doc/threadsafe_function_ex.md | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/doc/threadsafe.md b/doc/threadsafe.md index 86f63906b..b3fda8b54 100644 --- a/doc/threadsafe.md +++ b/doc/threadsafe.md @@ -14,7 +14,7 @@ easy way to do this. These APIs provide two types -- [`Napi::ThreadSafeFunctionEx`](threadsafe_function_ex.md) -- as well as APIs to create, destroy, and call objects of this type. The differences between the two are subtle and are [highlighted below](#implementation-differences). Regardless -of which type you choose, the API between the two are similar. +of which type you choose, the APIs between the two are similar. `Napi::ThreadSafeFunction[Ex]::New()` creates a persistent reference that holds a JavaScript function which can be called from multiple threads. The calls @@ -44,11 +44,11 @@ reaches zero, no further threads can start making use of it by calling The choice between `Napi::ThreadSafeFunction` and `Napi::ThreadSafeFunctionEx` depends largely on how you plan to execute your native C++ code (the "callback") -on the Node thread. +on the Node.js thread. ### [`Napi::ThreadSafeFunction`](threadsafe_function.md) -This API is designed without N-API 5 native support for [optional JavaScript +This API is designed without N-API 5 native support for [the optional JavaScript function callback feature](https://github.com/nodejs/node/commit/53297e66cb). `::New` methods that do not have a `Function` parameter will construct a _new_, no-op `Function` on the environment to pass to the underlying N-API @@ -62,12 +62,12 @@ This API has some dynamic functionality, in that: - Different C++ data types may be passed with each call of `[Non]BlockingCall()` to match the specific data type as specified in the `CallJs` callback. -However, this functionality comes with some **additional overhead** and +Note that this functionality comes with some **additional overhead** and situational **memory leaks**: -- The API acts as a "middle-man" between the underlying +- The API acts as a "broker" between the underlying `napi_threadsafe_function`, and dynamically constructs a wrapper for your callback on the heap for every call to `[Non]BlockingCall()`. -- In acting in this "middle-man" fashion, the API will call the underlying "make +- In acting in this "broker" fashion, the API will call the underlying "make call" N-API method on this packaged item. If the API has determined the thread-safe function is no longer accessible (eg. all threads have released yet there are still items on the queue), **the callback passed to @@ -88,15 +88,15 @@ drawbacks listed above. The API is designed with N-API 5's support of an optional function callback. The API will correctly allow developers to pass `std::nullptr` instead of a `const Function&` for the callback function specified in `::New`. It also provides helper APIs to _target_ N-API 4 and -construct a no-op `Function` **or** to target N-API 5 and "construct" an +construct a no-op `Function` **or** to target N-API 5 and "construct" a `std::nullptr` callback. This allows a single codebase to use the same APIs, with just a switch of the `NAPI_VERSION` compile-time constant. -The removal of the dynamic call functionality has the additional side effects: -- The API does _not_ act as a "middle-man" compared to the non-`Ex`. Once Node +The removal of the dynamic call functionality has the following implications: +- The API does _not_ act as a "broker" compared to the non-`Ex`. Once Node.js finalizes the thread-safe function, the `CallJs` callback will execute with an - empty `Napi::Env` for any remaining items on the queue. This provides the the - ability to handle any necessary clean up of the item's data. + empty `Napi::Env` for any remaining items on the queue. This provides the + ability to handle any necessary cleanup of the item's data. - The callback _does_ receive the context as a parameter, so a call to `GetContext()` is _not_ necessary. This context type is specified as the **first type argument** specified to `::New`, ensuring type safety. diff --git a/doc/threadsafe_function.md b/doc/threadsafe_function.md index 0b3202929..7c323fd4a 100644 --- a/doc/threadsafe_function.md +++ b/doc/threadsafe_function.md @@ -62,7 +62,7 @@ New(napi_env env, - `initialThreadCount`: The initial number of threads, including the main thread, which will be making use of this function. - `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`. - Can be retreived via `GetContext()`. + It can be retreived by calling `GetContext()`. - `[optional] finalizeCallback`: Function to call when the `ThreadSafeFunction` is being destroyed. This callback will be invoked on the main thread when the thread-safe function is about to be destroyed. It receives the context and the diff --git a/doc/threadsafe_function_ex.md b/doc/threadsafe_function_ex.md index 15188a96c..ad25abd21 100644 --- a/doc/threadsafe_function_ex.md +++ b/doc/threadsafe_function_ex.md @@ -8,7 +8,7 @@ of: a TSFN has no context. - `DataType = void*`: The data to use in the native callback. By default, a TSFN can accept *any* data type. -- `Callback = void*(Napi::Env, Napi::Function jsCallback, ContextType*, +- `Callback = void(*)(Napi::Env, Napi::Function jsCallback, ContextType*, DataType*)`: The callback to run for each item added to the queue. If no `Callback` is given, the API will call the function `jsCallback` with no arguments. @@ -73,7 +73,7 @@ New(napi_env env, - `initialThreadCount`: The initial number of threads, including the main thread, which will be making use of this function. - `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`. - Can be retreived via `GetContext()`. + It can be retreived via `GetContext()`. - `[optional] finalizeCallback`: Function to call when the `ThreadSafeFunctionEx` is being destroyed. This callback will be invoked on the main thread when the thread-safe function is about to be destroyed. It From fd6a2b40f27c4d12bb9cae4c474100825c3b6cd9 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Sun, 9 Aug 2020 19:49:07 +0200 Subject: [PATCH 232/696] Additional changes from review --- doc/threadsafe.md | 6 +++--- doc/threadsafe_function_ex.md | 24 +++++++++-------------- napi-inl.h | 2 +- test/index.js | 1 + test/threadsafe_function_ex/README.md | 4 +--- test/threadsafe_function_ex/test/basic.cc | 11 +++++------ test/threadsafe_function_ex/test/basic.js | 2 +- 7 files changed, 21 insertions(+), 29 deletions(-) diff --git a/doc/threadsafe.md b/doc/threadsafe.md index b3fda8b54..4254fc8cc 100644 --- a/doc/threadsafe.md +++ b/doc/threadsafe.md @@ -99,14 +99,14 @@ The removal of the dynamic call functionality has the following implications: ability to handle any necessary cleanup of the item's data. - The callback _does_ receive the context as a parameter, so a call to `GetContext()` is _not_ necessary. This context type is specified as the - **first type argument** specified to `::New`, ensuring type safety. + **first template argument** specified to `::New`, ensuring type safety. - The `New()` constructor accepts the `CallJs` callback as the **second type argument**. The callback must be statically defined for the API to access it. This affords the ability to statically pass the context as the correct type across all methods. - Only one C++ data type may be specified to every call to `[Non]BlockingCall()` - -- the **third type argument** specified to `::New`. Any "dynamic call data" - must be implemented by the user. + -- the **third template argument** specified to `::New`. Any "dynamic call + data" must be implemented by the user. ### Usage Suggestions diff --git a/doc/threadsafe_function_ex.md b/doc/threadsafe_function_ex.md index ad25abd21..f1c42599b 100644 --- a/doc/threadsafe_function_ex.md +++ b/doc/threadsafe_function_ex.md @@ -40,7 +40,7 @@ Napi::ThreadSafeFunctionEx::ThreadSafeFunctionE Returns a non-empty `Napi::ThreadSafeFunctionEx` instance. To ensure the API statically handles the correct return type for `GetContext()` and -`[Non]BlockingCall()`, pass the proper type arguments to +`[Non]BlockingCall()`, pass the proper template arguments to `Napi::ThreadSafeFunctionEx`. ### New @@ -90,14 +90,15 @@ Returns a non-empty `Napi::ThreadSafeFunctionEx` instance. Depending on the targetted `NAPI_VERSION`, the API has different implementations for `CallbackType callback`. -When targetting version 4, `CallbackType` is: -- `const Function&` -- skipped, in which case the API creates a new no-op `Function` +When targetting version 4, `callback` may be: +- of type `const Function&` +- not provided as an parameter, in which case the API creates a new no-op +`Function` -When targetting version 5+, `CallbackType` is: -- `const Function&` -- `std::nullptr_t` -- skipped, in which case the API passes `std::nullptr` +When targetting version 5+, `callback` may be: +- of type `const Function&` +- of type `std::nullptr_t` +- not provided as an parameter, in which case the API passes `std::nullptr` ### Acquire @@ -170,13 +171,6 @@ napi_status Napi::ThreadSafeFunctionEx::NonBloc - `[optional] data`: Data to pass to the callback which was passed to `ThreadSafeFunctionEx::New()`. -- `[optional] callback`: C++ function that is invoked on the main thread. The - callback receives the `ThreadSafeFunction`'s JavaScript callback function to - call as an `Napi::Function` in its parameters and the `DataType*` data pointer - (if provided). Must implement `void operator()(Napi::Env env, Function - jsCallback, DataType* data)`, skipping `data` if not provided. It is not - necessary to call into JavaScript via `MakeCallback()` because N-API runs - `callback` in a context appropriate for callbacks. Returns one of: - `napi_ok`: The call was successfully added to the queue. diff --git a/napi-inl.h b/napi-inl.h index 3f3f11464..46090470c 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4399,7 +4399,7 @@ inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { // ThreadSafeFunctionEx class //////////////////////////////////////////////////////////////////////////////// -// Starting with NAPI 4, the JavaScript function `func` parameter of +// Starting with NAPI 5, the JavaScript function `func` parameter of // `napi_create_threadsafe_function` is optional. #if NAPI_VERSION > 4 // static, with Callback [missing] Resource [missing] Finalizer [missing] diff --git a/test/index.js b/test/index.js index 86939af84..fc285f132 100644 --- a/test/index.js +++ b/test/index.js @@ -43,6 +43,7 @@ let testModules = [ 'object/set_property', 'promise', 'run_script', + 'threadsafe_function_ex', 'threadsafe_function/threadsafe_function_ctx', 'threadsafe_function/threadsafe_function_existing_tsfn', 'threadsafe_function/threadsafe_function_ptr', diff --git a/test/threadsafe_function_ex/README.md b/test/threadsafe_function_ex/README.md index 139838ae5..c5c74db07 100644 --- a/test/threadsafe_function_ex/README.md +++ b/test/threadsafe_function_ex/README.md @@ -1,5 +1,3 @@ # Napi::ThreadSafeFunctionEx tests -|Spec|Test|Native|Node|Description| -|----|---|---|---|---| -|call \ No newline at end of file +TODO \ No newline at end of file diff --git a/test/threadsafe_function_ex/test/basic.cc b/test/threadsafe_function_ex/test/basic.cc index 9c9b5e636..9f8488d20 100644 --- a/test/threadsafe_function_ex/test/basic.cc +++ b/test/threadsafe_function_ex/test/basic.cc @@ -263,9 +263,6 @@ class TSFNWrap : public base { napi_threadsafe_function napi_tsfn; - // A threadsafe function on N-API 4 still requires a callback function, so - // this uses the `EmptyFunctionFactory` helper method to return a no-op - // Function on N-API 5+. auto status = napi_create_threadsafe_function( info.Env(), info[0], nullptr, String::From(info.Env(), "Test"), 0, 1, nullptr, Finalizer, this, CallJs, &napi_tsfn); @@ -349,8 +346,8 @@ namespace simple { using ContextType = std::nullptr_t; // Full type of our ThreadSafeFunctionEx. We don't specify the `ContextType` -// here (even though the _default_ for the type argument is `std::nullptr_t`) to -// demonstrate construction with no type arguments. +// here (even though the _default_ for the template argument is +// `std::nullptr_t`) to demonstrate construction with no template arguments. using TSFN = ThreadSafeFunctionEx<>; class TSFNWrap; @@ -363,7 +360,9 @@ class TSFNWrap : public base { auto env = info.Env(); #if NAPI_VERSION == 4 - // A threadsafe function on N-API 4 still requires a callback function. + // A threadsafe function on N-API 4 still requires a callback function, so + // this uses the `EmptyFunctionFactory` helper method to return a no-op + // Function on N-API 4. _tsfn = TSFN::New( env, // napi_env env, TSFN::EmptyFunctionFactory( diff --git a/test/threadsafe_function_ex/test/basic.js b/test/threadsafe_function_ex/test/basic.js index 780116954..d45091530 100644 --- a/test/threadsafe_function_ex/test/basic.js +++ b/test/threadsafe_function_ex/test/basic.js @@ -140,7 +140,7 @@ class BasicTest extends TestRunner { } /** - * A `ThreadSafeFunctionEx<>` can be constructed with no type arguments. + * A `ThreadSafeFunctionEx<>` can be constructed with no template arguments. * - Creates a threadsafe function with no context or callback or callJs. * - The node-addon-api 'no callback' feature is implemented by passing either * a no-op `Function` on N-API 4 or `std::nullptr` on N-API 5+ to the From ad9333e5c3aa575ff91db09bdfd4aea53288bd05 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Tue, 18 Aug 2020 17:26:57 +0200 Subject: [PATCH 233/696] test: tsfnex uses ported tsfn tests --- doc/threadsafe_function_ex.md | 147 ++-- test/binding.cc | 20 +- test/binding.gyp | 9 +- test/index.js | 7 +- test/threadsafe_function_ex/README.md | 3 - test/threadsafe_function_ex/index.js | 13 - test/threadsafe_function_ex/test/basic.cc | 432 ----------- test/threadsafe_function_ex/test/basic.js | 161 ---- test/threadsafe_function_ex/test/example.cc | 694 ------------------ test/threadsafe_function_ex/test/example.js | 342 --------- .../threadsafe_function_ex/test/threadsafe.js | 182 ----- .../threadsafe.cc => threadsafe_function.cc} | 2 +- .../threadsafe_function.js | 194 +++++ .../threadsafe_function_ctx.cc | 62 ++ .../threadsafe_function_ctx.js | 14 + .../threadsafe_function_existing_tsfn.cc | 114 +++ .../threadsafe_function_existing_tsfn.js | 17 + .../threadsafe_function_ptr.cc | 28 + .../threadsafe_function_ptr.js | 10 + .../threadsafe_function_sum.cc | 220 ++++++ .../threadsafe_function_sum.js | 61 ++ .../threadsafe_function_unref.cc | 44 ++ .../threadsafe_function_unref.js | 55 ++ .../threadsafe_function_ex/util/TestRunner.js | 154 ---- test/threadsafe_function_ex/util/util.h | 62 -- 25 files changed, 945 insertions(+), 2102 deletions(-) delete mode 100644 test/threadsafe_function_ex/README.md delete mode 100644 test/threadsafe_function_ex/index.js delete mode 100644 test/threadsafe_function_ex/test/basic.cc delete mode 100644 test/threadsafe_function_ex/test/basic.js delete mode 100644 test/threadsafe_function_ex/test/example.cc delete mode 100644 test/threadsafe_function_ex/test/example.js delete mode 100644 test/threadsafe_function_ex/test/threadsafe.js rename test/threadsafe_function_ex/{test/threadsafe.cc => threadsafe_function.cc} (99%) create mode 100644 test/threadsafe_function_ex/threadsafe_function.js create mode 100644 test/threadsafe_function_ex/threadsafe_function_ctx.cc create mode 100644 test/threadsafe_function_ex/threadsafe_function_ctx.js create mode 100644 test/threadsafe_function_ex/threadsafe_function_existing_tsfn.cc create mode 100644 test/threadsafe_function_ex/threadsafe_function_existing_tsfn.js create mode 100644 test/threadsafe_function_ex/threadsafe_function_ptr.cc create mode 100644 test/threadsafe_function_ex/threadsafe_function_ptr.js create mode 100644 test/threadsafe_function_ex/threadsafe_function_sum.cc create mode 100644 test/threadsafe_function_ex/threadsafe_function_sum.js create mode 100644 test/threadsafe_function_ex/threadsafe_function_unref.cc create mode 100644 test/threadsafe_function_ex/threadsafe_function_unref.js delete mode 100644 test/threadsafe_function_ex/util/TestRunner.js delete mode 100644 test/threadsafe_function_ex/util/util.h diff --git a/doc/threadsafe_function_ex.md b/doc/threadsafe_function_ex.md index f1c42599b..ffc637217 100644 --- a/doc/threadsafe_function_ex.md +++ b/doc/threadsafe_function_ex.md @@ -82,7 +82,7 @@ New(napi_env env, calling `uv_thread_join()`. It is important that, aside from the main loop thread, there be no threads left using the thread-safe function after the finalize callback completes. Must implement `void operator()(Env env, - DataType* data, ContextType* hint)`. + FinalizerDataType* data, ContextType* hint)`. - `[optional] data`: Data to be passed to `finalizeCallback`. Returns a non-empty `Napi::ThreadSafeFunctionEx` instance. @@ -182,56 +182,109 @@ Returns one of: - `napi_generic_failure`: A generic error occurred when attemping to add to the queue. + ## Example -For an in-line documented example, please see the ThreadSafeFunctionEx CI tests hosted here. -- [test/threadsafe_function_ex/test/example.js](../test/threadsafe_function_ex/test/example.js) -- [test/threadsafe_function_ex/test/example.cc](../test/threadsafe_function_ex/test/example.cc) +```cpp +#include +#include +#include + +using namespace Napi; + +std::thread nativeThread; +ThreadSafeFunction tsfn; + +Value Start( const CallbackInfo& info ) +{ + Napi::Env env = info.Env(); + + if ( info.Length() < 2 ) + { + throw TypeError::New( env, "Expected two arguments" ); + } + else if ( !info[0].IsFunction() ) + { + throw TypeError::New( env, "Expected first arg to be function" ); + } + else if ( !info[1].IsNumber() ) + { + throw TypeError::New( env, "Expected second arg to be number" ); + } + + int count = info[1].As().Int32Value(); + + // Create a ThreadSafeFunction + tsfn = ThreadSafeFunction::New( + env, + info[0].As(), // JavaScript function called asynchronously + "Resource Name", // Name + 0, // Unlimited queue + 1, // Only one thread will use this initially + []( Napi::Env ) { // Finalizer used to clean threads up + nativeThread.join(); + } ); + + // Create a native thread + nativeThread = std::thread( [count] { + auto callback = []( Napi::Env env, Function jsCallback, int* value ) { + // Transform native data into JS data, passing it to the provided + // `jsCallback` -- the TSFN's JavaScript function. + jsCallback.Call( {Number::New( env, *value )} ); + + // We're finished with the data. + delete value; + }; + + for ( int i = 0; i < count; i++ ) + { + // Create new data + int* value = new int( clock() ); + + // Perform a blocking call + napi_status status = tsfn.BlockingCall( value, callback ); + if ( status != napi_ok ) + { + // Handle error + break; + } + + std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); + } + + // Release the thread-safe function + tsfn.Release(); + } ); + + return Boolean::New(env, true); +} + +Napi::Object Init( Napi::Env env, Object exports ) +{ + exports.Set( "start", Function::New( env, Start ) ); + return exports; +} + +NODE_API_MODULE( clock, Init ) +``` + +The above code can be used from JavaScript as follows: + +```js +const { start } = require('bindings')('clock'); -The example will create multiple set of threads. Each thread calls into -JavaScript with a numeric `base` value (deterministically calculated by the -thread id), with Node returning either a `number` or `Promise` that -resolves to `base * base`. +start(function () { + console.log("JavaScript callback called with arguments", Array.from(arguments)); +}, 5); +``` -From the root of the `node-addon-api` repository: +When executed, the output will show the value of `clock()` five times at one +second intervals: ``` -Usage: node ./test/threadsafe_function_ex/test/example.js [options] - - -c, --calls The number of calls each thread should make (number[]). - -a, --acquire [factor] Acquire a new set of `factor` call threads, using the - same `calls` definition. - -d, --call-delay The delay on callback resolution that each thread should - have (number[]). This is achieved via a delayed Promise - resolution in the JavaScript callback provided to the - TSFN. Using large delays here will cause all threads to - bottle-neck. - -D, --thread-delay The delay that each thread should have prior to making a - call (number[]). Using large delays here will cause the - individual thread to bottle-neck. - -l, --log-call Display console.log-based logging messages. - -L, --log-thread Display std::cout-based logging messages. - -n, --no-callback Do not use a JavaScript callback. - -e, --callback-error [thread[.call]] Cause an error to occur in the JavaScript callback for - the given thread's call (if provided; first thread's - first call otherwise). - - When not provided: - - defaults to [1,2,3,4,5] - - [factor] defaults to 1 - - defaults to [400,200,100,50,0] - - defaults to [400,200,100,50,0] - - -Examples: - - -c [1,2,3] -l -L - - Creates three threads that makes one, two, and three calls each, respectively. - - -c [5,5] -d [5000,5000] -D [0,0] -l -L - - Creates two threads that make five calls each. In this scenario, the threads will be - blocked primarily on waiting for the callback to resolve, as each thread's call takes - 5000 milliseconds. +JavaScript callback called with arguments [ 84745 ] +JavaScript callback called with arguments [ 103211 ] +JavaScript callback called with arguments [ 104516 ] +JavaScript callback called with arguments [ 105104 ] +JavaScript callback called with arguments [ 105691 ] ``` diff --git a/test/binding.cc b/test/binding.cc index cde830d29..fe3a3cd5b 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -49,9 +49,12 @@ Object InitThreadSafeFunctionPtr(Env env); Object InitThreadSafeFunctionSum(Env env); Object InitThreadSafeFunctionUnref(Env env); Object InitThreadSafeFunction(Env env); -Object InitThreadSafeFunctionExBasic(Env env); -Object InitThreadSafeFunctionExExample(Env env); -Object InitThreadSafeFunctionExThreadSafe(Env env); +Object InitThreadSafeFunctionExCtx(Env env); +Object InitThreadSafeFunctionExExistingTsfn(Env env); +Object InitThreadSafeFunctionExPtr(Env env); +Object InitThreadSafeFunctionExSum(Env env); +Object InitThreadSafeFunctionExUnref(Env env); +Object InitThreadSafeFunctionEx(Env env); #endif Object InitTypedArray(Env env); Object InitObjectWrap(Env env); @@ -111,10 +114,13 @@ Object Init(Env env, Object exports) { exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env)); exports.Set("threadsafe_function_sum", InitThreadSafeFunctionSum(env)); exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env)); - exports.Set("threadsafe_function", InitThreadSafeFunction(env)); - exports.Set("threadsafe_function_ex_basic", InitThreadSafeFunctionExBasic(env)); - exports.Set("threadsafe_function_ex_example", InitThreadSafeFunctionExExample(env)); - exports.Set("threadsafe_function_ex_threadsafe", InitThreadSafeFunctionExThreadSafe(env)); + exports.Set("threadsafe_function", InitThreadSafeFunctionEx(env)); + exports.Set("threadsafe_function_ex_ctx", InitThreadSafeFunctionExCtx(env)); + exports.Set("threadsafe_function_ex_existing_tsfn", InitThreadSafeFunctionExExistingTsfn(env)); + exports.Set("threadsafe_function_ex_ptr", InitThreadSafeFunctionExPtr(env)); + exports.Set("threadsafe_function_ex_sum", InitThreadSafeFunctionExSum(env)); + exports.Set("threadsafe_function_ex_unref", InitThreadSafeFunctionExUnref(env)); + exports.Set("threadsafe_function_ex", InitThreadSafeFunctionEx(env)); #endif exports.Set("typedarray", InitTypedArray(env)); exports.Set("objectwrap", InitObjectWrap(env)); diff --git a/test/binding.gyp b/test/binding.gyp index f1e46114f..702799d11 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -36,9 +36,12 @@ 'object/set_property.cc', 'promise.cc', 'run_script.cc', - 'threadsafe_function_ex/test/basic.cc', - 'threadsafe_function_ex/test/example.cc', - 'threadsafe_function_ex/test/threadsafe.cc', + 'threadsafe_function_ex/threadsafe_function_ctx.cc', + 'threadsafe_function_ex/threadsafe_function_existing_tsfn.cc', + 'threadsafe_function_ex/threadsafe_function_ptr.cc', + 'threadsafe_function_ex/threadsafe_function_sum.cc', + 'threadsafe_function_ex/threadsafe_function_unref.cc', + 'threadsafe_function_ex/threadsafe_function.cc', 'threadsafe_function/threadsafe_function_ctx.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', diff --git a/test/index.js b/test/index.js index fc285f132..adf6925cf 100644 --- a/test/index.js +++ b/test/index.js @@ -43,7 +43,12 @@ let testModules = [ 'object/set_property', 'promise', 'run_script', - 'threadsafe_function_ex', + 'threadsafe_function_ex/threadsafe_function_ctx', + 'threadsafe_function_ex/threadsafe_function_existing_tsfn', + 'threadsafe_function_ex/threadsafe_function_ptr', + 'threadsafe_function_ex/threadsafe_function_sum', + 'threadsafe_function_ex/threadsafe_function_unref', + 'threadsafe_function_ex/threadsafe_function', 'threadsafe_function/threadsafe_function_ctx', 'threadsafe_function/threadsafe_function_existing_tsfn', 'threadsafe_function/threadsafe_function_ptr', diff --git a/test/threadsafe_function_ex/README.md b/test/threadsafe_function_ex/README.md deleted file mode 100644 index c5c74db07..000000000 --- a/test/threadsafe_function_ex/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Napi::ThreadSafeFunctionEx tests - -TODO \ No newline at end of file diff --git a/test/threadsafe_function_ex/index.js b/test/threadsafe_function_ex/index.js deleted file mode 100644 index 917d1b060..000000000 --- a/test/threadsafe_function_ex/index.js +++ /dev/null @@ -1,13 +0,0 @@ -const tests = [ - 'threadsafe', - 'basic', - 'example' -]; - -// Threadsafe tests must run synchronously. If two threaded-tests are running -// and one fails, Node may exit while `std::thread`s are running. -module.exports = (async () => { - for (const test of tests) { - await require(`./test/${test}`); - } -})(); diff --git a/test/threadsafe_function_ex/test/basic.cc b/test/threadsafe_function_ex/test/basic.cc deleted file mode 100644 index 9f8488d20..000000000 --- a/test/threadsafe_function_ex/test/basic.cc +++ /dev/null @@ -1,432 +0,0 @@ -#include "../util/util.h" -#include "napi.h" -#include - -#if (NAPI_VERSION > 3) - -using namespace Napi; - -namespace call { - -// Context of the TSFN. -using ContextType = std::nullptr_t; - -// Data passed (as pointer) to [Non]BlockingCall -struct DataType { - Reference data; - Promise::Deferred deferred; -}; - -// CallJs callback function -static void CallJs(Napi::Env env, Napi::Function jsCallback, - ContextType * /*context*/, DataType *data) { - if (!(env == nullptr || jsCallback == nullptr)) { - if (data != nullptr) { - jsCallback.Call(env.Undefined(), {data->data.Value()}); - data->deferred.Resolve(data->data.Value()); - } - } - if (data != nullptr) { - delete data; - } -} - -// Full type of the ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; - -class TSFNWrap; -using base = tsfnutil::TSFNWrapBase; - -// A JS-accessible wrap that holds the TSFN. -class TSFNWrap : public base { -public: - TSFNWrap(const CallbackInfo &info) : base(info) { - Napi::Env env = info.Env(); - _tsfn = TSFN::New(env, // napi_env env, - info[0].As(), // const Function& callback, - "Test", // ResourceString resourceName, - 0, // size_t maxQueueSize, - 1, // size_t initialThreadCount, - nullptr, // ContextType* context - base::Finalizer, // Finalizer finalizer - &_deferred // FinalizerDataType* data - ); - } - - static std::array, 2> InstanceMethods() { - return {{InstanceMethod("release", &TSFNWrap::Release), - InstanceMethod("call", &TSFNWrap::Call)}}; - } - - Napi::Value Call(const CallbackInfo &info) { - Napi::Env env = info.Env(); - DataType *data = - new DataType{Napi::Reference(Persistent(info[0])), - Promise::Deferred::New(env)}; - _tsfn.NonBlockingCall(data); - return data->deferred.Promise(); - }; -}; - -} // namespace call - -namespace context { - -// Context of the TSFN. -using ContextType = Reference; - -// Data passed (as pointer) to [Non]BlockingCall -using DataType = Promise::Deferred; - -// CallJs callback function -static void CallJs(Napi::Env env, Napi::Function /*jsCallback*/, - ContextType *context, DataType *data) { - if (env != nullptr) { - if (data != nullptr) { - data->Resolve(context->Value()); - } - } - if (data != nullptr) { - delete data; - } -} - -// Full type of the ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; - -class TSFNWrap; -using base = tsfnutil::TSFNWrapBase; - -// A JS-accessible wrap that holds the TSFN. -class TSFNWrap : public base { -public: - TSFNWrap(const CallbackInfo &info) : base(info) { - Napi::Env env = info.Env(); - - ContextType *context = new ContextType(Persistent(info[0])); - - _tsfn = - TSFN::New(env, // napi_env env, - TSFN::EmptyFunctionFactory(env), // const Function& callback, - Value(), // const Object& resource, - "Test", // ResourceString resourceName, - 0, // size_t maxQueueSize, - 1, // size_t initialThreadCount, - context, // ContextType* context, - base::Finalizer, // Finalizer finalizer - &_deferred // FinalizerDataType* data - ); - } - - static std::array, 3> InstanceMethods() { - return {{InstanceMethod("call", &TSFNWrap::Call), - InstanceMethod("getContext", &TSFNWrap::GetContext), - InstanceMethod("release", &TSFNWrap::Release)}}; - } - - Napi::Value Call(const CallbackInfo &info) { - auto *callData = new DataType(info.Env()); - _tsfn.NonBlockingCall(callData); - return callData->Promise(); - }; - - Napi::Value GetContext(const CallbackInfo &) { - return _tsfn.GetContext()->Value(); - }; -}; -} // namespace context - -namespace empty { -#if NAPI_VERSION > 4 - -// Context of the TSFN. -using ContextType = std::nullptr_t; - -// Data passed (as pointer) to [Non]BlockingCall -struct DataType { - Promise::Deferred deferred; -}; - -// CallJs callback function -static void CallJs(Napi::Env env, Function jsCallback, - ContextType * /*context*/, DataType *data) { - if (env != nullptr) { - if (data != nullptr) { - if (jsCallback.IsEmpty()) { - data->deferred.Resolve(Boolean::New(env, true)); - } else { - data->deferred.Reject(String::New(env, "jsCallback is not empty")); - } - } - } - if (data != nullptr) { - delete data; - } -} - -// Full type of the ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; - -class TSFNWrap; -using base = tsfnutil::TSFNWrapBase; - -// A JS-accessible wrap that holds the TSFN. -class TSFNWrap : public base { -public: - TSFNWrap(const CallbackInfo &info) : base(info) { - - auto env = info.Env(); - _tsfn = TSFN::New(env, // napi_env env, - "Test", // ResourceString resourceName, - 0, // size_t maxQueueSize, - 1, // size_t initialThreadCount, - nullptr, // ContextType* context - base::Finalizer, // Finalizer finalizer - &_deferred // FinalizerDataType* data - ); - } - - static std::array, 2> InstanceMethods() { - return {{InstanceMethod("release", &TSFNWrap::Release), - InstanceMethod("call", &TSFNWrap::Call)}}; - } - - Napi::Value Call(const CallbackInfo &info) { - auto data = new DataType{Promise::Deferred::New(info.Env())}; - _tsfn.NonBlockingCall(data); - return data->deferred.Promise(); - }; -}; - -#endif -} // namespace empty - -namespace existing { - -// Data passed (as pointer) to [Non]BlockingCall -struct DataType { - Promise::Deferred deferred; - bool reject; -}; - -// CallJs callback function provided to `napi_create_threadsafe_function`. It is -// _NOT_ used by `Napi::ThreadSafeFunctionEx<>`, which is why these arguments -// are napi_*. -static void CallJs(napi_env env, napi_value jsCallback, void * /*context*/, - void *data) { - DataType *casted = static_cast(data); - if (env != nullptr) { - if (jsCallback != nullptr) { - Function(env, jsCallback).Call(0, nullptr); - } - if (data != nullptr) { - if (casted->reject) { - casted->deferred.Reject( - String::New(env, "The CallJs has rejected the promise")); - } else { - casted->deferred.Resolve( - String::New(env, "The CallJs has resolved the promise")); - } - } - } - if (casted != nullptr) { - delete casted; - } -} - -// This test creates a native napi_threadsafe_function itself, whose `context` -// parameter is the `TSFNWrap` object. We forward-declare, so we can use -// it as an argument inside `ThreadSafeFunctionEx<>`. This also allows us to -// statically get the correct type when using `tsfn.GetContext()`. The converse -// is true: if the ContextType does _not_ match that provided to the underlying -// napi_create_threadsafe_function, then the static type will be incorrect. -class TSFNWrap; - -// Context of the TSFN. -using ContextType = TSFNWrap; - -// Full type of our ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; -using base = tsfnutil::TSFNWrapBase; - -// A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public base { -public: - TSFNWrap(const CallbackInfo &info) : base(info) { - - auto env = info.Env(); - - if (info.Length() < 1 || !info[0].IsFunction()) { - NAPI_THROW_VOID(Napi::TypeError::New( - env, "Invalid arguments: Expected arg0 = function")); - } - - napi_threadsafe_function napi_tsfn; - - auto status = napi_create_threadsafe_function( - info.Env(), info[0], nullptr, String::From(info.Env(), "Test"), 0, 1, - nullptr, Finalizer, this, CallJs, &napi_tsfn); - if (status != napi_ok) { - NAPI_THROW_VOID(Error::New(env, "Could not create TSFN.")); - } - _tsfn = TSFN(napi_tsfn); - } - - static std::array, 2> InstanceMethods() { - return {{InstanceMethod("release", &TSFNWrap::Release), - InstanceMethod("call", &TSFNWrap::Call)}}; - } - - Napi::Value Call(const CallbackInfo &info) { - Napi::Env env = info.Env(); - if (info.Length() < 1) { - NAPI_THROW(Napi::TypeError::New( - env, "Invalid arguments: Expected arg0 = number [0,5]"), - Value()); - } - auto arg0 = info[0]; - if (!arg0.IsNumber()) { - NAPI_THROW(Napi::TypeError::New( - env, "Invalid arguments: Expected arg0 = number [0,5]"), - Value()); - } - auto mode = info[0].ToNumber().Int32Value(); - switch (mode) { - // Use node-addon-api to send a call that either resolves or rejects the - // promise in the data. - case 0: - case 1: { - auto *data = new DataType{Promise::Deferred::New(env), mode == 1}; - _tsfn.NonBlockingCall(data); - return data->deferred.Promise(); - } - // Use node-addon-api to send a call with no data - case 2: { - _tsfn.NonBlockingCall(); - return Boolean::New(env, true); - } - // Use napi to send a call that either resolves or rejects the promise in - // the data. - case 3: - case 4: { - auto *data = new DataType{Promise::Deferred::New(env), mode == 4}; - napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); - return data->deferred.Promise(); - } - // Use napi to send a call with no data - case 5: { - napi_call_threadsafe_function(_tsfn, nullptr, napi_tsfn_nonblocking); - return Boolean::New(env, true); - } - } - NAPI_THROW(Napi::TypeError::New( - env, "Invalid arguments: Expected arg0 = number [0,5]"), - Value()); - }; - -private: - // This test uses a custom napi (NOT node-addon-api) TSFN finalizer. - static void Finalizer(napi_env env, void * /*data*/, void *ctx) { - TSFNWrap *tsfn = static_cast(ctx); - tsfn->Finalizer(env); - } - - // Clean up the TSFNWrap by resolving the promise. - void Finalizer(napi_env e) { - if (_deferred) { - _deferred->Resolve(Boolean::New(e, true)); - _deferred.release(); - } - } -}; - -} // namespace existing -namespace simple { - -using ContextType = std::nullptr_t; - -// Full type of our ThreadSafeFunctionEx. We don't specify the `ContextType` -// here (even though the _default_ for the template argument is -// `std::nullptr_t`) to demonstrate construction with no template arguments. -using TSFN = ThreadSafeFunctionEx<>; - -class TSFNWrap; -using base = tsfnutil::TSFNWrapBase; - -// A JS-accessible wrap that holds a TSFN. -class TSFNWrap : public base { -public: - TSFNWrap(const CallbackInfo &info) : base(info) { - - auto env = info.Env(); -#if NAPI_VERSION == 4 - // A threadsafe function on N-API 4 still requires a callback function, so - // this uses the `EmptyFunctionFactory` helper method to return a no-op - // Function on N-API 4. - _tsfn = TSFN::New( - env, // napi_env env, - TSFN::EmptyFunctionFactory( - env), // N-API 5+: nullptr; else: const Function& callback, - "Test", // ResourceString resourceName, - 1, // size_t maxQueueSize, - 1 // size_t initialThreadCount - ); -#else - _tsfn = TSFN::New(env, // napi_env env, - "Test", // ResourceString resourceName, - 1, // size_t maxQueueSize, - 1 // size_t initialThreadCount - ); -#endif - } - - static std::array, 2> InstanceMethods() { - return {{InstanceMethod("release", &TSFNWrap::Release), - InstanceMethod("call", &TSFNWrap::Call)}}; - } - - // Since this test spec has no CALLBACK, CONTEXT, or FINALIZER. We have no way - // to know when the underlying ThreadSafeFunction has been finalized. - Napi::Value Release(const CallbackInfo &info) { - _tsfn.Release(); - return String::New(info.Env(), "TSFN may not have finalized."); - }; - - Napi::Value Call(const CallbackInfo &info) { - _tsfn.NonBlockingCall(); - return info.Env().Undefined(); - }; -}; - -} // namespace simple - -Object InitThreadSafeFunctionExBasic(Env env) { - -// A list of v4+ enabled spec namespaces. -#define V4_EXPORTS(V) \ - V(call) \ - V(simple) \ - V(existing) \ - V(context) - -// A list of v5+ enables spec namespaces. -#define V5_EXPORTS(V) V(empty) - -#if NAPI_VERSION == 4 -#define EXPORTS(V) V4_EXPORTS(V) -#else -#define EXPORTS(V) \ - V4_EXPORTS(V) \ - V5_EXPORTS(V) -#endif - - Object exports(Object::New(env)); - -#define V(modname) modname::TSFNWrap::Init(env, exports, #modname); - EXPORTS(V) -#undef V - - return exports; -} - -#endif diff --git a/test/threadsafe_function_ex/test/basic.js b/test/threadsafe_function_ex/test/basic.js deleted file mode 100644 index d45091530..000000000 --- a/test/threadsafe_function_ex/test/basic.js +++ /dev/null @@ -1,161 +0,0 @@ -// @ts-check -'use strict'; -const assert = require('assert'); - -const { TestRunner } = require('../util/TestRunner'); - -/** - * A "basic" test spec. This spec does NOT use threads, and is primarily used to - * verify the API. - */ -class BasicTest extends TestRunner { - /** - * This test ensures the data sent to the NonBlockingCall and the data - * received in the JavaScript callback are the same. - * - Creates a contexted threadsafe function with callback. - * - Makes one call, and waits for call to complete. - * - The callback forwards the item's data to the given JavaScript function in - * the test. - * - Asserts the data is the same. - */ - async call({ TSFNWrap }) { - const data = {}; - const tsfn = new TSFNWrap(tsfnData => { - assert(data === tsfnData, "Data in and out of tsfn call do not equal"); - }); - await tsfn.call(data); - return await tsfn.release(); - } - - /** - * The context provided to the threadsafe function's constructor is accessible - * on both (A) the threadsafe function's callback as well as (B) the - * threadsafe function itself. This test ensures the context across all three - * are the same. - * - Creates a contexted threadsafe function with callback. - * - The callback forwards the item's data to the given JavaScript function in - * the test. - * - Asserts the contexts are the same as the context passed during threadsafe - * function construction in two places: - * - (A) Makes one call, and waits for call to complete. - * - (B) Asserts that the context returns from the API's `GetContext()` - */ - async context({ TSFNWrap }) { - const ctx = {}; - const tsfn = new TSFNWrap(ctx); - assert(ctx === await tsfn.call(), "getContextByCall context not equal"); - assert(ctx === tsfn.getContext(), "getContextFromTsfn context not equal"); - return await tsfn.release(); - } - - /** - * **ONLY ON N-API 5+**. The optional JavaScript function callback feature is - * not available in N-API <= 4. This test creates uses a threadsafe function - * that handles all of its JavaScript processing on the callJs instead of the - * callback. - * - Creates a threadsafe function with no JavaScript context or callback. - * - Makes one call, waiting for completion. The internal `CallJs` resolves - * the call if jsCallback is empty, otherwise rejects. - */ - async empty({ TSFNWrap }) { - if (typeof TSFNWrap === 'function') { - const tsfn = new TSFNWrap(); - await tsfn.call(); - return await tsfn.release(); - } - return true; - } - - /** - * A `ThreadSafeFunctionEx` can be constructed with an existing - * napi_threadsafe_function. - * - Creates a native napi_threadsafe_function with no context, using the - * jsCallback passed from this test. - * - Makes six calls: - * - Use node-addon-api's `NonBlockingCall` *OR* napi's - * `napi_call_threadsafe_function` _cross_ - * - With data that resolves *OR rejects on CallJs - * - With no data that rejects on CallJs - * - Releases the TSFN. - */ - async existing({ TSFNWrap }) { - - /** - * Called by the TSFN's jsCallback below. - * @type {function|undefined} - */ - let currentCallback = undefined; - - const tsfn = new TSFNWrap(function () { - if (typeof currentCallback === 'function') { - currentCallback.apply(undefined, arguments); - } - }); - /** - * The input argument to `tsfn.call()`: 0-2: - * ThreadSafeFunctionEx.NonBlockingCall(data) with... - * - 0: data, resolve promise in CallJs - * - 1: data, reject promise in CallJs - * - 2: data = nullptr 3-5: napi_call_threadsafe_function(data, - * napi_tsfn_nonblocking) with... - * - 3: data, resolve promise in CallJs - * - 4: data, reject promise in CallJs - * - 5: data = nullptr - * @type {[0,1,2,3,4,5]} - */ - const input = [0, 1, 2, 3, 4, 5]; - - let caught = false; - - while (input.length) { - // Perform a call that resolves - await tsfn.call(input.shift()); - - // Perform a call that rejects - caught = false; - try { - await tsfn.call(input.shift()); - } catch (e) { - caught = true; - } finally { - assert(caught, "The rejection was not caught"); - } - - // Perform a call with no data - caught = false; - await new Promise((resolve, reject) => { - currentCallback = () => { - resolve(); - reject = undefined; - }; - tsfn.call(input.shift()); - setTimeout(() => { - if (reject) { - reject(new Error("tsfn.call() timed out")); - } - }, 1000); - }); - } - return await tsfn.release(); - } - - /** - * A `ThreadSafeFunctionEx<>` can be constructed with no template arguments. - * - Creates a threadsafe function with no context or callback or callJs. - * - The node-addon-api 'no callback' feature is implemented by passing either - * a no-op `Function` on N-API 4 or `std::nullptr` on N-API 5+ to the - * underlying `napi_create_threadsafe_function` call. - * - Makes one call, releases, then waits for finalization. - * - Inherently ignores the state of the item once it has been added to the - * queue. Since there are no callbacks or context, it is impossible to - * capture the state. - */ - async simple({ TSFNWrap }) { - const tsfn = new TSFNWrap(); - tsfn.call(); - return await tsfn.release(); - } - -} - -module.exports = new BasicTest('threadsafe_function_ex_basic', __filename).start(); diff --git a/test/threadsafe_function_ex/test/example.cc b/test/threadsafe_function_ex/test/example.cc deleted file mode 100644 index b0063a5cd..000000000 --- a/test/threadsafe_function_ex/test/example.cc +++ /dev/null @@ -1,694 +0,0 @@ -#include "../util/util.h" -#include "napi.h" -#include -#include -#include -#include -#include -#include - -using ThreadExitHandler = void (*)(size_t threadId); - -struct ThreadOptions { - size_t threadId; - int calls; - int callDelay; - int threadDelay; -}; - -static struct { - bool logCall = true; // Uses JS console.log to output when the TSFN is - // processing the NonBlockingCall(). - bool logThread = false; // Uses native std::cout to output when the thread's - // NonBlockingCall() request has finished. -} DefaultOptions; // Options from Start() - -#if (NAPI_VERSION > 3) - -using namespace Napi; - -namespace example { - -class TSFNWrap; - -// Context of the TSFN. -using Context = TSFNWrap; - -// Data returned to a thread when it requests a TSFN call. This example uses -// promises to synchronize between threads. Since this example needs to be built -// with exceptions both enabled and disabled, we will always use a static -// "positive" result, and dynamically at run-time determine if it failed. -// Otherwise, we could use std::promise.set_exception to handle errors. -struct CallJsResult { - int result; - bool isFinalized; - std::string error; -}; - -// The structure of data we send to `CallJs` -struct Data { - std::promise promise; - uint32_t threadId; - bool logCall; - int callDelay; - uint32_t base; // The "input" data, which CallJs will calculate `base * base` - int callId; // The call id (unique to the thread) -}; - -// Data passed (as pointer) to [Non]BlockingCall is shared among multiple -// threads (native thread and Node thread). -using DataType = std::shared_ptr; - -// When providing the `CallJs` result back to the thread, we pass information -// about where and how the result came (for logging). -enum ResultLocation { - NUMBER, // The callback returned a number - PROMISE, // The callback returned a Promise that resolved to a number - DEFAULT // There was no callback provided to TSFN::New -}; - -// CallJs callback function, used to transform the native C data to JS data. -static void CallJs(Napi::Env env, Napi::Function jsCallback, - Context * /*context*/, DataType *dataPtr) { - // If we have data - if (dataPtr != nullptr) { - std::weak_ptr weakData(*dataPtr); - // Create concrete reference to our DataType. - auto &data(*dataPtr); - - // The success handler ran by the following `CallJs` function. - auto handleResult = [=](Napi::Env env, int calculated, - ResultLocation location) { - // auto &data(*dataPtr); - if (auto data = - weakData - .lock()) { // Has to be copied into a shared_ptr before usage - // std::cout << *data << "\n"; - if (data->logCall) { - std::string message( - "[Thread " + std::to_string(data->threadId) + - "] [CallJs ] [Call " + std::to_string(data->callId) + - "] Receive answer: result = " + std::to_string(calculated) + - (location == ResultLocation::NUMBER - ? " (as number)" - : location == ResultLocation::PROMISE ? " (as Promise)" - : " (as default)")); - - auto console = env.Global().Get("console").As(); - console.Get("log").As().Call(console, - {String::New(env, message)}); - } - // Resolve the `std::promise` awaited on in the child thread. - data->promise.set_value(CallJsResult{calculated, false, ""}); - // Free the data. - // delete dataPtr; - } - }; - - // The error handler ran by the following `CallJs` function. - - auto handleError = [=](const std::string &what) { - if (auto data = - weakData - .lock()) { // Has to be copied into a shared_ptr before usage - // Resolve the `std::promise` awaited on in the child thread with an - // "errored success" value. Instead of erroring at the thread level, - // this could also return the default result. - data->promise.set_value(CallJsResult{0, false, what}); - } - - // // Free the data. - // delete dataPtr; - }; - - if (env != nullptr) { - // If the callback was provided at construction time via TSFN::New - if (!jsCallback.IsEmpty()) { - // Call the callback - auto value = jsCallback.Call( - {Number::New(env, data->threadId), Number::New(env, data->callId), - Number::New(env, data->logCall), Number::New(env, data->base), - Number::New(env, data->callDelay)}); - - // Check if the callback failed - if (env.IsExceptionPending()) { - const auto &error = env.GetAndClearPendingException(); - handleError(error.Message()); - } - - // Check for an immediate number result - else if (value.IsNumber()) { - handleResult(env, value.ToNumber(), ResultLocation::NUMBER); - } - - // Check for a Promise result - else if (value.IsPromise()) { - - // Construct the Promise.then and Promise.catch handlers. These could - // also be a statically-defined `Function`s. - - // Promise.then handler. - auto promiseHandlerThen = Function::New(env, [=](const CallbackInfo - &info) { - // Check for Promise result - if (info.Length() < 1 || !info[0].IsNumber()) { - handleError( - "Expected callback Promise resolution to be of type number"); - } else { - auto result = info[0].ToNumber().Int32Value(); - handleResult(info.Env(), result, ResultLocation::PROMISE); - } - }); - - // Promise.catch handler. - auto promiseHandlerCatch = - Function::New(env, [&](const CallbackInfo &info) { - if (info.Length() < 1 || !info[0].IsObject()) { - handleError("Unknown error in callback handler"); - } else { - auto errorAsValue(info[0] - .As() - .Get("toString") - .As() - .Call(info[0], {})); - handleError(errorAsValue.ToString()); - } - }); - - // Execute the JavaScript equivalent of `promise.then.call(promise, - // promiseHandlerThen).catch.call(promise, promiseHandlerCatch);` - value.As() - .Get("then") - .As() - .Call(value, {promiseHandlerThen}) - .As() - .Get("catch") - .As() - .Call(value, {promiseHandlerCatch}); - } - // When using N-API 4, the callback is a valid no-op Function that - // returns `undefined`. This also allows the callback itself to return - // `undefined` to take the default result. - else if (value.IsUndefined()) { - handleResult(env, data->base * data->base, ResultLocation::DEFAULT); - } else { - handleError("Expected callback return to be of type number " - "| Promise"); - } - } - - // If no callback provided, handle with default result that the callback - // would have provided. - else { - handleResult(env, data->base * data->base, ResultLocation::DEFAULT); - } - } - // If `env` is nullptr, then all threads have called finished their usage of - // the TSFN (either by calling `Release` or making a call and receiving - // `napi_closing`). In this scenario, it is not allowed to call into - // JavaScript, as the TSFN has been finalized. - else { - handleError("The TSFN has been finalized."); - } - } -} - -// Full type of the ThreadSafeFunctionEx -using TSFN = ThreadSafeFunctionEx; -using base = tsfnutil::TSFNWrapBase; - -// A JS-accessible wrap that holds the TSFN. -class TSFNWrap : public base { -public: - TSFNWrap(const CallbackInfo &info) : base(info) {} - - ~TSFNWrap() { - for (auto &thread : finalizerData.threads) { - // The TSFNWrap destructor runs when our ObjectWrap'd instance is - // garbage-collected. This should never happen with proper usage of - // `await` on `tsfn.release()`! - if (thread.joinable()) { - thread.join(); - } - } - } - - static std::array, 5> InstanceMethods() { - return {{InstanceMethod("getContext", &TSFNWrap::GetContext), - InstanceMethod("start", &TSFNWrap::Start), - InstanceMethod("acquire", &TSFNWrap::Acquire), - InstanceMethod("callCount", &TSFNWrap::CallCount), - InstanceMethod("release", &TSFNWrap::Release)}}; - } - - bool logThread = DefaultOptions.logThread; - bool logCall = DefaultOptions.logCall; - bool hasEmptyCallback; - std::atomic_uint succeededCalls; - std::atomic_int aggregate; - - // The structure of the data send to the finalizer. - struct FinalizerDataType { - std::vector threads; - std::vector outstandingCalls; - std::mutex - callMutex; // To protect multi-threaded accesses to `outstandingCalls` - std::unique_ptr deferred; - } finalizerData; - - // Used for logging. - std::mutex logMutex; - - Napi::Value Start(const CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (_tsfn) { - NAPI_THROW(Napi::Error::New(Env(), "TSFN already exists."), Value()); - } - - // Creates a list to hold how many times each thread should make a call. - std::vector callCounts; - - // The JS-provided callback to execute for each call (if provided) - Function callback; - - if (info.Length() > 0 && info[0].IsObject()) { - auto arg0 = info[0].ToObject(); - - if (arg0.Has("callback")) { - auto cb = arg0.Get("callback"); - if (cb.IsUndefined()) { - // An empty callback option will create a valid no-op function on - // N-API 4 or leave `callback` as `std::nullptr` on N-API 5+. - callback = TSFN::FunctionOrEmpty(env, callback); - } else if (cb.IsFunction()) { - callback = cb.As(); - } else { - NAPI_THROW(Napi::TypeError::New( - Env(), "Invalid arguments: callback is not a " - "function. See StartOptions definition."), - Value()); - } - } - - hasEmptyCallback = callback.IsEmpty(); - - // Ensure proper parameters and add to our list of threads. - if (arg0.Has("threads")) { - Napi::Value threads = arg0.Get("threads"); - if (threads.IsArray()) { - Napi::Array threadsArray = threads.As(); - for (auto i = 0U; i < threadsArray.Length(); ++i) { - Napi::Value elem = threadsArray.Get(i); - if (elem.IsObject()) { - Object o = elem.ToObject(); - if (!(o.Has("calls") && o.Has("callDelay") && - o.Has("threadDelay"))) { - NAPI_THROW(Napi::TypeError::New( - Env(), "Invalid arguments. See " - "StartOptions.threads definition."), - Value()); - } - callCounts.push_back(ThreadOptions{ - callCounts.size(), o.Get("calls").ToNumber(), - hasEmptyCallback ? -1 : o.Get("callDelay").ToNumber(), - o.Get("threadDelay").ToNumber()}); - } else { - NAPI_THROW(Napi::TypeError::New( - Env(), "Invalid arguments. See " - "StartOptions.threads definition."), - Value()); - } - } - } else { - NAPI_THROW( - Napi::TypeError::New( - Env(), - "Invalid arguments. See StartOptions.threads definition."), - Value()); - } - } - - if (arg0.Has("logCall")) { - auto logCallOption = arg0.Get("logCall"); - if (logCallOption.IsBoolean()) { - logCall = logCallOption.As(); - } else { - NAPI_THROW(Napi::TypeError::New( - Env(), "Invalid arguments: logCall is not a boolean. " - "See StartOptions definition."), - Value()); - } - } - - if (arg0.Has("logThread")) { - auto logThreadOption = arg0.Get("logThread"); - if (logThreadOption.IsBoolean()) { - logThread = logThreadOption.As(); - } else { - NAPI_THROW(Napi::TypeError::New( - Env(), "Invalid arguments: logThread is not a " - "boolean. See StartOptions definition."), - Value()); - } - } - } - - const auto threadCount = callCounts.size(); - - succeededCalls = 0; - aggregate = 0; - _tsfn = TSFN::New( - env, // napi_env env, - TSFN::FunctionOrEmpty(env, callback), // const Function& callback, - Value(), // const Object& resource, - "Test", // ResourceString resourceName, - 0, // size_t maxQueueSize, - threadCount + 1, // size_t initialThreadCount, +1 for Node thread - this, // Context* context, - Finalizer, // Finalizer finalizer - &finalizerData // FinalizerDataType* data - ); - - if (logThread) { - std::cout << "[Starting] Starting example with options: {\n[Starting] " - "Log Call = " - << (logCall ? "true" : "false") - << ",\n[Starting] Log Thread = " - << (logThread ? "true" : "false") - << ",\n[Starting] Callback = " - << (hasEmptyCallback ? "[empty]" : "function") - << ",\n[Starting] Threads = [\n"; - for (auto &threadOption : callCounts) { - std::cout << "[Starting] " << threadOption.threadId - << " -> { Calls: " << threadOption.calls << ", Call Delay: " - << (threadOption.callDelay == -1 - ? "[no callback]" - : std::to_string(threadOption.callDelay)) - << ", Thread Delay: " << threadOption.threadDelay << " },\n"; - } - std::cout << "[Starting] ]\n[Starting] }\n"; - } - - // for (auto threadId = 0U; threadId < threadCount; ++threadId) { - for (auto &threadOption : callCounts) { - finalizerData.threads.push_back( - std::thread(threadEntry, _tsfn, threadOption, this)); - } - - return Number::New(env, threadCount); - }; - - Napi::Value Acquire(const CallbackInfo &info) { - Napi::Env env = info.Env(); - - if (!_tsfn) { - NAPI_THROW(Napi::Error::New(Env(), "TSFN does not exist."), Value()); - } - - // Creates a list to hold how many times each thread should make a call. - std::vector callCounts; - if (info.Length() > 0 && info[0].IsArray()) { - Napi::Array threadsArray = info[0].As(); - for (auto i = 0U; i < threadsArray.Length(); ++i) { - Napi::Value elem = threadsArray.Get(i); - if (elem.IsObject()) { - Object o = elem.ToObject(); - if (!(o.Has("calls") && o.Has("callDelay") && o.Has("threadDelay"))) { - NAPI_THROW(Napi::TypeError::New(Env(), - "Invalid arguments. See " - "StartOptions.threads definition."), - Value()); - } - callCounts.push_back(ThreadOptions{ - callCounts.size() + finalizerData.threads.size(), - o.Get("calls").ToNumber(), - hasEmptyCallback ? -1 : o.Get("callDelay").ToNumber(), - o.Get("threadDelay").ToNumber()}); - } else { - NAPI_THROW(Napi::TypeError::New(Env(), - "Invalid arguments. See " - "StartOptions.threads definition."), - Value()); - } - } - } else { - NAPI_THROW( - Napi::TypeError::New( - Env(), "Invalid arguments. See StartOptions.threads definition."), - Value()); - } - - if (logThread) { - for (auto &threadOption : callCounts) { - std::cout << "[Acquire ] " << threadOption.threadId - << " -> { Calls: " << threadOption.calls << ", Call Delay: " - << (threadOption.callDelay == -1 - ? "[no callback]" - : std::to_string(threadOption.callDelay)) - << ", Thread Delay: " << threadOption.threadDelay << " },\n"; - } - std::cout << "[Acquire ] ]\n[Acquire ] }\n"; - } - - auto started = 0U; - - for (auto &threadOption : callCounts) { - // The `Acquire` call may be called from any thread, but we do it here to - // avoid a race condition where the thread starts but the TSFN has been - // finalized. - auto status = _tsfn.Acquire(); - if (status == napi_ok) { - finalizerData.threads.push_back( - std::thread(threadEntry, _tsfn, threadOption, this)); - ++started; - } - } - return Number::New(env, started); - } - - - // Release the TSFN from the Node thread. This will return a `Promise` that - // resolves in the Finalizer. - Napi::Value Release(const CallbackInfo &info) { - if (finalizerData.deferred) { - return finalizerData.deferred->Promise(); - } - finalizerData.deferred.reset( - new Promise::Deferred(Promise::Deferred::New(info.Env()))); - _tsfn.Release(); - return finalizerData.deferred->Promise(); - }; - - // Returns an array corresponding to the amount of succeeded calls and the sum - // aggregate. - Napi::Value CallCount(const CallbackInfo &info) { - Napi::Env env(info.Env()); - - auto results = Array::New(env, 2); - results.Set("0", Number::New(env, succeededCalls)); - results.Set("1", Number::New(env, aggregate)); - return results; - }; - - // Returns the TSFN's context. - Napi::Value GetContext(const CallbackInfo &) { - return _tsfn.GetContext()->Value(); - }; - - // The thread entry point. It receives as arguments the TSFN, the call - // options, and the context. - static void threadEntry(TSFN tsfn, ThreadOptions options, Context *context) { -#define THREADLOG(X) \ - if (context->logThread) { \ - std::lock_guard lock(context->logMutex); \ - std::cout << "[Thread " << threadId << "] [Native ] " \ - << (data->callId == -1 \ - ? "" \ - : "[Call " + std::to_string(data->callId) + "] ") \ - << X; \ - } - -#define THREADLOG_MAIN(X) \ - if (context->logThread) { \ - std::lock_guard lock(context->logMutex); \ - std::cout << "[Thread " << threadId << "] [Native ] " << X; \ - } - using namespace std::chrono_literals; - auto threadId = options.threadId; - - // To help with simultaneous threads using the logging mechanism, we'll - // delay at thread start. - std::this_thread::sleep_for(threadId * 10ms); - THREADLOG_MAIN("Thread " << threadId << " started.\n") - - enum ThreadState { - // Starting stating. - Running, - - // When all requests have been completed. - Release, - - // If a `NonBlockingCall` results in a Promise but while waiting - // for the resolution, the TSFN is finalized. - AlreadyFinalized, - - // If a `NonBlockingCall` receiving `napi_closing`, we do *NOT* `Release` - // it. - Closing - } state = ThreadState::Running; - - for (auto i = 0; state == Running; ++i) { - - if (i >= options.calls) { - state = Release; - break; - } - - DataType data(context->makeNewCall(threadId, i, context->logCall, - options.callDelay)); - - if (options.threadDelay > 0 && i > 0) { - THREADLOG("Delay for " << options.threadDelay - << "ms before next call\n") - std::this_thread::sleep_for(options.threadDelay * 1ms); - } - - THREADLOG("Performing call request: base = " << data->base << "\n") - - auto status = tsfn.NonBlockingCall(&data); - - if (status == napi_ok) { - auto future = data->promise.get_future(); - auto result = future.get(); - if (result.error.length() == 0) { - context->callSucceeded(data, result.result); - THREADLOG("Receive answer: result = " << result.result << "\n") - continue; - } else if (result.isFinalized) { - THREADLOG("Application Error: The TSFN has been finalized.\n") - // If the Finalizer has canceled this request, we do not call - // `Release()`. - state = AlreadyFinalized; - } - } else if (status == napi_closing) { - // A thread **MUST NOT** call `Abort()` or `Release()` if we receive an - // `napi_closing` call. - THREADLOG("N-API Error: The thread-safe function is aborted and " - "cannot accept more calls.\n") - state = Closing; - } else if (status == napi_queue_full) { - // The example will finish this thread's use of the TSFN if it is full. - THREADLOG("N-API Error: The queue was full when trying to call in a " - "non-blocking method.\n") - state = Release; - } else if (status == napi_invalid_arg) { - THREADLOG("N-API Error: The thread-safe function is closed.\n") - state = AlreadyFinalized; - } else { - THREADLOG("N-API Error: A generic error occurred when attemping to " - "add to the queue.\n") - state = AlreadyFinalized; - } - context->callFailed(data); - } - - THREADLOG_MAIN("Thread " << threadId << " finished. State: " - << (state == Closing ? "Closing" - : state == AlreadyFinalized - ? "Already Finalized" - : "Release") - << "\n") - - if (state == Release) { - tsfn.Release(); - } -#undef THREADLOG -#undef THREADLOG_MAIN - } - - // TSFN finalizer. Joins the threads and resolves the Promise returned by - // `Release()` above. - static void Finalizer(Napi::Env env, FinalizerDataType *finalizeDataPtr, - Context *ctx) { - - auto &finalizeData(*finalizeDataPtr); - auto outstanding = finalizeData.outstandingCalls.size(); - if (ctx->logThread) { - std::cout << "[Finalize] [Native ] Joining threads (" << outstanding - << " outstanding requests)...\n"; - } - if (outstanding > 0) { - for (auto &request : finalizeData.outstandingCalls) { - request->promise.set_value( - CallJsResult{-1, true, "The TSFN has been finalized."}); - } - } - for (auto &thread : finalizeData.threads) { - thread.join(); - } - - ctx->clearTSFN(); - if (ctx->logThread) { - std::cout << "[Finalize] [Native ] Threads joined.\n"; - } - - finalizeData.deferred->Resolve(Boolean::New(env, true)); - } - - // This method does not run on the Node thread. - void clearTSFN() { _tsfn = TSFN(); } - - // This method does not run on the Node thread. - void callSucceeded(DataType data, int result) { - std::lock_guard lock(finalizerData.callMutex); - succeededCalls++; - aggregate += result; - - auto &calls = finalizerData.outstandingCalls; - auto it = std::find_if( - calls.begin(), calls.end(), - [&](std::shared_ptr const &p) { return p.get() == data.get(); }); - - if (it != calls.end()) { - calls.erase(it); - } - } - - // This method does not run on the Node thread. - void callFailed(DataType data) { - std::lock_guard lock(finalizerData.callMutex); - auto &calls = finalizerData.outstandingCalls; - auto it = - std::find_if(calls.begin(), calls.end(), - [&](std::shared_ptr const &p) { return p == data; }); - - if (it != calls.end()) { - calls.erase(it); - } - } - - DataType makeNewCall(size_t threadId, int callId, bool logCall, - int callDelay) { - // x - // auto &calls(finalizerData.outstandingCalls); - finalizerData.outstandingCalls.emplace_back(std::make_shared()); - auto data(finalizerData.outstandingCalls.back()); - data->threadId = threadId; - data->logCall = logCall; - data->callDelay = callDelay; - data->base = threadId + 1; - data->callId = callId; - - return data; - } -}; -} // namespace example - -Object InitThreadSafeFunctionExExample(Env env) { - auto exports(Object::New(env)); - example::TSFNWrap::Init(env, exports, "example"); - return exports; -} - -#endif diff --git a/test/threadsafe_function_ex/test/example.js b/test/threadsafe_function_ex/test/example.js deleted file mode 100644 index 2afa36de8..000000000 --- a/test/threadsafe_function_ex/test/example.js +++ /dev/null @@ -1,342 +0,0 @@ -// @ts-check -'use strict'; -const assert = require('assert'); -const { TestRunner } = require('../util/TestRunner'); - -/** - * @typedef {(threadId: number, callId: number, logCall: boolean, value: number, callDelay: - * number)=>number|Promise} TSFNCallback - */ - -/** - * @typedef {Object} ThreadOptions - * @property {number} calls - * @property {number} callDelay - * @property {number} threadDelay - */ - -/** - * The options when starting the addon's TSFN. - * @typedef {Object} StartOptions - * @property {ThreadOptions[]} threads - * @property {boolean} [logCall] If `true`, log messages via `console.log`. - * @property {boolean} [logThread] If `true`, log messages via `std::cout`. - * @property {number} [acquireFactor] Acquire a new set of \`acquireFactor\` - * call threads. `NAPI_CPP_EXCEPTIONS`, allowing errors to be caught as - * exceptions. - * @property {TSFNCallback} callback The callback provided to the threadsafe - * function. - * @property {[number,number]} [callbackError] Tuple of `[threadId, callId]` to - * cause an error on -*/ - -/** - * Returns test options. - * @type {() => { options: StartOptions, calls: { aggregate: number } }} - */ -const getTestDetails = () => { - const TEST_CALLS = [1, 2, 3, 4, 5]; - const TEST_ACQUIRE = 1; - const TEST_CALL_DELAY = [400, 200, 100, 50, 0] - const TEST_THREAD_DELAY = TEST_CALL_DELAY.map(_ => _); - const TEST_LOG_CALL = false; - const TEST_LOG_THREAD = false; - const TEST_NO_CALLBACK = false; - - /** @type {[number, number] | undefined} [threadId, callId] */ - const TEST_CALLBACK_ERROR = undefined; - - // Set options as defaults - let testCalls = TEST_CALLS; - let testAcquire = TEST_ACQUIRE; - let testCallDelay = TEST_CALL_DELAY; - let testThreadDelay = TEST_THREAD_DELAY; - let testLogCall = TEST_LOG_CALL; - let testLogThread = TEST_LOG_THREAD; - let testNoCallback = TEST_NO_CALLBACK; - let testCallbackError = TEST_CALLBACK_ERROR; - - let args = process.argv.slice(2); - let arg; - - const showHelp = () => { - console.log( - ` -Usage: ${process.argv0} .${process.argv[1].replace(process.cwd(), '')} [options] - - -c, --calls The number of calls each thread should make (number[]). - -a, --acquire [factor] Acquire a new set of \`factor\` call threads, using the - same \`calls\` definition. - -d, --call-delay The delay on callback resolution that each thread should - have (number[]). This is achieved via a delayed Promise - resolution in the JavaScript callback provided to the - TSFN. Using large delays here will cause all threads to - bottle-neck. - -D, --thread-delay The delay that each thread should have prior to making a - call (number[]). Using large delays here will cause the - individual thread to bottle-neck. - -l, --log-call Display console.log-based logging messages. - -L, --log-thread Display std::cout-based logging messages. - -n, --no-callback Do not use a JavaScript callback. - -e, --callback-error [thread[.call]] Cause an error to occur in the JavaScript callback for - the given thread's call (if provided; first thread's - first call otherwise). - - When not provided: - - defaults to [${TEST_CALLS}] - - [factor] defaults to ${TEST_ACQUIRE} - - defaults to [${TEST_CALL_DELAY}] - - defaults to [${TEST_THREAD_DELAY}] - - -Examples: - - -c [1,2,3] -l -L - - Creates three threads that makes one, two, and three calls each, respectively. - - -c [5,5] -d [5000,5000] -D [0,0] -l -L - - Creates two threads that make five calls each. In this scenario, the threads will be - blocked primarily on waiting for the callback to resolve, as each thread's call takes - 5000 milliseconds. -` - ); - return undefined; - }; - - while ((arg = args.shift())) { - switch (arg) { - case "-h": - case "--help": - return showHelp(); - - case "--calls": - case "-c": - try { - testCalls = JSON.parse(args.shift()); - } catch (ex) { /* ignore */ } - break; - - case "--acquire": - case "-a": - testAcquire = parseInt(args[0]); - if (!isNaN(testAcquire)) { - args.shift(); - } else { - testAcquire = TEST_ACQUIRE; - } - break; - - case "--call-delay": - case "-d": - try { - testCallDelay = JSON.parse(args.shift()); - } catch (ex) { /* ignore */ } - break; - - case "--thread-delay": - case "-D": - try { - testThreadDelay = JSON.parse(args.shift()); - } catch (ex) { /* ignore */ } - break; - - case "--log-call": - case "-l": - testLogCall = true; - break; - - case "--log-thread": - case "-L": - testLogThread = true; - break; - - case "--no-callback": - case "-n": - testNoCallback = true; - break; - - case "-e": - case "--callback-error": - try { - if (!args[0].startsWith("-")) { - const split = args.shift().split(/\./); - testCallbackError = [parseInt(split[0], 10) || 0, parseInt(split[1], 10) || 0]; - } - } - catch (ex) { /*ignore*/ } - finally { - if (!testCallbackError) { - testCallbackError = [0, 0]; - } - } - break; - - default: - console.error("Unknown option:", arg); - return showHelp(); - } - } - - if (testCallbackError && testNoCallback) { - console.error("--error cannot be used in conjunction with --no-callback"); - return undefined; - } - - testCalls = Array.isArray(testCalls) ? testCalls : TEST_CALLS; - - const calls = { aggregate: testNoCallback ? null : 0 }; - - /** - * The JavaScript callback provided to our TSFN. - * @callback TSFNCallback - * @param {number} threadId Thread Id - * @param {number} callId Call Id - * @param {boolean} logCall If true, log messages to console regarding this - * call. - * @param {number} base The input as calculated from CallJs - * @param {number} callDelay If `> 0`, return a `Promise` that resolves with - * `value` after `callDelay` milliseconds. Otherwise, return a `number` - * whose value is `value`. - */ - - /** @type {undefined | TSFNCallback} */ - const callback = testNoCallback ? undefined : (threadId, callId, logCall, base, callDelay) => { - // Calculate the result value as `base * base`. - const value = base * base; - - // Add the value to our call aggregate - calls.aggregate += value; - - if (testCallbackError !== undefined && testCallbackError[0] === threadId && testCallbackError[1] === callId) { - return new Error(`Test throw error for ${threadId}.${callId}`); - } - - // If `callDelay > 0`, then return a Promise that resolves with `value` after - // `callDelay` milliseconds. - if (callDelay > 0) { - // Logging messages. - if (logCall) { - console.log(`[Thread ${threadId}] [Callback] [Call ${callId}] Receive request: base = ${base}, delay = ${callDelay}ms`); - } - - const start = Date.now(); - - return new Promise(resolve => setTimeout(() => { - // Logging messages. - if (logCall) { - console.log(`[Thread ${threadId}] [Callback] [Call ${callId}] Answer request: base = ${base}, value = ${value} after ${Date.now() - start}ms`); - } - resolve(value); - }, callDelay)); - } - - // Otherwise, return a `number` whose value is `value`. - else { - // Logging messages. - if (logCall) { - console.log(`[Thread ${threadId}] [Callback] [Call ${callId}] Receive, answer request: base = ${base}, value = ${value}`); - } - return value; - } - }; - - return { - options: { - // Construct `ThreadOption[] threads` from `number[] testCalls` - threads: testCalls.map((callCount, index) => ({ - calls: callCount, - callDelay: testCallDelay !== null && typeof testCallDelay[index] === 'number' ? testCallDelay[index] : 0, - threadDelay: testThreadDelay !== null && typeof testThreadDelay[index] === 'number' ? testThreadDelay[index] : 0, - })), - logCall: testLogCall, - logThread: testLogThread, - acquireFactor: testAcquire, - callback, - callbackError: testCallbackError - }, - calls - }; -} - -class ExampleTest extends TestRunner { - - async example({ TSFNWrap }) { - - /** - * @typedef {Object} TSFNWrap - * @property {(opts: StartOptions) => number} start Start the TSFN. Returns - * the number of threads started. - * @property {() => Promise} release Release the TSFN. - * @property {() => [number, number]} callCount Returns the call aggregates - * as counted by the TSFN: - * - `[0]`: The sum of the number of calls by each thread. - * - `[1]`: The sum of the `value`s returned by each call by each thread. - * @property {(threads: ThreadOptions[]) => number} acquire - */ - - /** @type {TSFNWrap} */ - const tsfn = new TSFNWrap(); - - const testDetails = getTestDetails(); - if (testDetails === undefined) { - throw new Error("No test details"); - } - const { options } = testDetails; - const { acquireFactor, threads, callback, callbackError } = options; - - /** - * Start the TSFN with the given options. This will create the TSFN with initial - * thread count of `threads.length + 1` (+1 due to the Node thread using the TSFN) - */ - const startedActual = tsfn.start(options); - - /** - * The initial - */ - const threadsPerSet = threads.length; - - /** - * Calculate the expected results. Create a new list of thread options by - * concatinating `threads` by `acquireFactor` times. - */ - const startedThreads = [...new Array(acquireFactor)].map(_ => threads).reduce((p, c) => p.concat(c), []); - - const expected = startedThreads.reduce((p, threadCallCount, threadId) => ( - ++threadId, - ++p.threadCount, - p.callCount += threadCallCount.calls, - p.aggregate += threadCallCount.calls * threadId ** 2, - p.callbackAggregate = p.callbackAggregate === null ? null : p.aggregate, - p - ), { threadCount: 0, callCount: 0, aggregate: 0, callbackAggregate: callback ? 0 : null }); - - if (typeof startedActual === 'number') { - const { threadCount, callCount, aggregate, callbackAggregate } = expected; - assert(startedActual === threadsPerSet, `The number of threads when starting the TSFN do not match: actual = ${startedActual}, expected = ${threadsPerSet}`) - for (let i = 1; i < acquireFactor; ++i) { - const acquiredActual = tsfn.acquire(threads); - assert(acquiredActual === threadsPerSet, `The number of threads when acquiring a new set of threads do not match: actual = ${acquiredActual}, expected = ${threadsPerSet}`) - } - const released = await tsfn.release(); - const [callCountActual, aggregateActual] = tsfn.callCount(); - const { calls } = testDetails; - const { aggregate: actualCallAggregate } = calls; - if (!callbackError) { - assert(callCountActual === callCount, `The number of calls do not match: actual = ${callCountActual}, expected = ${callCount}`); - assert(aggregateActual === aggregate, `The aggregate of calls do not match: actual = ${aggregateActual}, expected = ${aggregate}`); - assert(actualCallAggregate === callbackAggregate, `The number aggregated by the JavaScript callback and the thread calculated aggregate do not match: actual ${actualCallAggregate}, expected = ${aggregate}`) - return { released, ...expected }; - } - // The test runner erases the last line, so write an empty line. - if (options.logCall) { console.log(); } - return released; - } else { - throw new Error('The TSFN failed to start'); - } - } - -} - -module.exports = new ExampleTest('threadsafe_function_ex_example', __filename).start(); diff --git a/test/threadsafe_function_ex/test/threadsafe.js b/test/threadsafe_function_ex/test/threadsafe.js deleted file mode 100644 index 2d807b040..000000000 --- a/test/threadsafe_function_ex/test/threadsafe.js +++ /dev/null @@ -1,182 +0,0 @@ -'use strict'; - -const buildType = process.config.target_defaults.default_configuration; -const assert = require('assert'); -const common = require('../../common'); - -module.exports = run() - .catch((e) => { - console.error(`Test failed!`, e); - process.exit(1); - }); - -async function run() { - console.log(`Running tests in .${__filename.replace(process.cwd(),'')}`); - await test(require(`../../build/${buildType}/binding.node`)); - await test(require(`../../build/${buildType}/binding_noexcept.node`)); -} - -/** - * This spec replicates the non-`Ex` multi-threaded spec using the `Ex` API. - */ -function test(binding) { - const expectedArray = (function(arrayLength) { - const result = []; - for (let index = 0; index < arrayLength; index++) { - result.push(arrayLength - 1 - index); - } - return result; - })(binding.threadsafe_function_ex_threadsafe.ARRAY_LENGTH); - - function testWithJSMarshaller({ - threadStarter, - quitAfter, - abort, - maxQueueSize, - launchSecondary }) { - return new Promise((resolve) => { - const array = []; - binding.threadsafe_function_ex_threadsafe[threadStarter](function testCallback(value) { - array.push(value); - if (array.length === quitAfter) { - setImmediate(() => { - binding.threadsafe_function_ex_threadsafe.stopThread(common.mustCall(() => { - resolve(array); - }), !!abort); - }); - } - }, !!abort, !!launchSecondary, maxQueueSize); - if (threadStarter === 'startThreadNonblocking') { - // Let's make this thread really busy for a short while to ensure that - // the queue fills and the thread receives a napi_queue_full. - const start = Date.now(); - while (Date.now() - start < 200); - } - }); - } - - return new Promise(function testWithoutJSMarshaller(resolve) { - let callCount = 0; - binding.threadsafe_function_ex_threadsafe.startThreadNoNative(function testCallback() { - callCount++; - - // The default call-into-JS implementation passes no arguments. - assert.strictEqual(arguments.length, 0); - if (callCount === binding.threadsafe_function_ex_threadsafe.ARRAY_LENGTH) { - setImmediate(() => { - binding.threadsafe_function_ex_threadsafe.stopThread(common.mustCall(() => { - resolve(); - }), false); - }); - } - }, false /* abort */, false /* launchSecondary */, - binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE); - }) - - // Start the thread in blocking mode, and assert that all values are passed. - // Quit after it's done. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, - quitAfter: binding.threadsafe_function_ex_threadsafe.ARRAY_LENGTH - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) - - // Start the thread in blocking mode with an infinite queue, and assert that - // all values are passed. Quit after it's done. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - maxQueueSize: 0, - quitAfter: binding.threadsafe_function_ex_threadsafe.ARRAY_LENGTH - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) - - // Start the thread in non-blocking mode, and assert that all values are - // passed. Quit after it's done. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThreadNonblocking', - maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, - quitAfter: binding.threadsafe_function_ex_threadsafe.ARRAY_LENGTH - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) - - // Start the thread in blocking mode, and assert that all values are passed. - // Quit early, but let the thread finish. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, - quitAfter: 1 - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) - - // Start the thread in blocking mode with an infinite queue, and assert that - // all values are passed. Quit early, but let the thread finish. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - maxQueueSize: 0, - quitAfter: 1 - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) - - - // Start the thread in non-blocking mode, and assert that all values are - // passed. Quit early, but let the thread finish. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThreadNonblocking', - maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, - quitAfter: 1 - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) - - // Start the thread in blocking mode, and assert that all values are passed. - // Quit early, but let the thread finish. Launch a secondary thread to test - // the reference counter incrementing functionality. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - quitAfter: 1, - maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, - launchSecondary: true - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) - - // Start the thread in non-blocking mode, and assert that all values are - // passed. Quit early, but let the thread finish. Launch a secondary thread - // to test the reference counter incrementing functionality. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThreadNonblocking', - quitAfter: 1, - maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, - launchSecondary: true - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) - - // Start the thread in blocking mode, and assert that it could not finish. - // Quit early by aborting. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - quitAfter: 1, - maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, - abort: true - })) - .then((result) => assert.strictEqual(result.indexOf(0), -1)) - - // Start the thread in blocking mode with an infinite queue, and assert that - // it could not finish. Quit early by aborting. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - quitAfter: 1, - maxQueueSize: 0, - abort: true - })) - .then((result) => assert.strictEqual(result.indexOf(0), -1)) - - // Start the thread in non-blocking mode, and assert that it could not finish. - // Quit early and aborting. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThreadNonblocking', - quitAfter: 1, - maxQueueSize: binding.threadsafe_function_ex_threadsafe.MAX_QUEUE_SIZE, - abort: true - })) - .then((result) => assert.strictEqual(result.indexOf(0), -1)) -} diff --git a/test/threadsafe_function_ex/test/threadsafe.cc b/test/threadsafe_function_ex/threadsafe_function.cc similarity index 99% rename from test/threadsafe_function_ex/test/threadsafe.cc rename to test/threadsafe_function_ex/threadsafe_function.cc index cf3eddca8..cb604c17e 100644 --- a/test/threadsafe_function_ex/test/threadsafe.cc +++ b/test/threadsafe_function_ex/threadsafe_function.cc @@ -174,7 +174,7 @@ static Value StartThreadNoNative(const CallbackInfo& info) { return StartThreadInternal(info, ThreadSafeFunctionInfo::DEFAULT); } -Object InitThreadSafeFunctionExThreadSafe(Env env) { +Object InitThreadSafeFunctionEx(Env env) { for (size_t index = 0; index < ARRAY_LENGTH; index++) { ints[index] = index; } diff --git a/test/threadsafe_function_ex/threadsafe_function.js b/test/threadsafe_function_ex/threadsafe_function.js new file mode 100644 index 000000000..9be4b26dd --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe_function.js @@ -0,0 +1,194 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const common = require('../common'); + +module.exports = (async function() { + await test(require(`../build/${buildType}/binding.node`)); + await test(require(`../build/${buildType}/binding_noexcept.node`)); +})(); + +async function test(binding) { + const expectedArray = (function(arrayLength) { + const result = []; + for (let index = 0; index < arrayLength; index++) { + result.push(arrayLength - 1 - index); + } + return result; + })(binding.threadsafe_function_ex.ARRAY_LENGTH); + + function testWithJSMarshaller({ + threadStarter, + quitAfter, + abort, + maxQueueSize, + launchSecondary }) { + return new Promise((resolve) => { + const array = []; + binding.threadsafe_function_ex[threadStarter](function testCallback(value) { + array.push(value); + if (array.length === quitAfter) { + setImmediate(() => { + binding.threadsafe_function_ex.stopThread(common.mustCall(() => { + resolve(array); + }), !!abort); + }); + } + }, !!abort, !!launchSecondary, maxQueueSize); + if (threadStarter === 'startThreadNonblocking') { + // Let's make this thread really busy for a short while to ensure that + // the queue fills and the thread receives a napi_queue_full. + const start = Date.now(); + while (Date.now() - start < 200); + } + }); + } + + await new Promise(function testWithoutJSMarshaller(resolve) { + let callCount = 0; + binding.threadsafe_function_ex.startThreadNoNative(function testCallback() { + callCount++; + + // The default call-into-JS implementation passes no arguments. + assert.strictEqual(arguments.length, 0); + if (callCount === binding.threadsafe_function_ex.ARRAY_LENGTH) { + setImmediate(() => { + binding.threadsafe_function_ex.stopThread(common.mustCall(() => { + resolve(); + }), false); + }); + } + }, false /* abort */, false /* launchSecondary */, + binding.threadsafe_function_ex.MAX_QUEUE_SIZE); + }); + + // Start the thread in blocking mode, and assert that all values are passed. + // Quit after it's done. + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: binding.threadsafe_function_ex.MAX_QUEUE_SIZE, + quitAfter: binding.threadsafe_function_ex.ARRAY_LENGTH + }), + expectedArray, + ); + + // Start the thread in blocking mode with an infinite queue, and assert that + // all values are passed. Quit after it's done. + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: 0, + quitAfter: binding.threadsafe_function_ex.ARRAY_LENGTH + }), + expectedArray, + ); + + // Start the thread in non-blocking mode, and assert that all values are + // passed. Quit after it's done. + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + maxQueueSize: binding.threadsafe_function_ex.MAX_QUEUE_SIZE, + quitAfter: binding.threadsafe_function_ex.ARRAY_LENGTH + }), + expectedArray, + ); + + // Start the thread in blocking mode, and assert that all values are passed. + // Quit early, but let the thread finish. + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: binding.threadsafe_function_ex.MAX_QUEUE_SIZE, + quitAfter: 1 + }), + expectedArray, + ); + + // Start the thread in blocking mode with an infinite queue, and assert that + // all values are passed. Quit early, but let the thread finish. + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: 0, + quitAfter: 1 + }), + expectedArray, + ); + + + // Start the thread in non-blocking mode, and assert that all values are + // passed. Quit early, but let the thread finish. + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + maxQueueSize: binding.threadsafe_function_ex.MAX_QUEUE_SIZE, + quitAfter: 1 + }), + expectedArray, + ); + + // Start the thread in blocking mode, and assert that all values are passed. + // Quit early, but let the thread finish. Launch a secondary thread to test + // the reference counter incrementing functionality. + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function_ex.MAX_QUEUE_SIZE, + launchSecondary: true + }), + expectedArray, + ); + + // Start the thread in non-blocking mode, and assert that all values are + // passed. Quit early, but let the thread finish. Launch a secondary thread + // to test the reference counter incrementing functionality. + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function_ex.MAX_QUEUE_SIZE, + launchSecondary: true + }), + expectedArray, + ); + + // Start the thread in blocking mode, and assert that it could not finish. + // Quit early by aborting. + assert.strictEqual( + (await testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function_ex.MAX_QUEUE_SIZE, + abort: true + })).indexOf(0), + -1, + ); + + // Start the thread in blocking mode with an infinite queue, and assert that + // it could not finish. Quit early by aborting. + assert.strictEqual( + (await testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: 0, + abort: true + })).indexOf(0), + -1, + ); + + // Start the thread in non-blocking mode, and assert that it could not finish. + // Quit early and aborting. + assert.strictEqual( + (await testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function_ex.MAX_QUEUE_SIZE, + abort: true + })).indexOf(0), + -1, + ); +} \ No newline at end of file diff --git a/test/threadsafe_function_ex/threadsafe_function_ctx.cc b/test/threadsafe_function_ex/threadsafe_function_ctx.cc new file mode 100644 index 000000000..f186fc845 --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe_function_ctx.cc @@ -0,0 +1,62 @@ +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +using ContextType = Reference; +using TSFN = ThreadSafeFunctionEx; + +namespace { + +class TSFNWrap : public ObjectWrap { +public: + static Object Init(Napi::Env env, Object exports); + TSFNWrap(const CallbackInfo &info); + + Napi::Value GetContext(const CallbackInfo & /*info*/) { + ContextType *ctx = _tsfn.GetContext(); + return ctx->Value(); + }; + + Napi::Value Release(const CallbackInfo &info) { + Napi::Env env = info.Env(); + _deferred = std::unique_ptr(new Promise::Deferred(env)); + _tsfn.Release(); + return _deferred->Promise(); + }; + +private: + TSFN _tsfn; + std::unique_ptr _deferred; +}; + +Object TSFNWrap::Init(Napi::Env env, Object exports) { + Function func = + DefineClass(env, "TSFNWrap", + {InstanceMethod("getContext", &TSFNWrap::GetContext), + InstanceMethod("release", &TSFNWrap::Release)}); + + exports.Set("TSFNWrap", func); + return exports; +} + +TSFNWrap::TSFNWrap(const CallbackInfo &info) : ObjectWrap(info) { + ContextType *_ctx = new ContextType; + *_ctx = Persistent(info[0]); + + _tsfn = TSFN::New(info.Env(), this->Value(), "Test", 1, 1, _ctx, + [this](Napi::Env env, void *, ContextType *ctx) { + _deferred->Resolve(env.Undefined()); + ctx->Reset(); + delete ctx; + }); +} + +} // namespace + +Object InitThreadSafeFunctionExCtx(Env env) { + return TSFNWrap::Init(env, Object::New(env)); +} + +#endif diff --git a/test/threadsafe_function_ex/threadsafe_function_ctx.js b/test/threadsafe_function_ex/threadsafe_function_ctx.js new file mode 100644 index 000000000..2651586a0 --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe_function_ctx.js @@ -0,0 +1,14 @@ +'use strict'; + +const assert = require('assert'); +const buildType = process.config.target_defaults.default_configuration; + +module.exports = test(require(`../build/${buildType}/binding.node`)) + .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); + +async function test(binding) { + const ctx = { }; + const tsfn = new binding.threadsafe_function_ctx.TSFNWrap(ctx); + assert(tsfn.getContext() === ctx); + await tsfn.release(); +} diff --git a/test/threadsafe_function_ex/threadsafe_function_existing_tsfn.cc b/test/threadsafe_function_ex/threadsafe_function_existing_tsfn.cc new file mode 100644 index 000000000..fc28a06c5 --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe_function_existing_tsfn.cc @@ -0,0 +1,114 @@ +#include "napi.h" +#include + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +struct TestContext { + TestContext(Promise::Deferred &&deferred) + : deferred(std::move(deferred)), callData(nullptr){}; + + napi_threadsafe_function tsfn; + Promise::Deferred deferred; + double *callData; + + ~TestContext() { + if (callData != nullptr) + delete callData; + }; +}; + +using TSFN = ThreadSafeFunctionEx; + +void FinalizeCB(napi_env env, void * /*finalizeData */, void *context) { + TestContext *testContext = static_cast(context); + if (testContext->callData != nullptr) { + testContext->deferred.Resolve(Number::New(env, *testContext->callData)); + } else { + testContext->deferred.Resolve(Napi::Env(env).Undefined()); + } + delete testContext; +} + +void CallJSWithData(napi_env env, napi_value /* callback */, void *context, + void *data) { + TestContext *testContext = static_cast(context); + testContext->callData = static_cast(data); + + napi_status status = + napi_release_threadsafe_function(testContext->tsfn, napi_tsfn_release); + + NAPI_THROW_IF_FAILED_VOID(env, status); +} + +void CallJSNoData(napi_env env, napi_value /* callback */, void *context, + void * /*data*/) { + TestContext *testContext = static_cast(context); + testContext->callData = nullptr; + + napi_status status = + napi_release_threadsafe_function(testContext->tsfn, napi_tsfn_release); + + NAPI_THROW_IF_FAILED_VOID(env, status); +} + +static Value TestCall(const CallbackInfo &info) { + Napi::Env env = info.Env(); + bool isBlocking = false; + bool hasData = false; + if (info.Length() > 0) { + Object opts = info[0].As(); + if (opts.Has("blocking")) { + isBlocking = opts.Get("blocking").ToBoolean(); + } + if (opts.Has("data")) { + hasData = opts.Get("data").ToBoolean(); + } + } + + // Allow optional callback passed from JS. Useful for testing. + Function cb = Function::New(env, [](const CallbackInfo & /*info*/) {}); + + TestContext *testContext = new TestContext(Napi::Promise::Deferred(env)); + + napi_status status = napi_create_threadsafe_function( + env, cb, Object::New(env), String::New(env, "Test"), 0, 1, + nullptr, /*finalize data*/ + FinalizeCB, testContext, hasData ? CallJSWithData : CallJSNoData, + &testContext->tsfn); + + NAPI_THROW_IF_FAILED(env, status, Value()); + + TSFN wrapped = TSFN(testContext->tsfn); + + // Test the four napi_threadsafe_function direct-accessing calls + if (isBlocking) { + if (hasData) { + wrapped.BlockingCall(new double(std::rand())); + } else { + wrapped.BlockingCall(nullptr); + } + } else { + if (hasData) { + wrapped.NonBlockingCall(new double(std::rand())); + } else { + wrapped.NonBlockingCall(nullptr); + } + } + + return testContext->deferred.Promise(); +} + +} // namespace + +Object InitThreadSafeFunctionExExistingTsfn(Env env) { + Object exports = Object::New(env); + exports["testCall"] = Function::New(env, TestCall); + + return exports; +} + +#endif diff --git a/test/threadsafe_function_ex/threadsafe_function_existing_tsfn.js b/test/threadsafe_function_ex/threadsafe_function_existing_tsfn.js new file mode 100644 index 000000000..d42517d9f --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe_function_existing_tsfn.js @@ -0,0 +1,17 @@ +'use strict'; + +const assert = require('assert'); + +const buildType = process.config.target_defaults.default_configuration; + +module.exports = test(require(`../build/${buildType}/binding.node`)) + .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); + +async function test(binding) { + const testCall = binding.threadsafe_function_ex_existing_tsfn.testCall; + + assert.strictEqual(typeof await testCall({ blocking: true, data: true }), "number"); + assert.strictEqual(typeof await testCall({ blocking: true, data: false }), "undefined"); + assert.strictEqual(typeof await testCall({ blocking: false, data: true }), "number"); + assert.strictEqual(typeof await testCall({ blocking: false, data: false }), "undefined"); +} diff --git a/test/threadsafe_function_ex/threadsafe_function_ptr.cc b/test/threadsafe_function_ex/threadsafe_function_ptr.cc new file mode 100644 index 000000000..c8810ce50 --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe_function_ptr.cc @@ -0,0 +1,28 @@ +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +using TSFN = ThreadSafeFunctionEx<>; + +static Value Test(const CallbackInfo& info) { + Object resource = info[0].As(); + Function cb = info[1].As(); + TSFN tsfn = TSFN::New(info.Env(), cb, resource, "Test", 1, 1); + tsfn.Release(); + return info.Env().Undefined(); +} + +} + +Object InitThreadSafeFunctionExPtr(Env env) { + Object exports = Object::New(env); + exports["test"] = Function::New(env, Test); + + return exports; +} + +#endif diff --git a/test/threadsafe_function_ex/threadsafe_function_ptr.js b/test/threadsafe_function_ex/threadsafe_function_ptr.js new file mode 100644 index 000000000..1276d0930 --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe_function_ptr.js @@ -0,0 +1,10 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + binding.threadsafe_function_ex_ptr.test({}, () => {}); +} diff --git a/test/threadsafe_function_ex/threadsafe_function_sum.cc b/test/threadsafe_function_ex/threadsafe_function_sum.cc new file mode 100644 index 000000000..69c8a8fb2 --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe_function_sum.cc @@ -0,0 +1,220 @@ +#include "napi.h" +#include +#include +#include +#include + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +struct TestData { + + TestData(Promise::Deferred &&deferred) : deferred(std::move(deferred)){}; + + // Native Promise returned to JavaScript + Promise::Deferred deferred; + + // List of threads created for test. This list only ever accessed via main + // thread. + std::vector threads = {}; + + // These variables are only accessed from the main thread. + bool mainWantsRelease = false; + size_t expected_calls = 0; + + static void CallJs(Napi::Env env, Function callback, TestData *testData, + double *data) { + + // This lambda runs on the main thread so it's OK to access the variables + // `expected_calls` and `mainWantsRelease`. + testData->expected_calls--; + if (testData->expected_calls == 0 && testData->mainWantsRelease) + testData->tsfn.Release(); + callback.Call({Number::New(env, *data)}); + delete data; + } + + ThreadSafeFunctionEx tsfn; +}; + +using TSFN = ThreadSafeFunctionEx; + +void FinalizerCallback(Napi::Env env, void *, TestData *finalizeData) { + for (size_t i = 0; i < finalizeData->threads.size(); ++i) { + finalizeData->threads[i].join(); + } + finalizeData->deferred.Resolve(Boolean::New(env, true)); + delete finalizeData; +} + +/** + * See threadsafe_function_sum.js for descriptions of the tests in this file + */ + +void entryWithTSFN(TSFN tsfn, int threadId) { + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); + tsfn.BlockingCall(new double(threadId)); + tsfn.Release(); +} + +static Value TestWithTSFN(const CallbackInfo &info) { + int threadCount = info[0].As().Int32Value(); + Function cb = info[1].As(); + + // We pass the test data to the Finalizer for cleanup. The finalizer is + // responsible for deleting this data as well. + TestData *testData = new TestData(Promise::Deferred::New(info.Env())); + + TSFN tsfn = TSFN::New( + info.Env(), cb, "Test", 0, threadCount, testData, + std::function(FinalizerCallback), testData); + + for (int i = 0; i < threadCount; ++i) { + // A copy of the ThreadSafeFunction will go to the thread entry point + testData->threads.push_back(std::thread(entryWithTSFN, tsfn, i)); + } + + return testData->deferred.Promise(); +} + +// Task instance created for each new std::thread +class DelayedTSFNTask { +public: + // Each instance has its own tsfn + TSFN tsfn; + + // Thread-safety + std::mutex mtx; + std::condition_variable cv; + + // Entry point for std::thread + void entryDelayedTSFN(int threadId) { + std::unique_lock lk(mtx); + cv.wait(lk); + tsfn.BlockingCall(new double(threadId)); + tsfn.Release(); + }; +}; + +struct TestDataDelayed : TestData { + + TestDataDelayed(Promise::Deferred &&deferred) + : TestData(std::move(deferred)){}; + ~TestDataDelayed() { taskInsts.clear(); }; + + // List of DelayedTSFNThread instances + std::vector> taskInsts = {}; +}; + +void FinalizerCallbackDelayed(Napi::Env env, TestDataDelayed *finalizeData, + TestData *) { + for (size_t i = 0; i < finalizeData->threads.size(); ++i) { + finalizeData->threads[i].join(); + } + finalizeData->deferred.Resolve(Boolean::New(env, true)); + delete finalizeData; +} + +static Value TestDelayedTSFN(const CallbackInfo &info) { + int threadCount = info[0].As().Int32Value(); + Function cb = info[1].As(); + + TestDataDelayed *testData = + new TestDataDelayed(Promise::Deferred::New(info.Env())); + + testData->tsfn = TSFN::New(info.Env(), cb, "Test", 0, threadCount, testData, + std::function( + FinalizerCallbackDelayed), + testData); + + for (int i = 0; i < threadCount; ++i) { + testData->taskInsts.push_back( + std::unique_ptr(new DelayedTSFNTask())); + testData->threads.push_back(std::thread(&DelayedTSFNTask::entryDelayedTSFN, + testData->taskInsts.back().get(), + i)); + } + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); + + for (auto &task : testData->taskInsts) { + std::lock_guard lk(task->mtx); + task->tsfn = testData->tsfn; + task->cv.notify_all(); + } + + return testData->deferred.Promise(); +} + +void AcquireFinalizerCallback(Napi::Env env, TestData *finalizeData, + TestData *context) { + (void)context; + for (size_t i = 0; i < finalizeData->threads.size(); ++i) { + finalizeData->threads[i].join(); + } + finalizeData->deferred.Resolve(Boolean::New(env, true)); + delete finalizeData; +} + +void entryAcquire(TSFN tsfn, int threadId) { + tsfn.Acquire(); + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100 + 1)); + tsfn.BlockingCall(new double(threadId)); + tsfn.Release(); +} + +static Value CreateThread(const CallbackInfo &info) { + TestData *testData = static_cast(info.Data()); + // Counting expected calls like this only works because on the JS side this + // binding is called from a synchronous loop. This means the main loop has no + // chance to run the tsfn JS callback before we've counted how many threads + // the JS intends to create. + testData->expected_calls++; + TSFN tsfn = testData->tsfn; + int threadId = testData->threads.size(); + // A copy of the ThreadSafeFunction will go to the thread entry point + testData->threads.push_back(std::thread(entryAcquire, tsfn, threadId)); + return Number::New(info.Env(), threadId); +} + +static Value StopThreads(const CallbackInfo &info) { + TestData *testData = static_cast(info.Data()); + testData->mainWantsRelease = true; + return info.Env().Undefined(); +} + +static Value TestAcquire(const CallbackInfo &info) { + Function cb = info[0].As(); + Napi::Env env = info.Env(); + + // We pass the test data to the Finalizer for cleanup. The finalizer is + // responsible for deleting this data as well. + TestData *testData = new TestData(Promise::Deferred::New(info.Env())); + + testData->tsfn = TSFN::New(env, cb, "Test", 0, 1, testData, + std::function( + AcquireFinalizerCallback), + testData); + + Object result = Object::New(env); + result["createThread"] = + Function::New(env, CreateThread, "createThread", testData); + result["stopThreads"] = + Function::New(env, StopThreads, "stopThreads", testData); + result["promise"] = testData->deferred.Promise(); + + return result; +} +} // namespace + +Object InitThreadSafeFunctionExSum(Env env) { + Object exports = Object::New(env); + exports["testDelayedTSFN"] = Function::New(env, TestDelayedTSFN); + exports["testWithTSFN"] = Function::New(env, TestWithTSFN); + exports["testAcquire"] = Function::New(env, TestAcquire); + return exports; +} + +#endif diff --git a/test/threadsafe_function_ex/threadsafe_function_sum.js b/test/threadsafe_function_ex/threadsafe_function_sum.js new file mode 100644 index 000000000..ef7162c25 --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe_function_sum.js @@ -0,0 +1,61 @@ +'use strict'; +const assert = require('assert'); +const buildType = process.config.target_defaults.default_configuration; + +/** + * + * ThreadSafeFunction Tests: Thread Id Sums + * + * Every native C++ function that utilizes the TSFN will call the registered + * callback with the thread id. Passing Array.prototype.push with a bound array + * will push the thread id to the array. Therefore, starting `N` threads, we + * will expect the sum of all elements in the array to be `(N-1) * (N) / 2` (as + * thread IDs are 0-based) + * + * We check different methods of passing a ThreadSafeFunction around multiple + * threads: + * - `testWithTSFN`: The main thread creates the TSFN. Then, it creates + * threads, passing the TSFN at thread construction. The number of threads is + * static (known at TSFN creation). + * - `testDelayedTSFN`: The main thread creates threads, passing a promise to a + * TSFN at construction. Then, it creates the TSFN, and resolves each + * threads' promise. The number of threads is static. + * - `testAcquire`: The native binding returns a function to start a new. A + * call to this function will return `false` once `N` calls have been made. + * Each thread will acquire its own use of the TSFN, call it, and then + * release. + */ + +const THREAD_COUNT = 5; +const EXPECTED_SUM = (THREAD_COUNT - 1) * (THREAD_COUNT) / 2; + +module.exports = test(require(`../build/${buildType}/binding.node`)) + .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); + +/** @param {number[]} N */ +const sum = (N) => N.reduce((sum, n) => sum + n, 0); + +function test(binding) { + async function check(bindingFunction) { + const calls = []; + const result = await bindingFunction(THREAD_COUNT, Array.prototype.push.bind(calls)); + assert.ok(result); + assert.equal(sum(calls), EXPECTED_SUM); + } + + async function checkAcquire() { + const calls = []; + const { promise, createThread, stopThreads } = binding.threadsafe_function_ex_sum.testAcquire(Array.prototype.push.bind(calls)); + for (let i = 0; i < THREAD_COUNT; i++) { + createThread(); + } + stopThreads(); + const result = await promise; + assert.ok(result); + assert.equal(sum(calls), EXPECTED_SUM); + } + + return check(binding.threadsafe_function_ex_sum.testDelayedTSFN) + .then(() => check(binding.threadsafe_function_ex_sum.testWithTSFN)) + .then(() => checkAcquire()); +} diff --git a/test/threadsafe_function_ex/threadsafe_function_unref.cc b/test/threadsafe_function_ex/threadsafe_function_unref.cc new file mode 100644 index 000000000..2e2041380 --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe_function_unref.cc @@ -0,0 +1,44 @@ +#include "napi.h" + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +using TSFN = ThreadSafeFunctionEx<>; +using ContextType = std::nullptr_t; +using FinalizerDataType = void; +static Value TestUnref(const CallbackInfo& info) { + Napi::Env env = info.Env(); + Object global = env.Global(); + Object resource = info[0].As(); + Function cb = info[1].As(); + Function setTimeout = global.Get("setTimeout").As(); + TSFN* tsfn = new TSFN; + + *tsfn = TSFN::New(info.Env(), cb, resource, "Test", 1, 1, nullptr, [tsfn](Napi::Env /* env */, FinalizerDataType*, ContextType*) { + delete tsfn; + }, static_cast(nullptr)); + + tsfn->BlockingCall(); + + setTimeout.Call( global, { + Function::New(env, [tsfn](const CallbackInfo& info) { + tsfn->Unref(info.Env()); + }), + Number::New(env, 100) + }); + + return info.Env().Undefined(); +} + +} + +Object InitThreadSafeFunctionExUnref(Env env) { + Object exports = Object::New(env); + exports["testUnref"] = Function::New(env, TestUnref); + return exports; +} + +#endif diff --git a/test/threadsafe_function_ex/threadsafe_function_unref.js b/test/threadsafe_function_ex/threadsafe_function_unref.js new file mode 100644 index 000000000..eee3fcf8f --- /dev/null +++ b/test/threadsafe_function_ex/threadsafe_function_unref.js @@ -0,0 +1,55 @@ +'use strict'; + +const assert = require('assert'); +const buildType = process.config.target_defaults.default_configuration; + +const isMainProcess = process.argv[1] != __filename; + +/** + * In order to test that the event loop exits even with an active TSFN, we need + * to spawn a new process for the test. + * - Main process: spawns new node instance, executing this script + * - Child process: creates TSFN. Native module Unref's via setTimeout after some time but does NOT call Release. + * + * Main process should expect child process to exit. + */ + +if (isMainProcess) { + module.exports = test(`../build/${buildType}/binding.node`) + .then(() => test(`../build/${buildType}/binding_noexcept.node`)); +} else { + test(process.argv[2]); +} + +function test(bindingFile) { + if (isMainProcess) { + // Main process + return new Promise((resolve, reject) => { + const child = require('../napi_child').spawn(process.argv[0], [ + '--expose-gc', __filename, bindingFile + ], { stdio: 'inherit' }); + + let timeout = setTimeout( function() { + child.kill(); + timeout = 0; + reject(new Error("Expected child to die")); + }, 5000); + + child.on("error", (err) => { + clearTimeout(timeout); + timeout = 0; + reject(new Error(err)); + }) + + child.on("close", (code) => { + if (timeout) clearTimeout(timeout); + assert.strictEqual(code, 0, "Expected return value 0"); + resolve(); + }); + }); + } else { + // Child process + const binding = require(bindingFile); + binding.threadsafe_function_ex_unref.testUnref({}, () => { }); + } +} diff --git a/test/threadsafe_function_ex/util/TestRunner.js b/test/threadsafe_function_ex/util/TestRunner.js deleted file mode 100644 index 8b425ab65..000000000 --- a/test/threadsafe_function_ex/util/TestRunner.js +++ /dev/null @@ -1,154 +0,0 @@ -// @ts-check -'use strict'; -const assert = require('assert'); -const { basename, extname } = require('path'); -const buildType = process.config.target_defaults.default_configuration; - -const pad = (what, targetLength = 20, padString = ' ', padLeft) => { - const padder = (pad, str) => { - if (typeof str === 'undefined') - return pad; - if (padLeft) { - return (pad + str).slice(-pad.length); - } else { - return (str + pad).substring(0, pad.length); - } - }; - return padder(padString.repeat(targetLength), String(what)); -} - -/** - * If `true`, always show results as interactive. See constructor for more - * information. -*/ -const SHOW_OUTPUT = false; - -/** - * Test runner helper class. Each static method's name corresponds to the - * namespace the test as defined in the native addon. Each test specifics are - * documented on the individual method. The async test handler runs - * synchronously in the series of all tests so the test **MUST** wait on the - * finalizer. Otherwise, the test runner will assume the test completed. - */ -class TestRunner { - - /** - * @param {string} bindingKey The key to use when accessing the binding. - * @param {string} filename Name of file that the current TestRunner instance - * is being constructed. This determines how to log to console: - * - When the test is running as the current module, output is shown on both - * start and stop of test in an 'interactive' styling. - * - Otherwise, the output is more of a CI-like styling. - */ - constructor(bindingKey, filename) { - this.bindingKey = bindingKey; - this.filename = filename; - this.interactive = SHOW_OUTPUT || filename === require.main.filename; - this.specName = `${this.bindingKey}/${basename(this.filename, extname(this.filename))}`; - } - - async start() { - try { - this.log(`Running tests in .${this.filename.replace(process.cwd(), '')}:\n`); - // Run tests in both except and noexcept - await this.run(false); - await this.run(true); - } catch (ex) { - console.error(`Test failed!`, ex); - process.exit(1); - } - } - - /** - * @param {boolean} isNoExcept If true, use the 'noexcept' binding. - */ - async run(isNoExcept) { - const binding = require(`../../build/${buildType}/binding${isNoExcept ? '_noexcept' : ''}.node`); - const { bindingKey } = this; - const spec = binding[bindingKey]; - const runner = this; - - // If we can't find the key in the binding, error. - if (!spec) { - throw new Error(`Could not find '${bindingKey}' in binding.`); - } - - // A 'test' is defined as any function on the prototype of this object. - for (const nsName of Object.getOwnPropertyNames(Object.getPrototypeOf(this))) { - if (nsName !== 'constructor') { - const ns = spec[nsName]; - - // Interactive mode prints start and end messages - if (this.interactive) { - - /** @typedef {[string, string | null | number, boolean, string, any]} State [label, time, isNoExcept, nsName, returnValue] */ - - /** @type {State} */ - let state = [undefined, undefined, undefined, undefined, undefined] - - const stateLine = () => { - const [label, time, isNoExcept, nsName, returnValue] = state; - const except = () => pad(isNoExcept ? '[noexcept]' : '', 12); - const timeStr = () => time == null ? '...' : `${time}${typeof time === 'number' ? 'ms' : ''}`; - return `${pad(nsName, 10)} ${except()}| ${pad(timeStr(), 8)}| ${pad(label, 15)}${returnValue === undefined ? '' : `(return: ${JSON.stringify(returnValue)})`}`; - }; - - /** - * @param {string} label - * @param {string | number} time - * @param {boolean} isNoExcept - * @param {string} nsName - * @param {any} returnValue - */ - const setState = (label, time, isNoExcept, nsName, returnValue) => { - if (state[1] === null) { - // Move to last line - this.print(false, `\x1b[1A`); - } - state = [label, time, isNoExcept, nsName, returnValue]; - this.log(stateLine()); - }; - - if (ns && typeof runner[nsName] === 'function') { - setState('Running test', null, isNoExcept, nsName, undefined); - const start = Date.now(); - const returnValue = await runner[nsName](ns); - setState('Finished test', Date.now() - start, isNoExcept, nsName, returnValue); - } else { - setState('Skipping test', '-', isNoExcept, nsName, undefined); - } - } else if (ns) { - console.log(`Running test '${this.specName}/${nsName}' ${isNoExcept ? '[noexcept]' : ''}`); - await runner[nsName](ns); - } - } - } - } - - /** - * Print to console only when using interactive mode. - * - * @param {boolean} newLine If true, end with a new line. - * @param {any[]} what What to print - */ - print(newLine, ...what) { - if (this.interactive) { - let target, method; - target = newLine ? console : process.stdout; - method = target === console ? 'log' : 'write'; - return target[method].apply(target, what); - } - } - - /** - * Log to console only when using interactive mode. - * @param {string[]} what - */ - log(...what) { - this.print(true, ...what); - } -} - -module.exports = { - TestRunner -}; diff --git a/test/threadsafe_function_ex/util/util.h b/test/threadsafe_function_ex/util/util.h deleted file mode 100644 index 6f202601b..000000000 --- a/test/threadsafe_function_ex/util/util.h +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include "napi.h" - -#if (NAPI_VERSION > 3) - -using namespace Napi; - -namespace tsfnutil { -template -class TSFNWrapBase : public ObjectWrap { -public: - - - static void Init(Napi::Env env, Object exports, const std::string &ns) { - // Get methods defined by child - auto methods(TSFNWrapImpl::InstanceMethods()); - - // Create a vector, since DefineClass doesn't accept arrays. - std::vector> methodsVec(methods.begin(), methods.end()); - - auto locals(Object::New(env)); - locals.Set("TSFNWrap", ObjectWrap::DefineClass(env, "TSFNWrap", methodsVec)); - exports.Set(ns, locals); - } - - // Release the TSFN. Returns a Promise that is resolved in the TSFN's - // finalizer. - // NOTE: the 'simple' test overrides this method, because it has no finalizer. - Napi::Value Release(const CallbackInfo &info) { - if (_deferred) { - return _deferred->Promise(); - } - - auto env = info.Env(); - _deferred.reset(new Promise::Deferred(Promise::Deferred::New(env))); - - _tsfn.Release(); - return _deferred->Promise(); - }; - - // TSFN finalizer. Resolves the Promise returned by `Release()` above. - static void Finalizer(Napi::Env env, - std::unique_ptr *deferred, - Context * /*ctx*/) { - if (deferred->get()) { - (*deferred)->Resolve(Boolean::New(env, true)); - deferred->release(); - } - } - - - TSFNWrapBase(const CallbackInfo &callbackInfo) - : ObjectWrap(callbackInfo) {} - -protected: - TSFN _tsfn; - std::unique_ptr _deferred; -}; - -} // namespace tsfnutil - -#endif From e2239558351631fa01830fa825393da77ead711a Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Tue, 18 Aug 2020 23:10:35 +0200 Subject: [PATCH 234/696] doc: tsfnex example uses ported tsfn example --- doc/threadsafe_function.md | 2 +- doc/threadsafe_function_ex.md | 85 +++++++++++++++++++++++------------ 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/doc/threadsafe_function.md b/doc/threadsafe_function.md index 7c323fd4a..055478d84 100644 --- a/doc/threadsafe_function.md +++ b/doc/threadsafe_function.md @@ -71,7 +71,7 @@ New(napi_env env, `uv_thread_join()`. It is important that, aside from the main loop thread, there be no threads left using the thread-safe function after the finalize callback completes. Must implement `void operator()(Env env, DataType* data, - Context* hint)`, skipping `data` or `hint` if they are not provided. + ContextType* hint)`, skipping `data` or `hint` if they are not provided. - `[optional] data`: Data to be passed to `finalizeCallback`. Returns a non-empty `Napi::ThreadSafeFunction` instance. diff --git a/doc/threadsafe_function_ex.md b/doc/threadsafe_function_ex.md index ffc637217..a3aa7b48a 100644 --- a/doc/threadsafe_function_ex.md +++ b/doc/threadsafe_function_ex.md @@ -192,8 +192,14 @@ Returns one of: using namespace Napi; +using Context = Reference; +using DataType = int; +void CallJs( Napi::Env env, Function callback, Context* context, DataType* data ); +using TSFN = ThreadSafeFunctionEx; +using FinalizerDataType = void; + std::thread nativeThread; -ThreadSafeFunction tsfn; +TSFN tsfn; Value Start( const CallbackInfo& info ) { @@ -214,35 +220,32 @@ Value Start( const CallbackInfo& info ) int count = info[1].As().Int32Value(); + // Create a new context set to the the receiver (ie, `this`) of the function + // call + Context* context = new Reference( Persistent( info.This() ) ); + // Create a ThreadSafeFunction - tsfn = ThreadSafeFunction::New( - env, - info[0].As(), // JavaScript function called asynchronously - "Resource Name", // Name - 0, // Unlimited queue - 1, // Only one thread will use this initially - []( Napi::Env ) { // Finalizer used to clean threads up - nativeThread.join(); - } ); + tsfn = TSFN::New( env, + info[0].As(), // JavaScript function called asynchronously + "Resource Name", // Name + 0, // Unlimited queue + 1, // Only one thread will use this initially + context, + []( Napi::Env, FinalizerDataType*, + Context* ctx ) { // Finalizer used to clean threads up + nativeThread.join(); + delete ctx; + } ); // Create a native thread nativeThread = std::thread( [count] { - auto callback = []( Napi::Env env, Function jsCallback, int* value ) { - // Transform native data into JS data, passing it to the provided - // `jsCallback` -- the TSFN's JavaScript function. - jsCallback.Call( {Number::New( env, *value )} ); - - // We're finished with the data. - delete value; - }; - for ( int i = 0; i < count; i++ ) { // Create new data int* value = new int( clock() ); // Perform a blocking call - napi_status status = tsfn.BlockingCall( value, callback ); + napi_status status = tsfn.BlockingCall( value ); if ( status != napi_ok ) { // Handle error @@ -256,7 +259,29 @@ Value Start( const CallbackInfo& info ) tsfn.Release(); } ); - return Boolean::New(env, true); + return Boolean::New( env, true ); +} + +// Transform native data into JS data, passing it to the provided +// `callback` -- the TSFN's JavaScript function. +void CallJs( Napi::Env env, Function callback, Context* context, DataType* data ) +{ + // Is the JavaScript environment still available to call into, eg. the TSFN is + // not aborted + if ( env != nullptr ) + { + // On N-API 5+, the `callback` parameter is optional; however, this example + // does ensure a callback is provided. + if ( callback != nullptr ) + { + callback.Call( context->Value(), {Number::New( env, *data )} ); + } + } + if ( data != nullptr ) + { + // We're finished with the data. + delete data; + } } Napi::Object Init( Napi::Env env, Object exports ) @@ -273,18 +298,20 @@ The above code can be used from JavaScript as follows: ```js const { start } = require('bindings')('clock'); -start(function () { - console.log("JavaScript callback called with arguments", Array.from(arguments)); +start.call(new Date(), function (clock) { + const context = this; + console.log(context, clock); }, 5); ``` When executed, the output will show the value of `clock()` five times at one -second intervals: +second intervals, prefixed with the TSFN's context -- `start`'s receiver (ie, +`new Date()`): ``` -JavaScript callback called with arguments [ 84745 ] -JavaScript callback called with arguments [ 103211 ] -JavaScript callback called with arguments [ 104516 ] -JavaScript callback called with arguments [ 105104 ] -JavaScript callback called with arguments [ 105691 ] +2020-08-18T21:04:25.116Z 49824 +2020-08-18T21:04:25.116Z 62493 +2020-08-18T21:04:25.116Z 62919 +2020-08-18T21:04:25.116Z 63228 +2020-08-18T21:04:25.116Z 63531 ``` From 3405b2bf39793a573d420584ea81a411e5127634 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Tue, 18 Aug 2020 23:35:53 +0200 Subject: [PATCH 235/696] src,doc: final cleanup --- doc/threadsafe_function_ex.md | 4 ++-- napi-inl.h | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/threadsafe_function_ex.md b/doc/threadsafe_function_ex.md index a3aa7b48a..497484397 100644 --- a/doc/threadsafe_function_ex.md +++ b/doc/threadsafe_function_ex.md @@ -92,13 +92,13 @@ for `CallbackType callback`. When targetting version 4, `callback` may be: - of type `const Function&` -- not provided as an parameter, in which case the API creates a new no-op +- not provided as a parameter, in which case the API creates a new no-op `Function` When targetting version 5+, `callback` may be: - of type `const Function&` - of type `std::nullptr_t` -- not provided as an parameter, in which case the API passes `std::nullptr` +- not provided as a parameter, in which case the API passes `std::nullptr` ### Acquire diff --git a/napi-inl.h b/napi-inl.h index 46090470c..79a47a5cd 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -13,7 +13,6 @@ #include #include #include -#include namespace Napi { From 1c2a8d59b55eda6b1f2f5e52f9c686f2a125d83d Mon Sep 17 00:00:00 2001 From: pacop <1923775+pacop@users.noreply.github.com> Date: Mon, 24 Aug 2020 20:27:19 +0200 Subject: [PATCH 236/696] doc: Added required return to example (#793) PR-URL: https://github.com/nodejs/node-addon-api/pull/793 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- doc/function.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/function.md b/doc/function.md index c1b0fc9fb..610d1005c 100644 --- a/doc/function.md +++ b/doc/function.md @@ -30,6 +30,7 @@ Value Fn(const CallbackInfo& info) { Object Init(Env env, Object exports) { exports.Set(String::New(env, "fn"), Function::New(env)); + return exports; } NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) From c2cbbd9191e3b39c993b5417d9776d13a3045e75 Mon Sep 17 00:00:00 2001 From: Jim Schlight Date: Mon, 24 Aug 2020 11:28:07 -0700 Subject: [PATCH 237/696] doc: add link to n-api tutorial website (#794) PR-URL: https://github.com/nodejs/node-addon-api/pull/794 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 1f6e34e75..aa06eea67 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ APIs exposed by node-addon-api are generally used to create and manipulate JavaScript values. Concepts and operations generally map to ideas specified in the **ECMA262 Language Specification**. +The [N-API Resource](http://nodejs.github.io/node-addon-examples/) offers an +excellent orientation and tips for developers just getting started with N-API +and node-addon-api. + - **[Setup](#setup)** - **[API Documentation](#api)** - **[Examples](#examples)** From 518cfdcdc10301723b9d48848e8297f43dc54209 Mon Sep 17 00:00:00 2001 From: David Halls Date: Wed, 13 May 2020 23:03:15 +0100 Subject: [PATCH 238/696] test: test ObjectWrap destructor - no HandleScope Add test for ObjectWrap destructor (no HandleScope exception) REFS: https://github.com/nodejs/node-addon-api/issues/722 PR-URL: https://github.com/nodejs/node-addon-api/pull/729 Reviewed-By: Michael Dawson --- test/index.js | 5 +++++ test/objectwrap.cc | 10 ++++++++++ test/objectwrap_worker_thread.js | 14 ++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 test/objectwrap_worker_thread.js diff --git a/test/index.js b/test/index.js index 86939af84..33b128da0 100644 --- a/test/index.js +++ b/test/index.js @@ -55,6 +55,7 @@ let testModules = [ 'objectwrap_constructor_exception', 'objectwrap-removewrap', 'objectwrap_multiple_inheritance', + 'objectwrap_worker_thread', 'objectreference', 'reference', 'version_management' @@ -90,6 +91,10 @@ if (napiVersion < 6) { testModules.splice(testModules.indexOf('typedarray-bigint'), 1); } +if (majorNodeVersion < 12) { + testModules.splice(testModules.indexOf('objectwrap_worker_thread'), 1); +} + if (typeof global.gc === 'function') { (async function() { console.log(`Testing with N-API Version '${napiVersion}'.`); diff --git a/test/objectwrap.cc b/test/objectwrap.cc index 92a29ce74..2ffc85a25 100644 --- a/test/objectwrap.cc +++ b/test/objectwrap.cc @@ -40,6 +40,14 @@ class Test : public Napi::ObjectWrap { info.This().As().DefineProperty( Napi::PropertyDescriptor::Accessor("ownPropertyT", napi_enumerable, this)); + + bufref_ = Napi::Persistent(Napi::Buffer::New( + Env(), + static_cast(malloc(1)), + 1, + [](Napi::Env, uint8_t* bufaddr) { + free(bufaddr); + })); } static Napi::Value OwnPropertyGetter(const Napi::CallbackInfo& info) { @@ -183,6 +191,8 @@ class Test : public Napi::ObjectWrap { Napi::FunctionReference finalizeCb_; static std::string s_staticMethodText; + + Napi::Reference> bufref_; }; std::string Test::s_staticMethodText; diff --git a/test/objectwrap_worker_thread.js b/test/objectwrap_worker_thread.js new file mode 100644 index 000000000..348309976 --- /dev/null +++ b/test/objectwrap_worker_thread.js @@ -0,0 +1,14 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const { Worker, isMainThread } = require('worker_threads'); + +if (isMainThread) { + new Worker(__filename); +} else { + const test = binding => { + new binding.objectwrap.Test(); + }; + + test(require(`./build/${buildType}/binding.node`)); + test(require(`./build/${buildType}/binding_noexcept.node`)); +} From 2bc45bbffd0ffde6ba31f63eb1866ccf536f5050 Mon Sep 17 00:00:00 2001 From: Velmisov Date: Fri, 7 Aug 2020 18:46:20 +0300 Subject: [PATCH 239/696] test: refactor test to use async/await Refactor threadsafe_function test with async/await PR-URL: https://github.com/nodejs/node-addon-api/pull/787 Reviewed-By: Kevin Eady Reviewed-By: Michael Dawson --- .../threadsafe_function.js | 176 ++++++++++-------- 1 file changed, 100 insertions(+), 76 deletions(-) diff --git a/test/threadsafe_function/threadsafe_function.js b/test/threadsafe_function/threadsafe_function.js index a3690fcf3..4c4bbf8a3 100644 --- a/test/threadsafe_function/threadsafe_function.js +++ b/test/threadsafe_function/threadsafe_function.js @@ -4,10 +4,12 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const common = require('../common'); -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = async function() { + await test(require(`../build/${buildType}/binding.node`)); + await test(require(`../build/${buildType}/binding_noexcept.node`)); +}; -function test(binding) { +async function test(binding) { const expectedArray = (function(arrayLength) { const result = []; for (let index = 0; index < arrayLength; index++) { @@ -43,7 +45,7 @@ function test(binding) { }); } - return new Promise(function testWithoutJSMarshaller(resolve) { + await new Promise(function testWithoutJSMarshaller(resolve) { let callCount = 0; binding.threadsafe_function.startThreadNoNative(function testCallback() { callCount++; @@ -59,112 +61,134 @@ function test(binding) { } }, false /* abort */, false /* launchSecondary */, binding.threadsafe_function.MAX_QUEUE_SIZE); - }) + }); // Start the thread in blocking mode, and assert that all values are passed. // Quit after it's done. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, - quitAfter: binding.threadsafe_function.ARRAY_LENGTH - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + quitAfter: binding.threadsafe_function.ARRAY_LENGTH + }), + expectedArray, + ); // Start the thread in blocking mode with an infinite queue, and assert that // all values are passed. Quit after it's done. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - maxQueueSize: 0, - quitAfter: binding.threadsafe_function.ARRAY_LENGTH - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: 0, + quitAfter: binding.threadsafe_function.ARRAY_LENGTH + }), + expectedArray, + ); // Start the thread in non-blocking mode, and assert that all values are // passed. Quit after it's done. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThreadNonblocking', - maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, - quitAfter: binding.threadsafe_function.ARRAY_LENGTH - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + quitAfter: binding.threadsafe_function.ARRAY_LENGTH + }), + expectedArray, + ); // Start the thread in blocking mode, and assert that all values are passed. // Quit early, but let the thread finish. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, - quitAfter: 1 - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + quitAfter: 1 + }), + expectedArray, + ); // Start the thread in blocking mode with an infinite queue, and assert that // all values are passed. Quit early, but let the thread finish. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - maxQueueSize: 0, - quitAfter: 1 - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThread', + maxQueueSize: 0, + quitAfter: 1 + }), + expectedArray, + ); // Start the thread in non-blocking mode, and assert that all values are // passed. Quit early, but let the thread finish. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThreadNonblocking', - maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, - quitAfter: 1 - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + quitAfter: 1 + }), + expectedArray, + ); // Start the thread in blocking mode, and assert that all values are passed. // Quit early, but let the thread finish. Launch a secondary thread to test // the reference counter incrementing functionality. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - quitAfter: 1, - maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, - launchSecondary: true - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + launchSecondary: true + }), + expectedArray, + ); // Start the thread in non-blocking mode, and assert that all values are // passed. Quit early, but let the thread finish. Launch a secondary thread // to test the reference counter incrementing functionality. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThreadNonblocking', - quitAfter: 1, - maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, - launchSecondary: true - })) - .then((result) => assert.deepStrictEqual(result, expectedArray)) + assert.deepStrictEqual( + await testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + launchSecondary: true + }), + expectedArray, + ); // Start the thread in blocking mode, and assert that it could not finish. // Quit early by aborting. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - quitAfter: 1, - maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, - abort: true - })) - .then((result) => assert.strictEqual(result.indexOf(0), -1)) + assert.strictEqual( + (await testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + abort: true + })).indexOf(0), + -1, + ); // Start the thread in blocking mode with an infinite queue, and assert that // it could not finish. Quit early by aborting. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThread', - quitAfter: 1, - maxQueueSize: 0, - abort: true - })) - .then((result) => assert.strictEqual(result.indexOf(0), -1)) + assert.strictEqual( + (await testWithJSMarshaller({ + threadStarter: 'startThread', + quitAfter: 1, + maxQueueSize: 0, + abort: true + })).indexOf(0), + -1, + ); // Start the thread in non-blocking mode, and assert that it could not finish. // Quit early and aborting. - .then(() => testWithJSMarshaller({ - threadStarter: 'startThreadNonblocking', - quitAfter: 1, - maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, - abort: true - })) - .then((result) => assert.strictEqual(result.indexOf(0), -1)) + assert.strictEqual( + (await testWithJSMarshaller({ + threadStarter: 'startThreadNonblocking', + quitAfter: 1, + maxQueueSize: binding.threadsafe_function.MAX_QUEUE_SIZE, + abort: true + })).indexOf(0), + -1, + ); } From 9aceea71fc14b92a2dcec66141eb339c04c41be9 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Thu, 6 Aug 2020 11:24:17 -0700 Subject: [PATCH 240/696] src: concentrate callbacks provided to core N-API This change reduces the places where we declare private functions with the `napi_callback` signature for the purpose of using them with C++ callbacks passed as template arguments. We basically have 4 types: 1. static with `void` return 2. static with `napi_value` return 3. instance with `void` return 4. instance with `napi_value` return We can use one of these four calling patterns in the following places where we accept callbacks as template arguments: * `Napi::Function` (1. and 2.) * `Napi::PropertyDescriptor` (1. for the setter, 2. for the getter) * `Napi::InstanceWrap` (3., 4. for instance methods, 4. for instance getters) * `Napi::ObjectWrap` (1., 2. for static methods, 2. for static getters) In the case of `InstanceWrap` and `ObjectWrap` instance resp. static property descriptors we can also remove the infrastructure designed to allow for optional getters (`GetterTag` resp. `StaticGetterTag`) because the API for specifying instance resp. class property descriptors does not allow one to omit the getter. Signed-off-by: Gabriel Schulhof PR-URL: https://github.com/nodejs/node-addon-api/pull/786 Reviewed-By: Anna Henningsen Reviewed-By: Michael Dawson --- napi-inl.h | 162 ++++++++++++++++++++++++----------------------------- napi.h | 23 -------- 2 files changed, 72 insertions(+), 113 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 4f0636a08..3b2c558d5 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -135,6 +135,48 @@ struct CallbackData { void* data; }; +template +static napi_value +TemplatedVoidCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback([&] { + CallbackInfo cbInfo(env, info); + Callback(cbInfo); + return nullptr; + }); +} + +template +static napi_value +TemplatedCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback([&] { + CallbackInfo cbInfo(env, info); + return Callback(cbInfo); + }); +} + +template +static napi_value +TemplatedInstanceCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback([&] { + CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + return (instance->*UnwrapCallback)(cbInfo); + }); +} + +template +static napi_value +TemplatedInstanceVoidCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback([&] { + CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + (instance->*UnwrapCallback)(cbInfo); + return nullptr; + }); +} + template struct FinalizeData { static inline @@ -1845,15 +1887,12 @@ CreateFunction(napi_env env, template inline Function Function::New(napi_env env, const char* utf8name, void* data) { napi_value result = nullptr; - napi_status status = napi_create_function( - env, utf8name, NAPI_AUTO_LENGTH, - [](napi_env env, napi_callback_info info) { - CallbackInfo callbackInfo(env, info); - return details::WrapCallback([&] { - cb(callbackInfo); - return nullptr; - }); - }, data, &result); + napi_status status = napi_create_function(env, + utf8name, + NAPI_AUTO_LENGTH, + details::TemplatedVoidCallback, + data, + &result); NAPI_THROW_IF_FAILED(env, status, Function()); return Function(env, result); } @@ -1861,14 +1900,12 @@ inline Function Function::New(napi_env env, const char* utf8name, void* data) { template inline Function Function::New(napi_env env, const char* utf8name, void* data) { napi_value result = nullptr; - napi_status status = napi_create_function( - env, utf8name, NAPI_AUTO_LENGTH, - [](napi_env env, napi_callback_info info) { - CallbackInfo callbackInfo(env, info); - return details::WrapCallback([&] { - return cb(callbackInfo); - }); - }, data, &result); + napi_status status = napi_create_function(env, + utf8name, + NAPI_AUTO_LENGTH, + details::TemplatedCallback, + data, + &result); NAPI_THROW_IF_FAILED(env, status, Function()); return Function(env, result); } @@ -2859,7 +2896,7 @@ PropertyDescriptor::Accessor(const char* utf8name, napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = &GetterCallbackWrapper; + desc.getter = details::TemplatedCallback; desc.attributes = attributes; desc.data = data; @@ -2882,7 +2919,7 @@ PropertyDescriptor::Accessor(Name name, napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = &GetterCallbackWrapper; + desc.getter = details::TemplatedCallback; desc.attributes = attributes; desc.data = data; @@ -2900,8 +2937,8 @@ PropertyDescriptor::Accessor(const char* utf8name, napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = &GetterCallbackWrapper; - desc.setter = &SetterCallbackWrapper; + desc.getter = details::TemplatedCallback; + desc.setter = details::TemplatedVoidCallback; desc.attributes = attributes; desc.data = data; @@ -2928,31 +2965,14 @@ PropertyDescriptor::Accessor(Name name, napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = &GetterCallbackWrapper; - desc.setter = &SetterCallbackWrapper; + desc.getter = details::TemplatedCallback; + desc.setter = details::TemplatedVoidCallback; desc.attributes = attributes; desc.data = data; return desc; } -template -napi_value -PropertyDescriptor::GetterCallbackWrapper(napi_env env, - napi_callback_info info) { - CallbackInfo cbInfo(env, info); - return Getter(cbInfo); -} - -template -napi_value -PropertyDescriptor::SetterCallbackWrapper(napi_env env, - napi_callback_info info) { - CallbackInfo cbInfo(env, info); - Setter(cbInfo); - return nullptr; -} - template inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, @@ -3283,7 +3303,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = &InstanceWrap::WrappedMethod; + desc.method = details::TemplatedInstanceVoidCallback; desc.data = data; desc.attributes = attributes; return desc; @@ -3297,7 +3317,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = &InstanceWrap::WrappedMethod; + desc.method = details::TemplatedInstanceCallback; desc.data = data; desc.attributes = attributes; return desc; @@ -3311,7 +3331,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = &InstanceWrap::WrappedMethod; + desc.method = details::TemplatedInstanceVoidCallback; desc.data = data; desc.attributes = attributes; return desc; @@ -3325,7 +3345,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = &InstanceWrap::WrappedMethod; + desc.method = details::TemplatedInstanceCallback; desc.data = data; desc.attributes = attributes; return desc; @@ -3378,7 +3398,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = This::WrapGetter(This::GetterTag()); + desc.getter = details::TemplatedInstanceCallback; desc.setter = This::WrapSetter(This::SetterTag()); desc.data = data; desc.attributes = attributes; @@ -3394,7 +3414,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = This::WrapGetter(This::GetterTag()); + desc.getter = details::TemplatedInstanceCallback; desc.setter = This::WrapSetter(This::SetterTag()); desc.data = data; desc.attributes = attributes; @@ -3487,27 +3507,6 @@ inline napi_value InstanceWrap::InstanceSetterCallbackWrapper( }); } -template -template ::InstanceVoidMethodCallback method> -inline napi_value InstanceWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { - const CallbackInfo cbInfo(env, info); - T* instance = T::Unwrap(cbInfo.This().As()); - (instance->*method)(cbInfo); - return nullptr; - }); -} - -template -template ::InstanceMethodCallback method> -inline napi_value InstanceWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { - const CallbackInfo cbInfo(env, info); - T* instance = T::Unwrap(cbInfo.This().As()); - return (instance->*method)(cbInfo); - }); -} - template template ::InstanceSetterCallback method> inline napi_value InstanceWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { @@ -3732,7 +3731,7 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = &ObjectWrap::WrappedMethod; + desc.method = details::TemplatedVoidCallback; desc.data = data; desc.attributes = static_cast(attributes | napi_static); return desc; @@ -3746,7 +3745,7 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = &ObjectWrap::WrappedMethod; + desc.method = details::TemplatedVoidCallback; desc.data = data; desc.attributes = static_cast(attributes | napi_static); return desc; @@ -3760,7 +3759,7 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.method = &ObjectWrap::WrappedMethod; + desc.method = details::TemplatedCallback; desc.data = data; desc.attributes = static_cast(attributes | napi_static); return desc; @@ -3774,7 +3773,7 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.method = &ObjectWrap::WrappedMethod; + desc.method = details::TemplatedCallback; desc.data = data; desc.attributes = static_cast(attributes | napi_static); return desc; @@ -3827,7 +3826,7 @@ inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.utf8name = utf8name; - desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); + desc.getter = details::TemplatedCallback; desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); desc.data = data; desc.attributes = static_cast(attributes | napi_static); @@ -3843,7 +3842,7 @@ inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( void* data) { napi_property_descriptor desc = napi_property_descriptor(); desc.name = name; - desc.getter = This::WrapStaticGetter(This::StaticGetterTag()); + desc.getter = details::TemplatedCallback; desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); desc.data = data; desc.attributes = static_cast(attributes | napi_static); @@ -3969,23 +3968,6 @@ inline void ObjectWrap::FinalizeCallback(napi_env env, void* data, void* /*hi delete instance; } -template -template ::StaticVoidMethodCallback method> -inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { - method(CallbackInfo(env, info)); - return nullptr; - }); -} - -template -template ::StaticMethodCallback method> -inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { - return details::WrapCallback([&] { - return method(CallbackInfo(env, info)); - }); -} - template template ::StaticSetterCallback method> inline napi_value ObjectWrap::WrappedMethod(napi_env env, napi_callback_info info) noexcept { diff --git a/napi.h b/napi.h index cf0ce51e7..76a62f659 100644 --- a/napi.h +++ b/napi.h @@ -1623,10 +1623,6 @@ namespace Napi { operator const napi_property_descriptor&() const; private: - template - static napi_value GetterCallbackWrapper(napi_env env, napi_callback_info info); - template - static napi_value SetterCallbackWrapper(napi_env env, napi_callback_info info); napi_property_descriptor _desc; }; @@ -1748,16 +1744,8 @@ namespace Napi { template static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - template struct GetterTag {}; template struct SetterTag {}; - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - template - static napi_callback WrapGetter(GetterTag) noexcept { return &This::WrappedMethod; } - static napi_callback WrapGetter(GetterTag) noexcept { return nullptr; } template static napi_callback WrapSetter(SetterTag) noexcept { return &This::WrappedMethod; } static napi_callback WrapSetter(SetterTag) noexcept { return nullptr; } @@ -1892,22 +1880,11 @@ namespace Napi { StaticGetterCallback, StaticSetterCallback> StaticAccessorCallbackData; - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - - template - static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - template static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept; - template struct StaticGetterTag {}; template struct StaticSetterTag {}; - template - static napi_callback WrapStaticGetter(StaticGetterTag) noexcept { return &This::WrappedMethod; } - static napi_callback WrapStaticGetter(StaticGetterTag) noexcept { return nullptr; } - template static napi_callback WrapStaticSetter(StaticSetterTag) noexcept { return &This::WrappedMethod; } static napi_callback WrapStaticSetter(StaticSetterTag) noexcept { return nullptr; } From f27623ff611645cd7d927f0de81158a93e960542 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Tue, 14 Jul 2020 13:34:32 +0100 Subject: [PATCH 241/696] build: introduce include_dir Introduce `include_dir` for use with gyp in a scalar context Deprecate use of `include` in an gyp array context, which happens to work when paths are absolute, but can fail on Windows when paths are relative and a gyp file contains multiple entries in its `include_dirs` directive. This change corrects documentation and tooling, adds support for relative paths (e.g. those containing whitespace) in a backwards compatible manner and makes the approach holistically consistent with that used by nan. PR-URL: https://github.com/nodejs/node-addon-api/pull/766 Reviewed-By: Michael Dawson --- common.gypi | 2 +- doc/setup.md | 2 +- index.js | 7 ++++--- tools/conversion.js | 8 ++++---- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/common.gypi b/common.gypi index 088f961ea..9be254f0b 100644 --- a/common.gypi +++ b/common.gypi @@ -15,7 +15,7 @@ } }] ], - 'include_dirs': [" Date: Fri, 4 Sep 2020 01:21:35 +0200 Subject: [PATCH 242/696] test: fix the threasfafe function test test: fixed the execution for the threasfafe function test PR-URL: https://github.com/nodejs/node-addon-api/pull/807 Fixes: https://github.com/nodejs/node-addon-api/issues/806 Reviewed-By: Michael Dawson -sh-4.2$ --- test/threadsafe_function/threadsafe_function.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/threadsafe_function/threadsafe_function.js b/test/threadsafe_function/threadsafe_function.js index 4c4bbf8a3..419c214f8 100644 --- a/test/threadsafe_function/threadsafe_function.js +++ b/test/threadsafe_function/threadsafe_function.js @@ -4,10 +4,10 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const common = require('../common'); -module.exports = async function() { +module.exports = (async function() { await test(require(`../build/${buildType}/binding.node`)); await test(require(`../build/${buildType}/binding_noexcept.node`)); -}; +})(); async function test(binding) { const expectedArray = (function(arrayLength) { From 6562e6b0ab604fd55b9c2d0cf954c9ce93e4bdee Mon Sep 17 00:00:00 2001 From: NickNaso Date: Fri, 4 Sep 2020 02:41:14 +0200 Subject: [PATCH 243/696] test: added tests to check the build process Refs: https://github.com/nodejs/node-addon-api/pull/766 PR-URL: https://github.com/nodejs/node-addon-api/pull/808 Reviewed-By: Michael Dawson --- .gitignore | 1 + package.json | 1 + test/addon_build/index.js | 50 +++++++++++++++++++++++++ test/addon_build/tpl/.npmrc | 1 + test/addon_build/tpl/addon.cc | 17 +++++++++ test/addon_build/tpl/binding.gyp | 62 +++++++++++++++++++++++++++++++ test/addon_build/tpl/index.js | 9 +++++ test/addon_build/tpl/package.json | 11 ++++++ test/index.js | 1 + 9 files changed, 153 insertions(+) create mode 100644 test/addon_build/index.js create mode 100644 test/addon_build/tpl/.npmrc create mode 100644 test/addon_build/tpl/addon.cc create mode 100644 test/addon_build/tpl/binding.gyp create mode 100644 test/addon_build/tpl/index.js create mode 100644 test/addon_build/tpl/package.json diff --git a/.gitignore b/.gitignore index c10f4dffc..c5b8bc871 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /build /benchmark/build /benchmark/src +/test/addon_build/addons diff --git a/package.json b/package.json index 64a44f32a..0ec1d6ed6 100644 --- a/package.json +++ b/package.json @@ -252,6 +252,7 @@ "description": "Node.js API (N-API)", "devDependencies": { "benchmark": "^2.1.4", + "fs-extra": "^9.0.1", "bindings": "^1.5.0", "safe-buffer": "^5.1.1" }, diff --git a/test/addon_build/index.js b/test/addon_build/index.js new file mode 100644 index 000000000..0d0c6ea2f --- /dev/null +++ b/test/addon_build/index.js @@ -0,0 +1,50 @@ +'use strict'; + +const { promisify } = require('util'); +const exec = promisify(require('child_process').exec); +const { copy, remove } = require('fs-extra'); +const path = require('path'); +const assert = require('assert') + +const ADDONS_FOLDER = path.join(__dirname, 'addons'); + +const addons = [ + 'echo addon', + 'echo-addon' +] + +async function beforeAll(addons) { + console.log(' >Preparing native addons to build') + for (const addon of addons) { + await remove(path.join(ADDONS_FOLDER, addon)); + await copy(path.join(__dirname, 'tpl'), path.join(ADDONS_FOLDER, addon)); + } +} + +async function test(addon) { + console.log(` >Building addon: '${addon}'`); + const { stderr, stdout } = await exec('npm install', { + cwd: path.join(ADDONS_FOLDER, addon) + }) + console.log(` >Runting test for: '${addon}'`); + // Disabled the checks on stderr and stdout because of this issuue on npm: + // Stop using process.umask(): https://github.com/npm/cli/issues/1103 + // We should enable the following checks again after the resolution of + // the reported issue. + // assert.strictEqual(stderr, ''); + // assert.ok(stderr.length === 0); + // assert.ok(stdout.length > 0); + const binding = require(`${ADDONS_FOLDER}/${addon}`); + assert.strictEqual(binding.except.echo('except'), 'except'); + assert.strictEqual(binding.except.echo(101), 101); + assert.strictEqual(binding.noexcept.echo('noexcept'), 'noexcept'); + assert.strictEqual(binding.noexcept.echo(103), 103); +} + + +module.exports = (async function() { + await beforeAll(addons); + for (const addon of addons) { + await test(addon); + } +})() diff --git a/test/addon_build/tpl/.npmrc b/test/addon_build/tpl/.npmrc new file mode 100644 index 000000000..9cf949503 --- /dev/null +++ b/test/addon_build/tpl/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/test/addon_build/tpl/addon.cc b/test/addon_build/tpl/addon.cc new file mode 100644 index 000000000..1a86799c4 --- /dev/null +++ b/test/addon_build/tpl/addon.cc @@ -0,0 +1,17 @@ +#include + +Napi::Value Echo(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + if (info.Length() != 1) { + Napi::TypeError::New(env, "Wrong number of arguments. One argument expected.") + .ThrowAsJavaScriptException(); + } + return info[0].As(); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "echo"), Napi::Function::New(env, Echo)); + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/addon_build/tpl/binding.gyp b/test/addon_build/tpl/binding.gyp new file mode 100644 index 000000000..aa26f1acb --- /dev/null +++ b/test/addon_build/tpl/binding.gyp @@ -0,0 +1,62 @@ +{ + 'target_defaults': { + 'include_dirs': [ + " Date: Wed, 26 Aug 2020 10:22:31 -0700 Subject: [PATCH 244/696] doc: add inheritance links and other changes * README.md: * Change indentation to reflect class hierarchy. * Link to new doc/hierarchy.md which shows full class hierarchy. * Add single sentence with link to parent class at the top of class doc. * doc/addon.md: * Replace `Addon` with `Addon`. * Show templating in prototypes. * Move `InstanceWrap` method documentation to its own file, because it is shared with `ObjectWRap`, and link to said new file. * doc/array.md: Create the file from the `Array`-related contents of doc/basic_types.md. * Remove doc/basic_types.md, splitting its contents per-class into individual files. * Add doc/hierarchy.md, with full class hierarchy. * Add doc/instance_wrap.md for documenting `InstanceMethod`, `InstanceAccessor`, and `InstanceValue`. * Create doc/name.md from doc/basic_types.md, documenting `Napi::Name`. * doc/object_wrap.md: * Add templating notation `Napi::ObjectWrap`. * Show templating in prototypes. * Wrap file to 80 columns. * Remove methods provided by `InstanceWrap` and link to doc/instance_wrap.md. * doc/value.md: * Merge documentation from doc/basic_types.md. * Add namespacing. * Sort methods alphabetically. Signed-off-by: Gabriel Schulhof Fixes: https://github.com/nodejs/node-addon-api/issues/796 PR-URL: https://github.com/nodejs/node-addon-api/pull/798 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- README.md | 33 +- doc/addon.md | 397 +------------------ doc/array.md | 81 ++++ doc/array_buffer.md | 4 + doc/basic_types.md | 423 -------------------- doc/bigint.md | 4 + doc/boolean.md | 4 + doc/buffer.md | 4 + doc/dataview.md | 4 + doc/date.md | 4 +- doc/error.md | 5 + doc/external.md | 4 + doc/hierarchy.md | 91 +++++ doc/instance_wrap.md | 408 ++++++++++++++++++++ doc/name.md | 29 ++ doc/object.md | 8 +- doc/object_wrap.md | 530 ++++++-------------------- doc/promises.md | 5 + doc/string.md | 4 + doc/symbol.md | 4 + doc/typed_array.md | 4 + doc/typed_array_of.md | 4 + doc/value.md | 270 ++++++++----- doc/working_with_javascript_values.md | 2 +- 24 files changed, 989 insertions(+), 1337 deletions(-) create mode 100644 doc/array.md delete mode 100644 doc/basic_types.md create mode 100644 doc/hierarchy.md create mode 100644 doc/instance_wrap.md create mode 100644 doc/name.md diff --git a/README.md b/README.md index aa06eea67..dc316c1b0 100644 --- a/README.md +++ b/README.md @@ -81,28 +81,29 @@ The oldest Node.js version supported by the current version of node-addon-api is The following is the documentation for node-addon-api. + - [Full Class Hierarchy](doc/hierarchy.md) - [Addon Structure](doc/addon.md) - - [Basic Types](doc/basic_types.md) - - [Array](doc/basic_types.md#array) - - [Symbol](doc/symbol.md) - - [String](doc/string.md) - - [Name](doc/basic_types.md#name) - - [Number](doc/number.md) - - [Date](doc/date.md) - - [BigInt](doc/bigint.md) - - [Boolean](doc/boolean.md) + - Basic Types: - [Env](doc/env.md) - - [Value](doc/value.md) - [CallbackInfo](doc/callbackinfo.md) - [Reference](doc/reference.md) - - [External](doc/external.md) - - [Object](doc/object.md) - - [ObjectReference](doc/object_reference.md) - - [PropertyDescriptor](doc/property_descriptor.md) + - [Value](doc/value.md) + - [Name](doc/name.md) + - [Symbol](doc/symbol.md) + - [String](doc/string.md) + - [Number](doc/number.md) + - [Date](doc/date.md) + - [BigInt](doc/bigint.md) + - [Boolean](doc/boolean.md) + - [External](doc/external.md) + - [Object](doc/object.md) + - [Array](doc/array.md) + - [ObjectReference](doc/object_reference.md) + - [PropertyDescriptor](doc/property_descriptor.md) - [Error Handling](doc/error_handling.md) - [Error](doc/error.md) - - [TypeError](doc/type_error.md) - - [RangeError](doc/range_error.md) + - [TypeError](doc/type_error.md) + - [RangeError](doc/range_error.md) - [Object Lifetime Management](doc/object_lifetime_management.md) - [HandleScope](doc/handle_scope.md) - [EscapableHandleScope](doc/escapable_handle_scope.md) diff --git a/doc/addon.md b/doc/addon.md index b4c9c9d6b..ee85e7d5e 100644 --- a/doc/addon.md +++ b/doc/addon.md @@ -1,5 +1,7 @@ # Add-on Structure +Class `Napi::Addon` inherits from class [`Napi::InstanceWrap`][]. + Creating add-ons that work correctly when loaded multiple times from the same source package into multiple Node.js threads and/or multiple times into the same Node.js thread requires that all global data they hold be associated with the @@ -8,20 +10,20 @@ variables because doing so does not take into account the fact that an add-on may be loaded into multiple threads nor that an add-on may be loaded multiple times into a single thread. -The `Napi::Addon` class can be used to define an entire add-on. Instances of -`Napi::Addon` subclasses become instances of the add-on, stored safely by +The `Napi::Addon` class can be used to define an entire add-on. Instances of +`Napi::Addon` subclasses become instances of the add-on, stored safely by Node.js on its various threads and into its various contexts. Thus, any data -stored in the instance variables of a `Napi::Addon` subclass instance are stored -safely by Node.js. Functions exposed to JavaScript using -`Napi::Addon::InstanceMethod` and/or `Napi::Addon::DefineAddon` are instance -methods of the `Napi::Addon` subclass and thus have access to data stored inside -the instance. +stored in the instance variables of a `Napi::Addon` subclass instance are +stored safely by Node.js. Functions exposed to JavaScript using +`Napi::Addon::InstanceMethod` and/or `Napi::Addon::DefineAddon` are +instance methods of the `Napi::Addon` subclass and thus have access to data +stored inside the instance. -`Napi::Addon::DefineProperties` may be used to attach `Napi::Addon` subclass -instance methods to objects other than the one that will be returned to Node.js -as the add-on instance. +`Napi::Addon::DefineProperties` may be used to attach `Napi::Addon` +subclass instance methods to objects other than the one that will be returned to +Node.js as the add-on instance. -The `Napi::Addon` class can be used together with the `NODE_API_ADDON()` and +The `Napi::Addon` class can be used together with the `NODE_API_ADDON()` and `NODE_API_NAMED_ADDON()` macros to define add-ons. ## Example @@ -122,7 +124,8 @@ pass it to `DefineAddon()` as its first parameter if it wishes to replace the Defines an add-on instance with functions, accessors, and/or values. ```cpp -void Napi::Addon::DefineAddon(Napi::Object exports, +template +void Napi::Addon::DefineAddon(Napi::Object exports, const std::initializer_list& properties); ``` @@ -138,8 +141,9 @@ Defines function, accessor, and/or value properties on an object using add-on instance methods. ```cpp +template Napi::Object -Napi::Addon::DefineProperties(Napi::Object object, +Napi::Addon::DefineProperties(Napi::Object object, const std::initializer_list& properties); ``` @@ -150,369 +154,4 @@ See: [`Class property and descriptor`](class_property_descriptor.md). Returns `object`. -### InstanceMethod - -Creates a property descriptor that represents a method provided by the add-on. - -```cpp -static Napi::PropertyDescriptor -Napi::Addon::InstanceMethod(const char* utf8name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] utf8name`: Null-terminated string that represents the name of the method -provided by the add-on. -- `[in] method`: The native function that represents a method provided by the -add-on. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the method when it is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents a method provided -by the add-on. The method must be of the form - -```cpp -void MethodName(const Napi::CallbackInfo& info); -``` - -### InstanceMethod - -Creates a property descriptor that represents a method provided by the add-on. - -```cpp -static Napi::PropertyDescriptor -Napi::Addon::InstanceMethod(const char* utf8name, - InstanceMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] utf8name`: Null-terminated string that represents the name of the method -provided by the add-on. -- `[in] method`: The native function that represents a method provided by the -add-on. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the method when it is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents a method provided -by the add-on. The method must be of the form - -```cpp -Napi::Value MethodName(const Napi::CallbackInfo& info); -``` - -### InstanceMethod - -Creates a property descriptor that represents a method provided by the add-on. - -```cpp -static Napi::PropertyDescriptor -Napi::Addon::InstanceMethod(Napi::Symbol name, - InstanceVoidMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] name`: JavaScript symbol that represents the name of the method provided -by the add-on. -- `[in] method`: The native function that represents a method provided by the -add-on. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the method when it is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents a method provided -by the add-on. The method must be of the form - -```cpp -void MethodName(const Napi::CallbackInfo& info); -``` - -### InstanceMethod - -Creates a property descriptor that represents a method provided by the add-on. - -```cpp -static Napi::PropertyDescriptor -Napi::Addon::InstanceMethod(Napi::Symbol name, - InstanceMethodCallback method, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] name`: JavaScript symbol that represents the name of the method provided -by the add-on. -- `[in] method`: The native function that represents a method provided by the -add-on. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the method when it is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents a method provided -by the add-on. The method must be of the form - -```cpp -Napi::Value MethodName(const Napi::CallbackInfo& info); -``` - -### InstanceMethod - -Creates a property descriptor that represents a method provided by the add-on. - -```cpp -template -static Napi::PropertyDescriptor -Napi::Addon::InstanceMethod(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] method`: The native function that represents a method provided by the -add-on. -- `[in] utf8name`: Null-terminated string that represents the name of the method -provided by the add-on. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the method when it is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents a method provided -by the add-on. The method must be of the form - -```cpp -void MethodName(const Napi::CallbackInfo& info); -``` - -### InstanceMethod - -Creates a property descriptor that represents a method provided by the add-on. - -```cpp -template -static Napi::PropertyDescriptor -Napi::Addon::InstanceMethod(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] method`: The native function that represents a method provided by the -add-on. -- `[in] utf8name`: Null-terminated string that represents the name of the method -provided by the add-on. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the method when it is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents a method provided -by the add-on. The method must be of the form - -```cpp -Napi::Value MethodName(const Napi::CallbackInfo& info); -``` - -### InstanceMethod - -Creates a property descriptor that represents a method provided by the add-on. - -```cpp -template -static Napi::PropertyDescriptor -Napi::Addon::InstanceMethod(Napi::Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] method`: The native function that represents a method provided by the -add-on. -- `[in] name`: The `Napi::Symbol` object whose value is used to identify the -instance method for the class. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the method when it is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents a method provided -by the add-on. The method must be of the form - -```cpp -void MethodName(const Napi::CallbackInfo& info); -``` - -### InstanceMethod - -Creates a property descriptor that represents a method provided by the add-on. - -```cpp -template -static Napi::PropertyDescriptor -Napi::Addon::InstanceMethod(Napi::Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] method`: The native function that represents a method provided by the -add-on. -- `[in] name`: The `Napi::Symbol` object whose value is used to identify the -instance method for the class. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the method when it is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents a method provided -by the add-on. The method must be of the form - -```cpp -Napi::Value MethodName(const Napi::CallbackInfo& info); -``` - -### InstanceAccessor - -Creates a property descriptor that represents an instance accessor property -provided by the add-on. - -```cpp -static Napi::PropertyDescriptor -Napi::Addon::InstanceAccessor(const char* utf8name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] utf8name`: Null-terminated string that represents the name of the method -provided by the add-on. -- `[in] getter`: The native function to call when a get access to the property -is performed. -- `[in] setter`: The native function to call when a set access to the property -is performed. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the getter or the setter when it -is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents an instance accessor -property provided by the add-on. - -### InstanceAccessor - -Creates a property descriptor that represents an instance accessor property -provided by the add-on. - -```cpp -static Napi::PropertyDescriptor -Napi::Addon::InstanceAccessor(Symbol name, - InstanceGetterCallback getter, - InstanceSetterCallback setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] name`: The `Napi::Symbol` object whose value is used to identify the -instance accessor. -- `[in] getter`: The native function to call when a get access to the property of -a JavaScript class is performed. -- `[in] setter`: The native function to call when a set access to the property of -a JavaScript class is performed. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the getter or the setter when it -is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents an instance accessor -property provided by the add-on. - -### InstanceAccessor - -Creates a property descriptor that represents an instance accessor property -provided by the add-on. - -```cpp -template -static Napi::PropertyDescriptor -Napi::Addon::InstanceAccessor(const char* utf8name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] getter`: The native function to call when a get access to the property of -a JavaScript class is performed. -- `[in] setter`: The native function to call when a set access to the property of -a JavaScript class is performed. -- `[in] utf8name`: Null-terminated string that represents the name of the method -provided by the add-on. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the getter or the setter when it -is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents an instance accessor -property provided by the add-on. - -### InstanceAccessor - -Creates a property descriptor that represents an instance accessor property -provided by the add-on. - -```cpp -template -static Napi::PropertyDescriptor -Napi::Addon::InstanceAccessor(Symbol name, - napi_property_attributes attributes = napi_default, - void* data = nullptr); -``` - -- `[in] getter`: The native function to call when a get access to the property of -a JavaScript class is performed. -- `[in] setter`: The native function to call when a set access to the property of -a JavaScript class is performed. -- `[in] name`: The `Napi::Symbol` object whose value is used to identify the -instance accessor. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. -- `[in] data`: User-provided data passed into the getter or the setter when it -is invoked. - -Returns a `Napi::PropertyDescriptor` object that represents an instance accessor -property provided by the add-on. - -### InstanceValue - -Creates property descriptor that represents an instance value property provided -by the add-on. - -```cpp -static Napi::PropertyDescriptor -Napi::Addon::InstanceValue(const char* utf8name, - Napi::Value value, - napi_property_attributes attributes = napi_default); -``` - -- `[in] utf8name`: Null-terminated string that represents the name of the property. -- `[in] value`: The value that's retrieved by a get access of the property. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. - -Returns a `Napi::PropertyDescriptor` object that represents an instance value -property of an add-on. - -### InstanceValue - -Creates property descriptor that represents an instance value property provided -by the add-on. - -```cpp -static Napi::PropertyDescriptor -Napi::Addon::InstanceValue(Symbol name, - Napi::Value value, - napi_property_attributes attributes = napi_default); -``` - -- `[in] name`: The `Napi::Symbol` object whose value is used to identify the -name of the property. -- `[in] value`: The value that's retrieved by a get access of the property. -- `[in] attributes`: The attributes associated with the property. One or more of -`napi_property_attributes`. - -Returns a `Napi::PropertyDescriptor` object that represents an instance value -property of an add-on. +[`Napi::InstanceWrap`]: ./instance_wrap.md diff --git a/doc/array.md b/doc/array.md new file mode 100644 index 000000000..a12e92daf --- /dev/null +++ b/doc/array.md @@ -0,0 +1,81 @@ +# Array + +Class [`Napi::Array`][] inherits from class [`Napi::Object`][]. + +Arrays are native representations of JavaScript Arrays. `Napi::Array` is a wrapper +around `napi_value` representing a JavaScript Array. + +[`Napi::TypedArray`][] and [`Napi::ArrayBuffer`][] correspond to JavaScript data +types such as [`Napi::Int32Array`][] and [`Napi::ArrayBuffer`][], respectively, +that can be used for transferring large amounts of data from JavaScript to the +native side. An example illustrating the use of a JavaScript-provided +`ArrayBuffer` in native code is available [here](https://github.com/nodejs/node-addon-examples/tree/master/array_buffer_to_native/node-addon-api). + +## Constructor +```cpp +Napi::Array::Array(); +``` + +Returns an empty array. + +If an error occurs, a `Napi::Error` will be thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + +```cpp +Napi::Array::Array(napi_env env, napi_value value); +``` +- `[in] env` - The environment in which to create the array. +- `[in] value` - The primitive to wrap. + +Returns a `Napi::Array` wrapping a `napi_value`. + +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + +## Methods + +### New +```cpp +static Napi::Array Napi::Array::New(napi_env env); +``` +- `[in] env` - The environment in which to create the array. + +Returns a new `Napi::Array`. + +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + +### New + +```cpp +static Napi::Array Napi::Array::New(napi_env env, size_t length); +``` +- `[in] env` - The environment in which to create the array. +- `[in] length` - The length of the array. + +Returns a new `Napi::Array` with the given length. + +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + +### Length +```cpp +uint32_t Napi::Array::Length() const; +``` + +Returns the length of the array. + +Note: +This can execute JavaScript code implicitly according to JavaScript semantics. +If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not +being used, callers should check the result of `Env::IsExceptionPending` before +attempting to use the returned value. + +[`Napi::ArrayBuffer`]: ./array_buffer.md +[`Napi::Int32Array`]: ./typed_array_of.md +[`Napi::Object`]: ./object.md +[`Napi::TypedArray`]: ./typed_array.md diff --git a/doc/array_buffer.md b/doc/array_buffer.md index ca9d45c00..988a839a4 100644 --- a/doc/array_buffer.md +++ b/doc/array_buffer.md @@ -1,5 +1,7 @@ # ArrayBuffer +Class `Napi::ArrayBuffer` inherits from class [`Napi::Object`][]. + The `Napi::ArrayBuffer` class corresponds to the [JavaScript `ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) class. @@ -127,3 +129,5 @@ void* Napi::ArrayBuffer::Data() const; ``` Returns a pointer the wrapped data. + +[`Napi::Object`]: ./object.md diff --git a/doc/basic_types.md b/doc/basic_types.md deleted file mode 100644 index 03ec14b4b..000000000 --- a/doc/basic_types.md +++ /dev/null @@ -1,423 +0,0 @@ -# Basic Types - -Node Addon API consists of a few fundamental data types. These allow a user of -the API to create, convert and introspect fundamental JavaScript types, and -interoperate with their C++ counterparts. - -## Value - -`Napi::Value` is the base class of Node Addon API's fundamental object type hierarchy. -It represents a JavaScript value of an unknown type. It is a thin wrapper around -the N-API datatype `napi_value`. Methods on this class can be used to check -the JavaScript type of the underlying N-API `napi_value` and also to convert to -C++ types. - -### Constructor - -```cpp -Napi::Value::Value(); -``` - -Used to create a Node Addon API `Napi::Value` that represents an **empty** value. - -```cpp -Napi::Value::Value(napi_env env, napi_value value); -``` - -- `[in] env` - The `napi_env` environment in which to construct the `Napi::Value` -object. -- `[in] value` - The underlying JavaScript value that the `Napi::Value` instance -represents. - -Returns a Node.js Addon API `Napi::Value` that represents the `napi_value` passed -in. - -### Operators - -#### operator napi_value - -```cpp -Napi::Value::operator napi_value() const; -``` - -Returns the underlying N-API `napi_value`. If the instance is _empty_, this -returns `nullptr`. - -#### operator == - -```cpp -bool Napi::Value::operator ==(const Value& other) const; -``` - -Returns `true` if this value strictly equals another value, or `false` otherwise. - -#### operator != - -```cpp -bool Napi::Value::operator !=(const Value& other) const; -``` - -Returns `false` if this value strictly equals another value, or `true` otherwise. - -### Methods - -#### From -```cpp -template -static Napi::Value Napi::Value::From(napi_env env, const T& value); -``` - -- `[in] env` - The `napi_env` environment in which to construct the `Napi::Value` object. -- `[in] value` - The C++ type to represent in JavaScript. - -Returns a `Napi::Value` representing the input C++ type in JavaScript. - -This method is used to convert from a C++ type to a JavaScript value. -Here, `value` may be any of: -- `bool` - returns a `Napi::Boolean`. -- Any integer type - returns a `Napi::Number`. -- Any floating point type - returns a `Napi::Number`. -- `const char*` (encoded using UTF-8, null-terminated) - returns a `Napi::String`. -- `const char16_t*` (encoded using UTF-16-LE, null-terminated) - returns a `Napi::String`. -- `std::string` (encoded using UTF-8) - returns a `Napi::String`. -- `std::u16string` - returns a `Napi::String`. -- `napi::Value` - returns a `Napi::Value`. -- `napi_value` - returns a `Napi::Value`. - -#### As -```cpp -template T Napi::Value::As() const; -``` - -Returns the `Napi::Value` cast to a desired C++ type. - -Use this when the actual type is known or assumed. - -Note: -This conversion does NOT coerce the type. Calling any methods inappropriate for -the actual value type will throw `Napi::Error`. - -#### StrictEquals -```cpp -bool Napi::Value::StrictEquals(const Value& other) const; -``` - -- `[in] other` - The value to compare against. - -Returns true if the other `Napi::Value` is strictly equal to this one. - -#### Env -```cpp -Napi::Env Napi::Value::Env() const; -``` - -Returns the environment that the value is associated with. See -[`Napi::Env`](env.md) for more details about environments. - -#### IsEmpty -```cpp -bool Napi::Value::IsEmpty() const; -``` - -Returns `true` if the value is uninitialized. - -An empty value is invalid, and most attempts to perform an operation on an -empty value will result in an exception. An empty value is distinct from -JavaScript `null` or `undefined`, which are valid values. - -When C++ exceptions are disabled at compile time, a method with a `Napi::Value` -return type may return an empty value to indicate a pending exception. If C++ -exceptions are not being used, callers should check the result of -`Env::IsExceptionPending` before attempting to use the value. - -#### Type -```cpp -napi_valuetype Napi::Value::Type() const; -``` - -Returns the underlying N-API `napi_valuetype` of the value. - -#### IsUndefined -```cpp -bool Napi::Value::IsUndefined() const; -``` - -Returns `true` if the underlying value is a JavaScript `undefined` or `false` -otherwise. - -#### IsNull -```cpp -bool Napi::Value::IsNull() const; -``` - -Returns `true` if the underlying value is a JavaScript `null` or `false` -otherwise. - -#### IsBoolean -```cpp -bool Napi::Value::IsBoolean() const; -``` - -Returns `true` if the underlying value is a JavaScript `true` or JavaScript -`false`, or `false` if the value is not a `Napi::Boolean` value in JavaScript. - -#### IsNumber -```cpp -bool Napi::Value::IsNumber() const; -``` - -Returns `true` if the underlying value is a JavaScript `Napi::Number` or `false` -otherwise. - -#### IsString -```cpp -bool Napi::Value::IsString() const; -``` - -Returns `true` if the underlying value is a JavaScript `Napi::String` or `false` -otherwise. - -#### IsSymbol -```cpp -bool Napi::Value::IsSymbol() const; -``` - -Returns `true` if the underlying value is a JavaScript `Napi::Symbol` or `false` -otherwise. - -#### IsArray -```cpp -bool Napi::Value::IsArray() const; -``` - -Returns `true` if the underlying value is a JavaScript `Napi::Array` or `false` -otherwise. - -#### IsArrayBuffer -```cpp -bool Napi::Value::IsArrayBuffer() const; -``` - -Returns `true` if the underlying value is a JavaScript `Napi::ArrayBuffer` or `false` -otherwise. - -#### IsTypedArray -```cpp -bool Napi::Value::IsTypedArray() const; -``` - -Returns `true` if the underlying value is a JavaScript `Napi::TypedArray` or `false` -otherwise. - -#### IsObject -```cpp -bool Napi::Value::IsObject() const; -``` - -Returns `true` if the underlying value is a JavaScript `Napi::Object` or `false` -otherwise. - -#### IsFunction -```cpp -bool Napi::Value::IsFunction() const; -``` - -Returns `true` if the underlying value is a JavaScript `Napi::Function` or `false` -otherwise. - -#### IsPromise -```cpp -bool Napi::Value::IsPromise() const; -``` - -Returns `true` if the underlying value is a JavaScript `Napi::Promise` or `false` -otherwise. - -#### IsDataView -```cpp -bool Napi::Value::IsDataView() const; -``` - -Returns `true` if the underlying value is a JavaScript `Napi::DataView` or `false` -otherwise. - -#### IsBuffer -```cpp -bool Napi::Value::IsBuffer() const; -``` - -Returns `true` if the underlying value is a Node.js `Napi::Buffer` or `false` -otherwise. - -#### IsExternal -```cpp -bool Napi::Value::IsExternal() const; -``` - -Returns `true` if the underlying value is a N-API external object or `false` -otherwise. - -#### IsDate -```cpp -bool Napi::Value::IsDate() const; -``` - -Returns `true` if the underlying value is a JavaScript `Date` or `false` -otherwise. - -#### ToBoolean -```cpp -Napi::Boolean Napi::Value::ToBoolean() const; -``` - -Returns a `Napi::Boolean` representing the `Napi::Value`. - -This is a wrapper around `napi_coerce_to_boolean`. This will throw a JavaScript -exception if the coercion fails. If C++ exceptions are not being used, callers -should check the result of `Env::IsExceptionPending` before attempting to use -the returned value. - -#### ToNumber -```cpp -Napi::Number Napi::Value::ToNumber() const; -``` - -Returns a `Napi::Number` representing the `Napi::Value`. - -Note: -This can cause script code to be executed according to JavaScript semantics. -This is a wrapper around `napi_coerce_to_number`. This will throw a JavaScript -exception if the coercion fails. If C++ exceptions are not being used, callers -should check the result of `Env::IsExceptionPending` before attempting to use -the returned value. - -#### ToString -```cpp -Napi::String Napi::Value::ToString() const; -``` - -Returns a `Napi::String` representing the `Napi::Value`. - -Note that this can cause script code to be executed according to JavaScript -semantics. This is a wrapper around `napi_coerce_to_string`. This will throw a -JavaScript exception if the coercion fails. If C++ exceptions are not being -used, callers should check the result of `Env::IsExceptionPending` before -attempting to use the returned value. - -#### ToObject -```cpp -Napi::Object Napi::Value::ToObject() const; -``` - -Returns a `Napi::Object` representing the `Napi::Value`. - -This is a wrapper around `napi_coerce_to_object`. This will throw a JavaScript -exception if the coercion fails. If C++ exceptions are not being used, callers -should check the result of `Env::IsExceptionPending` before attempting to use -the returned value. - -## Name - -Names are JavaScript values that can be used as a property name. There are two -specialized types of names supported in Node.js Addon API [`Napi::String`](string.md) -and [`Napi::Symbol`](symbol.md). - -### Methods - -#### Constructor -```cpp -Napi::Name::Name(); -``` - -Returns an empty `Napi::Name`. - -```cpp -Napi::Name::Name(napi_env env, napi_value value); -``` -- `[in] env` - The environment in which to create the array. -- `[in] value` - The primitive to wrap. - -Returns a `Napi::Name` created from the JavaScript primitive. - -Note: -The value is not coerced to a string. - -## Array - -Arrays are native representations of JavaScript Arrays. `Napi::Array` is a wrapper -around `napi_value` representing a JavaScript Array. - -[`Napi::TypedArray`][] and [`Napi::ArrayBuffer`][] correspond to JavaScript data -types such as [`Int32Array`][] and [`ArrayBuffer`][], respectively, that can be -used for transferring large amounts of data from JavaScript to the native side. -An example illustrating the use of a JavaScript-provided `ArrayBuffer` in native -code is available [here](https://github.com/nodejs/node-addon-examples/tree/master/array_buffer_to_native/node-addon-api). - -### Constructor -```cpp -Napi::Array::Array(); -``` - -Returns an empty array. - -If an error occurs, a `Napi::Error` will be thrown. If C++ exceptions are not -being used, callers should check the result of `Env::IsExceptionPending` before -attempting to use the returned value. - -```cpp -Napi::Array::Array(napi_env env, napi_value value); -``` -- `[in] env` - The environment in which to create the array. -- `[in] value` - The primitive to wrap. - -Returns a `Napi::Array` wrapping a `napi_value`. - -If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not -being used, callers should check the result of `Env::IsExceptionPending` before -attempting to use the returned value. - -### Methods - -#### New -```cpp -static Napi::Array Napi::Array::New(napi_env env); -``` -- `[in] env` - The environment in which to create the array. - -Returns a new `Napi::Array`. - -If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not -being used, callers should check the result of `Env::IsExceptionPending` before -attempting to use the returned value. - -#### New - -```cpp -static Napi::Array Napi::Array::New(napi_env env, size_t length); -``` -- `[in] env` - The environment in which to create the array. -- `[in] length` - The length of the array. - -Returns a new `Napi::Array` with the given length. - -If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not -being used, callers should check the result of `Env::IsExceptionPending` before -attempting to use the returned value. - -#### Length -```cpp -uint32_t Napi::Array::Length() const; -``` - -Returns the length of the array. - -Note: -This can execute JavaScript code implicitly according to JavaScript semantics. -If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not -being used, callers should check the result of `Env::IsExceptionPending` before -attempting to use the returned value. - -[`Napi::TypedArray`]: ./typed_array.md -[`Napi::ArrayBuffer`]: ./array_buffer.md -[`Int32Array`]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int32Array -[`ArrayBuffer`]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer diff --git a/doc/bigint.md b/doc/bigint.md index 33ada43b3..d6f2ea68e 100644 --- a/doc/bigint.md +++ b/doc/bigint.md @@ -1,5 +1,7 @@ # BigInt +Class `Napi::Bigint` inherits from class [`Napi::Value`][]. + A JavaScript BigInt value. ## Methods @@ -91,3 +93,5 @@ void Napi::BigInt::ToWords(int* sign_bit, size_t* word_count, uint64_t* words); Returns a single `BigInt` value into a sign bit, 64-bit little-endian array, and the number of elements in the array. + +[`Napi::Value`]: ./value.md diff --git a/doc/boolean.md b/doc/boolean.md index 01b6a4c0a..d07daa210 100644 --- a/doc/boolean.md +++ b/doc/boolean.md @@ -1,5 +1,7 @@ # Boolean +Class `Napi::Boolean` inherits from class [`Napi::Value`][]. + `Napi::Boolean` class is a representation of the JavaScript `Boolean` object. The `Napi::Boolean` class inherits its behavior from the `Napi::Value` class (for more info see: [`Napi::Value`](value.md)). @@ -62,3 +64,5 @@ Napi::Boolean::operator bool() const; ``` Returns the boolean primitive type of the corresponding `Napi::Boolean` object. + +[`Napi::Value`]: ./value.md diff --git a/doc/buffer.md b/doc/buffer.md index 8f76b200d..97ed48a5a 100644 --- a/doc/buffer.md +++ b/doc/buffer.md @@ -1,5 +1,7 @@ # Buffer +Class `Napi::Buffer` inherits from class [`Napi::Uint8Array`][]. + The `Napi::Buffer` class creates a projection of raw data that can be consumed by script. @@ -138,3 +140,5 @@ size_t Napi::Buffer::Length() const; ``` Returns the number of `T` elements in the external data. + +[`Napi::Uint8Array`]: ./typed_array_of.md diff --git a/doc/dataview.md b/doc/dataview.md index 64b865b1c..66fb28919 100644 --- a/doc/dataview.md +++ b/doc/dataview.md @@ -1,5 +1,7 @@ # DataView +Class `Napi::DataView` inherits from class [`Napi::Object`][]. + The `Napi::DataView` class corresponds to the [JavaScript `DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) class. @@ -242,3 +244,5 @@ void Napi::DataView::SetUint32(size_t byteOffset, uint32_t value) const; - `[in] byteOffset`: The offset, in byte, from the start of the view where to read the data. - `[in] value`: The value to set. + +[`Napi::Object`]: ./object.md diff --git a/doc/date.md b/doc/date.md index 959b4b9a6..4c5fefa5e 100644 --- a/doc/date.md +++ b/doc/date.md @@ -1,8 +1,8 @@ # Date `Napi::Date` class is a representation of the JavaScript `Date` object. The -`Napi::Date` class inherits its behavior from `Napi::Value` class -(for more info see [`Napi::Value`](value.md)) +`Napi::Date` class inherits its behavior from the `Napi::Value` class +(for more info see [`Napi::Value`](value.md)). ## Methods diff --git a/doc/error.md b/doc/error.md index 1526bddf0..7530262d7 100644 --- a/doc/error.md +++ b/doc/error.md @@ -1,5 +1,7 @@ # Error +Class `Napi::Error` inherits from class [`Napi::ObjectReference`][] and class [`std::exception`][]. + The `Napi::Error` class is a representation of the JavaScript `Error` object that is thrown when runtime errors occur. The Error object can also be used as a base object for user-defined exceptions. @@ -113,3 +115,6 @@ const char* Napi::Error::what() const NAPI_NOEXCEPT override; Returns a pointer to a null-terminated string that is used to identify the exception. This method can be used only if the exception mechanism is enabled. + +[`Napi::ObjectReference`]: ./object_reference.md +[`std::exception`]: http://cplusplus.com/reference/exception/exception/ diff --git a/doc/external.md b/doc/external.md index 4022b61dd..814eb037c 100644 --- a/doc/external.md +++ b/doc/external.md @@ -1,5 +1,7 @@ # External (template) +Class `Napi::External` inherits from class [`Napi::Value`][]. + The `Napi::External` template class implements the ability to create a `Napi::Value` object with arbitrary C++ data. It is the user's responsibility to manage the memory for the arbitrary C++ data. `Napi::External` objects can be created with an optional Finalizer function and optional Hint value. The Finalizer function, if specified, is called when your `Napi::External` object is released by Node's garbage collector. It gives your code the opportunity to free any dynamically created data. If you specify a Hint value, it is passed to your Finalizer function. @@ -57,3 +59,5 @@ T* Napi::External::Data() const; ``` Returns a pointer to the arbitrary C++ data held by the `Napi::External` object. + +[`Napi::Value`]: ./value.md diff --git a/doc/hierarchy.md b/doc/hierarchy.md new file mode 100644 index 000000000..e40a65ff9 --- /dev/null +++ b/doc/hierarchy.md @@ -0,0 +1,91 @@ +# Full Class Hierarchy + +| Class | Parent Class(es) | +|---|---| +| [`Napi::Addon`][] | [`Napi::InstanceWrap`][] | +| [`Napi::Array`][] | [`Napi::Object`][] | +| [`Napi::ArrayBuffer`][] | [`Napi::Object`][] | +| [`Napi::AsyncContext`][] | | +| [`Napi::AsyncProgressQueueWorker`][] | `Napi::AsyncProgressWorkerBase` | +| [`Napi::AsyncProgressWorker`][] | `Napi::AsyncProgressWorkerBase` | +| [`Napi::AsyncWorker`][] | | +| [`Napi::BigInt`][] | [`Napi::Value`][] | +| [`Napi::Boolean`][] | [`Napi::Value`][] | +| [`Napi::Buffer`][] | [`Napi::Uint8Array`][] | +| [`Napi::CallbackInfo`][] | | +| [`Napi::CallbackScope`][] | | +| [`Napi::ClassPropertyDescriptor`][] | | +| [`Napi::DataView`][] | [`Napi::Object`][] | +| [`Napi::Date`][] | [`Napi::Value`][] | +| [`Napi::Env`][] | | +| [`Napi::Error`][] | [`Napi::ObjectReference`][], [`std::exception`][] | +| [`Napi::EscapableHandleScope`][] | | +| [`Napi::External`][] | [`Napi::Value`][] | +| [`Napi::Function`][] | [`Napi::Object`][] | +| [`Napi::FunctionReference`][] | [`Napi::Reference`][] | +| [`Napi::HandleScope`][] | | +| [`Napi::InstanceWrap`][] | | +| [`Napi::MemoryManagement`][] | | +| [`Napi::Name`][] | [`Napi::Value`][] | +| [`Napi::Number`][] | [`Napi::Value`][] | +| [`Napi::Object`][] | [`Napi::Value`][] | +| [`Napi::ObjectReference`][] | [`Napi::Reference`][] | +| [`Napi::ObjectWrap`][] | [`Napi::InstanceWrap`][], [`Napi::Reference`][] | +| [`Napi::Promise`][] | [`Napi::Object`][] | +| [`Napi::PropertyDescriptor`][] | | +| [`Napi::RangeError`][] | [`Napi::Error`][] | +| [`Napi::Reference`] | | +| [`Napi::String`][] | [`Napi::Name`][] | +| [`Napi::Symbol`][] | [`Napi::Name`][] | +| [`Napi::ThreadSafeFunction`][] | | +| [`Napi::TypeError`][] | [`Napi::Error`][] | +| [`Napi::TypedArray`][] | [`Napi::Object`][] | +| [`Napi::TypedArrayOf`][] | [`Napi::TypedArray`][] | +| [`Napi::Value`][] | | +| [`Napi::VersionManagement`][] | | + +[`Napi::Addon`]: ./addon.md +[`Napi::Array`]: ./array.md +[`Napi::ArrayBuffer`]: ./array_buffer.md +[`Napi::AsyncContext`]: ./async_context.md +[`Napi::AsyncProgressQueueWorker`]: ./async_worker_variants.md#asyncprogressqueueworker +[`Napi::AsyncProgressWorker`]: ./async_worker_variants.md#asyncprogressworker +[`Napi::AsyncWorker`]: ./async_worker.md +[`Napi::BigInt`]: ./bigint.md +[`Napi::Boolean`]: ./boolean.md +[`Napi::Buffer`]: ./buffer.md +[`Napi::CallbackInfo`]: ./callbackinfo.md +[`Napi::CallbackScope`]: ./callback_scope.md +[`Napi::ClassPropertyDescriptor`]: ./class_property_descriptor.md +[`Napi::DataView`]: ./dataview.md +[`Napi::Date`]: ./date.md +[`Napi::Env`]: ./env.md +[`Napi::Error`]: ./error.md +[`Napi::EscapableHandleScope`]: ./escapable_handle_scope.md +[`Napi::External`]: ./external.md +[`Napi::Function`]: ./function.md +[`Napi::FunctionReference`]: ./function_reference.md +[`Napi::HandleScope`]: ./handle_scope.md +[`Napi::InstanceWrap`]: ./instance_wrap.md +[`Napi::MemoryManagement`]: ./memory_management.md +[`Napi::Name`]: ./name.md +[`Napi::Number`]: ./number.md +[`Napi::Object`]: ./object.md +[`Napi::ObjectReference`]: ./object_reference.md +[`Napi::ObjectWrap`]: ./object_wrap.md +[`Napi::Promise`]: ./promise.md +[`Napi::PropertyDescriptor`]: ./property_descriptor.md +[`Napi::RangeError`]: ./range_error.md +[`Napi::Reference`]: ./reference.md +[`Napi::Reference`]: ./reference.md +[`Napi::Reference`]: ./reference.md +[`Napi::String`]: ./string.md +[`Napi::Symbol`]: ./symbol.md +[`Napi::ThreadSafeFunction`]: ./thread_safe_function.md +[`Napi::TypeError`]: ./type_error.md +[`Napi::TypedArray`]: ./typed_array.md +[`Napi::TypedArrayOf`]: ./typed_array_of.md +[`Napi::Uint8Array`]: ./typed_array_of.md +[`Napi::Value`]: ./value.md +[`Napi::VersionManagement`]: ./version_management.md +[`std::exception`]: http://cplusplus.com/reference/exception/exception/ diff --git a/doc/instance_wrap.md b/doc/instance_wrap.md new file mode 100644 index 000000000..1238e7e29 --- /dev/null +++ b/doc/instance_wrap.md @@ -0,0 +1,408 @@ +# InstanceWrap + +This class serves as the base class for [`Napi::ObjectWrap`][] and +[`Napi::Addon`][]. + +In the case of [`Napi::Addon`][] it provides the +methods for exposing functions to JavaScript on instances of an add-on. + +As a base class for [`Napi::ObjectWrap`][] it provides the methods for +exposing instance methods of JavaScript objects instantiated from the JavaScript +class corresponding to the subclass of [`Napi::ObjectWrap`][]. + +## Methods + +### InstanceMethod + +Creates a property descriptor that represents a method exposed on JavaScript +instances of this class. + +```cpp +template +static Napi::ClassPropertyDescriptor +Napi::InstanceWrap::InstanceMethod(const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by instances of the class. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::ClassPropertyDescriptor` object that represents a method +provided by instances of the class. The method must be of the form + +```cpp +void MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method exposed on JavaScript +instances of this class. + +```cpp +template +static Napi::ClassPropertyDescriptor +Napi::InstanceWrap::InstanceMethod(const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] utf8name`: Null-terminated string that represents the name of the method +provided by instances of the class. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::ClassPropertyDescriptor` object that represents a method +provided by instances of the class. The method must be of the form + +```cpp +Napi::Value MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method exposed on JavaScript +instances of this class. + +```cpp +template +static Napi::ClassPropertyDescriptor +Napi::InstanceWrap::InstanceMethod(Napi::Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: JavaScript symbol that represents the name of the method provided +by instances of the class. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::ClassPropertyDescriptor` object that represents a method +provided by instances of the class. The method must be of the form + +```cpp +void MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method exposed on JavaScript +instances of this class. + +```cpp +template +static Napi::ClassPropertyDescriptor +Napi::InstanceWrap::InstanceMethod(Napi::Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: JavaScript symbol that represents the name of the method provided +by instances of the class. +- `[in] method`: The native function that represents a method provided by the +add-on. +- `[in] attributes`: The attributes associated with the property. One or more of +`napi_property_attributes`. +- `[in] data`: User-provided data passed into the method when it is invoked. + +Returns a `Napi::ClassPropertyDescriptor` object that represents a method +provided by instances of the class. The method must be of the form + +```cpp +Napi::Value MethodName(const Napi::CallbackInfo& info); +``` + +### InstanceMethod + +Creates a property descriptor that represents a method exposed on JavaScript +instances of this class. + +```cpp +