Skip to content

Commit 2540375

Browse files
committed
Implement AsyncProgressWorker
1 parent dd9fa8a commit 2540375

File tree

8 files changed

+679
-0
lines changed

8 files changed

+679
-0
lines changed

doc/async_progress_worker.md

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
# AsyncProgressWorker
2+
3+
`Napi::AsyncProgressWorker` is an abstract class, which implements `Napi::AsyncWorker`
4+
while extends `Napi::AsyncWorker` internally with `Napi::ThreadSafeFunction` to
5+
moving work progress reports from worker to event loop threads.
6+
7+
Like `Napi::AsyncWorker`, once created, execution is requested by calling
8+
`Napi::AsyncProgressWorker::Queue`. When a thread is available for execution
9+
the `Napi::AsyncProgressWorker::Execute` method will be invoked. During the
10+
execution, `Napi::AsyncProgressWorker::ExecutionProgress::Send` could be used to
11+
indicate execution process, which would eventually invoke `Napi::AsyncProgressWorker::OnProgress`
12+
on JavaScript thread to safely call into JavaScript lands. Once `Napi::AsyncProgressWorker::Execute`
13+
completes either `Napi::AsyncProgressWorker::OnOK` or `Napi::AsyncProgressWorker::OnError`
14+
will be invoked. Once the `Napi::AsyncProgressWorker::OnOK` or `Napi::AsyncProgressWorker::OnError`
15+
methods are complete the `Napi::AsyncProgressWorker` instance is destructed.
16+
17+
For the most basic use, only the `Napi::AsyncProgressWorker::Execute` and
18+
`Napi::AsyncProgressWorker::OnProgress` method must be implemented in a subclass.
19+
20+
## Methods
21+
22+
Most methods could be referred to `Napi::AsyncWorker` to get detailed descriptions.
23+
24+
### Execute
25+
26+
This method is used to execute some tasks out of the **event loop** on a libuv
27+
worker thread. Subclasses must implement this method and the method is run on
28+
a thread other than that running the main event loop. As the method is not
29+
running on the main event loop, it must avoid calling any methods from node-addon-api
30+
or running any code that might invoke JavaScript. Instead, once this method is
31+
complete any interaction through node-addon-api with JavaScript should be implemented
32+
in the `Napi::AsyncProgressWorker::OnOK` method which runs on the main thread and is
33+
invoked when the `Napi::AsyncProgressWorker::Execute` method completes.
34+
35+
```cpp
36+
virtual void Napi::AsyncProgressWorker::Execute(const ExecutionProgress& progress) = 0;
37+
```
38+
39+
### OnOK
40+
41+
This method is invoked when the computation in the `Execute` method ends.
42+
The default implementation runs the Callback optionally provided when the
43+
AsyncProgressWorker class was created. The callback will by default receive no
44+
arguments. To provide arguments, override the `GetResult()` method.
45+
46+
```cpp
47+
virtual void Napi::AsyncProgressWorker::OnOK();
48+
```
49+
50+
### OnProgress
51+
52+
This method is invoked when the computation in the `Napi::AsyncProgressWorker::ExecutionProcess::Send`
53+
method was called on worker thread execution.
54+
55+
```cpp
56+
virtual void Napi::AsyncProgressWorker::OnProgress(const T* data, size_t count)
57+
```
58+
59+
### Constructor
60+
61+
Creates a new `Napi::AsyncProgressWorker`.
62+
63+
```cpp
64+
explicit Napi::AsyncProgressWorker(const Napi::Function& callback);
65+
```
66+
67+
- `[in] callback`: The function which will be called when an asynchronous
68+
operations ends. The given function is called from the main event loop thread.
69+
70+
Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by
71+
calling `Napi::AsyncWork::Queue`.
72+
73+
### Constructor
74+
75+
Creates a new `Napi::AsyncProgressWorker`.
76+
77+
```cpp
78+
explicit Napi::AsyncProgressWorker(const Napi::Function& callback, const char* resource_name);
79+
```
80+
81+
- `[in] callback`: The function which will be called when an asynchronous
82+
operations ends. The given function is called from the main event loop thread.
83+
- `[in] resource_name`: Null-terminated strings that represents the
84+
identifier for the kind of resource that is being provided for diagnostic
85+
information exposed by the async_hooks API.
86+
87+
Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by
88+
calling `Napi::AsyncWork::Queue`.
89+
90+
### Constructor
91+
92+
Creates a new `Napi::AsyncProgressWorker`.
93+
94+
```cpp
95+
explicit Napi::AsyncProgressWorker(const Napi::Function& callback, const char* resource_name, const Napi::Object& resource);
96+
```
97+
98+
- `[in] callback`: The function which will be called when an asynchronous
99+
operations ends. The given function is called from the main event loop thread.
100+
- `[in] resource_name`: Null-terminated strings that represents the
101+
identifier for the kind of resource that is being provided for diagnostic
102+
information exposed by the async_hooks API.
103+
- `[in] resource`: Object associated with the asynchronous operation that
104+
will be passed to possible async_hooks.
105+
106+
Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by
107+
calling `Napi::AsyncWork::Queue`.
108+
109+
### Constructor
110+
111+
Creates a new `Napi::AsyncProgressWorker`.
112+
113+
```cpp
114+
explicit Napi::AsyncProgressWorker(const Napi::Object& receiver, const Napi::Function& callback);
115+
```
116+
117+
- `[in] receiver`: The `this` object passed to the called function.
118+
- `[in] callback`: The function which will be called when an asynchronous
119+
operations ends. The given function is called from the main event loop thread.
120+
121+
Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by
122+
calling `Napi::AsyncWork::Queue`.
123+
124+
### Constructor
125+
126+
Creates a new `Napi::AsyncProgressWorker`.
127+
128+
```cpp
129+
explicit Napi::AsyncProgressWorker(const Napi::Object& receiver, const Napi::Function& callback, const char* resource_name);
130+
```
131+
132+
- `[in] receiver`: The `this` object passed to the called function.
133+
- `[in] callback`: The function which will be called when an asynchronous
134+
operations ends. The given function is called from the main event loop thread.
135+
- `[in] resource_name`: Null-terminated strings that represents the
136+
identifier for the kind of resource that is being provided for diagnostic
137+
information exposed by the async_hooks API.
138+
139+
Returns a `Napi::AsyncWork` instance which can later be queued for execution by
140+
calling `Napi::AsyncWork::Queue`.
141+
142+
### Constructor
143+
144+
Creates a new `Napi::AsyncProgressWorker`.
145+
146+
```cpp
147+
explicit Napi::AsyncProgressWorker(const Napi::Object& receiver, const Napi::Function& callback, const char* resource_name, const Napi::Object& resource);
148+
```
149+
150+
- `[in] receiver`: The `this` object passed to the called function.
151+
- `[in] callback`: The function which will be called when an asynchronous
152+
operations ends. The given function is called from the main event loop thread.
153+
- `[in] resource_name`: Null-terminated strings that represents the
154+
identifier for the kind of resource that is being provided for diagnostic
155+
information exposed by the async_hooks API.
156+
- `[in] resource`: Object associated with the asynchronous operation that
157+
will be passed to possible async_hooks.
158+
159+
Returns a `Napi::AsyncWork` instance which can later be queued for execution by
160+
calling `Napi::AsyncWork::Queue`.
161+
162+
### Constructor
163+
164+
Creates a new `Napi::AsyncProgressWorker`.
165+
166+
```cpp
167+
explicit Napi::AsyncProgressWorker(Napi::Env env);
168+
```
169+
170+
- `[in] env`: The environment in which to create the `Napi::AsyncProgressWorker`.
171+
172+
Returns an `Napi::AsyncProgressWorker` instance which can later be queued for execution by calling
173+
`Napi::AsyncProgressWorker::Queue`.
174+
175+
Available with `NAPI_VERSION` equal or greater than 5.
176+
177+
### Constructor
178+
179+
Creates a new `Napi::AsyncProgressWorker`.
180+
181+
```cpp
182+
explicit Napi::AsyncProgressWorker(Napi::Env env, const char* resource_name);
183+
```
184+
185+
- `[in] env`: The environment in which to create the `Napi::AsyncProgressWorker`.
186+
- `[in] resource_name`: Null-terminated strings that represents the
187+
identifier for the kind of resource that is being provided for diagnostic
188+
information exposed by the async_hooks API.
189+
190+
Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by
191+
calling `Napi::AsyncProgressWorker::Queue`.
192+
193+
Available with `NAPI_VERSION` equal or greater than 5.
194+
195+
### Constructor
196+
197+
Creates a new `Napi::AsyncProgressWorker`.
198+
199+
```cpp
200+
explicit Napi::AsyncProgressWorker(Napi::Env env, const char* resource_name, const Napi::Object& resource);
201+
```
202+
203+
- `[in] env`: The environment in which to create the `Napi::AsyncProgressWorker`.
204+
- `[in] resource_name`: Null-terminated strings that represents the
205+
identifier for the kind of resource that is being provided for diagnostic
206+
information exposed by the async_hooks API.
207+
- `[in] resource`: Object associated with the asynchronous operation that
208+
will be passed to possible async_hooks.
209+
210+
Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by
211+
calling `Napi::AsyncProgressWorker::Queue`.
212+
213+
Available with `NAPI_VERSION` equal or greater than 5.
214+
215+
### Destructor
216+
217+
Deletes the created work object that is used to execute logic asynchronously and
218+
release the internal `Napi::ThreadSafeFunction`, which would be aborted to prevent
219+
unexpected upcoming thread safe calls.
220+
221+
```cpp
222+
virtual Napi::AsyncProgressWorker::~AsyncProgressWorker();
223+
```
224+
225+
# AsyncProgressWorker::ExecutionProcess
226+
227+
A bridge class created hereby before the worker thread execution of `Napi::AsyncProgressWorker::Execute`.
228+
229+
## Methods
230+
231+
### Send
232+
233+
`Napi::AsyncProgressWorker::ExecutionProcess::Send` takes two argument, a pointer
234+
to generic type of data, and a `size_t` indicates how many items the pointer has pointed to.
235+
236+
Pointed data would be copied to internal slots of `Napi::AsyncProgressWorker` so
237+
after call of `Napi::AsyncProgressWorker::ExecutionProcess::Send` the data could
238+
be safely released.
239+
240+
Note that `Napi::AsyncProgressWorker::ExecutionProcess::Send` merely guarantees
241+
**eventual** invocation of `Napi::AsyncProgressWorker::OnProgress`, which means
242+
multiple send might be coalesced into single invocation of `Napi::AsyncProgressWorker::OnProgress`
243+
with latest data.
244+
245+
```cpp
246+
void Napi::AsyncProgressWorker::ExecutionProcess::Send(const T* data, size_t count) const;
247+
```
248+
249+
## Example
250+
251+
The first step to use the `Napi::AsyncProgressWorker` class is to create a new class that
252+
inherits from it and implement the `Napi::AsyncProgressWorker::Execute` abstract method.
253+
Typically input to your worker will be saved within class' fields generally
254+
passed in through its constructor.
255+
256+
During the worker thread execution, the first argument of `Napi::AsyncProgressWorker::Execute`
257+
could be used to report process of the execution.
258+
259+
When the `Napi::AsyncProgressWorker::Execute` method completes without errors the
260+
`Napi::AsyncProgressWorker::OnOK` function callback will be invoked. In this function the
261+
results of the computation will be reassembled and returned back to the initial
262+
JavaScript context.
263+
264+
`Napi::AsyncProgressWorker` ensures that all the code in the `Napi::AsyncProgressWorker::Execute`
265+
function runs in the background out of the **event loop** thread and at the end
266+
the `Napi::AsyncProgressWorker::OnOK` or `Napi::AsyncProgressWorker::OnError` function will be
267+
called and are executed as part of the event loop.
268+
269+
The code below show a basic example of `Napi::AsyncProgressWorker` the implementation:
270+
271+
```cpp
272+
#include<napi.h>
273+
274+
#include <chrono>
275+
#include <thread>
276+
277+
use namespace Napi;
278+
279+
class EchoWorker : public AsyncProgressWorker<uint32_t> {
280+
public:
281+
EchoWorker(Function& callback, std::string& echo)
282+
: AsyncProgressWorker(callback), echo(echo) {}
283+
284+
~EchoWorker() {}
285+
// This code will be executed on the worker thread
286+
void Execute(const ExecutionProgress& progress) {
287+
// Need to simulate cpu heavy task
288+
for (uint32_t i = 0; i < 100; ++i) {
289+
progress.Send(&i, 1)
290+
std::this_thread::sleep_for(std::chrono::seconds(1));
291+
}
292+
}
293+
294+
void OnOK() {
295+
HandleScope scope(Env());
296+
Callback().Call({Env().Null(), String::New(Env(), echo)});
297+
}
298+
299+
void OnProgress(const uint32_t* data, size_t /* count */) {
300+
HandleScope scope(Env());
301+
Callback().Call({Env().Null(), Env().Null(), Number::New(Env(), data)});
302+
}
303+
304+
private:
305+
std::string echo;
306+
};
307+
```
308+
309+
The `EchoWorker`'s contructor calls the base class' constructor to pass in the
310+
callback that the `Napi::AsyncProgressWorker` base class will store persistently. When
311+
the work on the `Napi::AsyncProgressWorker::Execute` method is done the
312+
`Napi::AsyncProgressWorker::OnOk` method is called and the results return back to
313+
JavaScript invoking the stored callback with its associated environment.
314+
315+
The following code shows an example on how to create and use an `Napi::AsyncProgressWorker`
316+
317+
```cpp
318+
#include<napi.h>
319+
320+
// Include EchoWorker class
321+
// ..
322+
323+
use namespace Napi;
324+
325+
Value Echo(const CallbackInfo& info) {
326+
// You need to check the input data here
327+
Function cb = info[1].As<Function>();
328+
std::string in = info[0].As<String>();
329+
EchoWorker* wk = new EchoWorker(cb, in);
330+
wk->Queue();
331+
return info.Env().Undefined();
332+
}
333+
```
334+
335+
Using the implementation of a `Napi::AsyncProgressWorker` is straight forward. You only
336+
need to create a new instance and pass to its constructor the callback you want to
337+
execute when your asynchronous task ends and other data you need for your
338+
computation. Once created the only other action you have to do is to call the
339+
`Napi::AsyncProgressWorker::Queue` method that will queue the created worker for execution.

0 commit comments

Comments
 (0)