node.js 筆記

一、以 C/C++ 編寫 node.js 的非同步 addon

如果單純按 http://nodejs.org/api/addons.html 的方法去寫具回傳函數的 addon,addon 仍是同步及 blocking 的。

C/C++:
#define BUILDING_NODE_EXTENSION
#include <node.h>

using namespace v8;

Handle<Value> RunCallback(const Arguments& args) {
  HandleScope scope;

  Local<Function> cb = Local<Function>::Cast(args[0]);
  const unsigned argc = 1;
  Local<Value> argv[argc] = { Local<Value>::New(String::New("hello world")) };
  cb->Call(Context::GetCurrent()->Global(), argc, argv);

  return scope.Close(Undefined());
}

void Init(Handle<Object> target) {
  target->Set(String::NewSymbol("runCallback"),
      FunctionTemplate::New(RunCallback)->GetFunction());
}

NODE_MODULE(addon, Init)

JavaScript:
var addon = require('./build/Release/addon');

addon.runCallback(function(msg){
  console.log(msg); // 'hello world'
});

如要寫成非同步,需利用 libuv 提供的 uv_queue_work 去處理,將程序拆成進入點,非同步部分,及完成後的處理部分。

C/C++:
#include <node.h>
#include <node_buffer.h>

struct Baton {
    uv_work_t request;
    Persistent<Function> callback;
    bool error;
    int value;
    // 可另加自定義變量
};

void AsyncWork(uv_work_t* req) {
    Baton* baton = static_cast<Baton*>(req->data);

    // ... 需時處理的非同步部分
}

void AsyncAfter(uv_work_t* req) {
    Baton* baton = static_cast<Baton*>(req->data);

    // ... 完成後處理及傳回 node.js,argc 及 argv 為傳入 callback 的資料

    baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);
}

Handle<Value> RunCallback(const Arguments& args) {
    // ... node.js 的進入點,args 為傳入資料,應放到 Baton 內

    Baton* baton = new Baton();

    baton->request.data = baton;
    baton->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));
 
    baton->error = false;

   // ... 準備工作

    int status = uv_queue_work(uv_default_loop(), &baton->request, AsyncWork, AsyncAfter);
    return Undefined();

}

static void Init(Handle<Object> target) {
    target->Set(String::NewSymbol("callback"),
            FunctionTemplate::New(RunCallback)->GetFunction());
}


NODE_MODULE(addon, Init)

JavaScript:
var addon = require('./build/Release/addon');

addon.runCallback(function(msg){
    console.log(msg);
});

而三個組件之間的溝通,則利用結構體去傳遞(如上文的 baton),次序為 RunCallback -> AsyncWork -> AsyncAfter,於 AsyncWork 內不應使用 v8 的函數。

但要注意,由於 node.js 是單線程,就算非同步,但執行時仍不可跟其他程式碼同時執行,解決方法為 Threading 或使用 Cluster 等方式。

參考資料:
https://github.com/kkaefer/node-cpp-modules
http://kkaefer.github.com/node-cpp-modules
http://www.slideshare.net/nsm.nikhil/writing-native-bindings-to-nodejs-in-c

二、監察 node.js 的垃圾回收

node.js 的 v8 JavaScript 引擎有自動垃圾回收機制,它在背後執行,若要監察它的運作,可安裝 memwatch 這個 addon。

var memwatch = require('memwatch');
memwatch.on('stats', function(stats) {
    console.log(stats);
});

聆聽 stats 這個事件,便可得知 node 何時進行垃圾回收。而我發現 node.js 在持續高負荷之下,便不會進行垃圾回收,這樣,用完的資料便不能清除,而記憶體使用量便會一直上升。memwatch 這個 addon 提供了 gc 的方法,在這情況下手動使 node.js 進行垃圾回收,或許是一種解決方法。

memwatch.gc();

另可參考:
node-memwatch-demo

此外,我亦找到一個 node.js 的 addon,叫做 node-weak,可令程式內的變量製造 Weak Reference,縱然變量非 null,node.js 亦可隨時將參照的資料清除。而不像一般變量般,只要參照著一些資料,資料便不會被回收。給我最大的用處是監察垃圾回收的情況。

例子:
var weak = require('weak');

function test(){
    var a = {"i": 0};
    var b = weak(a, function(){
        console.log("data will be garbage collected");
     }
}

b 便是 a 指著的資料的 Weak Reference,執行 test() 後,變量 a 會消失,當 node.js 進行垃圾回收,由於原本 a 指著的資料再沒有 Strong Reference ,所以可以被清除,到時候 "data will be garbage collected" 便會出現,說明那些資料會在即將發生的垃圾回收中清除。

而我發現以下寫法,由於 node.js 不會知道 testObj 內的資料會否再被引用,所以 "testObj will be garbage collected" 和 "testObj.a will be garbage collected" 並沒有出現,即是它們沒有被清除。而類似的寫法在 JavaScript 或 node.js 很普遍,所以要留意。

var testObj = {a: {b: "a"}};
weak(testObj, function(){console.log("testObj will be garbage collected");});
weak(testObj.a, function(){console.log("testObj.a will be garbage collected");});

function doSth(testObj){
    var intervalFunction = function(){
        var b = testObj;
        b = null;
    }

    var interval = setInterval(intervalFunction, 2000);

    setInterval(function(){
        clearInterval(interval);
    },5000);
}

doSth(testObj);

但改成這樣子卻可以,也就是在 clearInterval 時將 testObj 指向 null,原本的資料再沒有其他變量引用,故此能被回收,有點奇怪吧:

var testObj = {a: {b: "a"}};
weak(testObj, function(){console.log("testObj will be garbage collected");});
weak(testObj.a, function(){console.log("testObj.a will be garbage collected");});

function doSth(testObj){
    var intervalFunction = function(){
        var b = testObj;
        b = null;
    }

    var interval = setInterval(intervalFunction, 2000);

    setInterval(function(){
        clearInterval(interval);
        testObj = null;
    },5000);
}

doSth(testObj);

還有個有用的是 node-webkit-agent,可以幫忙做 profiling。

三、JavaScript 中的 this

參考資料:
http://dreamerslab.com/blog/tw/javascript-this/
http://dreamerslab.com/blog/tw/javascript-function-scopes-and-closures/
http://blog.darkthread.net/post-2009-03-11-js-this-and-closure.aspx
http://blog.ericsk.org/archives/1360
http://www.quirksmode.org/js/this.html
http://javascriptweblog.wordpress.com/2010/08/30/understanding-javascripts-this/
http://joshuakehn.com/2011/10/20/Understanding-JavaScript-Context.html
http://www.bennadel.com/blog/2265-Changing-The-Execution-Context-Of-JavaScript-Functions-Using-Call-And-Apply-.htm

四、其他連結
http://nodejs.org/
http://asyncionews.com/
http://toolbox.no.de/
http://docs.nodejitsu.com/
http://howtonode.org/
http://fred-zone.blogspot.hk/
將 file 或 socket handle 傳給另一 node.js process 的 addon

本文連結