I'm trying to provide data to a function call handler, but I'm unable to garbage collect it properly.
If I comment out the line containing "tpl->SetCallHandler(callFn, external);" the ObjWeakCallback is called. The function never gets collected, though (at least the FnWeakCallback is never called, no matter what).
Please note that static data is not an option as this needs to be ready for multiple isolates per process.
isolate->setData is not an option as well. Am I missing something completely?
What is the proper way to store data inside v8::Function, is there any?
edit: Let me rephrase the question to better describe my intention ...
I'd like to create a function template (because I need to use it as a constructor on JS side). I need some way to send a void* to it's call handler, but this data must be garbage collectible.
A few notes on what I've been trying so far:
using Function::New(isolate, callHandler, External::New(isolate, data)) works, but does not provide the JS side constructor capability (not possible to SetInternalFieldCount for the created object)
FunctionTemplate::New(isolate, callHandler, External::New(isolate, data)) provides the ability to pass data to call handler, but is never garbage collected (<not> working example below)
I tried using a plain function and return a freshly created Object, but then the following assumption new Fn() instanceof Fn fails
#include <v8.h>
#include <iostream>
// made static, just to simplify the example and prevent crashes
// the persistents would normally be wrapped inside a "baton" together with required data
v8::Persistent<v8::Value> p_obj;
v8::Persistent<v8::Value> p_fn;
void FnWeakCallback(const v8::WeakCallbackData<v8::Value, int>& data) {
int* number = data.GetParameter();
std::cout << "GC fn " << number << '\n';
delete number;
p_fn.Reset();
}
void ObjWeakCallback(const v8::WeakCallbackData<v8::Value, int>& data) {
int* number = data.GetParameter();
std::cout << "GC obj " << number << '\n';
delete number;
p_obj.Reset();
}
void callFn(const v8::FunctionCallbackInfo<v8::Value>& info) {
std::cout << "called\n";
}
void test(v8::Isolate* isolate) {
v8::HandleScope scope(isolate);
auto external = v8::External::New(isolate, new int{ 1 });
p_obj.Reset(isolate, external);
p_obj.SetWeak(new int{ 1 }, ObjWeakCallback);
auto tpl = v8::FunctionTemplate::New(isolate);
tpl->SetCallHandler(callFn, external); // <======
tpl->InstanceTemplate()->SetInternalFieldCount(1);
auto fn = tpl->GetFunction();
p_fn.Reset(isolate, fn);
p_fn.SetWeak(new int{ 2 }, FnWeakCallback);
}
int main() {
v8::V8::SetFlagsFromString("--expose-gc", 11);
auto isolate = v8::Isolate::GetCurrent();
v8::HandleScope handle_scope(isolate);
auto context = v8::Context::New(isolate);
context->Enter();
test(isolate);
isolate->RequestGarbageCollectionForTesting(v8::Isolate::kFullGarbageCollection);
context->Exit();
return 0;
}
Now I have cheated and looked at the code
void GCController::CollectAll(const gin::Arguments& args) {
// In order to collect a DOM wrapper, two GC cycles are needed.
// In the first GC cycle, a weak callback of the DOM wrapper is called back
// and the weak callback disposes a persistent handle to the DOM wrapper.
// In the second GC cycle, the DOM wrapper is reclaimed.
// Given that two GC cycles are needed to collect one DOM wrapper,
// more than two GC cycles are needed to collect all DOM wrappers
// that are chained. Seven GC cycles look enough in most tests.
So the answer must be: you won't see anything after first collection.