Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Nodeコアの歩き方 ~Nodeの開発に参加しよう~ [Node.js]

Nodeコアの歩き方 ~Nodeの開発に参加しよう~ [Node.js]

東京Node学園祭2012

Shigeki Ohtsu

November 17, 2012
Tweet

More Decks by Shigeki Ohtsu

Other Decks in Programming

Transcript

  1. 自己紹介 • 株式会社 インターネットイニシアティブ(IIJ) 所属 • 戦略的開発室というところで Node やら HTML5

    やら SPDY なんかをやってます。 • twitter: @jovi0608 • https://github.com/shigeki よろしくです。 こっちも 11月20日(火) 19:00~ @IIJ
  2. 内容 Nodeコアの中をうろうろ歩きます! (個人的に大事だと思うところを6点) 1. Node の ソース 2. イベントループの話(前々回のアップデート) 3.

    Node APIの土地勘を持とう! 4. JavaScript と C++ の出会い 5. WRAP! WRAP! WRAP! 6. Node 起動! • Nodeの開発に参加しよう(無理かも)
  3. libev c-ares http-parser zlib openssl V8 コアモジュール ユーザモジュール libuv Node.js

    アーキテクチャ概要(v0.9.3) epoll kqueue event port IOCP npm ネイティブ(JS) ビルトイン(C++) libeio削除 削除 予定
  4. 1. Node のソース https://github.com/joyent/node # Popular Star Popular Fork 1

    bootstrap (40,318) Spoon-Knife (10,441) 2 node (18,626) bootstrap(10,019) 3 jquery (17,532) homebrew (4,948) 4 rails (16,512) rails (4,246) 5 html5-boilerplate html5-boilerplate 6 impress.js hw3_rottenpotatoes 7 backbone oh-my-zsh (2,828) 8 homebrew node (2,753) 9 Spoon-Knife jquery 10 d3 phonegap-start みんなでNodeを Star & Fork しよう!
  5. Nodeソースツリー (ルート上のファイル) 情 報 系 AUTHORS 貢献者リスト(名前 、メールアドレス) ChangeLog 変更履歴

    LICENSE ライセンス Readme.md README ビ ル ド 系 vcbuild.bat MS Visual Studio向けビルドバッチスクリプト configure 初期設定スクリプト(config.gypi, config.mk を生成) Makefile Unix向けメイクファイル(gmake用) BSDmakefile BSD make向け案内用(gmakeコマンドを使えと表示) node.gyp ビルド用GYPファイル common.gypi GYP共通設定ファイル config.gypi gyp向け初期設定ファイル (configureで生成) config.mk Makefile向け設定ファイル(configureで生成)
  6. Nodeソースツリー (ルート上のディレクトリ) benchmark/ ベンチマークスクリプト群 deps/ 外部依存ライブラリ群 doc/ マニュアル等ドキュメント類 lib/ JavaScriptネイティブモジュール群

    src/ 本体(node.cc/node.js) と C++ビルドイン モジュール群 test/ テスト tools/ ビルド・インストール用ツール群他 out/ ビルド用ディレクトリ(gypが生成)
  7. • C++ で記述 • node_extensions.h 経由で 参照 • libuv やV8等の他のライブ

    ラリを利用するために使わ れる。 • process.binding()で読み込 み コアモジュールの種類 ビルトインモジュール src/*.cc • JavaScriptで記述 • node_natives.h 中に格納 (後述) • 主にユーザモジュールに提 供するAPIとして使われる。 • module.require()で読み込 み ネイティブモジュール src/node.js, lib/*.js
  8. libev c-ares http-parser zlib openssl V8 コアモジュール ユーザモジュール libuv epoll

    kqueue event port IOCP npm ネイティブ(JS) ビルトイン(C++) ここの 話題 2. イベントループの話
  9. Node-v0.9.3イベントループ概要 1:時刻更新 7:ハンドル終了 5:poll 始まり 終わり 4:run_prepare nextTick() 再帰展開 setImmeidate()

    setTimeout() setInterval() コールバック 3:run_idle イベントループ 一周(Tick) libev+kernel epoll: Linux kqueue: BSD event port: Solaris IOCP: WIn 2:run_timers 6:run_check (注: メインモジュールは 3: run_idle から始まる。) メイン モジュール nextTick() 再帰展開 New libev 削除予定 New New
  10. process.nextTick()の再帰計算はダメよ $ ./node ~/fib.js (node) warning: Recursive process.nextTick detected. This

    will break in the next version of node. Please use setImmediate for recursive deferral. 7.0330367711422765e+208 function fib(i, n1, n2, f) { if (i === 1) { f(n2); } else { process.nextTick(function() { fib(i-1, n2, n1 + n2, f); }); } } fib(1001, 0, 1, function(k) { console.log(k); }); node-v0.9では、 process.maxDepth (default:1000) まで展開 計算をしますが下記警 告が出ます。 setImmediate() を使おう! 去年学園祭でもやったフィボナッチ計算
  11. じゃsetImmediate()使ったら function fib(i, n1, n2, f) { if (i ===

    1) { f(n2); } else { setImmediate(function() { fib(i-1, n2, n1 + n2, f); }); } } fib(1001, 0, 1, function(k) { console.log(k); }); Immediate なのに 1msec 待つバグのPull Request 放置中です orz @isaacs Please check https://github.com/joyent/node/pull/3872 $ ./node ~/fib2.js 7.0330367711422765e+208
  12. libev c-ares http-parser zlib openssl V8 コアモジュール ユーザモジュール libuv epoll

    kqueue event port IOCP npm ネイティブ(JS) ビルトイン(C++) ここの 話題 3. Node APIの土地勘を持とう!
  13. ネイティブモジュールの構造概要 events. EventEmitter stream. Stream net. Server http.Outgoing Message http.Incoming

    Message tls. CryptStream net. Socket http.Server Response http.Client Request tls. ClearText http.Server Request http.Client Response http. Server tls. Server https. Server on(), emit() pipe() listen(),close() connection event Node APIの 土地勘を持つ
  14. Node APIの 土地勘を持つ http/https サーバには 固有メソッド がないね。 connection イベント listen()

    close() 等は共通だ • connection • secureConnection • connect イベントの違いがわ かるかな?
  15. 全部同じに書けます (socket生データの出力) // net.Server var net_server = require('net').createServer(); net_server.on('connection', function(socket)

    { socket.on('data', function(chunk) { console.log('net', chunk.toString()); }); }); net_server.listen(8080); // tls.Server var tls_server = require('tls').createServer(opts); tls_server.on('connection', function(socket) { socket.on('data', function(chunk) { console.log('tls', chunk.toString()); }); }); tls_server.listen(8081); // http.Server var http_server = require('http').createServer(); http_server.on('connection', function(socket) { socket.on('data', function(chunk) { console.log('http', chunk.toString()); }); }); http_server.listen(8082); // https.Server var https_server = require('https').createServer(opts); https_server.on('connection', function(socket) { socket.on('data', function(chunk) { console.log('https', chunk.toString()); }); }); https_server.listen(8083); Node APIの 土地勘を持つ
  16. この2つは一緒だよ // http.ClientResponse var client = require('http').get('http://example.jp', function(res) { for(

    var key in res) { console.log('client', key); } }); // http.ServerRequest var server = require('http').createServer(function(req, res) { for( var key in req) { console.log('server', key); } res.setHeader(200, {‘content-type’, ‘text/plain’}); res.end('Hello World'); }).listen(8080); Node APIの 土地勘を持つ
  17. libev c-ares http-parser zlib openssl V8 コアモジュール ユーザモジュール libuv epoll

    kqueue event port IOCP npm ネイティブ(JS) ビルトイン(C++) ここの 話題 4. JavaScript と C++ の出会い
  18. JavaScript と C++の出会い (ビルド編: js2c) Native Module src/node.js lib/*.js out/Release/obj/gen/

    node_natives.h ASCIIコードの配列 tools/js2c.py DefineJavaScript() src/node_javascript.cc 参照 via gyp
  19. process.binding() BuiltinModule C++ src/node_extensions.cc module.require() NativeModule JavaScript src/node_natives.h src/node_javascript.cc User

    Module JavaScript JavaScript と C++の出会い (呼び出し編:process.binding) process.binding()
  20. JavaScript と C++の出会い (実行編: WRAP) V8 JavaScript C++ WRAP HandleWrap::HandleWrap(Handle<Object>

    object, uv_handle_t* h) { if (h) { h->data = this; } HandleScope scope; object_ = v8::Persistent<v8::Object>::New(object); object_->SetPointerInInternalField(0, this); } src/handle_wrap.cc C++のインスタンスをV8 の JavaScript オブジェクトの中に包み隠しています。 V8オブジェクトはクラスの プライベー トメンバーに格納。 次で詳細説明
  21. libev c-ares http-parser zlib openssl V8 コアモジュール ユーザモジュール libuv epoll

    kqueue event port IOCP npm ネイティブ(JS) ビルトイン(C++) この 話題 5. WRAP! WRAP! WRAP!
  22. WARP! WRAP! WRAP! V8 JavaScript C++ WRAP var server =

    require(‘net’).createServer(); new (process.binding('tcp_wrap').TCP)(); 内部 では TCP オブジェクト
  23. ビルトインWRAPクラス src/*_wrap.{h,cc} HandleWrap StreamWrap TCPWrap ProcessWrap UDPWrap TimerWrap SignalWrap TTYWrap

    PipeWrap FSEventWrap ReqWrap FSReqWrap WriteWrap V8 JavaScript C++ WRAP 他にオブジェクト WRAPもあるよ
  24. handle と req の違い • handle – I/O が発生してない時でもイベントループを維持 –

    (例) server.listen() • req – I/Oが発生している時だけイベントループを維持 – (例) http.get()
  25. StreamWrapの役割 src/stream_wrap.{h,cc} HandleWrap object_ handle_ close() onclose StreamWrap writeBuffer() ReadStart()/Stop()

    shutdown() onread TCPWrap connect() onconnection oncomplete WriteWrap SlabAllocator Allocate() Shrink() データの 読み込み データの 書き込み libuv kernel ReqWrap object_ = req_obj data_ = handle (後述) req/handleオブジェクトをlibuuvに 渡して、コールバックで受け取る。
  26. var server = require('net').createServer(function(socket) { socket.on('data', function(chunk) { ....; });

    }); SlabAllocator src/slab_allocator.{h,cc} Read データ 1MB offset length 16KB単位 libuv+kernel uv_read chunkの正体 読み込み用にあら かじめ1MBのバッ ファスラブを用意。 読み込み毎に Allocate/Shrinkを繰 り返す。 既に読み込 まれた領域
  27. libev c-ares http-parser zlib openssl V8 コアモジュール ユーザモジュール libuv epoll

    kqueue event port IOCP npm ネイティブ(JS) ビルトイン(C++) この辺 の話題 6. Node 起動!
  28. 起動までの大雑把な流れ src/node.cc libuv/V8の 初期化 processオブ ジェクトの生成 src/node.jsの読 み込み イベントループ 開始

    uv_run() globalオブジェ クトの生成 • global 関数の登録 • lib/module.js の読み込み • process.nextTick にメインモ ジュールの登録 メインモジュール の実行
  29. src/node.js • global変数設定(process/Buffer) • global関数設定(setTimeout等timer関連、console関連) • process関連設定(nextTick、シグナルハンドリング) • process.nextTickでメインモジュール実行 –

    イベントループを起動した後にメインモジュール の実行をしないと exit や uncaughtException イベ ントを拾えなくなるため。
  30. Nodeクロージャスコープの様子 (function(process) { })(process); ); var global, process; global.setTimeout, global.setInterval,

    global.console(); process.nextTick( src/node.cc src/node.js lib/module.js Module.runMain ./foo.js console.log(‘hello’); require(‘vm’).runInThisContext( “foo.js” ); global._filename,global_dirname; global.require(), global.module, global.exports;
  31. CLA (Contributor License Agreement) http://www.nodejs.org/cla.html • パッチ等のOSSプロジェクトに対する貢献物の 知的財産権に関する同意書 • a

    non-exclusive, irrevocable, worldwide, royalty-free, sublicenseable, transferable license をNodeJSに譲渡する等々・・・ • 法律用語で難しいので必ず原文を参照のこと。 • 電子サインもしくはスキャンしてメール送付。 • パッチが取り込まれる前に必要。あらかじめ 内容を確認しておきましょう。
  32. コーディング規約 • JavaScript – ES5 – Google JavaScript Style Guide

    に従います。 – コンマファースト、セミコロン無しじゃありません。 – _proto_は使わない。(一部例外有) – make jslint でスタイル確認。 make test すると自動実行。 • C++ – Google C++ Style Guide に従います。 – 2SPインデントKNFスタイル、80文字/行、関数2行空けetc – STL/BOOSTを使わない – Better C っぽい。(個人的な感想) – libuv は c89 です。(thread safeに) – make cpplint でスタイル確認(がエラーが大量!)。
  33. ユニットテスト • バグが存在し、修正されていることを確認 • 新機能が正しく動作していることを確認 • test/common.js (テスト共通で使う変数) • test/fixture/

    (フィクスチャの保管場所) • test/simple/test-xxx-yyy.js (テストファイル) make test で他のテストがデグってないか確認
  34. プルリクエストを出す前に • 機能追加/変更 – Node Core vs UserLand https://github.com/joyent/node/wiki/node-core-vs- userland

    – ユースケースを提示する – APIのStability Indexに注意 – 大きい変更は事前にMLで相談 • バグフィックス – 客観的な再現データを提示 – 本当に make test したか? – できればマルチOSで確認。