2009年9月アーカイブ

デバッグログの出力用にNSLog()は普通に使うものだけど、リリースにあたってこの出力が鬱陶しいなと思う事がある。Console.app(コンソール)で見えてしまうというのもあるし、syslogで言うようなログレベルの設定もできないので、エラー時などに重要な意味を持つログメッセージから、「備えあれば憂い無し」的にたまに必要になるかもしれないときのために出しておくデバッグ情報まで、区別無く一カ所に出てしまうというのはどうしても扱いづらいなと感じるものだ。

そんなこんなで、リリースビルドの際にはNSLog()の出力をデバッグファイルにリダイレクトしてConsole.appには出さないような仕組みをIntermezzoには入れていた。おおむね下記の要領で。

int
redirectlog(const char* appname)
{
    char path[64], env[32], *p;
    time_t t;

    snprintf(path, sizeof(path) - 1, "/tmp/%s.debug.%lu.log", appname, time(&t));
    snprintf(env, sizeof(env) - 1, "%s_DEBUG", appname);
    for (p = env; *p; *p++) {
        *p = toupper(*p);
    }
    
    if (getenv(env)) {
        freopen(path, "w", stderr);
    } else {
        fclose(stderr);
    }
    return 0;
}

さてこれでSnow Leopardになって問題が起こった。Leopard時代にはこれで何事もなかったのだが、あるいは問題は発生していたのに発見できていなかったのだろうか。検証できる環境を無くして(=2台あるMacをともにSnow Leopardに上げて)しまったので、真相はひとまず分からなくなってしまったが。

どんな問題かと言うと、IntermezzoがIRCサーバとの通信を確立した直後にいきなり"Excess Flood"で通信を切断されてしまうのだった。どうも見てみると、ログ出力の内容がそのままIRCサーバに向けて送出されているらしい。何のことはない、IRCサーバとの接続でソケットディスクリプタがstderrがcloseされているために空きとなった2番に入ってしまったのである。NSLog()は仕様に沿ってSTDERR_FILENOすなわち2番に出力しようとするので、大量のログがIRCサーバとの間のソケットに書き出されてしまい、floodingと看做されて切断を余儀なくされたと言う訳だ。

という訳で、fclose()を以下のように置き換えて問題を回避することができた。まああとから考えれば自然なことだし、うんそうだよ普通に気付けよ俺っていうか。残るはLeopardでどうだったのか、ということだけども、うーん。どうなんでしょう。

int
redirectlog(const char* appname)
{
    ....

    if (getenv(env)) {
        freopen(path, "w", stderr);
    } else {
        freopen("/dev/null", "w", stderr);
    }
    return 0;
}

で、さっそく問題発声。ギャース。いや発生。

MT4プラグインであるStyleCatcherは、ネットワーク上のレポジトリに公開されているMT用のブログスタイルを取ってきて自分のブログに適用してくれるわけですが、標準のレポジトリ(http://www.sixapart.com/movabletype/styles/mt4/library)にとりにいくと、数が多いせいかかなり待たされます。で、SuiteXのHTTPサーバ(Apacheだよね?)のタイムアウト設定が短めで1分?かそこらに設定されていて、全ての処理が終わりきる前にタイムアウトが起こり、処理が継続できなくなるという事態に陥ってしまったのでした。うはあ。

"Timeout waiting for output from CGI script"てやつですな。

じゃ、一気に全部のスタイルを持ってこようとするのがよくないので、タイムアウトを起こさない程度の少量ずつを取ってくるようにすれば問題ないはずだよねと素朴に変更を加えてみたのが以下の通り。

diff -x mt-config.cgi -ur MT-4.261-ja/plugins/StyleCatcher/lib/StyleCatcher/CMS.pm MT-4.261-ja.new/plugins/StyleCatcher/lib/StyleCatcher/CMS.pm
--- MT-4.261-ja/plugins/StyleCatcher/lib/StyleCatcher/CMS.pm	2009-06-16 14:45:32.000000000 +0900
+++ MT-4.261-ja.new/plugins/StyleCatcher/lib/StyleCatcher/CMS.pm	2009-09-10 01:42:06.000000000 +0900
@@ -138,7 +138,7 @@
     my $app = shift;
     return $app->json_error( $app->errstr ) unless $app->validate_magic;
 
-    my $data = fetch_themes($app->param('url'))
+    my $data = fetch_themes($app->param('url'), $app->param('offs'), $app->param('count'))
         or return $app->json_error( $app->errstr );
     return $app->json_result( $data );
 }
@@ -449,7 +449,7 @@
 # pulls a list of themes available from a particular url
 sub fetch_themes {
     my $app = MT->app;
-    my ($url) = @_;
+    my ($url, $offs, $count) = @_;
     return undef unless $url;
 
     my $blog_id = $app->param('blog_id');
@@ -503,6 +503,10 @@
             }
             push @repo_themes, $css;
         }
+        $data->{paginate}{total} = $#repo_themes + 1;
+        $data->{paginate}{offset} = $offs;
+        $data->{paginate}{count} = $count;
+        @repo_themes = splice(@repo_themes, $offs, $count);
 
         my $themes = [];
         for my $repo_theme (@repo_themes) {
diff -x mt-config.cgi -ur MT-4.261-ja/plugins/StyleCatcher/tmpl/view.tmpl MT-4.261-ja.new/plugins/StyleCatcher/tmpl/view.tmpl
--- MT-4.261-ja/plugins/StyleCatcher/tmpl/view.tmpl	2009-06-16 14:26:14.000000000 +0900
+++ MT-4.261-ja.new/plugins/StyleCatcher/tmpl/view.tmpl	2009-09-10 02:04:15.000000000 +0900
@@ -393,6 +393,9 @@
         }
         if (data.result.themes)
             loadThemes(data.result.themes, cat);
+        var paginate = data.result.paginate;
+        if (paginate.offset + paginate.count < paginate.total)
+            getStyles(repo_id, data.result.repo['url'], paginate.offset + paginate.count, paginate.count);
     }
 
     function createCategory(cat_name, cat_title, url) {
@@ -484,7 +487,7 @@
         }
     }
 
-    function getStyles(repo_id, url) {
+    function getStyles(repo_id, url, offs, count) {
         var btn = getByID("find-button");
         TC.addClassName(btn, "hidden");
         TC.removeClassName(TC.elementOrId("loading-styles"), "hidden");
@@ -496,6 +499,8 @@
             'arguments': {
                 '__mode': 'stylecatcher_js',
                 'magic_token': '',
+                'offs': offs ? offs : 0,
+                'count': count ? count : 9,
                 'url': url
             }
         });

取得中状態の見せ方をもう少しかっこ良くすることもできると思うけど、とりあえずこんなかんじ。流れは以下のようになっちょります。まじで素朴にpaginate。

  • ブラウザからサーバへの取得リクエスト時に、「今回のオフセット」と「個数」を追加して送信
  • サーバ(CGI)は指定されたオフセットと個数分のみを切り出してブラウザに対して応答
  • このとき、応答に「今回のオフセット」と「個数」、さらに「全体の個数」を付加
  • サーバの応答を受け取り、ブラウザは表示処理を行う
  • 「今回のオフセット」+「個数」=「次回のオフセット」として計算
    • 「次回のオフセット」が「全体の個数」を下回れば、「次回のオフセット」と「個数」を用いて次の取得リクエストを送信し、同じ内容を繰り返す。
    • 「次回のオフセット」が「全体の個数」を上回るか等しければ、繰り返しを終了。

サーバ引っ越した

| コメント(0) | トラックバック(0)
今までWebARENAのSuitePRO V2にしてたんだけど、さすがに月々8k円超の値段なのにあまり活用できてないことに気がついたので、思い切って同じくWebARENAのSuiteXに移住してみた。これで月単価が約1/4くらいになった。

SuitePROではsshが使えてたのでApacheやらPostfixやらdovecotやら色々と細かく設定できてたのは嬉しいんだけど、それに見合う費用対効果っていうか、仕事で使うならいいにしてもメール受けとWebページ、blog(MT)くらいでしか使ってないのでねえ。それ以外で何かやる場合には自宅のサーバでやることにしたよ。

まだ生きてるよ!(挨拶)

ご他聞に漏れずうちもSnow Leopard入れましたが、Growlが現時点(1.1.6)でどうやらまだ対応してない(公式曰く1.2まで待ってけろとな)、というかx86_64用バイナリを持ってないようですね。

IntermezzoがGrowlを使用しているので、本体のx86_64対応を進めとこうと思ってもビルド時にリンクさせておくと当然エラーが出て怒られる。Growlが動かなくてもかまわないからとりあえずx86_64動作時にもGrowlの如何に関わらず動かしたいと思ったので、実行時の動的ロードに切り替えよっかってしてみたんだけどなかなか動かないからアレッと思ってギアいじったっけロー入っちゃってもうウィリーさ(何)

http://growl.info/documentation/developer/implementing-growl.php?lang=cocoa
Implementing Growl support in your Cocoa Application

ここに書いてあったのにね。

この"Using Cocoa, you can use the following code snipped..."を参考に少し修正したのが下です。

    Class bridgeClass = nil;

    NSString* growlPath = [[[NSBundle mainBundle] privateFrameworksPath] stringByAppendingPathComponent:@"Growl.framework"];
    NSBundle* growlBundle = [NSBundle bundleWithPath:growlPath];
    if (growlBundle && [growlBundle load]) {
        // bridgeClass = [growlBundle principalClass];
        bridgeClass = [growlBundle classNamed:@"GrowlApplicationBridge"];
        
        if ([bridgeClass isGrowlInstalled] == NO || [bridgeClass isGrowlRunning] == NO) {
            // Growl本体がインストールされてないか、動いてないよ
            return;
        }
        
        [bridgeClass setGrowlDelegate:self];
    } else {
        // Growlフレームワークがない、またはロードできないよ
        // x86_64の場合はここに来るよ
        return;
    }

    // ok

GrowlApplicationBridgeクラスがビルド時のリンクで解決できないので、classNamed:メソッドを使いバンドル(フレームワーク)から読み込んでリンクする方向に倒して、ようやく動くようになったとです。Growl.frameworkのInfo.plist読む限りでは、Principal ClassはGrowlApplicationBridgeクラスなので、classNamed:の代わりにprincipalClassメソッドでも行けるはずだけど、将来に渡ってどうかは分かんないのでとりあえずclassNamed:で名指しにしてます。まあそんときゃGrowlApplicationBridgeクラスだってdeprecatedになってるかもしんないけどねー。

まあ、Growl 1.2が出れば多分それで無問題なんだろうけどねー。

(*´・ω・)(・ω・`*)ネー