charto / nbind

:sparkles: Magical headers that make your C++ library accessible from JavaScript :rocket:
MIT License
1.98k stars 119 forks source link

Promise wrapped nbind functions blocks #100

Open neophob opened 6 years ago

neophob commented 6 years ago

I have a c function that might take several seconds to complete. So I wrap that call as a promise in the JS part of the code. I use this code in a express server - and while the c code is running, express does not answers any requests. this means the c code blocks. here's a little example

#include <unistd.h>
void foo() {
  sleep(30);
}

#include "nbind/nbind.h"
NBIND_GLOBAL() {
  function(foo);
}

My JS code calls this function like this:

return new BlueBirdPromise((resolve) => {
    lib.foo();
    resolve();
  });

Question:

neophob commented 6 years ago

I setup an minimal example project, that shows the issue: https://github.com/neophob/nbind-blocking

Tested with node v6 and v8.

So I guess to fix this behaviour I should use libuv threads (either native or using wrappers like NaN should provide some help) and callbacks. can you point me to an example where this is used?

neophob commented 6 years ago

Ok the correct way to address cpu bound tasks (or tasks which run long time) is using the libuv worker threads (uv_queue_work) so the EventLoop is not blocked

I found a excellent example here: https://github.com/paulhauner/example-async-node-addon/blob/master/async-addon/async-addon.cc

but I have now the problem, that I cannot when I call

struct AsyncDeviceInfo {
  uv_work_t request;
  std::unique_ptr<nbind::cbFunction> cb;
};

static void WorkAsync(uv_work_t *req) {
  AsyncDeviceInfo *work = static_cast<AsyncDeviceInfo *>(req->data);    
  sleep(3);
}

static void WorkAsyncComplete(uv_work_t *req) {
  AsyncDeviceInfo *asyncDeviceInfo = static_cast<AsyncDeviceInfo *>(req->data);
  (*asyncDeviceInfo->cb)(0,1);
  delete asyncDeviceInfo;
}

void myExposedFunction(nbind::cbFunction cb) {
  AsyncDeviceInfo *asyncDeviceInfo = new AsyncDeviceInfo();
  asyncDeviceInfo->cb = std::make_unique<nbind::cbFunction>(cb);
  // embed our datastructure to the request
  asyncDeviceInfo->request.data = asyncDeviceInfo;

  int status = uv_queue_work(uv_default_loop(), &asyncDeviceInfo->request, WorkAsync, (uv_after_work_cb) WorkAsyncComplete);
  assert(status == 0);
}

When I use this code, i get this error

FATAL ERROR: v8::HandleScope::CreateHandle() Cannot create a handle without a HandleScope
 1: node::Abort() [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]
 2: node::FatalException(v8::Isolate*, v8::Local<v8::Value>, v8::Local<v8::Message>) [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]
 3: v8::Utils::ReportApiFailure(char const*, char const*) [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]
 4: v8::internal::HandleScope::Extend(v8::internal::Isolate*) [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]
 5: v8::Integer::New(v8::Isolate*, int) [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]
 6: nbind::TypeTransformer<int, nbind::PolicyListType<> >::WireType nbind::convertToWire<int>(int) [/Users/foobar/project/node_modules/node-cec/build/Release/nbind.node]
 7: nbind::TypeTransformer<void, nbind::PolicyListType<> >::Type nbind::cbWrapper<void>::call<void, int, int>(int&&, int&&) const [/Users/foobar/project/node_modules/node-cec/build/Release/nbind.node]
 8: WorkAsyncComplete(uv_work_s*) [/Users/foobar/project/node_modules/node-cec/build/Release/nbind.node]
 9: uv__work_done [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]
10: uv__async_event [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]
11: uv__async_io [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]
12: uv__io_poll [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]
13: uv_run [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]
14: node::Start(int, char**) [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]
15: start [/Users/foobar/.nvm/versions/node/v6.9.1/bin/node]