| #include "threadimpl.h" |
| |
| /* |
| * One can go through a lot of effort to avoid this global lock. |
| * You have to put locks in all the channels and all the Alt |
| * structures. At the beginning of an alt you have to lock all |
| * the channels, but then to try to actually exec an op you |
| * have to lock the other guy's alt structure, so that other |
| * people aren't trying to use him in some other op at the |
| * same time. |
| * |
| * For Plan 9 apps, it's just not worth the extra effort. |
| */ |
| static QLock chanlock; |
| |
| Channel* |
| chancreate(int elemsize, int bufsize) |
| { |
| Channel *c; |
| |
| c = malloc(sizeof *c+bufsize*elemsize); |
| if(c == nil) |
| sysfatal("chancreate malloc: %r"); |
| memset(c, 0, sizeof *c); |
| c->elemsize = elemsize; |
| c->bufsize = bufsize; |
| c->nbuf = 0; |
| c->buf = (uchar*)(c+1); |
| return c; |
| } |
| |
| void |
| chansetname(Channel *c, char *fmt, ...) |
| { |
| char *name; |
| va_list arg; |
| |
| va_start(arg, fmt); |
| name = vsmprint(fmt, arg); |
| va_end(arg); |
| free(c->name); |
| c->name = name; |
| } |
| |
| /* bug - work out races */ |
| void |
| chanfree(Channel *c) |
| { |
| if(c == nil) |
| return; |
| free(c->name); |
| free(c->arecv.a); |
| free(c->asend.a); |
| free(c); |
| } |
| |
| static void |
| addarray(_Altarray *a, Alt *alt) |
| { |
| if(a->n == a->m){ |
| a->m += 16; |
| a->a = realloc(a->a, a->m*sizeof a->a[0]); |
| } |
| a->a[a->n++] = alt; |
| } |
| |
| static void |
| delarray(_Altarray *a, int i) |
| { |
| --a->n; |
| a->a[i] = a->a[a->n]; |
| } |
| |
| /* |
| * doesn't really work for things other than CHANSND and CHANRCV |
| * but is only used as arg to chanarray, which can handle it |
| */ |
| #define otherop(op) (CHANSND+CHANRCV-(op)) |
| |
| static _Altarray* |
| chanarray(Channel *c, uint op) |
| { |
| switch(op){ |
| default: |
| return nil; |
| case CHANSND: |
| return &c->asend; |
| case CHANRCV: |
| return &c->arecv; |
| } |
| } |
| |
| static int |
| altcanexec(Alt *a) |
| { |
| _Altarray *ar; |
| Channel *c; |
| |
| if(a->op == CHANNOP || (c=a->c) == nil) |
| return 0; |
| if(c->bufsize == 0){ |
| ar = chanarray(c, otherop(a->op)); |
| return ar && ar->n; |
| }else{ |
| switch(a->op){ |
| default: |
| return 0; |
| case CHANSND: |
| return c->nbuf < c->bufsize; |
| case CHANRCV: |
| return c->nbuf > 0; |
| } |
| } |
| } |
| |
| static void |
| altqueue(Alt *a) |
| { |
| _Altarray *ar; |
| |
| if(a->c == nil) |
| return; |
| ar = chanarray(a->c, a->op); |
| addarray(ar, a); |
| } |
| |
| static void |
| altdequeue(Alt *a) |
| { |
| int i; |
| _Altarray *ar; |
| |
| ar = chanarray(a->c, a->op); |
| if(ar == nil){ |
| fprint(2, "bad use of altdequeue op=%d\n", a->op); |
| abort(); |
| } |
| |
| for(i=0; i<ar->n; i++) |
| if(ar->a[i] == a){ |
| delarray(ar, i); |
| return; |
| } |
| fprint(2, "cannot find self in altdq\n"); |
| abort(); |
| } |
| |
| static void |
| altalldequeue(Alt *a) |
| { |
| int i; |
| |
| for(i=0; a[i].op!=CHANEND && a[i].op!=CHANNOBLK; i++) |
| if(a[i].op != CHANNOP) |
| altdequeue(&a[i]); |
| } |
| |
| static void |
| amove(void *dst, void *src, uint n) |
| { |
| if(dst){ |
| if(src == nil) |
| memset(dst, 0, n); |
| else |
| memmove(dst, src, n); |
| } |
| } |
| |
| /* |
| * Actually move the data around. There are up to three |
| * players: the sender, the receiver, and the channel itself. |
| * If the channel is unbuffered or the buffer is empty, |
| * data goes from sender to receiver. If the channel is full, |
| * the receiver removes some from the channel and the sender |
| * gets to put some in. |
| */ |
| static void |
| altcopy(Alt *s, Alt *r) |
| { |
| Alt *t; |
| Channel *c; |
| uchar *cp; |
| |
| /* |
| * Work out who is sender and who is receiver |
| */ |
| if(s == nil && r == nil) |
| return; |
| assert(s != nil); |
| c = s->c; |
| if(s->op == CHANRCV){ |
| t = s; |
| s = r; |
| r = t; |
| } |
| assert(s==nil || s->op == CHANSND); |
| assert(r==nil || r->op == CHANRCV); |
| |
| /* |
| * Channel is empty (or unbuffered) - copy directly. |
| */ |
| if(s && r && c->nbuf == 0){ |
| amove(r->v, s->v, c->elemsize); |
| return; |
| } |
| |
| /* |
| * Otherwise it's always okay to receive and then send. |
| */ |
| if(r){ |
| cp = c->buf + c->off*c->elemsize; |
| amove(r->v, cp, c->elemsize); |
| --c->nbuf; |
| if(++c->off == c->bufsize) |
| c->off = 0; |
| } |
| if(s){ |
| cp = c->buf + (c->off+c->nbuf)%c->bufsize*c->elemsize; |
| amove(cp, s->v, c->elemsize); |
| ++c->nbuf; |
| } |
| } |
| |
| static void |
| altexec(Alt *a) |
| { |
| int i; |
| _Altarray *ar; |
| Alt *other; |
| Channel *c; |
| |
| c = a->c; |
| ar = chanarray(c, otherop(a->op)); |
| if(ar && ar->n){ |
| i = rand()%ar->n; |
| other = ar->a[i]; |
| altcopy(a, other); |
| altalldequeue(other->thread->alt); |
| other->thread->alt = other; |
| _threadready(other->thread); |
| }else |
| altcopy(a, nil); |
| } |
| |
| #define dbgalt 0 |
| int |
| chanalt(Alt *a) |
| { |
| int i, j, ncan, n, canblock; |
| Channel *c; |
| _Thread *t; |
| |
| needstack(512); |
| for(i=0; a[i].op != CHANEND && a[i].op != CHANNOBLK; i++) |
| ; |
| n = i; |
| canblock = a[i].op == CHANEND; |
| |
| t = proc()->thread; |
| for(i=0; i<n; i++) |
| a[i].thread = t; |
| t->alt = a; |
| qlock(&chanlock); |
| if(dbgalt) print("alt "); |
| ncan = 0; |
| for(i=0; i<n; i++){ |
| c = a[i].c; |
| if(dbgalt) print(" %c:", "esrnb"[a[i].op]); |
| if(dbgalt) if(c->name) print("%s", c->name); else print("%p", c); |
| if(altcanexec(&a[i])){ |
| if(dbgalt) print("*"); |
| ncan++; |
| } |
| } |
| if(ncan){ |
| j = rand()%ncan; |
| for(i=0; i<n; i++){ |
| if(altcanexec(&a[i])){ |
| if(j-- == 0){ |
| if(dbgalt){ |
| c = a[i].c; |
| print(" => %c:", "esrnb"[a[i].op]); |
| if(c->name) print("%s", c->name); else print("%p", c); |
| print("\n"); |
| } |
| altexec(&a[i]); |
| qunlock(&chanlock); |
| return i; |
| } |
| } |
| } |
| } |
| if(dbgalt)print("\n"); |
| |
| if(!canblock){ |
| qunlock(&chanlock); |
| return -1; |
| } |
| |
| for(i=0; i<n; i++){ |
| if(a[i].op != CHANNOP) |
| altqueue(&a[i]); |
| } |
| qunlock(&chanlock); |
| |
| _threadswitch(); |
| |
| /* |
| * the guy who ran the op took care of dequeueing us |
| * and then set t->alt to the one that was executed. |
| */ |
| if(t->alt < a || t->alt >= a+n) |
| sysfatal("channel bad alt"); |
| return t->alt - a; |
| } |
| |
| static int |
| _chanop(Channel *c, int op, void *p, int canblock) |
| { |
| Alt a[2]; |
| |
| a[0].c = c; |
| a[0].op = op; |
| a[0].v = p; |
| a[1].op = canblock ? CHANEND : CHANNOBLK; |
| if(chanalt(a) < 0) |
| return -1; |
| return 1; |
| } |
| |
| int |
| chansend(Channel *c, void *v) |
| { |
| return _chanop(c, CHANSND, v, 1); |
| } |
| |
| int |
| channbsend(Channel *c, void *v) |
| { |
| return _chanop(c, CHANSND, v, 0); |
| } |
| |
| int |
| chanrecv(Channel *c, void *v) |
| { |
| return _chanop(c, CHANRCV, v, 1); |
| } |
| |
| int |
| channbrecv(Channel *c, void *v) |
| { |
| return _chanop(c, CHANRCV, v, 0); |
| } |
| |
| int |
| chansendp(Channel *c, void *v) |
| { |
| return _chanop(c, CHANSND, (void*)&v, 1); |
| } |
| |
| void* |
| chanrecvp(Channel *c) |
| { |
| void *v; |
| |
| if(_chanop(c, CHANRCV, (void*)&v, 1) > 0) |
| return v; |
| return nil; |
| } |
| |
| int |
| channbsendp(Channel *c, void *v) |
| { |
| return _chanop(c, CHANSND, (void*)&v, 0); |
| } |
| |
| void* |
| channbrecvp(Channel *c) |
| { |
| void *v; |
| |
| if(_chanop(c, CHANRCV, (void*)&v, 0) > 0) |
| return v; |
| return nil; |
| } |
| |
| int |
| chansendul(Channel *c, ulong val) |
| { |
| return _chanop(c, CHANSND, &val, 1); |
| } |
| |
| ulong |
| chanrecvul(Channel *c) |
| { |
| ulong val; |
| |
| if(_chanop(c, CHANRCV, &val, 1) > 0) |
| return val; |
| return -1; |
| } |
| |
| int |
| channbsendul(Channel *c, ulong val) |
| { |
| return _chanop(c, CHANSND, &val, 0); |
| } |
| |
| ulong |
| channbrecvul(Channel *c) |
| { |
| ulong val; |
| |
| if(_chanop(c, CHANRCV, &val, 0) > 0) |
| return val; |
| return -1; |
| } |
| |