#oss_gate5月28日に開催された東京Ruby会議11で「アプリケーションへのRubyインタープリターの組み込み」と題して、アプリケーションにRubyを組み込む実装について話しました。内容は前述のスライドや発表動画を参照してください。ここでは発表後の質疑応答の内容について補足します。質問:ruby_sysinit()は呼ばなくていいの?「ruby_sysinit()は呼ばなくていいの?」に対する当日の回答は「ruby_sysinit()の説明は省略した」だったのですが、どうして省略したかを補足します。Rubyインタプリターを組み込んだアプリケーションの1つであるrubyコマンドの実装(main.c)を見るとruby_sysinit()を呼んでいます。そのため、Rubyインタプリターの初期化には必要なAPIにみえます。ruby_sysinit()のコメントには次のように書いています。言うとrubyコマンドを初期化するためのもので、Rubyインタプリターを組み込むときはこの関数を呼ぶんじゃなくて自分で初期化してね、と言っています。initialization.中身を見ると、コマンドライン引数と標準入出力を初期化しています。Windowsで動く場合はもっと初期化しています。managerではruby_sysinit()を呼んでいません。コマンドライン引数は自前で処理しています。managerがWindowsをサポートしていないからできることです。Windows用の初期化をしているrb_w32_sysinit()をチラ見するとわかりますが、この関数がやっている処理のうち、自分に必要な分はどこかを判断してそれらを自前で実装することは難しいでしょう。(Windowsに詳しい人ならそうでもないかもしれません。)そのため、Windowsでも動くアプリケーションにRubyインタプリターを組み込む場合はruby_sysinit()を呼ぶのがよいでしょう。(コメントでは呼ぶなと書いていますが。)質問:共有ライブラリーの方にアプリケーションのメイン関数を渡せばアプリケーションでRUBY_INIT_STACKを呼ばなくていいんじゃない?スライドで言うと以下のページの実装についての質問です。アプリケーションへのRubyインタープリターの組み込み以下のようにしてembedded_init()内でapplication_main()を呼ぶようにすればいいんじゃない?という話です。application_main();}これに対する回答は「RubyインタプリターとPythonインタプリターを一緒に組み込めないのでやりたいことを実現できない」でした。一緒に組み込む時のイメージは次の通りです。↑でメイン関数が実行されちゃう。embedded_python_init(application_main);}それに対する別案が「他のインタプリターの初期化もやる関数を渡せば?」でした。}}質疑応答はここで時間切れでした。たしかにこれで動きそうです。会議後にはこんなアイディアもありました。2016年5月30日これはどういうことかというと、アプリケーション側ではRUBY_INIT_STACKに必要なデータを用意するだけにして、実際の呼び出しは別の共有ライブラリーの方に移す、という実装にすればいいんじゃない?ということです。RUBY_INIT_STACKはruby_init_stack()というアドレスを引数にとるAPIを呼び出しているだけなので、このアイディアも実現できます。managerでも実装できます。(アプリケーション本体に手を入れられないケースではこの方法の実装は難しいでしょう。)「アプリケーションがRUBY_INIT_STACKを呼ばないといけない問題」は複数の方法で解決できますね!このような場で話をするといろんなアイディアを聞けて便利ですね!みなさんも積極的に実装の話をしてはいかがでしょうか。RUBY_INIT_STACK問題が解決したので、複数言語のインタプリターを組み込んだケースのことを想像してみたところ、次はforkで問題にあたりそうです。Ruby以外の言語が複数のスレッドを作っている場合が問題になりそうです。外部ライブラリーとGC質疑応答では触れられませんでしたが、外部のライブラリーが使用しているメモリー量をRuby(mrubyもCRubyも)のGCシステムが知らないために適切なタイミングでGCが動かない問題についても補足します。発表では「外部からRubyのGCシステムにメモリー使用量を通知する仕組みが必要だと思う」と話しました。CRubyのTypedDataにはメモリー使用量を返すAPIがありますが、それは使えないはずです。それを使うと、GCシステム側が情報をpullする仕組みになるからです。pullする仕組みにすると、いつpullするの?pullした情報をどうやって管理するの?あたりが大変になりそうです。通知する仕組みだとこれらの問題を解決できそうです。いうことで、gc-triggerという拡張ライブラリーを作ってみました。このライブラリーは他の拡張ライブラリーに使用メモリー通知用のAPIを提供します。他の拡張ライブラリーがこのAPIを使ってメモリー使用量を通知すると、適切なタイミングでGC.startを実行します。本来はCで提供しているAPIを呼んだほうがよいですが、テスト用・説明用にRubyのAPIも用意したので、そっちを使って使い方を説明します。ここでは、例としてrcairoを使います。rcairoは画像を扱うからです。画像を扱う拡張ライブラリーはRuby管理外のメモリー使用量が大きめなので、この問題に遭遇しやすいケースなのです。次のコードは1000個画像オブジェクトを生成します。どの画像オブジェクトも参照されていないのですぐにGCで回収できます。height)endgc-triggerでメモリー使用量の増減を通知するコードは次のようになります。height)end画像オブジェクトのサイズは、単純化して「1画素のサイズ(4バイト)×縦×横」で計算しています。それぞれの場合でのメモリー使用量をグラフにすると次のようになります。従来の方はメモリー使用量が安定しませんが、gc-triggerを使った方はメモリー使用量が一定になります。発表で話したアイディアを動くようにしてみました。これで通じるでしょうか。Gateワークショップ2016-05-28を東京Ruby会議11と同時開催しました。東京Ruby会議11でポスターを展示していたので、そこではじめて知ったという方も多かったのではないでしょうか。Gateワークショップを紹介していて痛感したことは「口頭での説明でないとちゃんと伝えられない」ということでした。「私はすごくないのでメンターできないです。」に対して口頭では伝わるように回答できますが、資料では伝わらないです。これだとスケールしないので口頭での説明でなくても伝わる資料の作成が必要です。Gateワークショップ2016-07-30の登録者が10人以上増えました。東京Ruby会議11実行委員会に協力してもらえて本当によかったです。ありがとうございました。ちなみに、想像以上に登録者が増えたおかげでメンターが足りなそうです。Gateワークショップ2016-07-30に登録してください。定員オーバーでキャンセル待ちでの登録になりますが、気にしないでください。キャンセル待ち扱いでも気にせずに当日来てください。Gateワークショップ2016-05-28について補足しました。東京Ruby会議11の参加者アンケートはまだ回答を受け付けているので参加した方はぜひ回答してください。2014の10日目の記事です。Postfixは2.3からmilterという仕組みをサポートしました。filter」の略で、送信したメールまたは受信したメールになんらかの処理を行う仕組みです。Sendmailが作った仕組みですが、Sendmail・Postfix以外のMTAでもサポートしているMTAがあります。2.6ではSendmailとほぼ同等の機能をサポートしています。SendmailとPostfixでマクロ(後述)名が違うなど一部非互換な部分もありますが、SendmailでもPostfixでも同様に使えます。filterとの使い分けはどうすればよいか、という情報が有用かと思いますが、ここではそのような説明をしません。そのような説明ではなくmilterプロトコルについて説明します。milterプロトコルについての情報は、Postfixユーザーの人にとっては有用ではなく、milter開発者にとってもそんなに有用ではなく、一部のmilterライブラリー開発者にとっては有用*1です。Calendarっぽくなくてごめんなさい。残念ながら2014/12/10中に完成しませんでした。続きはいつかどこかで。はじめに多くのmilterはlibmilterというSendmailが提供しているライブラリーを利用して実装します。libmilter内でmilterプロトコルを実装しているのでmilter開発者がプロトコルの詳細を気にする必要はありません。libmilterのAPIを知っていれば十分です。ここではlibmilterのAPIは説明しません。milterを作るための情報を探している人は次のページを参考にしてください。CまたはC++でmilterを実装したい人向けlibmilterのAPI(英語):Sendmail提供のライブラリー。manager提供のライブラリーlibmilter-clientを使ったmilterの実装例。libmilter-clientはイベント駆動ベース。manager提供のlibmilter-clientのRubyバインディングの使い方。gem:Rubyでmilterプロトコルを実装したライブラリー。ppymilter(後述)をRubyで実装したもの。Pythonでmilterを実装したい人向けpymilter:libmilterのPythonバインディング。ppymilter:Pythonでmilterプロトコルを実装したライブラリー。Perlでmilterを実装したい人向けSendmail::Milter:libmilterのPerlバインディング。Sendmail::PMilter:Perlでmilterプロトコルを実装したライブラリー。Frameworkの実装。この中にmilterプロトコルの実装がある。普通の人向けの情報は提供したので、ここからは普通の人向けではない情報です。実はmilterプロトコルの仕様書はありません。Sendmailでの挙動が事実的な仕様になっています。Postfixや各言語でのmilterプロトコルの実装は、Sendmail・libmilterのソースコード*2や、実際のSendmail・libmilterの動きを参考にmilterプロトコルを実装したものです*3。milterプロトコルについてまとめたメモはあります。Perlでmilterプロトコルを実装した人が残したもの(英語)です。まとめた時期が古い(milterプロトコルのバージョンでいうと2)ので新しいmilterプロトコル(最新はバージョン6)についての情報はないのですが、基本的なことからまとまっているので有用な情報です*4。ベースのやりとりそれではmilterプロトコルについて説明します。ベースとなるやりとりの方法について説明します。milterプロトコルはMTAとmilter間でやりとりします。基本的に、MTAからコマンドを送り、milterが応答する、という流れになります。ーコマンドと応答のフォーマットは同じです。フォーマットは次の通りです。|「データサイズ」はネットワークバイトオーダーで表現されています。データサイズはデータサイズ自体のサイズ(4バイト)を含みません。「データID」と「データ」を合わせたサイズ(バイト数)です。「データID」はこのデータの種類を示す1文字のASCII文字です。「CONNECTというコマンド」を表すデータIDは「C」です。「データ」はデータIDに関連するデータです。データIDによって中身が変わります。0バイトのときもあります。milterプロトコルを実装するときは、まずは、このフォーマットをデコードする処理とこのフォーマットにエンコードする処理を実装します。managerでの実装です。一連のやりとりベースのやりとりがわかったところで、次は一連のやりとりについて説明します。個々のやりとりはこのあと説明します。milterプロトコルはSMTPと関連が深いです。SMTPの1つのセッションがmilterプロトコルでの1つのセッションに対応します。milterプロトコルでの1つのセッションは次のようになります。FROMコマンド」から「メッセージ終了コマンド」の「応答」までを複数回繰り返します。SMTPのセッションと似ていますね。TOコマンド」、「DATAコマンド」はそれぞれSMTPの対応するコマンドを実行したときに実行されます。SMTPで「DATA」の後に指定したメッセージはパースされて個別にmilterに送られます。次は個々のやりとりのうち「MTAから送るデータ」について説明します。MTAから送るデータをコマンドと呼びます。コマンドのID、名前、説明は次の通りです。メールトランザクション中で終了するコマンド。本文のチャンク(一部)を送るコマンド。本文が大きい場合は複数のチャンクにわけて渡される。(複数の本文チャンクコマンドが送られる。接続してきたSMTPクライアントに関する情報を送るコマンド。コマンドに対する付加情報を送るコマンド。メッセージ全体を送ったことを知らせるコマンド。このコマンドの応答でだけメールを変更できる。SMTPのHELO/EHLOに関する情報を送るコマンド。メッセージのヘッダーを1つ送るコマンド。複数のヘッダーがある場合は複数回送られる。FROMに関する情報を送るコマンド。すべてのヘッダーを送ったことを知らせるコマンド。milterセッション開始時に動作を調整するコマンド。