SyntaxHighlighter

2012年4月15日日曜日

Ruby の Garbage Collection に関するメモ

Exerb でのバグ報告に関連して、Ruby の Garbage Collection について調べていたときのメモです。

Ruby の EXE 化

Ruby 勉強会の準備をしていて、「Windows 上での Ruby の EXE 化」についてまとめていました。
私は Ruby 1.9.3 では Ocra を使っていますが、Ruby 1.8.7 の頃は Exerb を使っていました。

どちらもとてもよいツールでお世話になっていますが、どちらかというとレシピファイルで同梱ファイルを指定できる Exerb の方が好きです。
ただし、Exerb は Ruby 1.9 には対応していません。
もともとの作者の Yuya さんは、今は開発に関わっていないらしく、このまま 1.9 系列の開発はされないのかもしれません。
ちょっと残念です。

Exerb で報告されていた挙動

2月ぐらいに、Integer#to_s でごくまれに変な文字列になるという報告がされていました。
で、今回、勉強会の資料を作るついでに気になったので (というか、重大なバグがツールに含まれていると勉強会でおすすめしづらいので) 調べてみました。

たとえば、以下のようなソースで、ごくまれに変な文字列が表示されます。

原因

原因を調べるために、何ヶ月かぶりに Windows を立ち上げて調べてみました。

原因は、Bignum クラスのメソッド to_s 内で、テンポラリに作られた Bignum オブジェクトが、意図せず Garbage Collection で解放されてしまうためでした。

なお、これは Ruby 1.8.7 での話です。Ruby 1.9 では、このあたりがもう少し高速なアルゴリズムも併用するように変わった関係で、発生しません。

bignum.c の rb_big2str0 関数に以下のようなところがあります。

ここでは、(1) で変換元の値 x をコピーして、その中の実際に数値を保持している配列のアドレスを (2) でポインタ ds に取り出しています。
その後、戻り値用の文字列オブジェクトを (3) で用意し、これ以降で変換した後の文字列を生成しています。

障害が発生する場合は、(1) で生成したオブジェクトが (3) で解放されてしまっていました。
その結果、(3) 以降で t の中身である ds を参照しても、変な値しか残っていない状態になっていました。

Ruby の Garbage Collection

Ruby のスクリプトの中で new されたオブジェクトならともかく、C言語で書かれた Ruby ライブラリや、Ruby 自体のソースコードの中で生成されたオブジェクト (今回の t のようなもの) が、どうやって自動で解放されているのか、ずっと不思議でした。
オブジェクトの一覧は簡単に取得できるはずですが、どれを解放の対象とするかどうかの判断が難しいのではないかと思っていました。

WEB 上で資料を探してみると、少し古いですがRuby ソースコード完全解説の中で詳しく解説されていました。

こちらを読みながら、実際に現在のソースコードを見ていると、C言語ライブラリなどで生成されたオブジェクトは、スタック上に Ruby オブジェクトらしきものを探し、発見されればそれは Garbage Collection の対象としないという仕組みになっているようでした。

率直な感想としては、「そんな適当に判断して大丈夫なの」というものでしたが、Garbage Collection は、解放可能なものをすべて解放する必要はなく、解放すべきでないものを解放してしまわなければよいものなので、これでもうまくいくようです。

ちなみに、今回の障害は、上記ソースコードの t と ds が運悪くスタック上の同じ番地にとられてしまうと発生していました。
Exerb のコンパイルオプションだと、偶然そうなっていたようです。
その結果、(3) の時点では既にスタック上からは t そのものは消えており、Garbage Collection による解放の対象となってしまっていました。

修正

よって、ds を参照している間は、t もスタック上に残っているようにすれば、間違って解放されることもなくなります。

Ruby のソースコードでは、このためのマクロがあり、RB_GC_GUARD が定義されています。

これを使い、RB_GC_GUARD(t); を ds を最後に参照している箇所より後に書けば、うまくいくようになります。

マクロ RB_GC_GUARD は、volatile 修飾子を使って、引数である t を強制的にその場で参照するようにするものです。
その結果、t が ds と同じメモリ番地に割り当てられるようなこともなくなり、きちんと t がスタック上に残るようになります。

というわけで、念の為に Ruby 本家の方も直してもらおうとチケットを書きかけましたが、Exerb のメーリングリストを見ておられたのか、Ruby コミッタの nobu さんが既に修正されていました。

nobu さん、ありがとうございます。
そして、Ruby 1.8.7 の ChangeLog に私の名前も入ることになりました。わーい。

2012年4月9日月曜日

YouTube に動画をアップロードしてみました

YouTube に動画をアップロードしてみました

長い間、記事を書いていなかったので、内容はないですが、 YouTube に初めて動画をアップロードしてみたので載せます。

動画の説明

Subversion

Git, Mercurial など、分散型のバージョン管理システムがはやってきていますが、習得の容易さや、切り替えのコストなどの理由から、特に会社などでは Subversion もまだまだ使われているようです。

Subversion は、 Apache が開発していることもあり、標準で Apache 経由での HTTP によるリポジトリの公開ができます。

ブラウザでのリポジトリの表示

ブラウザで、 Subversion のリポジトリにアクセスすると、最新のリビジョンの内容を表示させることができます。 また、 p=1234 のようなオプションを URL に付けると、任意のリビジョンを指定して表示させることもできます。

しかし、ログや変更日時、作者やファイルサイズなどは、標準では表示されません。

私としては、ログを表示できないのはかなり不満です。

そういうわけで、 TortoiseSVN のリポジトリビューのようなものを、ブラウザ内で実現してみました。

こちらは、そのサンプル動画です。

暇があればきちんと書いて公開し、説明を書こうと思います。

2012年2月9日木曜日

tinyURL で、長い data スキームを処理させる

tinyURL で、長い data スキームを処理させる

忘れたらググればいい に書いてあったことを見て、こんなのを考えてみました。

原理上は、どのような長さの data スキームであっても、 tinyURL で短い URL に変換できるはずです (対応ブラウザなどは調べていません)。

こういうことを tinyURL がよいと思っているかどうか不明なので、 GitHub などには置きません。
自己責任でどうぞ。

長い data スキームであっても tinyURL で処理可能にするスクリプト

できること

原理上、どれだけ長くても tinyURL で data スキームへリダイレクトさせることができます。

例えば、前に作ってみた iPhone Safari 上で動くテトリスでは、 http://tinyurl.com/7zsxzglのようになります。

このリンクをクリックすると、 Loading... と出た後、 data スキームで記述されたテトリスの画面になります。

本題とは関係ないですが、このテトリスは、枠内を、フリック・タップすることで、移動・回転です。

手順

あらかじめ、任意の長さの data スキームに変換済みの HTML ページを用意し、以下のようなコマンドで、 URL が生成されます。

なお、 Ruby スクリプト test.rb は、記事の末尾につけておきます。

原理

概要としては、だいたい以下のようになります。

  • data スキームを短い断片に分割し、それぞれを tinyURL で短縮 URL にしておきます。
  • トップページとなる Loading 用のページを用意します。最終的に、このトップページを data スキーム化し、 tinyURL で短縮 URL にしたものにユーザーはアクセスします。
    このトップページでは、以下のようなことを行います。
    • JSONP を用いることで、動的に、分割した断片の data スキーム文字列を集めて結合します。
    • すべての文字列が結合されたら、 location.replace で、生成された data スキームにジャンプします。

実際には、リンクリストになるように、少し工夫した断片 tinyURL を生成したりしていますが、原理は以上のようなものです。

スクリプトでは、対象となる data スキームファイルを用意するだけで、上記をすべて自動で行ってくれます。

というような方法で、あらかじめ tinyURL を生成しておけば、 Twitter などでも容易に長いデータや画像などのやりとりができると思います。

なお、繰り返しておきますが、 tinyURL 側が、これを良いと思っているのかどうかは分かりませんし、私も推奨しているわけではありません。
あくまで、技術的にはこういったことができる、ということだとご理解ください。

なお、ターゲットとなる data スキームファイルは、以下のように、 data: なども付与した状態で作成しておいてください。

data:text/html;base64,PD94bWwgd...

以下にスクリプト test.rb 全文を載せます。 Ruby 1.8, 1.9 で動くはずです。

適当に作ったので、バグなどあるかもしれませんが、ご了承ください。

なお、このスクリプトは、プロキシ下の環境ではうまく動かないかもしれません。
get_tiny_url の中をプロキシ対応にするのと、12行目の length を短めにすると、うまく動作すると思います。

2012年2月2日木曜日

Mozilla Vision 2012 Conference Day

Mozilla Vision 2012 Conference Day

忙しくてブログを書く暇がとれませんでしたが、少し前の Mozilla Vision 2012 Conference Day について書こうと思います。

この Conference は、「Web テクノロジーからアート、教育まで、オープンを考える」という主題のもとに開かれたもので、私は以下のセッションに出ました。

  • モバイル業界に真のオープンを!
  • Web 標準の今、そして未来
  • pdf.js - HTML5 と JavaScript の限界に挑戦する

以下では、出席したセッションを中心に、適当な雑感を書こうと思います。なお、英語が聞きとれない箇所もあったので、いつにも増して不正確な内容が多いかもしれません。

モバイル業界に真のオープンを!

Boot2Gecko プロジェクトの話でした。

簡単に書くと、 Android の基盤を利用し、真にオープンな Mobile プラットフォーム (スマートフォン) を構築する、ということが目的のようです。
Chrome OS のスマートフォン版という印象を受けました。

Mozilla としては、 PC の環境では Web ブラウザ上で非常にリッチな体験が可能になっており、それが OS を問わずに可能になっているが、スマートフォンの分野では Android, iOS, Windows Phone など、各社の独自規格が乱立しており、仕様としてオープンなものが中心になっていない、と考えているようです。

そこで Mozilla の解としては、 HTML5 を中心としたテクノロジでスマートフォンを動作させることを目指しているようです。

個人的には、近い将来に実現するのは難しい印象を受けました。

PC の場合、連綿と続いてきた x86 アーキテクチャや、 ACPI という OS とファームウェアのインターフェース、 UEFI といった具合に (オープンかは別として) デファクトスタンダードな仕様が固まっています。

だから Linux のようなものを簡単にインストールできるわけであり、 Chrome OS も作ることが可能なのだろうと思います。

一方で、 Android の方に関しては、ハードウェアやファームウェアの仕様は今後まだまだ流動的な状態が続くのではないだろうかと思います。
そのため、 Mozilla が独自に各社からどんどん出てくる Android 機種に対して、対応する Boot2Gecko を開発するのは難しいのではないかと感じました。
少なくとも、近い将来に「スマートフォンの OS を手軽に入れかえる」ということが一般的になることはないと思いました。

また、私の隣りの人がとても流暢な英語で質問していましたが、結局 HTML5 上ですべてのアプリを動かそうとすると、 Android や iOS のように、 Native に近い環境でアプリを動かすのに比べ、どうしても動作速度の面で劣るのではないかという懸念があります。

これらのことから考えると、私としてはすぐに一般的になることはないように感じました。

Web 標準の今、そして未来

こちらは、「オープンな仕様を目指して」といった内容でした。

CSS の策定などにも関わってきた発表者の方が、「オープンな仕様がいかに大事か」や「オープンなプロセスを保つためにはどのようにするべきか」などについて、熱く話していました。

個人的には、 HTML, CSS の歴史の裏話は面白かったのですが、発表者と自分自身の温度差が大きかったです。

pdf.js - HTML5 と JavaScript の限界に挑戦する

こちらは、 JavaScript で PDF を表示するという挑戦でした。

技術寄りの話だったこともあり、非常に面白い内容でした。

冒頭は、お決まりの「PDF のプラグインは closed だからよくない」ということからスタートでした。

ただ、技術やユーザー視点でのメリットとしては、「JavaScript で提供することで、 PC, Mobile のいずれでも使える」や「ブラウザの挙動 (戻る/進む など) と、一貫性のある動作を提供できる」などがありました。

技術的には以下のような方法で取り組んだそうです。

基本的な描画

canvas で描画する。 SVG は用いない。

画像/文字の描画

PDF は、ブラウザがサポートしていない画像形式やフォント形式をサポートしている。

画像に関しては、 JavaScript でデータを解析し、 canvas に描画している。
フォントに関しては、ブラウザ未対応な形式のフォントデータを、ブラウザが対応している OpenType に変換することで、対応した。
このフォント部分の解決がなければ、プロジェクトは頓挫していたらしい。

リンク機能

リンク機能に関しては、ブラウザの「戻る」、「進む」と完全に連携しており、プラグインよりもずっと使いやすくなったとのことだった。

テキスト選択

canvas に画像として描画しているので、テキストを選択することができない。
これを解消するために、 canvas の上に、透明な色で別途文字を通常のテキストとして配置することにしたらしい。
この文字自体は、フォントなどはそれほど考慮されていないもので、あくまでテキスト選択ができるようにするためのものらしい。

このあたりまでくると、「そこまで頑張らなくても。。。」という気がしなくもない。

高速化

UI 以外は、極力 WebWorker を使って Core を分散することで高速化を実現したとのこと。

いずれも、技術的には面白かったのですが、少しやり過ぎな感がありました。


行く前は、 Google Developer Day のようなものを期待していたので、そういう意味では期待外れでした。

一方で、 Mozilla が「オープン」というものを、強烈に推進しようとしているという熱意は、異常なほどに強く感じました。WLAN のパスワードからして whatisopen でしたし。

そういう意味で、 Netscape という膨大な資産をつぎこんで作られたプログラムを公開するという、考えられないような決断をしたことからスタートした Mozilla という組織は、方向がぶれないまま進んでいるという印象を受けました。

2012年1月1日日曜日

NPAPI プラグインの作り方 for Windows その5

NPAPI プラグインの作り方 for Windows その5

今回は、サンプルの簡単な説明をします。

なお、今回でひとまず NPAPI プラグインの作り方は終了です。要望があればコメント欄に書いてください。気が向けば対応します。

これまでと同じく今回のサンプルは、GitHub のページからダウンロードできます。
この中の modified_files/npruntime/plugin.cpp が主に説明する対象です。

HTML ファイルからプラグインの呼び出し

テスト用の HTML ファイル test.html からプラグインを参照しているのは以下の箇所です。

embed タグはプラグインをロードするために使われるタグで、type 属性にプラグインの種類を指定することで、目的のプラグインをロードします。

type 属性には、プラグインの MIME type を指定します。
サンプルの場合は、application/mozilla-npruntime-scriptable-plugin になっています。

このプラグインの MIME type は、Windows の場合はリソースファイル nprt.rc 内で定義されています。

オリジナルのプラグインを作成する場合は、以下の MIMEType の箇所を好きなものに変更してください。
もちろん、変更した場合は test.html の embed タグの方も同じ文字列に変更してください。

なお、MIME type の仕様としては、独自の種類を定義する際は application/x- で始まる文字列にすることが推奨されているようです。

複数の MIME type に対応する場合は、| で区切ればよいようです。

なお、HTML ファイルからの呼び出しには直接は関係ありませんが、ProductName でプラグインの名前を、FileDescription でプラグインの説明を記述するなどが決められています。

また余談ですが、Windows 向けの NPAPI ではこのようにリソースファイルで MIME type を指定しますが、Unix などでは、np_entry.cpp 内の NP_GetMIMEDescription 関数で返す文字列で MIME type を指定します。
逆に言えば、Windows 環境では NP_GetMIMEDescription で返される文字列の値は、ブラウザに参照されることはありません。

プラグイン内で使われる変数

NPVariant

NPAPI プラグインの中では、JavaScript 側とやりとりをする際に、NPVariant という型がよく出てきます。

この型は、npruntime.h 内で以下のように定義されています。

type フィールドには、現在 value に保持している値の種類が格納されており、value にその値が保持されます。

Windows の COM を扱う際に登場する VARIANT 型と同様なものだと考えればよいと思います。

NPVariant 型の変数に値を代入したり値を読み出したりする際は、BOOLEAN_TO_NPVARIANT や NPVARIANT_TO_BOOLEAN マクロを使うとよいと思います。

変数の動的確保と解放

JavaScript 側とやりとりをする場合、特に文字列などをメソッドなどの戻り値として返す場合、その文字列の領域を動的にメモリ上に確保する必要があります。

通常の C 言語であれば、malloc と free などを呼び出しますが、NPAPI プラグインでは NPN_MemAlloc と NPN_MemFree を用いることになります。

ちなみに、前回の記事でサンプルの修正をしたのは以下の部分でした。

これは、本来であれば NPN_MemAlloc でメモリを確保すべきなのですが、_strdup で文字列用のメモリを確保してしまっていることが問題でした。

NPN_MemAlloc, NPN_MemFree などは、プラグインロード時にブラウザから関数ポインタとして渡されます。
そのため、プラグイン作成者側には、その内部でどのようにメモリが管理されているかを仮定することはできません。

そのため、NPN_MemAlloc 内部で malloc と同様のものを呼んでいるのであれば、サンプルを修正しなくても動作するかもしれませんが、そうでない場合は、ブラウザ側が戻り値の文字列を NPN_MemFree しようとしたタイミングでクラッシュすることになります。

プラグインのメソッドの呼び出し

ここでは、プラグインのメソッドの呼び出し方法と、新しいメソッドの追加方法について書きます。

プラグインのメソッドの呼び方

プラグインのメソッドを呼ぶには、以下のように embed 要素を取得し、その要素に対してメソッドを呼び出します。

プラグインのメソッドの定義

呼び出されるメソッドは、以下のように定義されます。

ここでは、実際にサンプルで定義されている foo メソッドについて説明します。

HasMethod の対応

まず、HasMethod 関数の対応をします。

この関数は、特定の名前のメソッドを持っているかどうかを判定するための関数です。

サンプル内では、下記のようになっている箇所です。

ここで、sFoo_id は、CPlugin::CPlugin で初期化されていますが、"foo" という文字列を表すものだと思ってください。

上の例では、sFoo_id が表す foo という名前か、sDiskSize_id が表す diskSize という名前の文字列なら true を返すことになります。

メソッドの実体の定義

メソッドの実体は、以下のような関数内で定義されています。

この Invoke 関数は、プラグインに対してメソッドが呼ばれたときに、メソッド名や引数の配列などとともに呼び出されます。

そこで、この関数の中で、メソッド名に応じて処理を分けてやることで、各メソッドの処理を実装することができます。

上の例では、もともと存在した foo メソッドに加え、新規に diskSize というメソッドを追加した場合です。

このように、いくらでも自由にメソッドを追加することができます。

もちろん、数が多くなればメソッド名とコールバックをハッシュに格納するなどして、効率化すべきでしょう。

プロパティの場合も、HasProperty, GetProperty, SetProperty 関数の内部で同様に実現されています。

ひとまずこれで簡単な NPAPI プラグインの作成方法の説明を終わります。
質問などがあれば、コメント欄までお願いします。

2011年12月30日金曜日

NPAPI プラグインの作り方 for Windows その4

NPAPI プラグインの作り方 for Windows その4

ずいぶん更新が止まってしまいましたが、NPAPI プラグインの作り方の続きです。

今回は、サンプルが動くようにしてみます。

なお、今回のサンプルは、GitHub のページからダウンロードできます。

サンプルの動作のさせ方

まず、前回作成したサンプルの NPAPI プラグインを動作させてみます。

ただし、もともとのサンプルのプラグインのバグが原因で、まだ正しく動作しません。

プラグインの登録

Firefox の場合

Firefox の場合は、インストールされたディレクトリ (C:\Program Files\Mozilla Firefox など) の下に plugins というディレクトリがあるので、その下に作成した DLL をコピーします。

これで、次回以降の起動でプラグインが動作するようになります。

Google Chrome の場合

Google Chrome の場合は、拡張機能を作成するのが簡単だと思います。

Google Chrome の拡張機能の作り方は、Chrome Extensions API リファレンスなどを参照してください。

バックグラウンドページのみを持つような拡張機能を作成し、以下のような plugins 属性を持った manifest ファイルを作成します。

public を true にしておくと、プラグインをどのページからでも参照できるようになります。

ただし、これはセキュリティホールになることが多いので、テスト環境のみにした方が無難だと思います。

「ツール」-「拡張機能」を開き、デベロッパーモードにチェックを入れた上で「パッケージ化されていない拡張を読み込む...」で読み込むと、プラグインを使えるようになります。

テスト用 HTML の用意

前回の Firefox の公式ページのサンプル npruntime には、テスト用のファイル test.html が含まれていますので、これをブラウザで開きます。

画面の下の方にはいくつかボタンが並んでいると思いますが、これらを押すと、本来ならメッセージがポップアップ表示されるはずです。

しかし、ページ読み込み時かボタンクリック時に、ブラウザが「プラグインがクラッシュした」というようなメッセージを出してくると思います。

そうならない場合は、プラグインが正しく読み込まれていない可能性が高いので、手順を確認してください。

なお、test.html 内には二つの embed タグがありますが、今回作成したサンプルは、一つ目の embed タグに対して読み込まれます。

二つ目の embed タグに関しては「対応するプラグインが見つかりません。」のように表示されたままですが、今回は特に対応したりはしないことにします。

GitHub に用意しておいた test.html から、二つ目の embed タグは削除してあります。

サンプルの修正

ブラウザがクラッシュしないように修正します。

修正するのは plugin.cpp 内の3箇所です。

GibHub の diff が見易いのでこちらを参照した方がよいかもしれません。

452行目付近の _strdup
以下のように、*result に _strdup の結果を代入するのではなく、NPN_MemAlloc でメモリを確保した上で *result に代入してやります。
466行目付近
こちらも、まったく同様に _strdup によるコピーを修正します。
540行目付近
以下のように、NPN_Evaluate の最後の引数に変数を渡してやり、戻り値を受け取れるようにします。
この値は不要なので、忘れず NPN_ReleaseVariantValue で解放してやります。

この状態でコンパイルしなおし、DLL をコピーした上で、もう一度サンプルの test.html を閲覧すると、クラッシュせずに動作することが確認できると思います。

次回は、このサンプルにディスクサイズを取得するメソッドを追加し、もう少しサンプルの動作を説明する予定です。

2011年12月12日月曜日

Redmine Plugin で ApplicationHelper を拡張する際の注意

Redmine Plugin で ApplicationHelper を拡張する際の注意

Windows PC の調子が悪いので、NPAPI プラグインの作り方は少し延期です。

今回は、Redmine 1.3.0もリリースされたので、 Redmine Plugin で ApplicationHelper を拡張する際に悩んだ問題について書きます。

Redmine プラグインで、既存のクラスやモジュールを拡張する

Redmine プラグインを作成し、 ApplicationHelper などの既存のクラスやモジュールを拡張する場合、基本的には以下のようにします。

  1. プラグインのlib以下にredmine_sample_plugin_application_helper_patch.rbのようなファイルを作成し、以下のように記述します。
  2. init.rb内部で、redmine_sample_plugin_application_helper_patch.rbをrequireします。

上に書いたように、一般的にはApplicationHelperを再定義せずに、追加したり置換したりしたいメソッドを別モジュールにまとめた上で、これをインクルードさせることが多いです。

View のフック関数からの呼び出し

Redmine のプラグインへのフレームワークとして、View の決められた場所に、プラグインのコードを埋め込む、という仕組みがあります。

ここでは説明しませんが、知りたい方はRedmine自体に手を入れずに見た目を変更する方法などをご覧ください。

いずれにせよ、Redmine::Hook::ViewListenerを継承したクラスを作成し、その中にあらかじめ定められた名前のフックメソッドを定義することで、View に任意のコードを追加できます。

View のフック関数から、追加したメソッドの呼び出し

上のSamplePluginApplicationHelperPatchのように定義した場合、新規に定義したメソッド (上の例ではsample_instance_method) は、View のフック関数から呼ぶことができません。

ApplicationHelper 内のメソッドは、すべてのコントローラーや View から呼べるはずなのですが、呼ぶことができないのです。

呼び出せない理由

ApplicationHelperは、Redmine::Hook::ViewListener内で適切にincludeされています。
また、上で定義したサンプルでは、確かにSamplePluginApplicationHelperPatch::InstanceMethodがApplicationHelperにインクルードされています。
そのため、間接的にSamplePluginApplicationHelperPatch::InstanceMethodはRedmine::Hook::ViewListenerからインクルードされているように思えます。

しかし、実際にはそうなりません。

Redmine::Hook::ViewListenerがApplicationHelperをインクルードするのは、プラグインの各ファイルがロードされるよりも早いタイミングで行われます。
そのため、Redmine::Hook::ViewListenerがApplicationHelperをインクルードする頃には、まだSamplePluginApplicationHelperPatch::InstanceMethodはApplicationHelperにインクルードされていません。

Ruby では、includeしたタイミングで継承関係が決定します。

例を挙げると、以下のようになります。

上の例で、B.ancestorsにCが含まれてもよさそうですが、含まれません。

これは奇妙なように思いますが、 Ruby の仕様であり、どうしようもありません。

余談ですが、 Ruby 作者のまつもとさんも、これは奇妙に思っているらしく、Matzにっきの Traits の項目に、今後mixを追加しようと思っている旨が書かれています。

そのため、もともとの Redmine の ViewListener に関しても同様に、Redmine::Hook::ViewListenerがApplicationHelperをインクルードするタイミングでは、まだSamplePluginApplicationHelperPatch::InstanceMethodがApplicationHelperにインクルードされていないので、結果としてRedmine::Hook::ViewListenerの ancestors にはSamplePluginApplicationHelperPatch::InstanceMethodが含まれないことになります。

回避策

これは、例えば以下のようにすれば回避することができます。

このようにすることで、sample_instance_method自体はApplicationHelperに直接追加されるので、Redmine::Hook::ViewListenerからでも呼び出すことが可能になります。

もちろん、base.class_eval doの内部で、直接sample_instance_methodを定義しても呼び出すことができますが、このあたりはコードの見易さの問題で、好みの分かれるところだろうと思います。