int main(int argc, char argv[]){ //Use our source file as the "key" int id=se207_semget("critical_example2.c",1); int pid=fork(); if(pid){ //P1 while(1){ se207_wait(id); printf("In critical section P1 ... \n"); rsleep(); printf("Ending critical section P1 ... \n"); se207_signal(id); } }else{ //P2 while(1){ se207_wait(id); printf("In critical section P2 ... \n"); rsleep(); printf("Ending critical section P2 ... \n"); se207_signal(id); } } }
void muxprocs(Mux *mux) { proccreate(_muxrecvproc, mux, STACK); qlock(&mux->lk); while(!mux->readq) rsleep(&mux->rpcfork); qunlock(&mux->lk); proccreate(_muxsendproc, mux, STACK); qlock(&mux->lk); while(!mux->writeq) rsleep(&mux->rpcfork); qunlock(&mux->lk); }
void spawnWaiter(Lock *l) { int pid; int64_t start; switch((pid = rfork(RFMEM|RFPROC|RFNOWAIT))) { case 0: /* wait for the alwaysLocked to be locked by the main process */ qlock(&rl); while(resInWaiter == 0xff) rsleep(&rStart); start = nsec(); resInWaiter = lockt(l, 6000); elapsedInWaiter = (nsec() - start) / (1000 * 1000); if(verbose) print("lockt returned %d, elapsed = %d ms\n", resInWaiter, elapsedInWaiter); rwakeup(&rCompleted); qunlock(&rl); exits(0); break; case -1: print("spawnWaiter: %r\n"); exits("rfork fails"); break; default: if(verbose) print("spawn waiter %d\n", pid); break; } }
static void icacheinsert(u8int score[VtScoreSize], IAddr *ia, int state) { IEntry *ie; if((ie = poplast(&icache.free)) == nil && (ie = evictlru()) == nil){ addstat(StatIcacheStall, 1); while((ie = poplast(&icache.free)) == nil && (ie = evictlru()) == nil){ // Could safely return here if state == IEClean. // But if state == IEDirty, have to wait to make // sure we don't lose an index write. // Let's wait all the time. flushdcache(); kickicache(); rsleep(&icache.full); } addstat(StatIcacheStall, -1); } memmove(ie->score, score, VtScoreSize); ie->state = state; ie->ia = *ia; if(state == IEClean){ addstat(StatIcachePrefetch, 1); pushfirst(&icache.clean, ie); }else{ addstat(StatIcacheWrite, 1); assert(state == IEDirty); icache.ndirty++; setstat(StatIcacheDirty, icache.ndirty); delaykickicache(); pushfirst(&icache.dirty, ie); } ihashinsert(icache.hash, ie); }
void delaykickroundproc(void *v) { Round *r = v; int n; threadsetname("delaykickproc %s", r->name); qlock(&r->lock); for(;;){ while(r->delaykick == 0){ trace(TraceProc, "sleep"); rsleep(&r->delaywait); } n = r->next; qunlock(&r->lock); trace(TraceProc, "waitround 0x%ux", (uint)n); sleep(r->delaytime); qlock(&r->lock); if(n == r->next){ trace(TraceProc, "kickround 0x%ux", (uint)n); _kickround(r, 1); } trace(TraceProc, "finishround 0x%ux", (uint)n); } }
/* * queue a lump & it's packet data for writing */ int queuewrite(Lump *u, Packet *p, int creator, uint ms) { LumpQueue *q; int i; trace(TraceProc, "queuewrite"); i = indexsect(mainindex, u->score); if(i < 0 || i >= nqs){ seterr(EBug, "internal error: illegal index section in queuewrite"); return -1; } q = &lumpqs[i]; qlock(&q->lock); while(q->r == ((q->w + 1) & (MaxLumpQ - 1))){ trace(TraceProc, "queuewrite sleep"); rsleep(&q->full); } q->q[q->w].u = u; q->q[q->w].p = p; q->q[q->w].creator = creator; q->q[q->w].ms = ms; q->q[q->w].gen = gen; q->w = (q->w + 1) & (MaxLumpQ - 1); trace(TraceProc, "queuewrite wakeup"); rwakeup(&q->empty); qunlock(&q->lock); return 0; }
void twritethread(void *v) { int n; Fd *f; f = v; qlock(&f->lk); for(;;){ n = 0; while(f->p == f->buf) rsleep(&f->r); qunlock(&f->lk); n = threadidle(); n = threadidle(); n = threadidle(); n = threadidle(); n = threadidle(); n = threadidle(); n = threadidle(); n = threadidle(); dbg(DbgFdbuf, "_twflush fd=%d from twritethread n=%d\n", f->fd, n); qlock(&f->lk); _twflush(f); } }
void flushqueue(void) { int i; LumpQueue *q; if(!lumpqs) return; trace(TraceProc, "flushqueue"); qlock(&glk); gen++; qunlock(&glk); for(i=0; i<mainindex->nsects; i++){ q = &lumpqs[i]; qlock(&q->lock); while(q->w != q->r && gen - q->q[q->r].gen > 0){ trace(TraceProc, "flushqueue sleep q%d", i); rsleep(&q->flush); } qunlock(&q->lock); } }
void fs_send(Queue *q, void *a) { int i, c; if(q == nil) panic("send null q"); if(!q->waitedfor) { for (i = 0; i < 5 && !q->waitedfor; i++) sleep(1000); if(!q->waitedfor) { /* likely a bug; don't wait forever */ print("no readers yet for %s q\n", q->name); abort(); } } qlock(q); while((c = q->count) >= q->size) rsleep(&q->full); i = q->loc + c; if(i >= q->size) i -= q->size; q->args[i] = a; q->count = c+1; rwakeup(&q->empty); /* no longer empty */ qunlock(q); }
void devwork(void *v) { Dev *d; Buf *b; Channel *r; uchar buf[BLOCK]; d = v; for(;;){ qlock(&d->workl); while(d->work.wnext == &d->work) rsleep(&d->workr); b = d->work.wnext; b->wnext->wprev = b->wprev; b->wprev->wnext = b->wnext; b->wnext = b->wprev = nil; qunlock(&d->workl); if(b->d == nil) /* this is a sync request */ goto reply; if(b->off >= d->size){ b->error = Einval; goto reply; } b->error = nil; if(b->op & BWRITE){ memset(buf, 0, sizeof(buf)); pack(b, buf); if(pwrite(d->fd, buf, BLOCK, b->off*BLOCK) < BLOCK){ dprint("hjfs: write: %r\n"); b->error = Eio; } }else{ int n, m; for(n = 0; n < BLOCK; n += m){ m = pread(d->fd, buf+n, BLOCK-n, b->off*BLOCK+n); if(m < 0) dprint("hjfs: read: %r\n"); if(m <= 0) break; } if(n < BLOCK) b->error = Eio; else unpack(b, buf); } reply: r = b->resp; b->resp = nil; if(r != nil) send(r, &b); } }
void main(void) { int64_t start, elapsed, res; static Lock l; rfork(RFNOTEG|RFREND); rStart.l = &rl; rCompleted.l = &rl; resInWaiter = 0xff; spawnWaiter(&l); lock(&l); alarm(20000); /* global timeout, FAIL if reached */ if (!atnotify(failOnTimeout, 1)){ fprint(2, "%r\n"); exits("atnotify fails"); } /* verify that lockt returns 0 on timeout */ start = nsec(); res = lockt(&l, 1000); elapsed = (nsec() - start) / (1000 * 1000); if(verbose) print("lockt returned %d, elapsed = %d ms\n", res, elapsed); if(res != 0 || elapsed < 900 || elapsed > 1300){ print("FAIL: lockt timeout\n"); exits("FAIL"); } /* verify that lockt returns 1 if the lock is released and * it can take it */ resInWaiter = -1; qlock(&rl); rwakeupall(&rStart); qunlock(&rl); sleep(1200); unlock(&l); qlock(&rl); while(elapsedInWaiter == 0) rsleep(&rCompleted); qunlock(&rl); if(resInWaiter != 1 || elapsedInWaiter < 1100 || elapsedInWaiter > 1500){ print("FAIL: lockt delayed acquisition\n"); exits("FAIL"); } print("PASS\n"); exits("PASS"); }
void qwrite(Queue *q, Block *db, u32int bno) { qlock(&q->lk); while(q->nel == MAXQ) rsleep(&q->r); q->el[q->wi].db = db; q->el[q->wi].bno = bno; if(++q->wi == MAXQ) q->wi = 0; if(q->nel++ == MAXQ/2) rwakeup(&q->r); qunlock(&q->lk); }
void waitforkick(Round *r) { int n; qlock(&r->lock); r->last = r->current; assert(r->current+1 == r->next); rwakeupall(&r->finish); while(!r->doanother) rsleep(&r->start); n = r->next++; r->current = n; r->doanother = 0; qunlock(&r->lock); }
void* recvq(Queue *q) { void *p; Qel *e; qlock(&q->lk); while(q->head == nil) rsleep(&q->r); e = q->head; q->head = e->next; qunlock(&q->lk); p = e->p; free(e); return p; }
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(; i<mux->mwait; i++) if(mux->wait[i] == 0) goto Found; for(i=0; i<mux->freetag; 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 int gettag(VtConn *z, Rwait *r) { int i; Again: while(z->ntag == 256) rsleep(&z->tagrend); for(i=0; i<256; i++) if(z->wait[i] == 0){ z->ntag++; z->wait[i] = r; return i; } fprint(2, "libventi: ntag botch\n"); goto Again; }
static void _kickround(Round *r, int wait) { int n; if(!r->doanother) trace(TraceProc, "kick %s", r->name); r->doanother = 1; rwakeup(&r->start); if(wait){ n = r->next; while((int)(n - r->last) > 0){ r->doanother = 1; rwakeup(&r->start); rsleep(&r->finish); } } }
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; }
static void queueproc(void *vq) { LumpQueue *q; Lump *u; Packet *p; int creator; uint ms; threadsetname("queueproc"); q = vq; for(;;){ qlock(&q->lock); while(q->w == q->r){ trace(TraceProc, "queueproc sleep empty"); rsleep(&q->empty); } u = q->q[q->r].u; p = q->q[q->r].p; creator = q->q[q->r].creator; ms = q->q[q->r].ms; q->r = (q->r + 1) & (MaxLumpQ - 1); trace(TraceProc, "queueproc wakeup flush"); rwakeupall(&q->flush); trace(TraceProc, "queueproc wakeup full"); rwakeup(&q->full); qunlock(&q->lock); trace(TraceProc, "queueproc writelump %V", u->score); if(writeqlump(u, p, creator, ms) < 0) fprint(2, "failed to write lump for %V: %r", u->score); trace(TraceProc, "queueproc wrotelump %V", u->score); putlump(u); } }
static void sumproc(void *unused) { ASum *as; Arena *arena; USED(unused); for(;;){ qlock(&sumlock); while(sumq == nil) rsleep(&sumwait); as = sumq; sumq = as->next; qunlock(&sumlock); arena = as->arena; free(as); sumarena(arena); } }
void vtfreeconn(VtConn *z) { vthangup(z); qlock(&z->lk); /* * Wait for send and recv procs to notice * the hangup and clear out the queues. */ while(z->readq || z->writeq){ if(z->readq) _vtqhangup(z->readq); if(z->writeq) _vtqhangup(z->writeq); rsleep(&z->rpcfork); } packetfree(z->part); vtfree(z->version); vtfree(z->sid); qunlock(&z->lk); vtfree(z); }
void* fs_recv(Queue *q, int) { void *a; int i, c; if(q == nil) panic("recv null q"); qlock(q); q->waitedfor = 1; while((c = q->count) <= 0) rsleep(&q->empty); i = q->loc; a = q->args[i]; i++; if(i >= q->size) i = 0; q->loc = i; q->count = c-1; rwakeup(&q->full); /* no longer full */ qunlock(q); return a; }
Block* qread(Queue *q, u32int *pbno) { Block *db; u32int bno; qlock(&q->lk); while(q->nel == 0 && !q->closed) rsleep(&q->r); if(q->nel == 0 && q->closed){ qunlock(&q->lk); return nil; } db = q->el[q->ri].db; bno = q->el[q->ri].bno; if(++q->ri == MAXQ) q->ri = 0; if(q->nel-- == MAXQ/2) rwakeup(&q->r); qunlock(&q->lk); *pbno = bno; return db; }
int main (int argc, char * argv[]) { // TODO: // (see message_queue_test() in interprocess_basic.c) // v open the two message queues (whose names are provided in the arguments) // v repeatingly: // - read from a message queue the new job to do // - wait a random amount of time (e.g. rsleep(10000);) // - do that job (use mandelbrot_point() if you like) // - write the results to a message queue // until there are no more jobs to do // v close the message queues if (argc != 2) { printf("Wrong number of arguments.\n"); exit(0); } mqd_t mq_fd_request; /* request message queue farmer -> worker */ mqd_t mq_fd_response; /* response message queue worker -> farmer */ MQ_REQUEST_MESSAGE req; /* request message */ MQ_RESPONSE_MESSAGE rsp; /* response message */ // open message queues mq_fd_request = mq_open(argv[0], O_RDONLY); mq_fd_response = mq_open(argv[1], O_WRONLY); while (true) { // sleep a random amount of time rsleep(1000); // read a message from the message queue // this blocks the execution until the worker receives a message printf("worker: waiting for message...\n"); mq_receive(mq_fd_request, (char *) &req, sizeof (req), NULL); // debug printf("worker: message received...\n"); // if the message is a signal that the worker can finish.. if (req.done) { //..break out of the loop break; } // build response message rsp.y = req.y; int x; for (x = 0; x < X_PIXEL; x += 1) { rsp.v[x] = mandelbrot_point(X_LOWERLEFT + ((double) x) * STEP, Y_LOWERLEFT + ((double) req.y) * STEP); } printf("worker: sending response...\n"); mq_send(mq_fd_response, (char *) &rsp, sizeof (rsp), 0); printf("worker: sent\n"); } printf("worker: closing message queues\n"); // close message queues mq_close(mq_fd_response); mq_close(mq_fd_request); printf("worker: stopped\n"); return 0; }
int Writer::svc() { try { ::DDS::Topic_var topic = writer_->get_topic(); CORBA::String_var topic_name = topic->get_name(); ACE_DEBUG((LM_DEBUG,"(%P|%t) %C: Writer::svc begins.\n", topic_name.in())); if (!ACE_OS::strcmp(topic_name, MY_TOPIC1) || !ACE_OS::strcmp(topic_name, MY_TOPIC3) || !ACE_OS::strcmp(topic_name, MY_TOPIC4) || !ACE_OS::strcmp(topic_name, MY_TOPIC5) || !ACE_OS::strcmp(topic_name, MY_TOPIC6) || !ACE_OS::strcmp(topic_name, MY_TOPIC7)) { OpenDDS::Model::WriterSync::wait_match(writer_); ::T1::Foo1 foo; foo.x = -1; foo.y = -1; foo.key = ++key; ::T1::Foo1DataWriter_var foo_dw = ::T1::Foo1DataWriter::_narrow(writer_); TEST_CHECK(!CORBA::is_nil(foo_dw)); ACE_OS::sleep(2); ACE_DEBUG((LM_DEBUG, ACE_TEXT("(%P|%t) %T Writer::svc starting to write.\n"))); ::DDS::InstanceHandle_t handle = foo_dw->register_instance(foo); for (int i = 0; i < num_writes_per_thread_; ++i) { rsleep(); foo.x = (float)i; foo.c = 'A' + (i % 26); foo_dw->write(foo, handle); } } else if (!ACE_OS::strcmp(topic_name, MY_TOPIC2)) { OpenDDS::Model::WriterSync::wait_match(writer_, 3 /*expected readers*/); ::T4::Foo4 foo; foo.key = ++key; ::T4::Foo4DataWriter_var foo_dw = ::T4::Foo4DataWriter::_narrow(writer_); TEST_CHECK(!CORBA::is_nil(foo_dw)); ACE_OS::sleep(2); ACE_DEBUG((LM_DEBUG, ACE_TEXT("(%P|%t) %T Writer::svc starting to write.\n"))); ::DDS::InstanceHandle_t handle = foo_dw->register_instance(foo); for (int i = 0; i < num_writes_per_thread_; ++i) { rsleep(); const int sequence_length = 10; foo.values.length(sequence_length); for (int j = 0; j < sequence_length; ++j) { foo.values[j] = (float) (i * i - j); } foo_dw->write(foo, handle); } } } catch (const CORBA::Exception& ex) { ex._tao_print_exception("Exception caught in svc:"); } finished_sending_ = true; ACE_DEBUG((LM_DEBUG, ACE_TEXT("(%P|%t) Writer::svc finished.\n"))); return 0; }
Packet* _vtrpc(VtConn *z, Packet *p, VtFcall *tx) { int i; uchar tag, buf[2], *top; Rwait *r, *rr; /* must malloc because stack could be private */ r = vtmallocz(sizeof(Rwait)); qlock(&z->lk); r->r.l = &z->lk; tag = gettag(z, r); if(tx){ /* vtfcallrpc can't print packet because it doesn't have tag */ tx->tag = tag; if(chattyventi) fprint(2, "%s -> %F\n", argv0, tx); } /* slam tag into packet */ top = packetpeek(p, buf, 0, 2); if(top == nil){ packetfree(p); return nil; } if(top == buf){ werrstr("first two bytes must be in same packet fragment"); packetfree(p); vtfree(r); return nil; } top[1] = tag; qunlock(&z->lk); if(vtsend(z, p) < 0){ vtfree(r); return nil; } qlock(&z->lk); /* wait for the muxer to give us our packet */ r->sleeping = 1; z->nsleep++; while(z->muxer && !r->done) rsleep(&r->r); z->nsleep--; r->sleeping = 0; /* if not done, there's no muxer: start muxing */ if(!r->done){ if(z->muxer) abort(); z->muxer = 1; while(!r->done){ qunlock(&z->lk); if((p = vtrecv(z)) == nil){ werrstr("unexpected eof on venti connection"); z->muxer = 0; vtfree(r); return nil; } qlock(&z->lk); muxrpc(z, p); } z->muxer = 0; /* if there is anyone else sleeping, wake first unfinished to mux */ if(z->nsleep) for(i=0; i<256; i++){ rr = z->wait[i]; if(rr && rr->sleeping && !rr->done){ rwakeup(&rr->r); break; } } } p = r->p; puttag(z, r, tag); vtfree(r); qunlock(&z->lk); return p; }
int Writer::svc () { try { finished_sending_ = false; ::DDS::Topic_var topic = writer_->get_topic() ; ACE_DEBUG((LM_DEBUG,"(%P|%t) %C: Writer::svc begins.\n", topic->get_name())); if (!ACE_OS::strcmp(topic->get_name(), MY_TOPIC1)) { ::T1::Foo1 foo; //foo.key set below. foo.x = -1; foo.y = -1; foo.key = ++key; ::T1::Foo1DataWriter_var foo_dw = ::T1::Foo1DataWriter::_narrow(writer_.in ()); TEST_CHECK (! CORBA::is_nil (foo_dw.in ())); rsleep1() ; ACE_DEBUG((LM_DEBUG, ACE_TEXT("(%P|%t) %T Writer::svc starting to write.\n"))); ::DDS::InstanceHandle_t handle = foo_dw->register_instance(foo); for (int i = 0; i< num_writes_per_thread_; i ++) { rsleep() ; foo.x = (float)i; foo.c = 'A' + (i % 26) ; foo_dw->write(foo, handle); } } else if (!ACE_OS::strcmp(topic->get_name(), MY_TOPIC2)) { ::T2::Foo2 foo; foo.key = ++key; ::T2::Foo2DataWriter_var foo_dw = ::T2::Foo2DataWriter::_narrow(writer_.in ()); TEST_CHECK (! CORBA::is_nil (foo_dw.in ())); rsleep1() ; ACE_DEBUG((LM_DEBUG, ACE_TEXT("(%P|%t) %T Writer::svc starting to write.\n"))); ::DDS::InstanceHandle_t handle = foo_dw->register_instance(foo); char buff[512] ; for (int i = 0; i< num_writes_per_thread_; i ++) { rsleep() ; ACE_OS::sprintf(buff, "message %d", i + 1) ; foo.text = (const char *)buff ; foo_dw->write(foo, handle); } } else if (!ACE_OS::strcmp(topic->get_name(), MY_TOPIC3)) { ::T3::Foo3 foo; foo.key = ++key; ::T3::Foo3DataWriter_var foo_dw = ::T3::Foo3DataWriter::_narrow(writer_.in ()); TEST_CHECK (! CORBA::is_nil (foo_dw.in ())); rsleep1() ; ACE_DEBUG((LM_DEBUG, ACE_TEXT("(%P|%t) %T Writer::svc starting to write.\n"))); ::DDS::InstanceHandle_t handle = foo_dw->register_instance(foo); char buff[512] ; for (int i = 0; i< num_writes_per_thread_; i ++) { rsleep() ; ACE_OS::sprintf(buff, "message %d", i + 1) ; foo.c = 'A' + (i % 26) ; foo.text = (const char *)buff ; foo.s = i + 1 ; foo.l = i * 100 ; foo_dw->write(foo, handle); } } } catch (const CORBA::Exception& ex) { ex._tao_print_exception ("Exception caught in svc:"); } finished_sending_ = true; ACE_DEBUG((LM_DEBUG, ACE_TEXT("(%P|%t) Writer::svc finished.\n"))); return 0; }
static void connproc(void *v) { VtSconn *sc; VtConn *c; Packet *p; VtReq *r; int fd; static int first=1; if(first && chattyventi){ first=0; fmtinstall('F', vtfcallfmt); } r = nil; sc = v; sc->c = nil; if(0) fprint(2, "new call %s on %d\n", sc->dir, sc->ctl); fd = accept(sc->ctl, sc->dir); close(sc->ctl); if(fd < 0){ fprint(2, "accept %s: %r\n", sc->dir); goto out; } c = vtconn(fd, fd); sc->c = c; if(vtversion(c) < 0){ fprint(2, "vtversion %s: %r\n", sc->dir); goto out; } if(vtsrvhello(c) < 0){ fprint(2, "vtsrvhello %s: %r\n", sc->dir); goto out; } if(0) fprint(2, "new proc %s\n", sc->dir); proccreate(vtsendproc, c, STACK); qlock(&c->lk); while(!c->writeq) rsleep(&c->rpcfork); qunlock(&c->lk); while((p = vtrecv(c)) != nil){ r = vtmallocz(sizeof(VtReq)); if(vtfcallunpack(&r->tx, p) < 0){ vtlog(VtServerLog, "<font size=-1>%T %s:</font> recv bad packet %p: %r<br>\n", c->addr, p); fprint(2, "bad packet on %s: %r\n", sc->dir); packetfree(p); continue; } vtlog(VtServerLog, "<font size=-1>%T %s:</font> recv packet %p (%F)<br>\n", c->addr, p, &r->tx); if(chattyventi) fprint(2, "%s <- %F\n", argv0, &r->tx); packetfree(p); if(r->tx.msgtype == VtTgoodbye) break; r->rx.tag = r->tx.tag; r->sc = sc; scincref(sc); if(_vtqsend(sc->srv->q, r) < 0){ scdecref(sc); fprint(2, "hungup queue\n"); break; } r = nil; } if(0) fprint(2, "eof on %s\n", sc->dir); out: if(r){ vtfcallclear(&r->tx); vtfree(r); } if(0) fprint(2, "freed %s\n", sc->dir); scdecref(sc); return; }