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.
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.
Related
v8 version : 10.5.0
IDE : vs2022
I am trying to embed v8 in MFC. To test simply, I created a dialog project, initialized v8 on OnInitDialog(), disposed it OnDestroy(). And then, I wrote the code to create the context in BN_CLICKED event and run script("Hello World!)". So, when the button was pressed, I wanted to show "Hello World!". But the crash took place in the code that create the context(v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr);).
As a result of testing, this only happens in MFC. This crash does not happen in console programs.
Somebody, anybody help me~. there is my whole code below.
cpp
BOOL Cv8TestDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
...
// Initialize V8.
v8::V8::InitializeICUDefaultLocation("");
v8::V8::InitializeExternalStartupData("");
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
v8::V8::Initialize();
// Create a new Isolate and make it the current one.
m_create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
m_isolate = v8::Isolate::New(m_create_params);
return TRUE;
}
void Cv8TestDlg::OnDestroy()
{
CDialogEx::OnDestroy();
m_isolate->Dispose();
v8::V8::Dispose();
v8::V8::DisposePlatform();
delete m_create_params.array_buffer_allocator;
}
void Cv8TestDlg::OnRun()
{
CString text;
v8::Isolate* isolate = m_isolate;
v8::Isolate::Scope isolate_scope(isolate);
// Create a stack-allocated handle scope.
v8::HandleScope handle_scope(isolate);
// Create a new context.
v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr);
// Enter the context for compiling and running the hello world script.
v8::Context::Scope context_scope(context);
{
// Create a string containing the JavaScript source code.
v8::Local<v8::String> source = v8::String::NewFromUtf8Literal(isolate, "'Hello' + ', World!'");
// Compile the source code.
v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();
// Run the script to get the result.
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
// Convert the result to an UTF8 string and print it.
v8::String::Value utf8(isolate, result);
text = (LPCWSTR)*utf8;
}
GetDlgItem(IDC_EDIT1)->SetWindowText(text);
}
h
class Cv8TestDlg : public CDialogEx
{
...
v8::Isolate::CreateParams m_create_params;
v8::Isolate* m_isolate = nullptr;
virtual BOOL OnInitDialog();
...
afx_msg void OnDestroy();
afx_msg void OnRun();
};
crash call stack
v8.dll!v8::internal::tracing::TraceEventHelper::GetTracingController()
v8.dll!v8::NewContext(v8::Isolate * external_isolate, v8::ExtensionConfiguration * extensions, v8::MaybeLocal<v8::ObjectTemplate> global_template, v8::MaybeLocal<v8::Value> global_object, unsigned __int64 context_snapshot_index, v8::DeserializeInternalFieldsCallback embedder_fields_deserializer, v8::MicrotaskQueue * microtask_queue) line 6391 C++
v8.dll!v8::Context::New(v8::Isolate * external_isolate, v8::ExtensionConfiguration * extensions, v8::MaybeLocal<v8::ObjectTemplate> global_template, v8::MaybeLocal<v8::Value> global_object, v8::DeserializeInternalFieldsCallback internal_fields_deserializer, v8::MicrotaskQueue * microtask_queue) line 6413 C++
v8Test.exe!Cv8TestDlg::OnRun() line 198 C++
exception message
(0x00007FFADBB43D53(v8.dll), v8Test.exe): 0xC0000005: 0x0000000000000088 access violation
break position(trace-event.cc)
v8::TracingController* TraceEventHelper::GetTracingController() {
-> return v8::internal::V8::GetCurrentPlatform()->GetTracingController();
}
I think the problem is std::unique_ptr<v8::Platform> platform in OnInitDialog. The purpose of a std::unique_ptr is to destroy the thing it points at when the pointer goes out of scope (i.e. in this case, at the end of the function). The v8::Platform should be long-lived, all the way until the v8::V8::DisposePlatform() call.
I'm trying to connect some widgets with the same user_function callback. In some cases, the signal has diferent signatures.
Searching i found gtk_signal_connect_full, I think.
My Code for example:
int MyObject::connect()
{
callback_object ....
// Create the callback
int ret = g_signal_connect(instance, "clicked", G_CALLBACK (connect_callback), callback_object);
int ret = g_signal_connect(instance, "button-release-event", G_CALLBACK (connect_callback), callback_object);
// Return handler id
return ret;
}
void MyObject::connect_callback(GObject *passedInstance, gpointer param1, gpointer param2)
{
// Return to st_callback
struct st_callback *callback_object = (struct st_callback *) param2;
if(sizeof(param1) == 0) {
callback_object = (struct st_callback *) param1;
}
}
Can i "abstract" user_function like that? and if I can, How to get extra parameters like GdkEvent or GdkEventButton, or gchar, ...
EDIT
- This question is a GTK+ issue, because in the first g_signal_connect, param1 is my struct. I'ts ok, I know my struct to cast back. In the second g_signal_connect, param1 is a GdkEventButton. It's OK too, becouse I know it's a GdkEventButton to cast back. BUT, how to do this, if I dont know param1 is my struct, if its a GdkEvent, GdkEventButton, gchar, or all others possibilities of sinal signatures?
EDIT 2
- I have found this info on Closures doc
Closures allow the callee to get the types of the callback parameters, which means that language bindings don't have to write individual glue for each callback type.
This seen perfect for what i'm looking for, but I don't found nothing more that it
EDIT 3
With ebassi ideia about, g_signal_query is what I need. I do this for abstract:
1 - query the params of signal with g_signal_query, set into my structure to pass with callback user_data
2 - connect with g_cclosure_new_swap and g_signal_connect_closure, this set gpointer user_data as first param
3 - created callback like this: connect_callback(gpointer user_data, ...), with variable parameter list
4 - inside callback, get back my struct with custom + g_signal_query result
5 - loop into param_types of GSignalQuery, verifying each fundamental types
6 - get va_arg with correct type
Complete code for call the callback
// Create gpoint param
struct st_callback *callback_object = (struct st_callback *)malloc(sizeof(struct st_callback));
memset(callback_object, 0, sizeof(struct st_callback));
callback_object->callback_name = callback_name;
callback_object->callback_params = callback_params;
// Get params of signal
GSignalQuery signal_info;
g_signal_query(g_signal_lookup (callback_signal, G_OBJECT_TYPE (instance)), &signal_info);
// Save
callback_object->signal_id = signal_info.signal_id;
callback_object->signal_name = signal_info.signal_name;
callback_object->itype = signal_info.itype;
callback_object->signal_flags = signal_info.signal_flags;
callback_object->return_type = signal_info.return_type;
callback_object->n_params = signal_info.n_params;
callback_object->param_types = signal_info.param_types;
GClosure *closure;
closure = g_cclosure_new_swap (G_CALLBACK (connect_callback), callback_object, NULL);
int ret = g_signal_connect_closure (instance, callback_event, closure, TRUE);
And the callback
static bool connect_callback(gpointer user_data, ...)
{
// Return to st_callback
struct st_callback *callback_object = (struct st_callback *) user_data;
// get parameters count
int param_count = callback_object->n_params;
va_list ap;
va_start(ap, param_count);
// loop paramters
for (int i=0; i<param_count; i++) {
switch (G_TYPE_FUNDAMENTAL(callback_object->param_types[i])) {
case G_TYPE_CHAR:
break;
case G_TYPE_UCHAR:
break;
case G_TYPE_STRING:
{
char *path = va_arg(ap, char *);
break;
}
case G_TYPE_OBJECT:
break;
case G_TYPE_POINTER:
break;
case G_TYPE_INTERFACE:
break;
case G_TYPE_PARAM:
break;
case G_TYPE_BOXED:
{
// Example, try to cast correct boxed
GdkEvent *e = va_arg(ap, GdkEvent *);
break;
}
}
}
va_end(ap);
}
Needs to correct return and boxed cast, but with this I can work fine
You should not use the same function for different types of callbacks. Some callbacks take different parameters, others have different return values — for instance, in your example, clicked does not return anything, whereas button-press-event returns a boolean value.
If you have common code that needs to be executed in different signal handlers, write a function and then call it from the various handlers.
I have one problem. In c++ app, I am using sd-bus and signal does not call my callback function.
I hooked to org.freedesktop.login1, interface is org.freedesktop.DBus.Properties, member is PropertiesChanged and path is /org/freedesktop/login1/seat/seat0
In my connect method I have this:
sd_bus_add_match(m_bus, NULL, "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path='/org/freedesktop/login1/seat/seat0',type='signal'", on_properties_changed, NULL)
On properties changed method is this:
static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
printf("got some signal");
}
So, when I ran this program, I also ran following command in cmd:¸
gdbus monitor --system --dest org.freedesktop.login1 --object-path /org/freedesktop/login1/seat/seat0
However, when I switch from userA to userB, I got following line in cmd window:
/org/freedesktop/login1/seat/seat0: org.freedesktop.DBus.Properties.PropertiesChanged ('org.freedesktop.login1.Seat', {'ActiveSession': <('c7', objectpath '/org/freedesktop/login1/session/c7')>}, #as [])
Also when I tried this
busctl --system --match "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path='/org/freedesktop/login1/seat/seat0',type='signal' monitor
then I also get proper response
Type=signal Endian=l Flags=1 Version=1 Priority=0 Cookie=2281
Sender=:1.0 Path=/org/freedesktop/login1/seat/seat0 Interface=org.freedesktop.DBus.Properties Member=PropertiesChanged
UniqueName=:1.0
MESSAGE "sa{sv}as" {
STRING "org.freedesktop.login1.Seat";
ARRAY "{sv}" {
DICT_ENTRY "sv" {
STRING "ActiveSession";
VARIANT "(so)" {
STRUCT "so" {
STRING "c2";
OBJECT_PATH "/org/freedesktop/login1/session/c2";
};
};
};
};
ARRAY "s" {
};
};
But in c++ that callback function is not called. Any idea why is it not called?
I am using Ubuntu 16.04 and my systemd version is 229.
I found a solution to this problem. Problem was that I wasn't hooked to any event loop.
So I created new function run() and in this function I say this:
while(m_running) {
sd_bus_message *m = NULL;
r = sd_bus_process(m_bus, &m);
if (r < 0) {
//error handling
}
r = sd_bus_wait(m_bus, (uint64_t)-1);
if (r < 0) {
//error handling
}
}
and now I call this function after I connect to signal, and callback function of sd_bus_add_match is normally called
I am trying to save JavaScript function in my c++ app and call it from another thread.
But I got "Unhandled exception at 0x0101B5D5 in Console.exe: 0xC0000005: Access violation reading location 0x00000017."
Saving the JavaScript function (in requestCallbacks vector):
Handle<Value> HttpEngine::addListener(const Arguments& args)
{
Locker locker;
HandleScope scope;
HttpEngine* pThis = UnwrapHttpEngine( args.This() );
Persistent<Function> callback = Persistent<Function>::New(Handle<Function>::Cast(args[0]));
pThis->requestCallbacks.push_back(*callback);
return Boolean::New(true);
}
An attempt to call it from another thread:
void HttpEngine::emit()
{
Locker locker;
HandleScope scope;
for (size_t i = 0; i < requestCallbacks.size(); i++)
{
Persistent<Function> func = static_cast<Function*>(requestCallbacks[i]);
Handle<Value> args[1];
args[0] = v8::String::New("http://google.com");
func->Call(Context::GetCurrent()->Global(), 1, args);
}
}
JavaScript code (where the httpEngine is my c++ object in global scope):
httpEngine.addListener(function (url) {
print('on request: ' + url);
});
What's the type of requestCallbacks? Try putting the Persistent<Function> in there, not a raw Function* (using the * operator on a Persistent is both unsupported and most certainly not what you want to do).
I want to call JSON.stringify method from the C++ side with modified arguments, but any solution that came to my mind results in a weird segfault with all frames being at "??".
I want to do the following:
api::Console is a custom console implementation for debugging purposes and has therefore static methods like api::Console::handleLog or api::Console::handleDebug.
For handleDebug which is passed correctly to the console's ObjectTemplate, the following doesn't work, v8gl::context is the current execution context and is correctly usable in other api implementations:
v8::Handle<v8::Value> Console::handleDebug(const v8::Arguments& args) {
if (args.Length() < 1) {
return v8::Undefined();
}
v8::HandleScope scope;
v8::Handle<v8::Object> global = v8gl::context->Global();
v8::Handle<v8::Object> JSON = global->Get(v8::String::New("JSON"))->ToObject();
v8::Handle<v8::Function> JSON_stringify = v8::Handle<v8::Function>::Cast(JSON->Get(v8::String::New("stringify")));
for (signed a = 0; a < args.Length(); a++) {
for (int m = 0; m < consoleMargin; m++) {
fprintf(stdout, "\t");
}
v8::Handle<v8::Value> passargs[1];
// alternative try was:
// passargs[0] = v8::String::New("{foo:'bar'}");
passargs[0] = v8::String::New(*v8::String::Utf8Value(args[a]->ToString()));
v8::String::Utf8Value value(JSON_stringify->Call(JSON, 1, passargs));
char* message = *value;
fprintf(stdout, "%s\n", message);
}
return scope.Close(v8::Undefined());
}
The backtrace in gdb is somehow weird and I have no idea why:
(gdb) backtrace
#0 0x00000000004a0880 in v8::Context::Global() ()
#1 0x00000000004128ea in api::Console::handleDebug(v8::Arguments const&) ()
#2 0x00000000004b9eab in v8::internal::Builtin_HandleApiCall(v8::internal::(anonymous namespace)::BuiltinArguments<(v8::internal::BuiltinExtraArguments)1>, v8::internal::Isolate*) ()
#3 0x000004cd67f0618e in ?? ()
#4 0x000004cd67f12998 in ?? ()
#5 0x000004cd67f060e1 in ?? ()
# (... etc ...)
So my question is the following:
How do I cast correctly from the Local value"v8::Arguments& args" to "v8::Handle<v8::Value>*" for usage with the Call() method of v8::Function?
If I want to use the args[a] directly in the loop, compiler errors are thrown for the differing signature of v8::Function::Call, which is correct due to args being a Local Value. The signature of v8::Function::Call is the following:
v8::Local<v8::Value> v8::Function::Call(v8::Handle<v8::Object>, int, v8::Handle <v8::Value>*)
// Edit: Updated passargs[false index]
You have error there:
v8::Handle<v8::Value> passargs[1];
passargs[1/* SHOULD BE 0!!!*/] =
v8::String::New(*v8::String::Utf8Value(args[a]->ToString()));
So you try to access element out of array bounds.
BTW: v8::Local<> is inherited from v8::Handle<> so you don't need any magic to convert Local to Handle.
Edit: Most of v8 features require not only v8::HandleScope but also v8::Context::Scope seems you need to create context scope too.
You can get valid context form args:
Local<Object> self = args.Holder();
Persistent<Context> context(self->CreationContext());
Then create handle scope and context scope:
Context::Scope work_in_context_scope(context);
HandleScope work_in_this_function_scope;
Then do you job.