/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ #include <u.h>#include <libc.h>#include <oventi.h> enum{ QueuingW, /* queuing for write lock */ QueuingR, /* queuing for read lock */}; typedef struct Thread Thread; struct Thread { int pid; int ref; char *error; int state; Thread *next;}; struct VtLock { Lock lk; Thread *writer; /* thread writering write lock */ int readers; /* number writering read lock */ Thread *qfirst; Thread *qlast; uintptr pc;}; struct VtRendez { VtLock *lk; Thread *wfirst; Thread *wlast;}; enum { ERROR = 0,}; static Thread **vtRock; static void vtThreadInit(void);static void threadSleep(Thread*);static void threadWakeup(Thread*); intvtThread(void (*f)(void*), void *rock){ int tid; tid = rfork(RFNOWAIT|RFMEM|RFPROC); switch(tid){ case -1: vtOSError(); return -1; case 0: break; default: return tid; } vtAttach(); (*f)(rock); vtDetach(); _exits(0); return 0;} static Thread *threadLookup(void){ return *vtRock;} voidvtAttach(void){ int pid; Thread *p; static int init; static Lock lk; lock(&lk); if(!init) { rfork(RFREND); vtThreadInit(); init = 1; } unlock(&lk); pid = getpid(); p = *vtRock; if(p != nil && p->pid == pid) { p->ref++; return; } p = vtMemAllocZ(sizeof(Thread)); p->ref = 1; p->pid = pid; *vtRock = p;} voidvtDetach(void){ Thread *p; p = *vtRock; assert(p != nil); p->ref--; if(p->ref == 0) { vtMemFree(p->error); vtMemFree(p); *vtRock = nil; }} char *vtGetError(void){ char *s; if(ERROR) fprint(2, "vtGetError: %s\n", threadLookup()->error); s = threadLookup()->error; if(s == nil) return "unknown error"; return s;} char*vtSetError(char* fmt, ...){ Thread *p; char *s; va_list args; p = threadLookup(); va_start(args, fmt); s = vsmprint(fmt, args); vtMemFree(p->error); p->error = s; va_end(args); if(ERROR) fprint(2, "vtSetError: %s\n", p->error); werrstr("%s", p->error); return p->error;} static voidvtThreadInit(void){ static Lock lk; lock(&lk); if(vtRock != nil) { unlock(&lk); return; } vtRock = privalloc(); if(vtRock == nil) vtFatal("can't allocate thread-private storage"); unlock(&lk);} VtLock*vtLockAlloc(void){ return vtMemAllocZ(sizeof(VtLock));} /* * RSC: I think the test is backward. Let's see who uses it. *voidvtLockInit(VtLock **p){ static Lock lk; lock(&lk); if(*p != nil) *p = vtLockAlloc(); unlock(&lk);} */ voidvtLockFree(VtLock *p){ if(p == nil) return; assert(p->writer == nil); assert(p->readers == 0); assert(p->qfirst == nil); vtMemFree(p);} VtRendez*vtRendezAlloc(VtLock *p){ VtRendez *q; q = vtMemAllocZ(sizeof(VtRendez)); q->lk = p; setmalloctag(q, getcallerpc(&p)); return q;} voidvtRendezFree(VtRendez *q){ if(q == nil) return; assert(q->wfirst == nil); vtMemFree(q);} intvtCanLock(VtLock *p){ Thread *t; lock(&p->lk); t = *vtRock; if(p->writer == nil && p->readers == 0) { p->writer = t; unlock(&p->lk); return 1; } unlock(&p->lk); return 0;} voidvtLock(VtLock *p){ Thread *t; lock(&p->lk); p->pc = getcallerpc(&p); t = *vtRock; if(p->writer == nil && p->readers == 0) { p->writer = t; unlock(&p->lk); return; } /* * venti currently contains code that assume locks can be passed between threads :-( * assert(p->writer != t); */ if(p->qfirst == nil) p->qfirst = t; else p->qlast->next = t; p->qlast = t; t->next = nil; t->state = QueuingW; unlock(&p->lk); threadSleep(t); assert(p->writer == t && p->readers == 0);} intvtCanRLock(VtLock *p){ lock(&p->lk); if(p->writer == nil && p->qfirst == nil) { p->readers++; unlock(&p->lk); return 1; } unlock(&p->lk); return 0;} voidvtRLock(VtLock *p){ Thread *t; lock(&p->lk); t = *vtRock; if(p->writer == nil && p->qfirst == nil) { p->readers++; unlock(&p->lk); return; } /* * venti currently contains code that assumes locks can be passed between threads * assert(p->writer != t); */ if(p->qfirst == nil) p->qfirst = t; else p->qlast->next = t; p->qlast = t; t->next = nil; t->state = QueuingR; unlock(&p->lk); threadSleep(t); assert(p->writer == nil && p->readers > 0);} voidvtUnlock(VtLock *p){ Thread *t, *tt; lock(&p->lk); /* * venti currently has code that assumes lock can be passed between threads :-) * assert(p->writer == *vtRock); */ assert(p->writer != nil); assert(p->readers == 0); t = p->qfirst; if(t == nil) { p->writer = nil; unlock(&p->lk); return; } if(t->state == QueuingW) { p->qfirst = t->next; p->writer = t; unlock(&p->lk); threadWakeup(t); return; } p->writer = nil; while(t != nil && t->state == QueuingR) { tt = t; t = t->next; p->readers++; threadWakeup(tt); } p->qfirst = t; unlock(&p->lk);} voidvtRUnlock(VtLock *p){ Thread *t; lock(&p->lk); assert(p->writer == nil && p->readers > 0); p->readers--; t = p->qfirst; if(p->readers > 0 || t == nil) { unlock(&p->lk); return; } assert(t->state == QueuingW); p->qfirst = t->next; p->writer = t; unlock(&p->lk); threadWakeup(t);} intvtSleep(VtRendez *q){ Thread *s, *t, *tt; VtLock *p; p = q->lk; lock(&p->lk); s = *vtRock; /* * venti currently contains code that assume locks can be passed between threads :-( * assert(p->writer != s); */ assert(p->writer != nil); assert(p->readers == 0); t = p->qfirst; if(t == nil) { p->writer = nil; } else if(t->state == QueuingW) { p->qfirst = t->next; p->writer = t; threadWakeup(t); } else { p->writer = nil; while(t != nil && t->state == QueuingR) { tt = t; t = t->next; p->readers++; threadWakeup(tt); } } if(q->wfirst == nil) q->wfirst = s; else q->wlast->next = s; q->wlast = s; s->next = nil; unlock(&p->lk); threadSleep(s); assert(p->writer == s); return 1;} intvtWakeup(VtRendez *q){ Thread *t; VtLock *p; /* * take off wait and put on front of queue * put on front so guys that have been waiting will not get starved */ p = q->lk; lock(&p->lk); /* * venti currently has code that assumes lock can be passed between threads :-) * assert(p->writer == *vtRock); */ assert(p->writer != nil); t = q->wfirst; if(t == nil) { unlock(&p->lk); return 0; } q->wfirst = t->next; if(p->qfirst == nil) p->qlast = t; t->next = p->qfirst; p->qfirst = t; t->state = QueuingW; unlock(&p->lk); return 1;} intvtWakeupAll(VtRendez *q){ int i; for(i=0; vtWakeup(q); i++) ; return i;} static voidthreadSleep(Thread *t){ if(rendezvous(t, (void*)0x22bbdfd6) != (void*)0x44391f14) sysfatal("threadSleep: rendezvous failed: %r");} static voidthreadWakeup(Thread *t){ if(rendezvous(t, (void*)0x44391f14) != (void*)0x22bbdfd6) sysfatal("threadWakeup: rendezvous failed: %r");}
/*
* This file is part of the UCB release of Plan 9. It is subject to the license
* terms in the LICENSE file found in the top-level directory of this
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
* part of the UCB release of Plan 9, including this file, may be copied,
* modified, propagated, or distributed except according to the terms contained
* in the LICENSE file.
*/
#include <u.h>
#include <libc.h>
#include <oventi.h>
enum
{
QueuingW, /* queuing for write lock */
QueuingR, /* queuing for read lock */
};
typedef struct Thread Thread;
struct Thread {
int pid;
int ref;
char *error;
int state;
Thread *next;
};
struct VtLock {
Lock lk;
Thread *writer; /* thread writering write lock */
int readers; /* number writering read lock */
Thread *qfirst;
Thread *qlast;
uintptr pc;
};
struct VtRendez {
VtLock *lk;
Thread *wfirst;
Thread *wlast;
};
enum {
ERROR = 0,
};
static Thread **vtRock;
static void vtThreadInit(void);
static void threadSleep(Thread*);
static void threadWakeup(Thread*);
int
vtThread(void (*f)(void*), void *rock)
{
int tid;
tid = rfork(RFNOWAIT|RFMEM|RFPROC);
switch(tid){
case -1:
vtOSError();
return -1;
case 0:
break;
default:
return tid;
}
vtAttach();
(*f)(rock);
vtDetach();
_exits(0);
return 0;
}
static Thread *
threadLookup(void)
{
return *vtRock;
}
void
vtAttach(void)
{
int pid;
Thread *p;
static int init;
static Lock lk;
lock(&lk);
if(!init) {
rfork(RFREND);
vtThreadInit();
init = 1;
}
unlock(&lk);
pid = getpid();
p = *vtRock;
if(p != nil && p->pid == pid) {
p->ref++;
return;
}
p = vtMemAllocZ(sizeof(Thread));
p->ref = 1;
p->pid = pid;
*vtRock = p;
}
void
vtDetach(void)
{
Thread *p;
p = *vtRock;
assert(p != nil);
p->ref--;
if(p->ref == 0) {
vtMemFree(p->error);
vtMemFree(p);
*vtRock = nil;
}
}
char *
vtGetError(void)
{
char *s;
if(ERROR)
fprint(2, "vtGetError: %s\n", threadLookup()->error);
s = threadLookup()->error;
if(s == nil)
return "unknown error";
return s;
}
char*
vtSetError(char* fmt, ...)
{
Thread *p;
char *s;
va_list args;
p = threadLookup();
va_start(args, fmt);
s = vsmprint(fmt, args);
vtMemFree(p->error);
p->error = s;
va_end(args);
if(ERROR)
fprint(2, "vtSetError: %s\n", p->error);
werrstr("%s", p->error);
return p->error;
}
static void
vtThreadInit(void)
{
static Lock lk;
lock(&lk);
if(vtRock != nil) {
unlock(&lk);
return;
}
vtRock = privalloc();
if(vtRock == nil)
vtFatal("can't allocate thread-private storage");
unlock(&lk);
}
VtLock*
vtLockAlloc(void)
{
return vtMemAllocZ(sizeof(VtLock));
}
/*
* RSC: I think the test is backward. Let's see who uses it.
*
void
vtLockInit(VtLock **p)
{
static Lock lk;
lock(&lk);
if(*p != nil)
*p = vtLockAlloc();
unlock(&lk);
}
*/
void
vtLockFree(VtLock *p)
{
if(p == nil)
return;
assert(p->writer == nil);
assert(p->readers == 0);
assert(p->qfirst == nil);
vtMemFree(p);
}
VtRendez*
vtRendezAlloc(VtLock *p)
{
VtRendez *q;
q = vtMemAllocZ(sizeof(VtRendez));
q->lk = p;
setmalloctag(q, getcallerpc(&p));
return q;
}
void
vtRendezFree(VtRendez *q)
{
if(q == nil)
return;
assert(q->wfirst == nil);
vtMemFree(q);
}
int
vtCanLock(VtLock *p)
{
Thread *t;
lock(&p->lk);
t = *vtRock;
if(p->writer == nil && p->readers == 0) {
p->writer = t;
unlock(&p->lk);
return 1;
}
unlock(&p->lk);
return 0;
}
void
vtLock(VtLock *p)
{
Thread *t;
lock(&p->lk);
p->pc = getcallerpc(&p);
t = *vtRock;
if(p->writer == nil && p->readers == 0) {
p->writer = t;
unlock(&p->lk);
return;
}
/*
* venti currently contains code that assume locks can be passed between threads :-(
* assert(p->writer != t);
*/
if(p->qfirst == nil)
p->qfirst = t;
else
p->qlast->next = t;
p->qlast = t;
t->next = nil;
t->state = QueuingW;
unlock(&p->lk);
threadSleep(t);
assert(p->writer == t && p->readers == 0);
}
int
vtCanRLock(VtLock *p)
{
lock(&p->lk);
if(p->writer == nil && p->qfirst == nil) {
p->readers++;
unlock(&p->lk);
return 1;
}
unlock(&p->lk);
return 0;
}
void
vtRLock(VtLock *p)
{
Thread *t;
lock(&p->lk);
t = *vtRock;
if(p->writer == nil && p->qfirst == nil) {
p->readers++;
unlock(&p->lk);
return;
}
/*
* venti currently contains code that assumes locks can be passed between threads
* assert(p->writer != t);
*/
if(p->qfirst == nil)
p->qfirst = t;
else
p->qlast->next = t;
p->qlast = t;
t->next = nil;
t->state = QueuingR;
unlock(&p->lk);
threadSleep(t);
assert(p->writer == nil && p->readers > 0);
}
void
vtUnlock(VtLock *p)
{
Thread *t, *tt;
lock(&p->lk);
/*
* venti currently has code that assumes lock can be passed between threads :-)
* assert(p->writer == *vtRock);
*/
assert(p->writer != nil);
assert(p->readers == 0);
t = p->qfirst;
if(t == nil) {
p->writer = nil;
unlock(&p->lk);
return;
}
if(t->state == QueuingW) {
p->qfirst = t->next;
p->writer = t;
unlock(&p->lk);
threadWakeup(t);
return;
}
p->writer = nil;
while(t != nil && t->state == QueuingR) {
tt = t;
t = t->next;
p->readers++;
threadWakeup(tt);
}
p->qfirst = t;
unlock(&p->lk);
}
void
vtRUnlock(VtLock *p)
{
Thread *t;
lock(&p->lk);
assert(p->writer == nil && p->readers > 0);
p->readers--;
t = p->qfirst;
if(p->readers > 0 || t == nil) {
unlock(&p->lk);
return;
}
assert(t->state == QueuingW);
p->qfirst = t->next;
p->writer = t;
unlock(&p->lk);
threadWakeup(t);
}
int
vtSleep(VtRendez *q)
{
Thread *s, *t, *tt;
VtLock *p;
p = q->lk;
lock(&p->lk);
s = *vtRock;
/*
* venti currently contains code that assume locks can be passed between threads :-(
* assert(p->writer != s);
*/
assert(p->writer != nil);
assert(p->readers == 0);
t = p->qfirst;
if(t == nil) {
p->writer = nil;
} else if(t->state == QueuingW) {
p->qfirst = t->next;
p->writer = t;
threadWakeup(t);
} else {
p->writer = nil;
while(t != nil && t->state == QueuingR) {
tt = t;
t = t->next;
p->readers++;
threadWakeup(tt);
}
}
if(q->wfirst == nil)
q->wfirst = s;
else
q->wlast->next = s;
q->wlast = s;
s->next = nil;
unlock(&p->lk);
threadSleep(s);
assert(p->writer == s);
return 1;
}
int
vtWakeup(VtRendez *q)
{
Thread *t;
VtLock *p;
/*
* take off wait and put on front of queue
* put on front so guys that have been waiting will not get starved
*/
p = q->lk;
lock(&p->lk);
/*
* venti currently has code that assumes lock can be passed between threads :-)
* assert(p->writer == *vtRock);
*/
assert(p->writer != nil);
t = q->wfirst;
if(t == nil) {
unlock(&p->lk);
return 0;
}
q->wfirst = t->next;
if(p->qfirst == nil)
p->qlast = t;
t->next = p->qfirst;
p->qfirst = t;
t->state = QueuingW;
unlock(&p->lk);
return 1;
}
int
vtWakeupAll(VtRendez *q)
{
int i;
for(i=0; vtWakeup(q); i++)
;
return i;
}
static void
threadSleep(Thread *t)
{
if(rendezvous(t, (void*)0x22bbdfd6) != (void*)0x44391f14)
sysfatal("threadSleep: rendezvous failed: %r");
}
static void
threadWakeup(Thread *t)
{
if(rendezvous(t, (void*)0x44391f14) != (void*)0x22bbdfd6)
sysfatal("threadWakeup: rendezvous failed: %r");
}