Subversion Repositories planix.SVN

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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