node-flowless 0.0.0 リリース

しました.

npm install flowlessでインストールすることができます.


node-flowless は Node.js 用の小さな (less) フロー制御 (control-flow) モジュールです.名前は flawless に引っかけたのであって typo じゃないんだからねっ!


id:taedium さんが自作のフロー制御モジュール nue に関するエントリを書いているのに刺激されて,自分もブログネタとして書いてみました.nue はたぶん,Step にインスパイアされたのだと思われますが,flowless は Slide に強く強くインスパイアされてます.Slide のコンセプトは以下のスライド (これが名前の由来w) が分かりやすいです.

Node の非同期 API には EventEmitter を使ったものと,コールバック関数を使ったもの,2 種類あるよーなんて時々書いてたりしますが,それはこのスライドからの受け売りです.


コールバックスタイルには以下のお約束があります.

  • 非同期関数は最後の引数としてコールバック関数を受け取る.
    • function(... cb)
  • コールバック関数は最初の引数としてエラーを受け取る.
    • function(err, ...)

フロー制御モジュールの役割は結局の所,この形式に従った非同期関数をつなぐためのコールバック関数を提供してあげることだけです.
バリエーションとしても複数の非同期関数を順番に実行するか,並行に実行するか,それだけで大抵は十分だったりします.Async でいうところの series と parallel ですね.あとは map() なんかの非同期版も重宝ではありますが,そういうのは Async 使えばいいかなー? もしかしたら flowless に入れるかもだけど.


ともあれ (JW),flowless では

  • flowless.series(functions, cb)
  • flowless.parallel(functions, cb)

という関数を提供しています.functions は関数の配列で,series() は一つずつ逐次的に,parallel() は全部まとめて並行的に呼び出します.


series() の場合,エラー無しでコールバック関数が呼ばれると,その第 2 引数以降 + コールバック関数を引数として次の非同期関数を呼び出します.全ての非同期関数が実行されると,最後に cb が呼び出されます.
コールバック関数にエラーが渡されたり,非同期関数が例外をスローすると残りの非同期関数は呼び出されることなく,cb が呼び出されます.

parallel() の場合,全ての非同期関数からエラー無しでコールバック関数が呼ばれると,その第 2 引数 (または第 2 引数以降の配列) を要素とする配列を引数として cb が呼び出されます.
コールバック関数にエラーが渡されたり,非同期関数が例外をスローすると,即座に cb が呼び出されます.


series()parallel(),エラーがあってもなくても,どの場合でも cb が呼び出されるのは一度だけです.


非同期関数の呼び出しを trycatch で囲っているのは特徴と言えるかもしれません.Slide も Async も囲んでないんですよね.Slide の作者 Isaacs は例外なんぞスローするなという主張なのですが,実際は Node のコア API でも引数のエラーチェックなどで例外をスローするわけで,これは必要じゃないかなと思ってます.
ちなみに Step は trycatch で囲ってくれるのですが,例外がスローされてもコールバックにエラーが渡されても,次の非同期関数が呼び出されるだけでエラー時のルーティングをしてくれないんですよね.trycatch とエラー時のルーティングをサポートしたシンプルなフロー制御モジュールってあまりない気がします.nue は両方やってくれるようです.


上記の関数は呼び出されると即座に functions で与えられた関数 (series() の場合は functions の先頭要素のみ,parallel() は全部) を実行します.しかし,それだと不便な場合もあるので,非同期関数をフロー制御の元で実行する関数を返す関数も提供しています (日本語が変?).

  • flowless.makeSeries(functions)
  • flowless.makeParallel(functions)

これらが返す関数は非同期関数なので,引数の最後にコールバックを付けて呼び出すことができます (その他の引数は makeSeries() の場合は最初の非同期関数,makeParallel() の場合は全ての非同期関数に引数として渡されます).実際の所,series()parallel() の実装は

function series(functions, cb) {
  makeSeries(functions)(cb);
}

function parallel(functions, cb) {
  makeParallel(functions)(cb);
}

ってだけだったりします.(^^;


そして flowless が Slide から一番影響を受けているのが,非同期 API を呼び出すためだけに一行だけの無名関数を書く手間を減らせることです.
つまり,

flowless.series([
  function(cb) {
    fs.readFile('path1', 'utf8', cb);
  },
  function(cb) {
    fs.readFile('path2', 'utf8', cb);
  },
  ...

なんて書く代わりに,

flowless.series([
  [fs.readFile, 'path1', 'utf8'],
  [fs.readFile, 'path2', 'utf8'],
  ...

って書くことができます.可読性? なにそれおいしいの?
っていうわけでもありませんが,一行野郎がだらだら続くコードの可読性が優れているとも思ってないので,これがいいのです.


んで,asyncblock のサンプルを flowless で書くとこうなります (taedium さんの nue 版は こちら).

var fs = require('fs');
var flowless = require('flowless');

flowless.series([
  flowless.makeParallel([
    [fs.readFile, 'path1', 'utf8'],
    [fs.readFile, 'path2', 'utf8']
  ]),
  function(results, cb) {
    fs.writeFile('path3', result.join(''), cb);
  },
  [fs.readFile, 'path3', 'utf8']
], function(err, result) {
  if (err) throw err;
  console.log(result);
  console.log('all done');
});

一行野郎が一人残ってますが,引数を加工したりする場合はしょうがないかなーってことで.ほぼ同等の記述をしている Async の例 (まじめに無名関数を書いている) と比べて,可読性は劣るどころか勝ってると言えるんじゃないかな?


一行野郎の配列表現では,直前のコールバックに渡された引数 (エラーを除く) を参照することもできます.

var fs = require('fs');
var flowless = require('flowless');

flowless.series([
  [fs.readFile, 'path1', 'utf8'],
  [fs.writeFile, 'path2', flowless.first, 'utf8']
], function(err, result) {
  if (err) throw err;
});

この例では,fs.writeFile() の 第 2 引数として flowless.first を指定しています.これにより,直前の非同期関数 fs.readFile() がコールバック function(err, data) に渡した (エラーを除いた後の) 最初の引数 datafs.writeFile() に渡されます.
つまり,上記は以下と等価です.

var fs = require('fs');
var flowless = require('flowless');

flowless.series([
  function(cb) {
    fs.readFile('path1', 'utf8', cb);
  },
  function(data, cb) {
    fs.writeFile('path2', data, 'utf8', cb);
  }
], function(err, result) {
  if (err) throw err;
});

ちなみに,secondthird もあります.それ以上必要なら無名関数を書いた方がいいでしょう.sixth とかじゃそれが何か分からないから.


ということで,flowless が現時点で提供している機能はこれだけです.
Less is more.Less but better.
そんなわけで (どんなわけで?),みんなも自分のフロー制御モジュールを書くといいと思うよ!!




id:taedium さんの反応を見て追記.

Map のたぐいは Async が充実しているのでそれを使えばいいかと思ってたのですが,せっかくなので試しに実装してみました.ほとんど parallel() のこぴぺですが.
まだコミットもプッシュもしていませんが,上記エントリと同じ例はこうなりました.

'use strict';

var fs = require('fs');
var flowless = require('../index');

flowless.series([
  [fs.readdir, __dirname],
  flowless.makeAsync(function(files) {
    return files.filter(function(filename) {
      return /\.txt$/.test(filename);
    }).map(function(filename) {
      return __dirname + '/' + filename;
    });
  }),
  flowless.makeParallelMap([fs.readFile, flowless.first, 'utf8'])
], function(err, files) {
  if (err) throw err;
  files.forEach(function(file) {
    console.log('-----');
    console.log(file);
  });
});

しれっと makeAsync() なんて増えてますが,これは同期的な (値を返す) 関数を非同期関数の皮で包んであげるだけのものです.つまり,戻り値やスローされた例外をコールバックに渡してくれます.


どうしようかなー,含めてもいいかなー.でもあまり増やすと less じゃなくて more になっちゃうしなー.実践で使ってないから判断材料に乏しい...

node-tunnel 0.0.1 リリース

しました.

0.0.0 からの変更点は次のとおりです.

  • Node v0.6 系 (0.6.11 以降) に対応しました.

Node v0.6 系までは CONNECT メソッドがちゃんとサポートされてなくて,TLS/SSL コネクションを既存のソケット上で確立する手段もなかったので,node-tunnel は Node v0.7 系 (ただし v0.7.1 および v0.7.2 はバグがあって動かない) のみを対象にしていました.

しかし,「企業の中からだと必要に決まってるだろ常識的に考えて」ということで,特例的に TLS/SSL コネクションを既存ソケット上で確立するパッチだけが Node v0.6 にバックポートされたので (v0.6.11 に含まれています),node-tunnel でも少々 (かなり?) 無理矢理ですが,Node v0.6 系をサポートすることにしました.

npm install tunnel でインストール,または npm update tunnel でアップデートできます.

node-tunnel 0.0.0 リリース

しました.

node-tunnel は,Node の http および https モジュールで HTTP の CONNECT メソッドを利用したトンネリングを扱う Agent を提供するモジュールです.

元々は Node 本体の issue (#2474) に対処するために書いたコードだったのですが,本体にはいらんだろうということになってしまったので,モジュールとしてリリースしました.
インストールは npm から.

$ npm install tunnel

こんな感じで使えます.

var tunnel = require('tunnel');

var myAgent = tunnel.httpsOverHttp({
  proxy: {
    host: 'localhost',
    port: 3128
  }
});

var req = https.request({
  host: 'example.com',
  port: 443,
  agent: myAgent
});

需要があるかどうかはよく分からないんですけどね...


なお,対応する Node のバージョンは v0.7.0 および v0.7.3 以降になります.
v0.6 系や,v0.7.1 および v0.7.2 では動作しません.

node-handlersocket 0.1.0 リリース

しました.

0.0.3 からの変更点は次のとおりです.

  • HandlerSocket の最新版が提供する機能に対応しました.
    • 認証
    • IN およびフィルタ
    • インクリメント/デクリメント
    • 更新または削除と同時にその前の値を取得
  • オプションの引数だった limit および offsetoptions 引数のプロパティになりました.
  • Connection.end()Connection.close() に変更されました.
  • Connection'end' イベントではなく 'close' イベントを生成するようになりました.

npm install node-handlersocket でインストール,または npm update node-handlersocket でアップデートできます.

Node.js で重い処理をしてしまったときにタイムアウトするの法 (TerminateExecution 編)

ちょっと前にこんなブログが書かれていました.

なぜ JavaScript Advent Calendar 2011 (Node.js/WebSocketsコース) に参加してくれなかったのか問い詰めたい.小一時間問い詰めたい.それはともかくとして,本題の懸念については誰もが一度は思うことですよね.ずいぶん前に話題になったこちらのエントリでも,赤文字で超目立つように問題としてあげられています.

でもまぁ,自分はどちらかというと「ブロックしちゃうような処理とか書かないし!!」みたいなことを顔を真っ赤にして答える方かな.タイムアウトするほどではなくても,イベントループを止めるような重い処理は避けるべきだから.普段からループは小さく分けてイベントループに戻るようにしておくべきだし,それを簡単にというか何も考えなくてもそうなるようにしてもいいくらいでしょう.


とはいえ,Node.js 日本ユーザグループの ML でも最近投稿があったように,数秒間ブロックするような処理を書いておいて,その間他のリクエストが処理できないからクラスタで複数プロセス立てて回避するという,マルチスレッド脳そのまんまな人たちも参入してくるほどに Node が普及しつつあるというのも現実です.たくさん不幸な人たちが出てきちゃいそうですねっ.
なので,長時間実行しているスクリプトというかコールバックをタイムアウトさせる方法を Node が提供すべきなのでしょう.しかし,これまではいくつかの障害があってそれは困難でした (少なくとも自分には).

V8 の問題

一つ目の障害は JavaScript の実行エンジンである V8 の制約です (自分は V8 は全然詳しくないので以下には多くの間違いがあるかもしれません).Node の前の安定版である v0.4 が採用していた V8 v3.4 まで,V8 は本格的なマルチスレッドには対応していませんでした.全く対応していなかったわけではありませんが.


v3.4 (もしかしたら v3.5) までの V8 は,プロセス中で VM が一つしか存在できませんでした.その VM を複数のスレッドから使うことはできましたが,ある瞬間に VMスクリプトを実行することができるのは実行権 (Locker によって管理されます) をもつスレッド一つだけに限られていました.あるスレッドでコールバックを呼び出してすぐに戻る,別のスレッドでコールバックを呼び出してすぐに戻る,みたいな場合はそれなりにうまく動くかもしれませんが,一つのスレッドが時間のかかるスクリプトを実行すると,他のスレッドはそれが終了するまで待たされてしまいます.協調的 (co-operative) なマルチスレッドに近い感じですね (ネイティブスレッドを複数使うにも関わらず!).
それじゃあんまりだよねってことなのか,VM の実行権を強制的 (preemptive) に切り替えることも可能です.そのために ContextSwitcher というスレッドが使われます.これを使っても,ある瞬間に VMスクリプトを実行できるのは一つのスレッドだけです.ただ,その実行が終了する前に他のスレッドに実行権を譲る仕掛けが提供されるだけ.
そんな時代から,実行中のスクリプトを強制終了する API が提供されていました.

  • static void TerminateExecution(int thread_id)

当然ですよね,Chrome とかで必要でしょうから.しかしこれ,コメントにはこう書いてあります.

   * TerminateExecution should only be called when then V8 lock has
   * been acquired with a Locker object.  Therefore, in order to be
   * able to terminate long-running threads, preemption must be
   * enabled to allow the user of TerminateExecution to acquire the
   * lock.

なんということでしょうか,TerminateExecution(int) を呼び出すには,V8 の実行権 (Locker) を持ってないといけないのです.そしてそのためにはスクリプトを実行しているスレッドは (ContextSwitcher スレッドを使って) preemptive に実行権を譲らないといけない...
言うまでもなく,ここでスクリプトを実行しているスレッドというのは Node のメインのスレッドのことです.それに対して余計な割り込みをかけなきゃいけないようだと,タイムアウトする機能を実装したところで Ryan に受け入れてもらうことは無理だっただろうと思います.実際のオーバーヘッドがどれくらいか測定したわけじゃないけど,きっといやがられるだろうな,と.


そんな状況も,Node の最新安定版 v0.6 に採用されている V8 v3.6 から (もしかしたら v3.5 かも) 変わりました.Isolate が導入されたのです.Isolate により,プロセスは独立した VMインスタンスを複数持つことができるようになりました.一つの Isolate は従来の VM そのまんまな感じで,複数のスレッドで同じ Isolate を使うことはできますが,その場合ある瞬間にその Isolate でスクリプトを実行できるスレッドは一つだけです.しかし,異なる Isolate ではそのような制限はなく,複数のスレッドはそれぞれの Isolate を同時 (simultaneously) に実行することが可能です.
そして新しいバージョンの TerminateExecution() も導入されました.

  • static void TerminateExecution(Isolate* isolate = NULL)

こちらのコメントにはこう書いてあります.

   * This method can be used by any thread even if that thread has not
   * acquired the V8 lock with a Locker object.

これですよ,欲しかったのは...
これによって,Node がスクリプトを実行しているスレッドとは (ほとんど) 無関係なスレッドからスクリプトの実行を強制的に終了することができます.やったね!!

エラーハンドリングの問題

もう一つの問題は,スクリプトの実行を終了した後にどうするか,です.
いくらなんでも強制終了した後何もなかったかのように知らん顔をするわけにはいかないでしょう.なんらかのエラー処理ができないと困ります.しかし,以前のエントリでも書いたように,従来 Node のエラーハンドリングは EventEmitter'error' か,process'uncaughtException' か? という二択しか無く,この場合は実質後者しかないという状況でした.それじゃ辛いよねー.


そんな状況も,Node の次期安定版で変わります.Domains の導入です.Domains については先日の東京 Node 学園でも紹介したのでそちらのスライドも参考にどぞー.

これにより,Domain ごとにタイムアウトの上限を設定し,それを超えて実行を打ち切った場合は Domain で 'error' イベントを生成すればいいことになります.
要するにこういうこと.

var myDomain = domains.create(null, function(arg) {
  for (;;); // 無限ループしちゃっても…
});
myDomain.deadline = 1000; //  デッドラインは 1 秒なので…
myDomain.on('error', function(err) {
  // 1 秒後にはここに来る!
});

新しい Domain を作成して,その実行時間の上限を設定します.Domain に関連づけられたコールバックがこの上限を超えてもイベントループに戻らなかった場合,その実行は中断されて Domain 上で 'error' イベントが生成されます.その Domain の他の I/O はキャンセルされますが (Domains の仕様),他の Domain はそのままなので,HTTP リクエストごとに Domain を作成するなどしていれば,他のリクエストに影響を与えることもありません.いい感じでしょ?

デモ

ということで,実際に実装してみました.

とはいえ,まだ Isolates と Domains は別々に開発されていて,両方を同時に使うことができません.しょうがないので,ひとまず Isolates のブランチ (isolates2 ブランチ) をベースにしました.そのため,実行時間の上限は固定で持っています (5 秒).また,実行が打ち切られた場合は process'uncaughtException' で通知されます.


実際に動くサンプルです.

var http = require('http');
http.createServer(function(req, res) {
  process.once('uncaughtException', function(err) {
    res.end(err + '\n');
  });
  for (;;);
}).listen(3000);

これ,普通に動かすと最初のリクエストで無限ループに入り,それっきりレスポンスを返すことはありません.しかし! このパッチを適用すると 5 秒でタイムアウトして,レスポンスとしてエラーメッセージが返ってきます.やってみましょう.

$ curl http://localhost:3000/
Error: Deadline Exceeded
$ 

ねっ!!
もちろん,複数リクエストを同時に飛ばしても,いずれは全部返ってきます.5 個投げると最後のやつが返ってくるのは約 25 秒後だけどw


なので,


Node を避けるべき「たった一つ」の,「たった一つ」の (大事なことなので ry) 理由が無くなるかもっ!?


といいたいところですが,本家の issue ではあまり反応がないので,これを Node 本体に入れられるかどうかは微妙です.っていうか,無理な気がする (既に諦め気味).10 月末の「東京 Node 学園祭 2011」で,「Isolates がサポートされるならこれをやりたい」って Ryan に話した時もあまり興味なさそうだったし,英語圏ではこの問題を取り上げるブログとかも多くはないようなので,グローバルではそもそも必要とされていないのかもしれません.

開発にたずさわる人数が増えたりすれば、開発者の品質もそろいずらくなるし、みたいなことを思ってしまう僕は異端なのでしょうか。

こういう懸念自体が日本 (の SIer) 固有なのかも,とか思ってしまう今日この頃でした.


P.S.
上記デモは Const なんとか先生 (id:Constellation) に相談に乗ってもらったり,サンプルモジュールを作ってもらったりしながら実装しました.ありがとうございました.

Seasar2.4.45 リリース

しました.


■変更点

Seasar2.4.44 からの変更点は次のとおりです.

  • Bug
    • [CONTAINER-438] - [S2DBCP] minPoolSizeを設定するとNullPointerExceptionが発生する問題を修正しました.[Seasar-user:21031]
    • [CONTAINER-439] - [S2JDBC-Gen] gen-entitytestタスクで、@Idが注釈されたbyte[]型のプロパティを持つエンティティクラスを対象とすると例外が発生する問題に対応しました。
    • [CONTAINER-440] - [S2Container] Hotdeploy モードで DbSession を使用すると NotSerializableException が発生する問題を修正しました.[Seasar-user:21179]

■移行の注意点 (今回はありません)


■ダウンロードはこちらからどうぞ.


Maven2からのご利用はこちらを参照ください.

Dolteng 0.42.0 リリース

しました.


■変更点

0.41.0 からの変更点は次のとおりです.

  • Bug
    • [DOLTENG-128] - JRE が Java5 以降の場合,クラスパスおよび pom.xml の dependencies に複数のバージョンの JUnit が設定される問題を修正しました.[Seasar-user:20284]
    • [DOLTENG-129] - プロジェクト作成ウィザードで作成されるプロジェクトのファイルエンコーディングUTF-8 なのに,dao.dicon がデフォルトのままのため,SQL ファイルのエンコーディングが "JISAutoDetect" になっている問題を修正しました.[Seasar-user:20408]
    • [DOLTENG-130] - HTML からページクラスを作成する機能で、<input type="image"> 要素に対して do〜() メソッドを作成できない問題を修正しました.[Seasar-user:20633]
    • [DOLTENG-131] - HTML テンプレートの disabled 属性や readonly 属性に対するダイナミックプロパティが boolean 型ではなく String 型で作成されてしまう問題を修正しました.[Seasar-user:20781]

  • Task
    • [DOLTENG-132] - Seasar2 のバージョンを 2.4.45 に更新しました.
    • [DOLTENG-133] - Teeda のバージョンを 1.0.13-sp11 に更新しました.
    • [DOLTENG-134] - SAStruts のバージョンを 1.0.4-sp9 に更新しました.
    • [DOLTENG-135] - Java7 でプロジェクト作成ウィザードのファセットが表示されるようにしました. [Seasar-user:21129]


■セットアップ手順はこちらからどうぞ.