9Pメッセージは非同期実行に対応できるのか
9Pのメッセージは並列に送受信可能なのかを調べた。ここで扱う型などは9Pにおける識別子でまとめてある。
lib9pの調査
Section titled “lib9pの調査”lib9p では srv でリクエスト読み込んで処理する。srv は主に postmountsrv から呼ばれるもので、9Pサーバーのメインループを実装している。
voidsrv(Srv *srv){ Req *r; // ... if(srv->start) srv->start(srv); while(r = getreq(srv)){ if(r->error){ respond(r, r->error); continue; } switch(r->ifcall.type){ default: respond(r, "unknown message"); break; case Tversion: sversion(srv, r); break; case Tauth: sauth(srv, r); break; // ...ここでは sauth などを呼び出しているが、sxxxx 関数は fid を設定して、必要であればユーザーコードに処理を移譲している。この時点では並列性が無い。
r->fid = allocfid(srv->pool, r->ifcall.fid)
// satuthの場合はr->afidを設定r->afid = allocfid(srv->pool, r->ifcall.afid)応答は respond でクライアントに返す。これも直列で送っている。
voidrespond(Req *r, char *error){ Srv *srv = r->srv; r->error = error; switch(r->ifcall.type){ default: assert(0); case Tflush: rflush(r, error); break; case Tversion: rversion(r, error); break; case Tauth: rauth(r, error); break; // ...なので lib9p としてみると、ネットワークに流れるデータを逐次処理しているだけで、並列要素は無い。
非同期処理は可能なのか
Section titled “非同期処理は可能なのか”lib9p では非同期サポートが無かったけれど、ではユーザーコード側で非同期化することは可能なのか調べる。基本的に9Pのプロトコル実装では戻り値を受け取っていないので、ユーザーコードを非同期化することは特に問題がないと思う。
voidsrv(Srv *srv){ // ... while(r = getreq(srv)){ switch(r->ifcall.type){ case Tread: sread(srv, r); break; } }}
static voidsread(Srv *srv, Req *r){ // ... if(srv->read) srv->read(r); // ...ではリクエストとレスポンスの順番が入れ替わった場合、もしくは応答が遅れた場合はプロトコル的に対応できているのだろうか。リクエストの後に必ずレスポンスが応答されることに依存していないのだろうか。
これの答えは「Fid に tag があって、これがリクエストに対応しているので順番が前後しても区別される」で良い。具体的には lib9p にある getreq のコードで、リクエストを読み込んで、その中に含まれる Fcall.tag を使って Req を生成している。
// 必要なところだけ抜粋static Req*getreq(Srv *s){ Fcall *f; Req *r; n = read9pmsg(s->infd, s->rbuf, s->msize); buf = emalloc9p(n); memmove(buf, s->rbuf, n); convM2S(buf, n, &f); r = allocreq(s->rpool, f.tag);ここで使っている allocreq の説明は9p-fid(3)で書かれているが、Reqpool にハッシュマップを持っていて、tag でこれを管理しているようだった。
ではクライアント側、一般的にはカーネルになるが、カーネルは devmnt.c でタグの生成を行っている。
Mntrpc*mntralloc(Chan *c, ulong msize){ new = mntalloc.rpcfree; if(new == nil){ new = malloc(sizeof(Mntrpc)); new->rpc = mallocz(msize, 0); new->request.tag = alloctag(); }else{ mntalloc.rpcfree = new->list; mntalloc.nrpcfree--; // 再利用可能なrpcがあればtagも含めてそのまま使う } // ...}
Chan*mntauth(Chan *c, char *spec){ Mnt *mnt; Mntrpc *r; mnt = c->mux; if(mnt == nil){ mntversion(c, MAXRPC, VERSION9P, 0); mnt = c->mux; } c = mntchan(); r = mntralloc(0, mnt->msize); r->request.type = Tauth; r->request.afid = c->fid; mountrpc(mnt, r); c->qid = r->reply.aqid; c->mchan = mnt->c; c->mqid = c->qid; // ...}このタグは mountmux で使っている。
voidmountmux(Mnt *mnt, Mntrpc *r){ Mntrpc **l, *q; l = &mnt->queue; for(q = *l; q; q = q->list) { if(q->request.tag == r->reply.tag) { *l = q->list; // ... q->done = 1; if(q != r) wakeup(&q->r); return; } }}なので、ここまで見た限りでは tag によってリクエストを識別しているので、9Pプロトコルはメッセージの届く順序が異なっていても問題ない。
- 9Pプロトコル自体は非同期呼び出しに対応できる仕様になっている
- Plan 9カーネルも9Pメッセージが前後しても問題ない実装がある
- lib9p それ自体は全てを直列に処理している
- ただしファイルサーバーのコードで非同期化することは可能