2013-09-23 23:00:39 +02:00
|
|
|
/*
|
|
|
|
|
* Archiver. In charge of sending blocks to Venti.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "stdinc.h"
|
|
|
|
|
#include "dat.h"
|
|
|
|
|
#include "fns.h"
|
|
|
|
|
#include "error.h"
|
|
|
|
|
|
|
|
|
|
#include "9.h" /* for consPrint */
|
|
|
|
|
|
|
|
|
|
#define DEBUG 0
|
|
|
|
|
|
|
|
|
|
static void archThread(void*);
|
|
|
|
|
|
|
|
|
|
struct Arch
|
|
|
|
|
{
|
|
|
|
|
int ref;
|
|
|
|
|
uint blockSize;
|
|
|
|
|
uint diskSize;
|
|
|
|
|
Cache *c;
|
|
|
|
|
Fs *fs;
|
2013-09-23 23:16:25 +02:00
|
|
|
VtConn *z;
|
2013-09-23 23:00:39 +02:00
|
|
|
|
2013-09-23 23:16:25 +02:00
|
|
|
QLock lk;
|
|
|
|
|
Rendez starve;
|
|
|
|
|
Rendez die;
|
2013-09-23 23:00:39 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Arch *
|
2013-09-23 23:16:25 +02:00
|
|
|
archInit(Cache *c, Disk *disk, Fs *fs, VtConn *z)
|
2013-09-23 23:00:39 +02:00
|
|
|
{
|
|
|
|
|
Arch *a;
|
|
|
|
|
|
2013-09-23 23:16:25 +02:00
|
|
|
a = vtmallocz(sizeof(Arch));
|
2013-09-23 23:00:39 +02:00
|
|
|
|
|
|
|
|
a->c = c;
|
|
|
|
|
a->z = z;
|
|
|
|
|
a->fs = fs;
|
|
|
|
|
a->blockSize = diskBlockSize(disk);
|
2013-09-23 23:16:25 +02:00
|
|
|
a->starve.l = &a->lk;
|
2013-09-23 23:00:39 +02:00
|
|
|
|
|
|
|
|
a->ref = 2;
|
2013-09-23 23:16:25 +02:00
|
|
|
proccreate(archThread, a, STACK);
|
2013-09-23 23:00:39 +02:00
|
|
|
|
|
|
|
|
return a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
archFree(Arch *a)
|
|
|
|
|
{
|
|
|
|
|
/* kill slave */
|
2013-09-23 23:16:25 +02:00
|
|
|
qlock(&a->lk);
|
|
|
|
|
a->die.l = &a->lk;
|
|
|
|
|
rwakeup(&a->starve);
|
2013-09-23 23:00:39 +02:00
|
|
|
while(a->ref > 1)
|
2013-09-23 23:16:25 +02:00
|
|
|
rsleep(&a->die);
|
|
|
|
|
qunlock(&a->lk);
|
|
|
|
|
vtfree(a);
|
2013-09-23 23:00:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
ventiSend(Arch *a, Block *b, uchar *data)
|
|
|
|
|
{
|
|
|
|
|
uint n;
|
|
|
|
|
uchar score[VtScoreSize];
|
|
|
|
|
|
|
|
|
|
if(DEBUG > 1)
|
|
|
|
|
fprint(2, "ventiSend: sending %#ux %L to venti\n", b->addr, &b->l);
|
2013-09-23 23:16:25 +02:00
|
|
|
n = vtzerotruncate(vtType[b->l.type], data, a->blockSize);
|
2013-09-23 23:00:39 +02:00
|
|
|
if(DEBUG > 1)
|
|
|
|
|
fprint(2, "ventiSend: truncate %d to %d\n", a->blockSize, n);
|
2013-09-23 23:16:25 +02:00
|
|
|
if(vtwrite(a->z, score, vtType[b->l.type], data, n) < 0){
|
|
|
|
|
fprint(2, "ventiSend: vtwrite block %#ux failed: %r\n", b->addr);
|
2013-09-23 23:00:39 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
2013-09-23 23:16:25 +02:00
|
|
|
if(vtsha1check(score, data, n) < 0){
|
2013-09-23 23:00:39 +02:00
|
|
|
uchar score2[VtScoreSize];
|
2013-09-23 23:16:25 +02:00
|
|
|
vtsha1(score2, data, n);
|
|
|
|
|
fprint(2, "ventiSend: vtwrite block %#ux failed vtsha1check %V %V\n",
|
2013-09-23 23:00:39 +02:00
|
|
|
b->addr, score, score2);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2013-09-23 23:16:25 +02:00
|
|
|
if(vtsync(a->z) < 0)
|
2013-09-23 23:00:39 +02:00
|
|
|
return 0;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* parameters for recursion; there are so many,
|
|
|
|
|
* and some only change occasionally. this is
|
|
|
|
|
* easier than spelling things out at each call.
|
|
|
|
|
*/
|
|
|
|
|
typedef struct Param Param;
|
|
|
|
|
struct Param
|
|
|
|
|
{
|
|
|
|
|
/* these never change */
|
|
|
|
|
uint snapEpoch; /* epoch for snapshot being archived */
|
|
|
|
|
uint blockSize;
|
|
|
|
|
Cache *c;
|
|
|
|
|
Arch *a;
|
|
|
|
|
|
|
|
|
|
/* changes on every call */
|
|
|
|
|
uint depth;
|
|
|
|
|
|
|
|
|
|
/* statistics */
|
|
|
|
|
uint nfixed;
|
|
|
|
|
uint nsend;
|
|
|
|
|
uint nvisit;
|
|
|
|
|
uint nfailsend;
|
|
|
|
|
uint maxdepth;
|
|
|
|
|
uint nreclaim;
|
|
|
|
|
uint nfake;
|
|
|
|
|
uint nreal;
|
|
|
|
|
|
|
|
|
|
/* these occasionally change (must save old values and put back) */
|
|
|
|
|
uint dsize;
|
|
|
|
|
uint psize;
|
|
|
|
|
|
|
|
|
|
/* return value; avoids using stack space */
|
|
|
|
|
Label l;
|
|
|
|
|
uchar score[VtScoreSize];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
shaBlock(uchar score[VtScoreSize], Block *b, uchar *data, uint bsize)
|
|
|
|
|
{
|
2013-09-23 23:16:25 +02:00
|
|
|
vtsha1(score, data, vtzerotruncate(vtType[b->l.type], data, bsize));
|
2013-09-23 23:00:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static uint
|
|
|
|
|
etype(Entry *e)
|
|
|
|
|
{
|
|
|
|
|
uint t;
|
|
|
|
|
|
2013-09-23 23:16:25 +02:00
|
|
|
if(e->flags&_VtEntryDir)
|
2013-09-23 23:00:39 +02:00
|
|
|
t = BtDir;
|
|
|
|
|
else
|
|
|
|
|
t = BtData;
|
|
|
|
|
return t+e->depth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static uchar*
|
|
|
|
|
copyBlock(Block *b, u32int blockSize)
|
|
|
|
|
{
|
|
|
|
|
uchar *data;
|
|
|
|
|
|
2013-09-23 23:16:25 +02:00
|
|
|
data = vtmalloc(blockSize);
|
2013-09-23 23:00:39 +02:00
|
|
|
if(data == nil)
|
|
|
|
|
return nil;
|
|
|
|
|
memmove(data, b->data, blockSize);
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Walk over the block tree, archiving it to Venti.
|
|
|
|
|
*
|
|
|
|
|
* We don't archive the snapshots. Instead we zero the
|
|
|
|
|
* entries in a temporary copy of the block and archive that.
|
|
|
|
|
*
|
|
|
|
|
* Return value is:
|
|
|
|
|
*
|
|
|
|
|
* ArchFailure some error occurred
|
|
|
|
|
* ArchSuccess block and all children archived
|
|
|
|
|
* ArchFaked success, but block or children got copied
|
|
|
|
|
*/
|
|
|
|
|
enum
|
|
|
|
|
{
|
|
|
|
|
ArchFailure,
|
|
|
|
|
ArchSuccess,
|
|
|
|
|
ArchFaked,
|
|
|
|
|
};
|
|
|
|
|
static int
|
|
|
|
|
archWalk(Param *p, u32int addr, uchar type, u32int tag)
|
|
|
|
|
{
|
|
|
|
|
int ret, i, x, psize, dsize;
|
|
|
|
|
uchar *data, score[VtScoreSize];
|
|
|
|
|
Block *b;
|
|
|
|
|
Label l;
|
|
|
|
|
Entry *e;
|
|
|
|
|
WalkPtr w;
|
2013-09-23 23:16:25 +02:00
|
|
|
char err[ERRMAX];
|
2013-09-23 23:00:39 +02:00
|
|
|
|
|
|
|
|
p->nvisit++;
|
|
|
|
|
|
|
|
|
|
b = cacheLocalData(p->c, addr, type, tag, OReadWrite,0);
|
|
|
|
|
if(b == nil){
|
2013-09-23 23:16:25 +02:00
|
|
|
fprint(2, "archive(%ud, %#ux): cannot find block: %r\n", p->snapEpoch, addr);
|
|
|
|
|
rerrstr(err, sizeof err);
|
|
|
|
|
if(strcmp(err, ELabelMismatch) == 0){
|
2013-09-23 23:00:39 +02:00
|
|
|
/* might as well plod on so we write _something_ to Venti */
|
2013-09-23 23:16:25 +02:00
|
|
|
memmove(p->score, vtzeroscore, VtScoreSize);
|
2013-09-23 23:00:39 +02:00
|
|
|
return ArchFaked;
|
|
|
|
|
}
|
|
|
|
|
return ArchFailure;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(DEBUG) fprint(2, "%*sarchive(%ud, %#ux): block label %L\n",
|
|
|
|
|
p->depth*2, "", p->snapEpoch, b->addr, &b->l);
|
|
|
|
|
p->depth++;
|
|
|
|
|
if(p->depth > p->maxdepth)
|
|
|
|
|
p->maxdepth = p->depth;
|
|
|
|
|
|
|
|
|
|
data = b->data;
|
|
|
|
|
if((b->l.state&BsVenti) == 0){
|
|
|
|
|
initWalk(&w, b, b->l.type==BtDir ? p->dsize : p->psize);
|
|
|
|
|
for(i=0; nextWalk(&w, score, &type, &tag, &e); i++){
|
|
|
|
|
if(e){
|
|
|
|
|
if(!(e->flags&VtEntryActive))
|
|
|
|
|
continue;
|
|
|
|
|
if((e->snap && !e->archive)
|
|
|
|
|
|| (e->flags&VtEntryNoArchive)){
|
|
|
|
|
if(0) fprint(2, "snap; faking %#ux\n", b->addr);
|
|
|
|
|
if(data == b->data){
|
|
|
|
|
data = copyBlock(b, p->blockSize);
|
|
|
|
|
if(data == nil){
|
|
|
|
|
ret = ArchFailure;
|
|
|
|
|
goto Out;
|
|
|
|
|
}
|
|
|
|
|
w.data = data;
|
|
|
|
|
}
|
2013-09-23 23:16:25 +02:00
|
|
|
memmove(e->score, vtzeroscore, VtScoreSize);
|
2013-09-23 23:00:39 +02:00
|
|
|
e->depth = 0;
|
|
|
|
|
e->size = 0;
|
|
|
|
|
e->tag = 0;
|
|
|
|
|
e->flags &= ~VtEntryLocal;
|
|
|
|
|
entryPack(e, data, w.n-1);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
addr = globalToLocal(score);
|
|
|
|
|
if(addr == NilBlock)
|
|
|
|
|
continue;
|
|
|
|
|
dsize = p->dsize;
|
|
|
|
|
psize = p->psize;
|
|
|
|
|
if(e){
|
|
|
|
|
p->dsize= e->dsize;
|
|
|
|
|
p->psize = e->psize;
|
|
|
|
|
}
|
2013-09-23 23:16:25 +02:00
|
|
|
qunlock(&b->lk);
|
2013-09-23 23:00:39 +02:00
|
|
|
x = archWalk(p, addr, type, tag);
|
2013-09-23 23:16:25 +02:00
|
|
|
qlock(&b->lk);
|
2013-09-23 23:00:39 +02:00
|
|
|
if(e){
|
|
|
|
|
p->dsize = dsize;
|
|
|
|
|
p->psize = psize;
|
|
|
|
|
}
|
|
|
|
|
while(b->iostate != BioClean && b->iostate != BioDirty)
|
2013-09-23 23:16:25 +02:00
|
|
|
rsleep(&b->ioready);
|
2013-09-23 23:00:39 +02:00
|
|
|
switch(x){
|
|
|
|
|
case ArchFailure:
|
|
|
|
|
fprint(2, "archWalk %#ux failed; ptr is in %#ux offset %d\n",
|
|
|
|
|
addr, b->addr, i);
|
|
|
|
|
ret = ArchFailure;
|
|
|
|
|
goto Out;
|
|
|
|
|
case ArchFaked:
|
|
|
|
|
/*
|
|
|
|
|
* When we're writing the entry for an archive directory
|
|
|
|
|
* (like /archive/2003/1215) then even if we've faked
|
|
|
|
|
* any data, record the score unconditionally.
|
|
|
|
|
* This way, we will always record the Venti score here.
|
|
|
|
|
* Otherwise, temporary data or corrupted file system
|
|
|
|
|
* would cause us to keep holding onto the on-disk
|
|
|
|
|
* copy of the archive.
|
|
|
|
|
*/
|
|
|
|
|
if(e==nil || !e->archive)
|
|
|
|
|
if(data == b->data){
|
|
|
|
|
if(0) fprint(2, "faked %#ux, faking %#ux (%V)\n", addr, b->addr, p->score);
|
|
|
|
|
data = copyBlock(b, p->blockSize);
|
|
|
|
|
if(data == nil){
|
|
|
|
|
ret = ArchFailure;
|
|
|
|
|
goto Out;
|
|
|
|
|
}
|
|
|
|
|
w.data = data;
|
|
|
|
|
}
|
|
|
|
|
/* fall through */
|
|
|
|
|
if(0) fprint(2, "falling\n");
|
|
|
|
|
case ArchSuccess:
|
|
|
|
|
if(e){
|
|
|
|
|
memmove(e->score, p->score, VtScoreSize);
|
|
|
|
|
e->flags &= ~VtEntryLocal;
|
|
|
|
|
entryPack(e, data, w.n-1);
|
|
|
|
|
}else
|
|
|
|
|
memmove(data+(w.n-1)*VtScoreSize, p->score, VtScoreSize);
|
|
|
|
|
if(data == b->data){
|
|
|
|
|
blockDirty(b);
|
|
|
|
|
/*
|
|
|
|
|
* If b is in the active tree, then we need to note that we've
|
|
|
|
|
* just removed addr from the active tree (replacing it with the
|
|
|
|
|
* copy we just stored to Venti). If addr is in other snapshots,
|
|
|
|
|
* this will close addr but not free it, since it has a non-empty
|
|
|
|
|
* epoch range.
|
|
|
|
|
*
|
|
|
|
|
* If b is in the active tree but has been copied (this can happen
|
|
|
|
|
* if we get killed at just the right moment), then we will
|
|
|
|
|
* mistakenly leak its kids.
|
|
|
|
|
*
|
|
|
|
|
* The children of an archive directory (e.g., /archive/2004/0604)
|
|
|
|
|
* are not treated as in the active tree.
|
|
|
|
|
*/
|
|
|
|
|
if((b->l.state&BsCopied)==0 && (e==nil || e->snap==0))
|
|
|
|
|
blockRemoveLink(b, addr, p->l.type, p->l.tag, 0);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!ventiSend(p->a, b, data)){
|
|
|
|
|
p->nfailsend++;
|
|
|
|
|
ret = ArchFailure;
|
|
|
|
|
goto Out;
|
|
|
|
|
}
|
|
|
|
|
p->nsend++;
|
|
|
|
|
if(data != b->data)
|
|
|
|
|
p->nfake++;
|
|
|
|
|
if(data == b->data){ /* not faking it, so update state */
|
|
|
|
|
p->nreal++;
|
|
|
|
|
l = b->l;
|
|
|
|
|
l.state |= BsVenti;
|
|
|
|
|
if(!blockSetLabel(b, &l, 0)){
|
|
|
|
|
ret = ArchFailure;
|
|
|
|
|
goto Out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shaBlock(p->score, b, data, p->blockSize);
|
|
|
|
|
if(0) fprint(2, "ventisend %V %p %p %p\n", p->score, data, b->data, w.data);
|
|
|
|
|
ret = data!=b->data ? ArchFaked : ArchSuccess;
|
|
|
|
|
p->l = b->l;
|
|
|
|
|
Out:
|
|
|
|
|
if(data != b->data)
|
2013-09-23 23:16:25 +02:00
|
|
|
vtfree(data);
|
2013-09-23 23:00:39 +02:00
|
|
|
p->depth--;
|
|
|
|
|
blockPut(b);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
archThread(void *v)
|
|
|
|
|
{
|
|
|
|
|
Arch *a = v;
|
|
|
|
|
Block *b;
|
|
|
|
|
Param p;
|
|
|
|
|
Super super;
|
|
|
|
|
int ret;
|
|
|
|
|
u32int addr;
|
|
|
|
|
uchar rbuf[VtRootSize];
|
|
|
|
|
VtRoot root;
|
|
|
|
|
|
2013-09-23 23:16:25 +02:00
|
|
|
threadsetname("arch");
|
2013-09-23 23:00:39 +02:00
|
|
|
|
|
|
|
|
for(;;){
|
|
|
|
|
/* look for work */
|
2013-09-23 23:16:25 +02:00
|
|
|
wlock(&a->fs->elk);
|
2013-09-23 23:00:39 +02:00
|
|
|
b = superGet(a->c, &super);
|
|
|
|
|
if(b == nil){
|
2013-09-23 23:16:25 +02:00
|
|
|
wunlock(&a->fs->elk);
|
|
|
|
|
fprint(2, "archThread: superGet: %r\n");
|
2013-09-23 23:00:39 +02:00
|
|
|
sleep(60*1000);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
addr = super.next;
|
|
|
|
|
if(addr != NilBlock && super.current == NilBlock){
|
|
|
|
|
super.current = addr;
|
|
|
|
|
super.next = NilBlock;
|
|
|
|
|
superPack(&super, b->data);
|
|
|
|
|
blockDirty(b);
|
|
|
|
|
}else
|
|
|
|
|
addr = super.current;
|
|
|
|
|
blockPut(b);
|
2013-09-23 23:16:25 +02:00
|
|
|
wunlock(&a->fs->elk);
|
2013-09-23 23:00:39 +02:00
|
|
|
|
|
|
|
|
if(addr == NilBlock){
|
|
|
|
|
/* wait for work */
|
2013-09-23 23:16:25 +02:00
|
|
|
qlock(&a->lk);
|
|
|
|
|
rsleep(&a->starve);
|
|
|
|
|
if(a->die.l != nil)
|
2013-09-23 23:00:39 +02:00
|
|
|
goto Done;
|
2013-09-23 23:16:25 +02:00
|
|
|
qunlock(&a->lk);
|
2013-09-23 23:00:39 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sleep(10*1000); /* window of opportunity to provoke races */
|
|
|
|
|
|
|
|
|
|
/* do work */
|
|
|
|
|
memset(&p, 0, sizeof p);
|
|
|
|
|
p.blockSize = a->blockSize;
|
|
|
|
|
p.dsize = 3*VtEntrySize; /* root has three Entries */
|
|
|
|
|
p.c = a->c;
|
|
|
|
|
p.a = a;
|
|
|
|
|
|
|
|
|
|
ret = archWalk(&p, addr, BtDir, RootTag);
|
|
|
|
|
switch(ret){
|
|
|
|
|
default:
|
|
|
|
|
abort();
|
|
|
|
|
case ArchFailure:
|
2013-09-23 23:16:25 +02:00
|
|
|
fprint(2, "archiveBlock %#ux: %r\n", addr);
|
2013-09-23 23:00:39 +02:00
|
|
|
sleep(60*1000);
|
|
|
|
|
continue;
|
|
|
|
|
case ArchSuccess:
|
|
|
|
|
case ArchFaked:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(0) fprint(2, "archiveSnapshot 0x%#ux: maxdepth %ud nfixed %ud"
|
|
|
|
|
" send %ud nfailsend %ud nvisit %ud"
|
|
|
|
|
" nreclaim %ud nfake %ud nreal %ud\n",
|
|
|
|
|
addr, p.maxdepth, p.nfixed,
|
|
|
|
|
p.nsend, p.nfailsend, p.nvisit,
|
|
|
|
|
p.nreclaim, p.nfake, p.nreal);
|
|
|
|
|
if(0) fprint(2, "archiveBlock %V (%ud)\n", p.score, p.blockSize);
|
|
|
|
|
|
|
|
|
|
/* tie up vac root */
|
|
|
|
|
memset(&root, 0, sizeof root);
|
|
|
|
|
strecpy(root.type, root.type+sizeof root.type, "vac");
|
|
|
|
|
strecpy(root.name, root.name+sizeof root.name, "fossil");
|
|
|
|
|
memmove(root.score, p.score, VtScoreSize);
|
|
|
|
|
memmove(root.prev, super.last, VtScoreSize);
|
2013-09-23 23:16:25 +02:00
|
|
|
root.blocksize = a->blockSize;
|
|
|
|
|
vtrootpack(&root, rbuf);
|
|
|
|
|
if(vtwrite(a->z, p.score, VtRootType, rbuf, VtRootSize) < 0
|
|
|
|
|
|| vtsha1check(p.score, rbuf, VtRootSize) < 0){
|
|
|
|
|
fprint(2, "vtWriteBlock %#ux: %r\n", addr);
|
2013-09-23 23:00:39 +02:00
|
|
|
sleep(60*1000);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* record success */
|
2013-09-23 23:16:25 +02:00
|
|
|
wlock(&a->fs->elk);
|
2013-09-23 23:00:39 +02:00
|
|
|
b = superGet(a->c, &super);
|
|
|
|
|
if(b == nil){
|
2013-09-23 23:16:25 +02:00
|
|
|
wunlock(&a->fs->elk);
|
|
|
|
|
fprint(2, "archThread: superGet: %r\n");
|
2013-09-23 23:00:39 +02:00
|
|
|
sleep(60*1000);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
super.current = NilBlock;
|
|
|
|
|
memmove(super.last, p.score, VtScoreSize);
|
|
|
|
|
superPack(&super, b->data);
|
|
|
|
|
blockDirty(b);
|
|
|
|
|
blockPut(b);
|
2013-09-23 23:16:25 +02:00
|
|
|
wunlock(&a->fs->elk);
|
2013-09-23 23:00:39 +02:00
|
|
|
|
|
|
|
|
consPrint("archive vac:%V\n", p.score);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Done:
|
|
|
|
|
a->ref--;
|
2013-09-23 23:16:25 +02:00
|
|
|
rwakeup(&a->die);
|
|
|
|
|
qunlock(&a->lk);
|
2013-09-23 23:00:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
archKick(Arch *a)
|
|
|
|
|
{
|
|
|
|
|
if(a == nil){
|
|
|
|
|
fprint(2, "warning: archKick nil\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2013-09-23 23:16:25 +02:00
|
|
|
qlock(&a->lk);
|
|
|
|
|
rwakeup(&a->starve);
|
|
|
|
|
qunlock(&a->lk);
|
2013-09-23 23:00:39 +02:00
|
|
|
}
|