/* Copyright (C) 2003-2006 Russ Cox, Massachusetts Institute of Technology */ /* See COPYRIGHT */ /* * Generic RPC packet multiplexor. Inspired by but not derived from * Plan 9 kernel. Originally developed as part of Tra, later used in * libnventi, and then finally split out into a generic library. */ #include #include #include static int gettag(Mux*, Muxrpc*); static void puttag(Mux*, Muxrpc*); static void enqueue(Mux*, Muxrpc*); static void dequeue(Mux*, Muxrpc*); void muxinit(Mux *mux) { memset(&mux->lk, 0, sizeof(Mux)-offsetof(Mux, lk)); mux->tagrend.l = &mux->lk; mux->rpcfork.l = &mux->lk; mux->sleep.next = &mux->sleep; mux->sleep.prev = &mux->sleep; } static Muxrpc* allocmuxrpc(Mux *mux) { Muxrpc *r; /* must malloc because stack could be private */ r = mallocz(sizeof(Muxrpc), 1); if(r == nil){ werrstr("mallocz: %r"); return nil; } r->mux = mux; r->r.l = &mux->lk; r->waiting = 1; return r; } static int tagmuxrpc(Muxrpc *r, void *tx) { int tag; Mux *mux; mux = r->mux; /* assign the tag, add selves to response queue */ qlock(&mux->lk); tag = gettag(mux, r); /*print("gettag %p %d\n", r, tag); */ enqueue(mux, r); qunlock(&mux->lk); /* actually send the packet */ if(tag < 0 || mux->settag(mux, tx, tag) < 0 || _muxsend(mux, tx) < 0){ werrstr("settag/send tag %d: %r", tag); fprint(2, "%r\n"); qlock(&mux->lk); dequeue(mux, r); puttag(mux, r); qunlock(&mux->lk); return -1; } return 0; } void muxmsgandqlock(Mux *mux, void *p) { int tag; Muxrpc *r2; tag = mux->gettag(mux, p) - mux->mintag; /*print("mux tag %d\n", tag); */ qlock(&mux->lk); /* hand packet to correct sleeper */ if(tag < 0 || tag >= mux->mwait){ fprint(2, "%s: bad rpc tag %ux\n", argv0, tag); /* must leak packet! don't know how to free it! */ return; } r2 = mux->wait[tag]; if(r2 == nil || r2->prev == nil){ fprint(2, "%s: bad rpc tag %ux (no one waiting on that tag)\n", argv0, tag); /* must leak packet! don't know how to free it! */ return; } r2->p = p; dequeue(mux, r2); rwakeup(&r2->r); } void electmuxer(Mux *mux) { Muxrpc *rpc; /* if there is anyone else sleeping, wake them to mux */ for(rpc=mux->sleep.next; rpc != &mux->sleep; rpc = rpc->next){ if(!rpc->async){ mux->muxer = rpc; rwakeup(&rpc->r); return; } } mux->muxer = nil; } void* muxrpc(Mux *mux, void *tx) { int tag; Muxrpc *r; void *p; if((r = allocmuxrpc(mux)) == nil) return nil; if((tag = tagmuxrpc(r, tx)) < 0) return nil; qlock(&mux->lk); /* wait for our packet */ while(mux->muxer && mux->muxer != r && !r->p) rsleep(&r->r); /* if not done, there's no muxer: start muxing */ if(!r->p){ if(mux->muxer != nil && mux->muxer != r) abort(); mux->muxer = r; while(!r->p){ qunlock(&mux->lk); _muxrecv(mux, 1, &p); if(p == nil){ /* eof -- just give up and pass the buck */ qlock(&mux->lk); dequeue(mux, r); break; } muxmsgandqlock(mux, p); } electmuxer(mux); } p = r->p; puttag(mux, r); qunlock(&mux->lk); if(p == nil) werrstr("unexpected eof"); return p; } Muxrpc* muxrpcstart(Mux *mux, void *tx) { int tag; Muxrpc *r; if((r = allocmuxrpc(mux)) == nil) return nil; r->async = 1; if((tag = tagmuxrpc(r, tx)) < 0) return nil; return r; } int muxrpccanfinish(Muxrpc *r, void **vp) { void *p; Mux *mux; int ret; mux = r->mux; qlock(&mux->lk); ret = 1; if(!r->p && !mux->muxer){ mux->muxer = r; while(!r->p){ qunlock(&mux->lk); p = nil; if(!_muxrecv(mux, 0, &p)) ret = 0; if(p == nil){ qlock(&mux->lk); break; } muxmsgandqlock(mux, p); } electmuxer(mux); } p = r->p; if(p) puttag(mux, r); qunlock(&mux->lk); *vp = p; return ret; } static void enqueue(Mux *mux, Muxrpc *r) { r->next = mux->sleep.next; r->prev = &mux->sleep; r->next->prev = r; r->prev->next = r; } static void dequeue(Mux *mux, Muxrpc *r) { r->next->prev = r->prev; r->prev->next = r->next; r->prev = nil; r->next = nil; } static int gettag(Mux *mux, Muxrpc *r) { int i, mw; Muxrpc **w; for(;;){ /* wait for a free tag */ while(mux->nwait == mux->mwait){ if(mux->mwait < mux->maxtag-mux->mintag){ mw = mux->mwait; if(mw == 0) mw = 1; else mw <<= 1; w = realloc(mux->wait, mw*sizeof(w[0])); if(w == nil) return -1; memset(w+mux->mwait, 0, (mw-mux->mwait)*sizeof(w[0])); mux->wait = w; mux->freetag = mux->mwait; mux->mwait = mw; break; } rsleep(&mux->tagrend); } i=mux->freetag; if(mux->wait[i] == 0) goto Found; for(; imwait; i++) if(mux->wait[i] == 0) goto Found; for(i=0; ifreetag; i++) if(mux->wait[i] == 0) goto Found; /* should not fall out of while without free tag */ fprint(2, "libfs: nwait botch\n"); abort(); } Found: mux->nwait++; mux->wait[i] = r; r->tag = i+mux->mintag; return r->tag; } static void puttag(Mux *mux, Muxrpc *r) { int i; i = r->tag - mux->mintag; assert(mux->wait[i] == r); mux->wait[i] = nil; mux->nwait--; mux->freetag = i; rwakeup(&mux->tagrend); free(r); }