Skip to content

9Pメッセージは非同期実行に対応できるのか

9Pのメッセージは並列に送受信可能なのかを調べた。ここで扱う型などは9Pにおける識別子でまとめてある。

lib9p では srv でリクエスト読み込んで処理する。srv は主に postmountsrv から呼ばれるもので、9Pサーバーのメインループを実装している。

void
srv(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 でクライアントに返す。これも直列で送っている。

void
respond(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 としてみると、ネットワークに流れるデータを逐次処理しているだけで、並列要素は無い。

lib9p では非同期サポートが無かったけれど、ではユーザーコード側で非同期化することは可能なのか調べる。基本的に9Pのプロトコル実装では戻り値を受け取っていないので、ユーザーコードを非同期化することは特に問題がないと思う。

void
srv(Srv *srv)
{
// ...
while(r = getreq(srv)){
switch(r->ifcall.type){
case Tread:
sread(srv, r);
break;
}
}
}
static void
sread(Srv *srv, Req *r)
{
// ...
if(srv->read)
srv->read(r);
// ...

ではリクエストとレスポンスの順番が入れ替わった場合、もしくは応答が遅れた場合はプロトコル的に対応できているのだろうか。リクエストの後に必ずレスポンスが応答されることに依存していないのだろうか。

これの答えは「Fidtag があって、これがリクエストに対応しているので順番が前後しても区別される」で良い。具体的には 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 で使っている。

void
mountmux(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 それ自体は全てを直列に処理している
  • ただしファイルサーバーのコードで非同期化することは可能