0

I am trying to call some native api from node js and perform long running task. I am using libuv for that perposes, store js callback to perform some meaningful task in separate thread and call callback later. This is what I've done so far:

task.cpp

struct Work {
  uv_work_t request;
  Local<Function> callback;
};

static void WorkAsyncComplete(uv_work_t* req, int status)
{
    Isolate* isolate = Isolate::GetCurrent();
    v8::HandleScope handleScope(isolate);

    Work* work = static_cast<Work*>(req->data);

    const unsigned argc = 1;
    Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "Hello world").ToLocalChecked() };

    Local<Function> callback = Local<Function>::New(isolate, work->callback);
    callback->Call(isolate->GetCurrentContext(), Null(isolate), argc, argv);

    work->callback.Clear();

    delete work;
}

static void WorkAsync(uv_work_t* req)
{
    Work* work = static_cast<Work*>(req->data);

    using namespace std::chrono_literals;
    std::this_thread::sleep_for(std::chrono::seconds(5));
}

void RunCallback(const FunctionCallbackInfo<Value>& args)
{
    Isolate* isolate = args.GetIsolate();

    Work* work = new Work();
    work->request.data = work;
    
    Local<Function> callback = Local<Function>::Cast(args[1]);
    work->callback.New(isolate, callback);

    uv_queue_work(uv_default_loop(), &work->request, WorkAsync, WorkAsyncComplete);

    args.GetReturnValue().Set(Undefined(isolate));
}

void Init(Local<Object> exports, Local<Object> module)
{
    //isolate = exports->GetIsolate();
    NODE_SET_METHOD(module, "exports", RunCallback);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

task.js:

let task = require('./build/Release/task');

task((msg) => {
  console.log(msg);
}

But I got crash in WorkAsyncComplete() when calling callback. Here is the error detail:

Debugger attached.
FATAL ERROR: v8::Function::Call Function to be called is a null pointer
 1: 00007FF6579E03DF napi_wrap+109311
 2: 00007FF657985096 v8::internal::OrderedHashTable<v8::internal::OrderedHashSet,1>::NumberOfElementsOffset+33302
 3: 00007FF657985E66 node::OnFatalError+294
 4: 00007FF65822A9BD v8::Function::Call+573
 5: 00007FFA62FD1172 load_exe_hook+258
 6: 00007FF657A37D90 uv_timer_stop+560
 7: 00007FF657A37E67 uv_timer_stop+775
 8: 00007FF657A3469B uv_async_send+331
 9: 00007FF657A33E2C uv_loop_init+1292
10: 00007FF657A33FCA uv_run+202
11: 00007FF6579400A5 v8::internal::OrderedHashTable<v8::internal::OrderedHashSet,1>::NumberOfBucketsOffset+9365
12: 00007FF6579B3867 node::Start+311
13: 00007FF65781686C RC4_options+339820
14: 00007FF6587B523C v8::internal::compiler::RepresentationChanger::Uint32OverflowOperatorFor+153532
15: 00007FFA761A7C24 BaseThreadInitThunk+20
16: 00007FFA7694D4D1 RtlUserThreadStart+33

Please advice how to fix this issue. Thanks in advance.

1 Answer 1

1

Looks like the issue is that you're using a v8::Local<...> in a long-lived object. That doesn't work because the lifetime of a Local is tied to the surrounding HandleScope: the Local becomes invalid when the HandleScope dies. See https://v8.dev/docs/embed#handles-and-garbage-collection for details. The solution is to use a v8::Persistent<...> instead of a Local.

For the record, I'd also like to point out that while you can do C++ work in the background task, you won't be able to extend this example so that it takes a JavaScript function to execute concurrently. That would be multi-threaded programming, which JavaScript as a language doesn't support, and hence V8 as an implementation doesn't support either.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.