Ganesh Rathinavel
Ganesh Rathinavel

Reputation: 1335

Nodejs - libuv non-blocking event callback

I am trying to build an application using Node.Js which requires the native module support. I have used libuv library through the application and I was able to make most of the async methods work except for the part where I had to implement the progress event callback. I want to implement the progress event callback asynchronously and without blocking the Node.js event loop.

Here are code snippets:

native.cc

#include <node.h>
#include <uv.h>
#include "nbind/nbind.h"    

using v8::Isolate;
using v8::HandleScope;


int FileProgressCallback(uint64_t const sent, uint64_t const total, void const *const data) {
    nbind::cbFunction cb = *((nbind::cbFunction *) data);
    cb(sent, total);
    return 0;
}

class WorkerFileTransfer {
public:
    WorkerFileTransfer(std::string path, nbind::cbFunction cb) :
            callback(cb), path(path) {};

    uv_work_t worker;
    nbind::cbFunction callback;

    bool error;
    std::string errorMsg;

    std::string path;
};

void FileTransferDone(uv_work_t *order, int status) {
    Isolate *isolate = Isolate::GetCurrent();
    HandleScope handleScope(isolate);

    WorkerFileTransfer *work = static_cast< WorkerFileTransfer * >( order->data );

    if (work->error) {
        work->callback.call<void>(work->errorMsg.c_str(), work->output);
    } else {
        ThirdPartyLibraryFileCopy(work->path.c_str(), FileProgressCallback, (const void *) &work->callback);
    }

    // Memory cleanup
    work->callback.reset();
    delete work;
}

void FileTransferRunner(uv_work_t *order) {
    WorkerFileTransfer *work = static_cast< WorkerFileTransfer * >( order->data );

    try {
        work->output = true;
    }
    catch (...) {
        work->error = true;
        work->errorMsg = "Error occured while executing the method...";
    }
}

void FileTransfer(const std::string path, nbind::cbFunction &callback) {
    WorkerFileTransfer *work = new WorkerFileTransfer(path, callback);

    work->worker.data = work;
    work->path = path;
    work->error = false;

    uv_queue_work(uv_default_loop(), &work->worker, FileTransferRunner, FileTransferDone);
}

function(FileTransfer);

test.js

FileTransfer(
  '/path/file.txt',
  (sent, total) => {

    console.log(`sent`, sent);
    console.log('total', total);
  }
);

I was able to achieve the file transfer done because of the below lines but the Node.Js event loop gets blocked here.

void FileTransferDone(uv_work_t *order, int status) {
   ...

   ThirdPartyLibraryFileCopy(work->path.c_str(), FileProgressCallback, (const void *) &work->callback);

   ...
}

When I move the line ThirdPartyLibraryFileCopy(work->path.c_str(), FileProgressCallback, (const void *) &work->callback); into the FileTransferRunner(uv_work_t *order) method then I am not getting any output in the javascript callback function.

How do I get the progress values in the javascript callback function, asynchronously, without blocking the Node.Js event loop?

Upvotes: 1

Views: 603

Answers (1)

saghul
saghul

Reputation: 2010

As per the uv_queue_work documentation: http://docs.libuv.org/en/v1.x/threadpool.html#c.uv_queue_work The work callback (FileTransferRunner in your case) runs on the threadpool but the done callback (FileTransferDone in your case) runs on the loop thread. Thus, if you perform a blocking operation on the latter, you are going to block the loop.

If you want to send periodic progress reports this model won't work well for you. You could use an async handle uv_async_t and report progress using uv_async_send (which is thread-safe) from the work function. Or use multiple work requests to send chuncks (that will probably be slower).

Upvotes: 2

Related Questions