gihanchanuka gihanchanuka - 2 months ago 24
C++ Question

How to use V8::AddMemoryAllocationCallback method in C++ NodeJS Addon

I'm trying to use V8::AddMemoryAllocationCallback method for a NodeJS C++ Addon. I want to call to that method and return that size value. I came up with following code. Seems to be it's not calling to callback method.
The code is taken form goingnative npm module.

But the

memCallback
method didn't get trigger. Why? How to fix it?

It'll use the following C++ code to access V8 library.

//myaddon.cc
#include <nan.h>
# include <unistd.h>
#include <iostream>
#include <fstream>

using namespace std;
using namespace v8;

static int x = 0;
static int y = 0;

void memCallback(ObjectSpace space, AllocationAction action, int size) {
ofstream myfile;
myfile.open ("/tmp/example.txt");
myfile << "Writing this to a file.\n";
myfile.close();

x = size;
}

NAN_METHOD(Delay) {
NanScope();

int delay = args[0].As<Number>()->IntegerValue();
Local<Function> callback = args[1].As<Function>();

V8::AddMemoryAllocationCallback(&memCallback, kObjectSpaceNewSpace, kAllocationActionAllocate);

for(int i = 0; i < 10; i++) {
y = i;
Local<Value> argv[] = {NanNew(x), NanNew(y)};
NanMakeCallback(NanGetCurrentContext()->Global(), callback, 2, argv);
usleep(delay * 1000);
}

NanReturnUndefined();
}

void Init(Handle<Object> exports) {
exports->Set(NanNew("delay"), NanNew<FunctionTemplate>(Delay)->GetFunction());
}

NODE_MODULE(myaddon, Init)


node-gyp has use to build and run the code (try
node-gyp rebuild && node index.js 1000 && ls /tmp/
from current folder)

//binding.gyp
{
"targets": [
{
"target_name": "myaddon",
"sources": [ "myaddon.cc" ],
"include_dirs": [ "<!(node -e \"require('nan')\")" ]
}
]
}


Following is the JavaScript code. I have created few variables in order to allocate some memory.

//index.js
var addon = require('bindings')('myaddon')

addon.delay(process.argv[2], function(x, y) {
console.log("X: ", x, " Y:", y);

var arr = [], obj = {};
for (var i = 0; i < 100; i++) {
arr.push("Text " + i);
}
for (var i = 0; i < 100; i++) {
obj[i] = arr[i];
delete arr[i];
}
console.log('Done!');
})

console.log("The End");


Current output is;

X: 0 Y: 0
Done!
X: 0 Y: 1
Done!
X: 0 Y: 2
Done!
X: 0 Y: 3
Done!
X: 0 Y: 4
Done!
X: 0 Y: 5
Done!
X: 0 Y: 6
Done!
X: 0 Y: 7
Done!
X: 0 Y: 8
Done!
X: 0 Y: 9
Done!
The End

Answer

Using less limiting notification settings may help (kObjectSpaceAll instead of kObjectSpaceNewSpace and/or kAllocationActionAll instead of kAllocationActionAllocate).

For passing size to your callback, here's couple ways to go about it:

  • First, you might consider using an EventEmitter instead of a callback since the memCallback will be called many times, unless you really only want callbacks to fire for the next memCallback invocation. With the EventEmitter solution you would only register a JS callback once in C++ land, which would get stored globally and would get called inside memCallback. Then in JS land, you would just emit an event when your JS callback is executed.

    You could even tweak that so that the JS callback (and maybe even the C++ callback) only stays registered in C++ land if there are listeners for your event (EventEmitter provides events to notify you when listeners are added and removed for an event). This might help improve performance.

  • If you really want to use callbacks that get executed multiple times (which really goes against general expectations in the node community), then you will have to both store the JS callbacks in a global C++ array structure of some kind and provide some way to "unregister" JS callbacks so they don't get called anymore (would remove the JS callback from the C++ array).