ref: f2cfee358f329519e913a20142d96b1e0029633c
parent: c6ec2041ad9bb2dbb6b710c7efdbf2fc8de4f5ba
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sun Oct 22 09:21:23 EDT 2023
ndb/dns: implement concurrent garbage collection Get rid of the blocking in getactivity()/putactivity(), and instead split garbage collection over 2 phases (mark bit) so that dnageall() runs the mark phase only for the current mark bit and sweep for the previous mark (which then becomes the current mark after the collection). Note that dnageall() only runs once the previous phase (previous mark) has no more active procs. Also, fix the ageing period controller and make it more agressive once dnvars.names is twice the target so we can actually sustain being hammered with new domain name queries.
--- a/sys/src/cmd/ndb/dblookup.c
+++ b/sys/src/cmd/ndb/dblookup.c
@@ -815,9 +815,6 @@
*/
dnauthdb();
- /* remove old entries */
- dnageall(1);
-
doit = 0;
lastyoungest = youngest;
createptrs();
--- a/sys/src/cmd/ndb/dn.c
+++ b/sys/src/cmd/ndb/dn.c
@@ -15,7 +15,7 @@
enum {
/* these settings will trigger frequent aging */
Deftarget = 4000,
- Minage = 5*Min,
+ Minage = 1*Min,
Defagefreq = 15*Min, /* age names this often (seconds) */
};
@@ -26,12 +26,13 @@
DN *ht[HTLEN];
static struct {
- Lock;
+ QLock;
ulong names; /* names allocated */
ulong oldest; /* longest we'll leave a name around */
- int active; /* number of active processes */
- int mutex; /* 0 or pid of process doing garbage collection */
+ ulong lastage;
ushort id; /* same size as in packet */
+ uchar mark; /* mark bit for gc */
+ int active[2]; /* number of active processes per mark */
} dnvars;
/* names of RR types */
@@ -134,7 +135,7 @@
};
ulong target = Deftarget;
-Lock dnlock;
+Lock dnlock;
static ulong agefreq = Defagefreq;
@@ -169,12 +170,14 @@
fmtinstall('Q', rravfmt);
fmtinstall('H', encodefmt);
- dnvars.oldest = maxage;
+ timems();
+
dnvars.names = 0;
+ dnvars.oldest = maxage;
+ dnvars.lastage = now;
dnvars.id = truerand(); /* don't start with same id every time */
+ dnvars.mark = 0;
- timems();
-
notify(ding);
}
@@ -193,6 +196,15 @@
}
/*
+ * mark dn with current mark bit
+ */
+static void
+dnmark(DN *dp)
+{
+ dp->mark = (dp->mark & ~1) | dnvars.mark;
+}
+
+/*
* lookup a symbol. if enter is not zero and the name is
* not found, create it.
*/
@@ -206,17 +218,14 @@
lock(&dnlock);
for(dp = *l; dp; dp = dp->next) {
assert(dp->magic == DNmagic);
- if(dp->class == class && cistrcmp(dp->name, name) == 0){
- dp->referenced = now;
- unlock(&dnlock);
- return dp;
- }
+ if(dp->class == class && cistrcmp(dp->name, name) == 0)
+ goto out;
l = &dp->next;
}
if(!enter){
unlock(&dnlock);
- return 0;
+ return nil;
}
dnvars.names++;
dp = emalloc(sizeof(*dp));
@@ -224,10 +233,11 @@
dp->name = estrdup(name);
dp->class = class;
dp->rr = nil;
- dp->referenced = now;
/* add new DN to tail of the hash list. *l points to last next ptr. */
dp->next = nil;
*l = dp;
+out:
+ dnmark(dp);
unlock(&dnlock);
return dp;
@@ -368,6 +378,83 @@
}
/*
+ * return all refernced domain names of a RR.
+ * call with dnlock held.
+ */
+static int
+rrnames(RR *rp, DN **dn)
+{
+ int n = 0;
+
+ dn[n++] = rp->owner;
+ if(rp->negative){
+ if((dn[n] = rp->negsoaowner) != nil) n++;
+ return n;
+ }
+ switch(rp->type){
+ case Thinfo:
+ if((dn[n] = rp->cpu) != nil) n++;
+ if((dn[n] = rp->os) != nil) n++;
+ case Ttxt:
+ break;
+ case Tcname:
+ case Tmb:
+ case Tmd:
+ case Tmf:
+ case Tns:
+ case Tmx:
+ case Tsrv:
+ if((dn[n] = rp->host) != nil) n++;
+ break;
+ case Tmg:
+ case Tmr:
+ if((dn[n] = rp->mb) != nil) n++;
+ break;
+ case Tminfo:
+ if((dn[n] = rp->rmb) != nil) n++;
+ if((dn[n] = rp->mb) != nil) n++;
+ break;
+ case Trp:
+ if((dn[n] = rp->rmb) != nil) n++;
+ if((dn[n] = rp->rp) != nil) n++;
+ break;
+ case Ta:
+ case Taaaa:
+ if((dn[n] = rp->ip) != nil) n++;
+ break;
+ case Tptr:
+ if((dn[n] = rp->ptr) != nil) n++;
+ break;
+ case Tsoa:
+ if((dn[n] = rp->host) != nil) n++;
+ if((dn[n] = rp->rmb) != nil) n++;
+ break;
+ case Tsig:
+ if((dn[n] = rp->sig->signer) != nil) n++;
+ break;
+ case Tcaa:
+ if((dn[n] = rp->caa->tag) != nil) n++;
+ break;
+ }
+ return n;
+}
+
+/*
+ * mark all refernced domain names of an RR.
+ * call with dnlock held.
+ */
+static void
+rrmark(RR *rp)
+{
+ DN *dn[RRnames];
+ int i, n;
+
+ n = rrnames(rp, dn);
+ for(i = 0; i < n; i++)
+ dnmark(dn[i]);
+}
+
+/*
* delete head of *l and free the old head.
* call with dnlock held.
*/
@@ -376,8 +463,6 @@
{
RR *rp;
- if (canlock(&dnlock))
- abort(); /* rrdelhead called with dnlock not held */
rp = *l;
if(rp == nil)
return;
@@ -390,22 +475,20 @@
* check the age of resource records, free any that have timed out.
* call with dnlock held.
*/
-void
+static void
dnage(DN *dp)
{
RR **l, *rp;
- ulong diff;
- if (canlock(&dnlock))
- abort(); /* dnage called with dnlock not held */
- diff = now - dp->referenced;
- if(diff < Reserved || dp->mark != 0)
+ /* see dnagenever() below */
+ if(dp->mark & ~1)
return;
l = &dp->rr;
while ((rp = *l) != nil){
assert(rp->magic == RRmagic && rp->cached);
- if(!rp->db && ((long)(rp->expire - now) <= 0 || diff > dnvars.oldest))
+ if(!rp->db && ((long)(rp->expire - now) <= 0
+ || (long)(now - (rp->expire - rp->ttl)) > dnvars.oldest))
rrdelhead(l); /* rp == *l before; *l == rp->next after */
else
l = &rp->next;
@@ -412,176 +495,98 @@
}
}
-#define MARK(dp) { if (dp) (dp)->mark |= 2; }
-
/* mark a domain name and those in its RRs as never to be aged */
void
dnagenever(DN *dp)
{
+ DN *dn[RRnames];
RR *rp;
+ int i, n;
lock(&dnlock);
/* mark all referenced domain names */
- MARK(dp);
for(rp = dp->rr; rp; rp = rp->next){
- MARK(rp->owner);
- if(rp->negative){
- MARK(rp->negsoaowner);
- continue;
- }
- switch(rp->type){
- case Thinfo:
- MARK(rp->cpu);
- MARK(rp->os);
- break;
- case Ttxt:
- break;
- case Tcname:
- case Tmb:
- case Tmd:
- case Tmf:
- case Tns:
- case Tmx:
- case Tsrv:
- MARK(rp->host);
- break;
- case Tmg:
- case Tmr:
- MARK(rp->mb);
- break;
- case Tminfo:
- MARK(rp->rmb);
- MARK(rp->mb);
- break;
- case Trp:
- MARK(rp->rmb);
- MARK(rp->rp);
- break;
- case Ta:
- case Taaaa:
- MARK(rp->ip);
- break;
- case Tptr:
- MARK(rp->ptr);
- break;
- case Tsoa:
- MARK(rp->host);
- MARK(rp->rmb);
- break;
- case Tsig:
- MARK(rp->sig->signer);
- break;
- case Tcaa:
- MARK(rp->caa->tag);
- break;
- }
+ assert(rp->owner == dp);
+ n = rrnames(rp, dn);
+ for(i = 0; i < n; i++)
+ dn[i]->mark |= ~1;
}
unlock(&dnlock);
}
-#define REF(dp) { if (dp) (dp)->mark |= 1; }
-
/*
* periodicly sweep for old records and remove unreferenced domain names
*
- * only called when all other threads are locked out
+ * this is called once all activity ceased for the non-current
+ * mark bit (previous cycle), meaning there are no more
+ * unaccounted references to DN's with the non-current mark
+ * from other activity procs.
+ *
+ * this can run concurrently to current mark bit activity procs
+ * as DN's with current mark bit are not freed in this cycle, but
+ * in the next cycle when the previously current mark bit activity
+ * has ceased.
*/
void
dnageall(int doit)
{
DN *dp, **l;
- int i;
RR *rp;
- static ulong nextage;
+ int i;
- if(dnvars.names < target || ((long)(nextage - now) > 0 && !doit)){
- dnvars.oldest = maxage;
- return;
- }
+ if(!doit){
+ ulong period;
- if(dnvars.names >= target) {
- dnslog("more names (%lud) than target (%lud)", dnvars.names,
- target);
+ if(dnvars.names < target){
+ dnvars.oldest = maxage;
+ return;
+ }
+ if(dnvars.names < target*2) {
+ period = dnvars.oldest / 2;
+ if(period > agefreq)
+ period = agefreq;
+ } else {
+ period = Minage / 2;
+ }
+ if((long)(now - dnvars.lastage) < period)
+ return;
+
+ dnslog("more names (%lud) than target (%lud)", dnvars.names, target);
+
dnvars.oldest /= 2;
- if (dnvars.oldest < Minage)
+ if(dnvars.oldest < Minage)
dnvars.oldest = Minage; /* don't be silly */
}
- if (agefreq > dnvars.oldest / 2)
- nextage = now + dnvars.oldest / 2;
- else
- nextage = now + (ulong)agefreq;
+ dnvars.lastage = now;
lock(&dnlock);
- /* time out all old entries (and set refs to 0) */
+ /* timeout all expired records */
for(i = 0; i < HTLEN; i++)
- for(dp = ht[i]; dp; dp = dp->next){
- dp->mark &= ~1;
+ for(dp = ht[i]; dp; dp = dp->next)
dnage(dp);
- }
/* mark all referenced domain names */
for(i = 0; i < HTLEN; i++)
for(dp = ht[i]; dp; dp = dp->next)
for(rp = dp->rr; rp; rp = rp->next){
- REF(rp->owner);
- if(rp->negative){
- REF(rp->negsoaowner);
- continue;
- }
- switch(rp->type){
- case Thinfo:
- REF(rp->cpu);
- REF(rp->os);
- break;
- case Ttxt:
- break;
- case Tcname:
- case Tmb:
- case Tmd:
- case Tmf:
- case Tns:
- case Tmx:
- case Tsrv:
- REF(rp->host);
- break;
- case Tmg:
- case Tmr:
- REF(rp->mb);
- break;
- case Tminfo:
- REF(rp->rmb);
- REF(rp->mb);
- break;
- case Trp:
- REF(rp->rmb);
- REF(rp->rp);
- break;
- case Ta:
- case Taaaa:
- REF(rp->ip);
- break;
- case Tptr:
- REF(rp->ptr);
- break;
- case Tsoa:
- REF(rp->host);
- REF(rp->rmb);
- break;
- case Tsig:
- REF(rp->sig->signer);
- break;
- }
+ assert(rp->owner == dp);
+ rrmark(rp);
}
+ /* bump mark */
+ dnvars.mark ^= 1;
+ assert(dnvars.active[dnvars.mark] == 0);
+
/* sweep and remove unreferenced domain names */
for(i = 0; i < HTLEN; i++){
l = &ht[i];
for(dp = *l; dp; dp = *l){
- if(dp->rr == nil && dp->mark == 0){
+ if(dp->mark == dnvars.mark){
assert(dp->magic == DNmagic);
+ assert(dp->rr == nil);
*l = dp->next;
free(dp->name);
@@ -595,7 +600,6 @@
l = &dp->next;
}
}
-
unlock(&dnlock);
}
@@ -614,7 +618,7 @@
/* time out all database entries */
for(i = 0; i < HTLEN; i++)
for(dp = ht[i]; dp; dp = dp->next) {
- dp->mark = 0;
+ dp->mark &= 1;
for(rp = dp->rr; rp; rp = rp->next)
if(rp->db)
rp->expire = 0;
@@ -668,73 +672,39 @@
* keep track of other processes to know if we can
* garbage collect. block while garbage collecting.
*/
-int
-getactivity(Request *req, int recursive)
+void
+getactivity(Request *req)
{
- int rv;
-
if(traceactivity)
dnslog("get: %d active by pid %d from %p",
- dnvars.active, getpid(), getcallerpc(&req));
+ dnvars.active[0] + dnvars.active[1],
+ getpid(), getcallerpc(&req));
- /*
- * can't block here if we're already holding one
- * of the dnvars.active (recursive). will deadlock.
- */
- lock(&dnvars);
- while(!recursive && dnvars.mutex){
- unlock(&dnvars);
- sleep(100); /* tune; was 200 */
- lock(&dnvars);
- }
- rv = ++dnvars.active;
- req->id = ++dnvars.id;
+ qlock(&dnvars);
req->aux = nil;
- unlock(&dnvars);
-
- return rv;
+ req->id = ++dnvars.id;
+ req->mark = dnvars.mark;
+ dnvars.active[req->mark]++;
+ qunlock(&dnvars);
}
+
void
-putactivity(int recursive)
+putactivity(Request *req)
{
- int pid = getpid();
-
if(traceactivity)
- dnslog("put: %d active by pid %d", dnvars.active, pid);
+ dnslog("put: %d active by pid %d from %p",
+ dnvars.active[0] + dnvars.active[1],
+ getpid(), getcallerpc(&req));
- lock(&dnvars);
- dnvars.active--;
- assert(dnvars.active >= 0); /* "dnvars.active %d", dnvars.active */
-
- /*
- * clean out old entries and check for new db periodicly
- * can't block here if being called to let go a "recursive" lock
- * or we'll deadlock waiting for ourselves to give up the dnvars.active.
- * also don't block if we are the 9p process (needrefresh == pid).
- */
- if(recursive || dnvars.mutex
- || dnvars.active > 0 && (needrefresh == 0 || needrefresh == pid)){
- unlock(&dnvars);
- return;
+ qlock(&dnvars);
+ dnvars.active[req->mark]--;
+ assert(dnvars.active[req->mark] >= 0);
+ if(dnvars.active[dnvars.mark^1] == 0){
+ db2cache(needrefresh);
+ dnageall(needrefresh);
+ needrefresh = 0;
}
-
- /* wait till we're alone */
- dnvars.mutex = pid;
- while(dnvars.active > 0){
- unlock(&dnvars);
- sleep(100); /* tune; was 100 */
- lock(&dnvars);
- }
- unlock(&dnvars);
-
- db2cache(needrefresh != 0);
- dnageall(0);
-
- /* let others back in */
- lock(&dnvars);
- needrefresh = 0;
- dnvars.mutex = 0;
- unlock(&dnvars);
+ qunlock(&dnvars);
}
int
@@ -1057,6 +1027,8 @@
}
out:
+ for(rp = first; rp; rp = rp->next)
+ rrmark(rp);
unlock(&dnlock);
unique(first);
return first;
@@ -1557,22 +1529,10 @@
if(req->isslave)
return; /* we're already a slave process */
- /*
- * These calls to putactivity cannot block.
- * After getactivity(), the current process is counted
- * twice in dnvars.active (one will pass to the child).
- * If putactivity tries to wait for dnvars.active == 0,
- * it will never happen.
- */
-
- /* limit parallelism */
- procs = getactivity(req, 1);
- if(procs > stats.slavehiwat)
- stats.slavehiwat = procs;
- if(procs > Maxactive){
+ procs = dnvars.active[0] + dnvars.active[1];
+ if(procs >= Maxactive){
if(traceactivity)
dnslog("[%d] too much activity", getpid());
- putactivity(1);
return;
}
@@ -1583,20 +1543,22 @@
ppid = getpid();
switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
case -1:
- putactivity(1);
break;
case 0:
procsetname("request slave of pid %d", ppid);
- if(traceactivity)
+ if(traceactivity)
dnslog("[%d] take activity from %d", getpid(), ppid);
- req->isslave = 1; /* why not `= getpid()'? */
- break;
- default:
+
/*
* this relies on rfork producing separate, initially-identical
* stacks, thus giving us two copies of `req', one in each
* process.
*/
+ req->isslave = 1;
+ break;
+ default:
+ if(++procs > stats.slavehiwat)
+ stats.slavehiwat = procs;
alarm(0);
longjmp(req->mret, 1);
}
--- a/sys/src/cmd/ndb/dnnotify.c
+++ b/sys/src/cmd/ndb/dnnotify.c
@@ -168,9 +168,9 @@
req.isslave = 1; /* don't fork off subprocesses */
for(;;){
- getactivity(&req, 0);
+ getactivity(&req);
notify_areas(mntpt, owned, &req);
- putactivity(0);
+ putactivity(&req);
sleep(60*1000);
}
}
--- a/sys/src/cmd/ndb/dns.c
+++ b/sys/src/cmd/ndb/dns.c
@@ -392,15 +392,15 @@
Mfile *volatile mf;
volatile Request req;
- memset(&req, 0, sizeof req);
/*
* a slave process is sometimes forked to wait for replies from other
* servers. The master process returns immediately via a longjmp
* through 'mret'.
*/
- if(setjmp(req.mret))
- putactivity(0);
+ memset(&req, 0, sizeof req);
+ setjmp(req.mret);
req.isslave = 0;
+
while((n = read9pmsg(mfd[0], mdata, sizeof mdata)) != 0){
if(n < 0){
dnslog("error reading 9P from %s: %r", mntpt);
@@ -448,16 +448,16 @@
rread(job, mf);
break;
case Twrite:
- getactivity(&req, 0);
+ getactivity(&req);
req.aborttime = timems() + Maxreqtm;
req.from = "9p";
rwrite(job, mf, &req);
freejob(job);
if(req.isslave){
- putactivity(0);
+ putactivity(&req);
_exits(0);
}
- putactivity(0);
+ putactivity(&req);
continue;
case Tclunk:
rclunk(job, mf);
@@ -693,7 +693,7 @@
else if(strcmp(job->request.data, "dump")==0)
dndump("/lib/ndb/dnsdump");
else if(strcmp(job->request.data, "refresh")==0)
- needrefresh = getpid();
+ needrefresh = 1;
else if(strcmp(job->request.data, "stats")==0)
dnstats("/lib/ndb/dnsstats");
else if(strncmp(job->request.data, "target ", 7)==0){
--- a/sys/src/cmd/ndb/dns.h
+++ b/sys/src/cmd/ndb/dns.h
@@ -127,9 +127,6 @@
Year= 52*Week,
DEFTTL= Day,
- /* reserved time (can't be timed out earlier) */
- Reserved= 5*Min,
-
/* packet sizes */
Maxudp= 512, /* maximum bytes per udp message sent */
Maxudpin= 2048, /* maximum bytes per udp message rcv'd */
@@ -140,6 +137,8 @@
Maxpath= 128, /* size of mntpt */
Maxlcks= 10, /* max. query-type locks per domain name */
+ RRnames= 8, /* # of referenced names per RR */
+
RRmagic= 0xdeadbabe,
DNmagic= 0xa110a110,
@@ -177,7 +176,8 @@
int isslave; /* pid of slave */
uvlong aborttime; /* time in ms at which we give up */
jmp_buf mret; /* where master jumps to after starting a slave */
- int id;
+ ushort id;
+ uchar mark;
char *from; /* who asked us? */
void *aux;
};
@@ -188,14 +188,13 @@
struct DN
{
DN *next; /* hash collision list */
- ulong magic;
char *name; /* owner */
RR *rr; /* resource records off this name */
- ulong referenced; /* time last referenced */
ulong ordinal;
ushort class; /* RR class */
uchar respcode; /* response code */
uchar mark; /* for mark and sweep */
+ ulong magic;
};
/*
@@ -446,7 +445,6 @@
int bslashfmt(Fmt*);
Server* copyserverlist(Server*);
void db2cache(int);
-void dnage(DN*);
void dnageall(int);
void dnagedb(void);
void dnagenever(DN *);
@@ -464,9 +462,9 @@
char* estrdup(char*);
void freeanswers(DNSmsg *mp);
void freeserverlist(Server*);
-int getactivity(Request*, int);
+void getactivity(Request*);
Area* inmyarea(char*);
-void putactivity(int);
+void putactivity(Request*);
RR* randomize(RR*);
RR* rralloc(int);
void rrattach(RR*, int);
--- a/sys/src/cmd/ndb/dnsdebug.c
+++ b/sys/src/cmd/ndb/dnsdebug.c
@@ -341,7 +341,7 @@
strncpy(buf, name, sizeof buf);
memset(&req, 0, sizeof req);
- getactivity(&req, 0);
+ getactivity(&req);
req.isslave = 1;
req.aborttime = timems() + Maxreqtm;
rr = dnresolve(buf, Cin, type, &req, nil, 0, Recurse, rooted, nil);
@@ -352,8 +352,7 @@
print("----------------------------\n");
}
rrfreelist(rr);
-
- putactivity(0);
+ putactivity(&req);
}
void
@@ -367,7 +366,7 @@
if(strcmp(f[0], "refresh") == 0){
db2cache(1);
- dnageall(0);
+ dnageall(1);
return;
}
--- a/sys/src/cmd/ndb/dnsgetip.c
+++ b/sys/src/cmd/ndb/dnsgetip.c
@@ -34,7 +34,7 @@
errmsg = nil;
memset(&req, 0, sizeof req);
- getactivity(&req, 0);
+ getactivity(&req);
req.isslave = 1;
req.aborttime = timems() + Maxreqtm;
--- a/sys/src/cmd/ndb/dnstcp.c
+++ b/sys/src/cmd/ndb/dnstcp.c
@@ -97,13 +97,13 @@
alarm(10*1000);
/* loop on requests */
- for(;; putactivity(0)){
+ for(;; putactivity(&req)){
memset(&repmsg, 0, sizeof repmsg);
len = readmsg(0, buf, sizeof buf);
if(len <= 0)
break;
- getactivity(&req, 0);
+ getactivity(&req);
req.aborttime = timems() + 15*Min*1000;
rcode = 0;
memset(&reqmsg, 0, sizeof reqmsg);
@@ -156,7 +156,7 @@
rrfreelist(reqmsg.ar);
if(req.isslave){
- putactivity(0);
+ putactivity(&req);
_exits(0);
}
}
--- a/sys/src/cmd/ndb/dnudpserver.c
+++ b/sys/src/cmd/ndb/dnudpserver.c
@@ -17,7 +17,7 @@
Udphdr uh;
DN *owner;
ushort type;
- int id;
+ ushort id;
};
Inprogress inprog[Maxactive+2];
@@ -162,12 +162,11 @@
}
memset(&req, 0, sizeof req);
- if(setjmp(req.mret))
- putactivity(0);
+ setjmp(req.mret);
req.isslave = 0;
/* loop on requests */
- for(;; putactivity(0)){
+ for(;; putactivity(&req)){
procsetname("%s: udp server %s: served %d", mntpt, addr, served);
memset(&repmsg, 0, sizeof repmsg);
memset(&reqmsg, 0, sizeof reqmsg);
@@ -188,7 +187,7 @@
// dnslog("read received UDP from %I to %I", uh->raddr, uh->laddr);
snprint(ipstr, sizeof(ipstr), "%I", uh->raddr);
- getactivity(&req, 0);
+ getactivity(&req);
req.aborttime = timems() + Maxreqtm;
req.from = ipstr;
@@ -266,7 +265,7 @@
freereq:
freeanswers(&reqmsg);
if(req.isslave){
- putactivity(0);
+ putactivity(&req);
_exits(0);
}
}