Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement AsyncProgressQueueWorker #585

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 115 additions & 3 deletions doc/async_progress_worker.md → doc/async_worker_variants.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<napi.h>
#include <napi.h>

#include <chrono>
#include <thread>

use namespace Napi;
using namespace Napi;

class EchoWorker : public AsyncProgressWorker<uint32_t> {
public:
Expand Down Expand Up @@ -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
Expand All @@ -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 <napi.h>

#include <chrono>
#include <thread>

using namespace Napi;

class EchoWorker : public AsyncProgressQueueWorker<uint32_t> {
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this really do what we think it does? After all, we send the same pointer multiple times, so at the point of retrieval it will have the value it currently has on the thread. Also, if i goes out of scope, the main thread might crash. How about

Suggested change
progress.Send(&i, 1)
progress.Send(new uint32_t(i), 1);

and then we delete on the main thread?
Nit: missing semcolon.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Internally, the passed in data items are copied to internal slots and these allocations are managed by AsyncProgressQueueWorker.
Exposing passing raw pointers may have better control over the Send operation, but it also made the lifetime management of these data mandatory. Users have to manually delete the data pointers after the progress been handled.

I'm leaning on the current behavior (copy the data) and this is what NAN provides. WDYT?

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)});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
delete data;
}

if we go with the new uint32_t(i) above.


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 <napi.h>

// Include EchoWorker class
// ..

using namespace Napi;

Value Echo(const CallbackInfo& info) {
// We need to validate the arguments here.
Function cb = info[1].As<Function>();
std::string in = info[0].As<String>();
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
Loading