#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");
}
|