2013年9月19日

harmony:spread (6)

おととい mozilla-central に無事入って、昨日の Firefox Nightly 27.0a1 から使えるようになりましたとさ。
めでたしめでたし。

コードレビュー、テストなどなど手助けをしてくれた jorendorff、jandem、sfink
コードの基礎になる spreadarray を実装してくれた benjamin
ドキュメントの整備に協力してくれた teoli, sheppy、rwaldron
その他かかわった大勢に感謝。

ちなみに仕様との不一致は仕様の方を変更する形で解消するそうですよ。

2013年9月9日

Task.jsm でハマったおはなし

Task.jsm は Firefox 17 から入った JavaScript モジュールで、yield と Promise を使って非同期処理をコールバックなしに書けるという素晴らしいヤツです。

ところが、Task.jsm は Firefox 24 の時点では resource://gre/modules/commonjs/sdk/core/promise.js を使用していて、yield に Promise を渡す度に、then(promise.js:155)、then(promise.js:45)、resolve(promise.js:120)、TaskImpl_run(Task.jsm:207) という感じでスタックを少しずつ食います。
このため、同じジェネレータ内で yield を 1000 回くらい呼んだ時点で too much recursion で落ちてしまいます。

Cu.import("resource://gre/modules/Task.jsm");

let simpleTask = function() {
  return Task.spawn(function() {
  });
};
  
let loopTask = function() {
  Task.spawn(function() {
    for (let i = 0; i < 1000; i ++) {
      yield simpleTask();
    }
    throw new Task.Result("ok");
  }).then(function(result) {
    Cu.reportError("result:" + result);
  }, function(e) {
    Cu.reportError("error:" + e);
  });
};

loopTask();

もちろん、そんな処理を書く方が悪いといえばそうかもしれませんけど。 (UnMHT で保存する時に各 DOM ノードの情報を取得するのに Task を使ってみたところ落ちた)

試験的に Task.jsm の中で Promise.jsm を使うように書き換えてみたところ、こちらは非同期になってスタックを食わないので平気なようです。

さて、そうなると Task.jsm が Promise.jsm を使うようになってくれると嬉しいのですが、Bug 887923Bug 881047 を見る限り、(同期的な処理前提のコードがあるため?) 簡単には行かないようです。

とりあえず、自分が使う範囲においては問題ないと思うので、どうしても Task.jsm を使いたくなったら Promise.jsm と一緒にして自分のアドオンの中に放り込む事にしましょうか。

ちなみに yield に渡すのが Promise じゃなくても TaskImpl_run(Task.jsm:211) の 1 段食いますが、実際のスタックの消費量が少ないので (Firefox 24 からスタックの制限は呼び出しの数ではなくメモリの使用量になったので) 60000 回くらいでようやく落ちました。