ASAN can't deal with the coroutine stacks. In theory we can call into ASAN runtime to let it know about them, but ASAN still has problems with fork or exit happening from a non-system stack. Bypass all possible problems by just having a full OS thread for each libthread thread. The threads are still cooperatively scheduled within a proc (in thos mode, a group of OS threads). Setting the environment variable LIBTHREAD=pthreadperthread will enable the pthreadperthread mode, as will building with CC9FLAGS='-fsanitize=address' in $PLAN9/config. This solution is much more general than ASAN - for example if you are trying to find all the thread stacks in a reproducible crash you can use pthreadperthread mode with any debugger that knows only about OS threads.
417 lines
6.6 KiB
C
417 lines
6.6 KiB
C
#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 altdequeue\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 0;
|
|
}
|
|
|
|
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 0;
|
|
}
|