From 5ec04b82e16ae2df47bab9d11c9ffc8099c68406 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sat, 27 Feb 2016 15:45:55 +0100 Subject: [PATCH] delegate checking and loading server config files to own thread "readserverconfigsthread" gets triggered by poll_serverthreads() once a minute to check the filesizes of all server config files. Altered files get loaded into memory. poll_serverthreads() in the main thread then processes the buffered files one at a time. During processing, a semaphore is locked, so that other threads can use the data structures derived from config files - if they use the semaphore to lock the ressource. Using the data from main thread can be done without using the semaphore, since _all_ changes are done in the main thread. If the main thread wants to update data that is locked by a thread, the main thread will defer the changes for a minute. The main thread will never lock. --- source/src/protos.h | 11 ++-- source/src/server.cpp | 42 +++++++-------- source/src/serverfiles.h | 107 +++++++++++++++++++++++++-------------- source/src/tools.h | 2 +- 4 files changed, 98 insertions(+), 64 deletions(-) diff --git a/source/src/protos.h b/source/src/protos.h index 2023b18d..7fe06307 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1119,12 +1119,17 @@ struct serverconfigfile string filename; int filelen; char *buf; - serverconfigfile() : filelen(0), buf(NULL) { filename[0] = '\0'; } + sl_semaphore busy; + serverconfigfile() : filelen(0), buf(NULL), busy(1, NULL) { filename[0] = '\0'; } virtual ~serverconfigfile() { DELETEA(buf); } + virtual void clear() {} virtual void read() {} - void init(const char *name); + void init(const char *name, bool tracking = true); bool load(); + bool isbusy(); + void trypreload(); + void process(); }; // server commandline parsing @@ -1268,7 +1273,7 @@ struct servercommandline case 'C': if(*a && clfilenesting < 3) { serverconfigfile cfg; - cfg.init(a); + cfg.init(a, false); cfg.load(); int line = 1; clfilenesting++; diff --git a/source/src/server.cpp b/source/src/server.cpp index bbecd61c..9167b02b 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -108,7 +108,7 @@ int cn2boot; void poll_serverthreads() // called once per mainloop-timeslice { static vector servermapstodelete; - static int stage = 0, lastworkerthreadstart = 0; + static int stage = 0, lastworkerthreadstart = 0, nextserverconfig = 0; switch(stage) { @@ -158,24 +158,28 @@ void poll_serverthreads() // called once per mainloop-timeslice } break; } - - case 2: // wake readconfigsthread -#if 0 + case 2: // wake readserverconfigsthread { - readallconfigfiles = true; - readconfigsthread_sem.post(); + recheckallserverconfigs = true; + nextserverconfig = 0; + readserverconfigsthread_sem->post(); stage++; break; } - - case 3: // readconfigsthread + case 3: // readserverconfigsthread { - stage++; + if(!recheckallserverconfigs) + { + if(serverconfigs.inrange(nextserverconfig)) + { + serverconfigs[nextserverconfig]->process(); // processing of config files is done in mainloop, one at a time + nextserverconfig++; + } + else stage++; + } break; } - case 4: // pause worker threads for a while (restart once a minute) -#endif { if(servmillis - lastworkerthreadstart > 60 * 1000) stage = 0; else if(numclients() == 0) @@ -3842,16 +3846,6 @@ void sendworldstate() if(demorecord) recordpackets = true; // enable after 'old' worldstate is sent } -void rereadcfgs(void) -{ - maprot.read(); - ipblacklist.read(); - geoiplist.read(); - nickblacklist.read(); - forbiddenlist.read(); - passwords.read(); -} - void loggamestatus(const char *reason) { int fragscore[2] = {0, 0}, flagscore[2] = {0, 0}, pnum[2] = {0, 0}; @@ -4049,7 +4043,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(!isdedicated) return; // below is network only - poll_serverthreads(); + poll_serverthreads(); // read config and map files in the background, process the results in the main thread serverms(sg->smode, numclients(), sg->minremain, sg->smapname, servmillis, serverhost->address, &mnum, &msend, &mrec, &cnum, &csend, &crec, SERVER_PROTOCOL_VERSION, sg->servdesc_current, sg->interm); @@ -4065,7 +4059,6 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(servmillis - laststatus > 60 * 1000) // display bandwidth stats, useful for server ops { laststatus = servmillis; - if(!nonlocalclients) rereadcfgs(); // configs are read from actual files - only attempt to do that, when the server is empty if(nonlocalclients || serverhost->totalSentData || serverhost->totalReceivedData) { if(nonlocalclients) loggamestatus(NULL); @@ -4390,6 +4383,9 @@ void initserver(bool dedicated) defformatstring(readmapslogfilename)("%sreadmaps_log.txt", scl.logfilepath); sl_createthread(readmapsthread, (void *)path(readmapslogfilename)); + readserverconfigsthread_sem = new sl_semaphore(0, NULL); + sl_createthread(readserverconfigsthread, NULL); + for(;;) serverslice(5); } } diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index b3b011b9..72b7278b 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -565,11 +565,16 @@ bool serverwritemap(const char *mapname, int mapsize, int cfgsize, int cfgsizegz // server config files -void serverconfigfile::init(const char *name) +vector serverconfigs; // list of server config files, automatically polled in the background +volatile bool recheckallserverconfigs = false; // signal readserverconfigsthread to poll all files +sl_semaphore *readserverconfigsthread_sem = NULL; // sync readmapsthread with main thread + +void serverconfigfile::init(const char *name, bool tracking) { copystring(filename, name); path(filename); - read(); + clear(); + if(tracking) serverconfigs.add(this); } bool serverconfigfile::load() @@ -581,6 +586,7 @@ bool serverconfigfile::load() logline(ACLOG_INFO,"could not read config file '%s'", filename); return false; } + entropy_add_block((const uchar *)buf, filelen); char *p; if('\r' != '\n') // this is not a joke! { @@ -596,6 +602,41 @@ bool serverconfigfile::load() return true; } +bool serverconfigfile::isbusy() +{ + return busy.getvalue() < 1; +} + +void serverconfigfile::trypreload() // in readserverconfigsthread: load changed files into RAM +{ + if(!buf && !isbusy() && getfilesize(filename) != filelen && !load()) buf = newstring(""); +} + +void serverconfigfile::process() // in main thread: refill data structures with preloaded file content +{ + if(buf && !isbusy() && !busy.trywait()) + { + clear(); + if(strlen(buf)) read(); + DELETEA(buf); + busy.post(); + } +} + +int readserverconfigsthread(void *data) +{ + while(1) + { + while(!recheckallserverconfigs) readserverconfigsthread_sem->wait(); // wait without using the cpu + + loopv(serverconfigs) serverconfigs[i]->trypreload(); + + entropy_save(); + recheckallserverconfigs = false; + } + return 0; +} + // maprot.cfg #define CONFIG_MAXPAR 6 @@ -619,12 +660,13 @@ struct servermaprot : serverconfigfile servermaprot() : curcfgset(-1) {} + void clear() + { + configsets.shrink(0); + } + void read() { - if(getfilesize(filename) == filelen) return; - configsets.shrink(0); - if(!load()) return; - const char *sep = ": "; configset c; int i, line = 0; @@ -651,9 +693,7 @@ struct servermaprot : serverconfigfile else logline(ACLOG_INFO," error in line %d, file %s", line, filename); } } - DELETEA(buf); logline(ACLOG_INFO,"read %d map rotation entries from '%s'", configsets.length(), filename); - return; } int next(bool notify = true, bool nochange = false) // load next maprotation set @@ -733,12 +773,13 @@ struct serveripcclist : serverconfigfile return ipr1.ci - ipr2.ci; } + void clear() + { + ipranges.shrink(0); + } + void read() { - if(getfilesize(filename) == filelen) return; - ipranges.shrink(0); - if(!load()) return; - iprangecc ir; int line = 0, errors = 0, concatd = 0; char *l, *r, *p = buf; @@ -760,7 +801,6 @@ struct serveripcclist : serverconfigfile else logline(ACLOG_INFO," error in line %d, file %s: failed to parse '%s'", line, filename, r ? r : l), errors++; } - DELETEA(buf); ipranges.sort(cmpiprange); int orglength = ipranges.length(); loopv(ipranges) @@ -806,13 +846,16 @@ struct serveripcclist : serverconfigfile logline(ACLOG_INFO,"read %d (%d) IP list entries from '%s', %d errors, %d concatenated", ipranges.length(), orglength, filename, errors, concatd); } - const char *check(enet_uint32 ip) // ip: host byte order + const char *check(enet_uint32 ip, bool lock = false) // ip: host byte order { + if(lock) busy.wait(); iprangecc t, *res; t.lr = ip; t.ur = 0; res = ipranges.search(&t, cmpipmatch); - return res ? printcomment(*res) : NULL; + const char *cc = res ? printcomment(*res) : NULL; + if(lock) busy.post(); + return cc; } }; @@ -822,12 +865,13 @@ struct serveripblacklist : serverconfigfile { vector ipranges; + void clear() + { + ipranges.shrink(0); + } + void read() { - if(getfilesize(filename) == filelen) return; - ipranges.shrink(0); - if(!load()) return; - iprange ir; int line = 0, errors = 0; char *l, *r, *p = buf; @@ -847,7 +891,6 @@ struct serveripblacklist : serverconfigfile errors++; } } - DELETEA(buf); ipranges.sort(cmpiprange); int orglength = ipranges.length(); loopv(ipranges) @@ -895,7 +938,7 @@ struct servernickblacklist : serverconfigfile vector blacklines; vector blfraglist; - void destroylists() + void clear() { whitelistranges.setsize(0); enumeratek(whitelist, const char *, key, delete key); @@ -906,10 +949,6 @@ struct servernickblacklist : serverconfigfile void read() { - if(getfilesize(filename) == filelen) return; - destroylists(); - if(!load()) return; - const char *sep = " "; int line = 1, errors = 0; iprchain iprc; @@ -972,7 +1011,6 @@ struct servernickblacklist : serverconfigfile } line++; } - DELETEA(buf); if(logcheck(ACLOG_VERBOSE)) { logline(ACLOG_VERBOSE," nickname whitelist (%d entries):", whitelist.numelems); @@ -1105,7 +1143,7 @@ struct serverforbiddenlist : serverconfigfile int num; char entries[100][2][FORBIDDENSIZE+1]; // 100 entries and 2 words per entry is more than enough - void initlist() + void clear() { num = 0; memset(entries,'\0',2*100*(FORBIDDENSIZE+1)); @@ -1129,10 +1167,6 @@ struct serverforbiddenlist : serverconfigfile void read() { - if(getfilesize(filename) == filelen) return; - initlist(); - if(!load()) return; - char *l, *p = buf; logline(ACLOG_VERBOSE,"reading forbidden list '%s'", filename); while(p < buf + filelen) @@ -1140,7 +1174,6 @@ struct serverforbiddenlist : serverconfigfile l = p; p += strlen(p) + 1; addentry(l); } - DELETEA(buf); } bool canspeech(char *s) @@ -1187,12 +1220,13 @@ struct serverpasswords : serverconfigfile serverconfigfile::init(name); } + void clear() + { + adminpwds.shrink(staticpasses); + } + void read() { - if(getfilesize(filename) == filelen) return; - adminpwds.shrink(staticpasses); - if(!load()) return; - pwddetail c; const char *sep = " "; int i, line = 1, par[ADMINPWD_MAXPAR]; @@ -1221,7 +1255,6 @@ struct serverpasswords : serverconfigfile } line++; } - DELETEA(buf); logline(ACLOG_INFO,"read %d admin passwords from '%s'", adminpwds.length() - staticpasses, filename); } diff --git a/source/src/tools.h b/source/src/tools.h index 446c66f6..b882d5d4 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -1018,7 +1018,7 @@ struct sl_semaphore sl_semaphore(int init, int *errorcount); // init: initial semaphore value; errorcount: pointer to error counter for semaphore-related errors or NULL ~sl_semaphore(); void wait(); // blocks, until semaphore gets available - int trywait(); // returns 0, if semaphore was locked (like wait(), but returns !=0 instead of blocking) + int trywait(); // returns 0, if semaphore was successfully locked (like wait(), but returns !=0 instead of blocking) int timedwait(int howlongmillis); // like trywait(), but waits for a litte before returning failure int getvalue(); // returns current semaphore value void post(); // increments (unlocks) semaphore