Waylandプロトコルデザイン: オブジェクトの寿命

原題: Wayland protocol design: object lifespan

本記事は"Wayland protocol design: object lifespan"の翻訳である。
This post is translated from the original post, "Wayland protocol design: object lifespan"
Date: 2014/8/4 07:50:42 (Tuesday) UTC

我々は今や、数年間に渡ってWaylandプロトコルを手掛けてきた。よって、私の意見をいくつか文章にしようと思い至った。
願わくば単独の投稿ではなく連投になればと思っているが、ここではいかに正しい方法でWaylandプロトコルの拡張を設計するか考察する。

最初の投稿ではプロトコルオブジェクトの寿命とそれに関係するコンポジタ/サーバーとクライアント間の競合について考える。
私は読者がすでにWaylandプロトコルの基礎について把握していることを想定してるので、そうで無い場合は
"Chapter 4. Wayland Protocol and Model of Operation"
を読むこと勧める。


如何にして、プロトコルオブジェクトが作成されるのか
(訳注: ドキュメントのWire Formatか前のブログ記事参照)
ある新しいWaylandコネクション(訳注: おそらくクライアントとコンポジタ間の接続のこと)においては、特別に作成されたるwl_displayが存在する唯一のオブジェクトである。
wl_displayは常に存在し、wl_displayを作るプロトコルは無い。

クライアントが次に作ることのできるオブジェクトがwl_registryでありwl_displayを用いる。
レジストリは全てのインターフェース(クラス)の階層の頂点である。wl_registryは数値で表される名前(name)によってグローバルオブジェクトを知らせる。
又、wl_registry.bind要求を使ってオブジェクトへ束縛するのが、プロトコルオブジェクトを作る最初で普通の方法である。

束縛することはまた少し特別である。XMLで書かれたwl_registryのプロトコル仕様書(訳注: Waylandのprotocol/wayland.xml参照)はnew_id引数型を使っているが、
新しく作るオブジェクトのインターフェース(クラス)を指定していないのである。
この特別な引数型は3つの引数へ変様する。インターフェース名(string)、インターフェースバージョン(uint32_t)、新しいオブジェクトID(uint32_t)である。
これはWaylandのコアプロトコルにおいて、唯一である。

新しいプロトコルオブジェクトを作る通常の方法はクライアントにとってはnew_id引数型を持つ要求を送ることである。
プロトコル仕様書(XML)はインターフェースが何であるかを定義する、であるからインターフェース型をワイヤープロトコルで伝える必要はない。
ワイヤープロトコルで必要なのは、新しいオブジェクトIDなのである。ほとんど全てのオブジェクトの作成がこの方法で行われる。
(訳注: ワイヤー, wireとはUNIXドメインソケットで実際に送信されるパケットやそのフォーマットに関することである)

稀ではあるが、クライアントのためにサーバーがプロトコルオブジェクトを作ることがある。
これはイベントにおいてnew_id型の引数を持つことによって起きる。
クライアントがこのイベントを受信をする度に新しいプロトコルオブジェクトを受け取ることとなる。

全てのリクエストやイベントは(クラスのメンバーの様に)何かのインターフェースの一部であるから、このことがインターフェースの階層を作り出している。
例えば、wl_compositorオブジェクトはwl_registryから作られ、wl_surfaceオブジェクトはwl_compositorから作られる。

オブジェクトの作成は絶対に失敗しない。一度リクエストやイベントが送信されれば、それが作った新しいオブジェクトが存在する、以上。
このことがプロトコルを非同期に保っており、それ故、オブジェクト作成が成功したかどうかを返信したり確認したりする必要が無い。


如何にして、プロトコルオブジェクトは破棄されるのか
プロトコルオブジェクトを破棄する方法は2通りある。最も一般的な方法はデストラクタとして指定されたリクエストをインターフェース内に持つことである。
ほとんどの場合、このリクエストは"destroy"と呼ばれる。
クライアントのコードがwl_foobar_destroy()関数を呼ぶ時、そのリクエストはサーバーに送られ、そのオブジェクトのクライアント側のプロキシ(struct wl_proxy)が破棄される。
その後、サーバーは将来のいずれかの時点において破棄要求を処理する。

もう一つの方法はイベントによってオブジェクトを破棄することである。
そのような場合では、インターフェースのプロトコル仕様書でデストラクターを定義する必要は無い。
又、自動化や安全装置の役割を果たすものが無いので、そのイベントが破壊的であることをドキュメントに明記しなければならない。
これはオブジェクトが何時死ぬかをサーバーが決める場合のものであり、全ての状況において正しく動くよう、プロトコル設計に細心の注意を払わなければならない。
クライアントがこの様なイベントを受け取った時できることはプロキシオブジェクトを破棄することだけである。この様なインターフェースの(非)有名な例がwl_callbackである。


ブギーマンに入る: 競合
クライアントとサーバーの双方がどのプロトコルオブジェクトが存在するかに同意することは非常に重要である。
もしクライアントが、サーバーの考えにないオブジェクト、若しくは引数としての参照のリクエストを送信した場合、サーバーはプロトコルエラーを起こし、クライントとの接続を切断する。
明らかにこれは絶対に起きるべきでは無く、サーバーがクライアントが破棄したオブジェクトへのイベントを送信することも又、同様である。

Waylandは完全な非同期プロトコルであるので、私達には明確な保証が無い。
サーバーはクライアントがオブジェクトを破棄するのと同時にイベントを送るかもしれず、その時イベントはクライアントがもう知らないオブジェクトを対象としているのである。
クライアントが自滅するのではなく(これはサーバーの仕事である)、libwayland-clientにはうまいやり方が存在する:
それは暗黙的にサーバーがそのオブジェクトが本当に亡くなったことを確認するまで、破棄されたオブジェクトへのイベントを無視するのである。

これはデストラクターがリクエストである様なインターフェースにとっては非常にうまく行く。
もしクライアントが最初に破棄要求を送信し破棄されたオブジェクトのリクエストを送信した場合、そのオブジェクトが自分で頭を撃ちぬくのである。如何なる競合も必要で無い。

他の場合、デストラクターイベントにおいては物事はややこしくなる。
サーバーはクライアントがオブジェクトのリクエストを送っているのと同じ時に同じオブジェクトへのデストラクターイベントを送信するかもしれない。
サーバーが最終的にリクエストを取得した時、オブジェクトはすでに破棄されている。
それ故、デストラクターイベントを安全に使うほとんど唯一の方法はインターフェースが如何なるリクエストも持たないことなのである。
常にそうであり、将来の拡張においさえもそうである。
更に、そのインターフェースのオブジェクトは如何なる箇所においても引数として使われるべきでは無く、さもなくば、競合に遭遇することとなるであろう。
これがデストラクターイベントを正しく使うのが難しい理由である。


ギーマンの兄弟
オブジェクト(例えばサーバーが作るオブジェクト)を作るイベントに関するもう一つのたちの悪い競合がある。
もしサーバーがある新しい(子)オブジェクトを作成しながら、その(親)オブジェクトのイベントを送信している最中に、
クライアントがその(親)オブジェクトを破棄していていたなら、サーバーはクライアントが実際にイベントを処理したかどうか知り得ない。
もし、クライアントがそのイベントを無視したなら、クライアントは決してその新しいオブジェクトを破棄するようサーバーには伝えないであろう、そしてサーバーでリークすることになる。

その(親)オブジェクトが破棄されたら、その子オブジェクトを全て破棄するようなプロトコル仕様書を書くことによってこの落とし穴を回避することができる。
しかし、そうなれば、クライアントはその親オブジェクトを破棄した後、その子オブジェクトのデストラクターリクエストを送ることができない。
これは、サーバーが知らないオブジェクトのリクエストを見ることになるからである。
もし子のインターフェースがデストラクターを定義するなら、クライアントは親オブジェクトを破棄した後、その(子オブジェクトの)プロキシを破棄することができない。
もし子のインターフェースがデストラクターを定義しないなら、親オブジェクトが破棄されるまでサーバー側の資源を決して開放できない。

クライアントは全ての子オブジェクトをある定義されたコンストラクターでまとめて破棄し、その後直ちに親オブジェクトを破棄することもできる。
私はこれが動くかどうかわからないが、たぶん動くであろう。もし動かないなら、全体の破棄するプロトコルの順序を定める必要がある。
クライアントがサーバーにその親オブジェクトを破棄したいと伝え、サーバーはそれに応答しそのオブジェクトのイベントを以後送信しないことを保証する。
その後、クライアントが実際に親オブジェクトを破棄する。一周して、単に美しい非同期プロトコルから同期プロトコルにしただけである、おめでとう。


まとめと提案
以下がWaylandプロトコルの拡張を設計する時の私の提案である。

  • 常に全てのオブジェクトを破棄すると保証された方法を用意する

これは自明に思えるが、サーバーとクライアントの両方の資源が開放できるようなオブジェクトを破棄する方法が無い、Waylandコアプロトコルの問題をいくつか修正してきた。
更に、未だいくつか問題が修正されていない。

あなたの新しいインターフェースがデストラクタリクエストを必要とするのではないかという疑問が少しでもあるなら、デストラクタリクエストを加えておきなさい。
普通のリクエストより後から追加するのが厄介である。
もしデストラクタリクエストがないなら、クライアントはサーバーにこれらのオブジェクトの資源を開放するよう伝えることができない。

  • デストラクターイベントを使用しない

正しく設計するのは難しく、後からインターフェースを拡張するのは良くないであろう。
クライアントはサーバーに資源を開放するよう伝えることができないので、デストラクターイベントを有するオブジェクトは短命でなければならない。
又、その破棄は必ず保証されなければならない。

  • 深い考えなしにサーバー側で作られるオブジェクトを使用しない。

リークしたり爆発したりしない様な破棄の順序を設計するのは難しい。