Further improve ipv4.games server

This commit is contained in:
Justine Tunney 2022-10-03 15:05:33 -07:00
parent 3b4fcd8575
commit 32321ab1e9
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
9 changed files with 307 additions and 113 deletions

View file

@ -554,6 +554,8 @@ static const uint16_t kPledgeStdio[] = {
__NR_linux_set_robust_list, // __NR_linux_set_robust_list, //
__NR_linux_get_robust_list, // __NR_linux_get_robust_list, //
__NR_linux_prlimit | STDIO, // __NR_linux_prlimit | STDIO, //
__NR_linux_sched_getaffinity, //
__NR_linux_sched_setaffinity, //
}; };
static const uint16_t kPledgeFlock[] = { static const uint16_t kPledgeFlock[] = {
@ -705,8 +707,6 @@ static const uint16_t kPledgeProc[] = {
__NR_linux_sched_setscheduler, // __NR_linux_sched_setscheduler, //
__NR_linux_sched_get_priority_min, // __NR_linux_sched_get_priority_min, //
__NR_linux_sched_get_priority_max, // __NR_linux_sched_get_priority_max, //
__NR_linux_sched_getaffinity, //
__NR_linux_sched_setaffinity, //
__NR_linux_sched_getparam, // __NR_linux_sched_getparam, //
__NR_linux_sched_setparam, // __NR_linux_sched_setparam, //
}; };

View file

@ -19,11 +19,11 @@
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/pledge.internal.h" #include "libc/calls/pledge.internal.h"
#include "libc/calls/state.internal.h" #include "libc/calls/state.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/promises.internal.h" #include "libc/intrin/promises.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
@ -110,7 +110,7 @@
* fcntl(F_SETFD), fcntl(F_GETFL), fcntl(F_SETFL), sched_yield, * fcntl(F_SETFD), fcntl(F_GETFL), fcntl(F_SETFL), sched_yield,
* epoll_create, epoll_create1, epoll_ctl, epoll_wait, epoll_pwait, * epoll_create, epoll_create1, epoll_ctl, epoll_wait, epoll_pwait,
* epoll_pwait2, clone(CLONE_THREAD), futex, set_robust_list, * epoll_pwait2, clone(CLONE_THREAD), futex, set_robust_list,
* get_robust_list, sigpending. * get_robust_list, setaffinity, sigpending.
* *
* - "rpath" (read-only path ops) allows chdir, getcwd, open(O_RDONLY), * - "rpath" (read-only path ops) allows chdir, getcwd, open(O_RDONLY),
* openat(O_RDONLY), stat, fstat, lstat, fstatat, access, faccessat, * openat(O_RDONLY), stat, fstat, lstat, fstatat, access, faccessat,

View file

@ -77,6 +77,6 @@ int sched_getaffinity(int tid, size_t size, cpu_set_t *bitset) {
} }
rc = 0; rc = 0;
} }
STRACE("sched_getaffinity(%d, %'zu, %p) → %d% m", tid, size, bitset); STRACE("sched_getaffinity(%d, %'zu, %p) → %d% m", tid, size, bitset, rc);
return rc; return rc;
} }

View file

@ -85,6 +85,6 @@ int sched_setaffinity(int tid, size_t size, const cpu_set_t *bitset) {
} else { } else {
rc = sys_sched_setaffinity(tid, size, bitset); rc = sys_sched_setaffinity(tid, size, bitset);
} }
STRACE("sched_setaffinity(%d, %'zu, %p) → %d% m", tid, size, bitset); STRACE("sched_setaffinity(%d, %'zu, %p) → %d% m", tid, size, bitset, rc);
return rc; return rc;
} }

View file

@ -52,6 +52,9 @@ enum PosixThreadStatus {
// - kPosixThreadZombie -> _pthread_free() will happen whenever // - kPosixThreadZombie -> _pthread_free() will happen whenever
// convenient, e.g. pthread_create() entry or atexit handler. // convenient, e.g. pthread_create() entry or atexit handler.
kPosixThreadZombie, kPosixThreadZombie,
// special main thread
kPosixThreadMain,
}; };
struct PosixThread { struct PosixThread {

View file

@ -36,7 +36,8 @@
*/ */
wontreturn void pthread_exit(void *rc) { wontreturn void pthread_exit(void *rc) {
struct PosixThread *pt; struct PosixThread *pt;
if ((pt = (struct PosixThread *)__get_tls()->tib_pthread)) { pt = (struct PosixThread *)pthread_self();
if (pt->status != kPosixThreadMain) {
pt->rc = rc; pt->rc = rc;
_gclongjmp(pt->exiter, 1); _gclongjmp(pt->exiter, 1);
} else { } else {

View file

@ -29,7 +29,7 @@
*/ */
int pthread_join(pthread_t thread, void **value_ptr) { int pthread_join(pthread_t thread, void **value_ptr) {
struct PosixThread *pt; struct PosixThread *pt;
if (thread == __get_tls()->tib_pthread) { if (thread == pthread_self()) {
return EDEADLK; return EDEADLK;
} }
if (!(pt = (struct PosixThread *)thread) || // if (!(pt = (struct PosixThread *)thread) || //

View file

@ -16,6 +16,8 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/calls.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/tls.h" #include "libc/thread/tls.h"
@ -25,3 +27,10 @@
pthread_t pthread_self(void) { pthread_t pthread_self(void) {
return __get_tls()->tib_pthread; return __get_tls()->tib_pthread;
} }
static struct PosixThread pthread_main;
__attribute__((__constructor__)) static void pthread_self_init(void) {
pthread_main.tid = gettid();
pthread_main.status = kPosixThreadMain;
__get_tls()->tib_pthread = (pthread_t)&pthread_main;
}

View file

@ -16,6 +16,7 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/pledge.h" #include "libc/calls/pledge.h"
#include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.h"
@ -37,6 +38,8 @@
#include "libc/nexgen32e/crc32.h" #include "libc/nexgen32e/crc32.h"
#include "libc/runtime/internal.h" #include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/runtime/sysconf.h"
#include "libc/sock/sock.h" #include "libc/sock/sock.h"
#include "libc/sock/struct/pollfd.h" #include "libc/sock/struct/pollfd.h"
#include "libc/sock/struct/sockaddr.h" #include "libc/sock/struct/sockaddr.h"
@ -48,12 +51,14 @@
#include "libc/sysv/consts/clock.h" #include "libc/sysv/consts/clock.h"
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h" #include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/so.h" #include "libc/sysv/consts/so.h"
#include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/sol.h" #include "libc/sysv/consts/sol.h"
#include "libc/sysv/consts/tcp.h" #include "libc/sysv/consts/tcp.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/thread2.h"
#include "libc/time/struct/tm.h" #include "libc/time/struct/tm.h"
#include "libc/x/x.h" #include "libc/x/x.h"
#include "libc/x/xasprintf.h" #include "libc/x/xasprintf.h"
@ -75,16 +80,19 @@
*/ */
#define PORT 8080 #define PORT 8080
#define WORKERS 2000 #define WORKERS 9001
#define HEARTBEAT 2000 #define HEARTBEAT 2000
#define KEEPALIVE_MS 2000 #define KEEPALIVE_MS 1000
#define DATE_UPDATE_MS 500
#define POLL_ASSETS_MS 250 #define POLL_ASSETS_MS 250
#define BOARD_GENERATE_MS 10000 #define DATE_UPDATE_MS 500
#define CLAIM_DEADLINE_MS 1000 #define SCORE_UPDATE_MS 15000
#define CLAIM_MAX 200 #define CLAIM_DEADLINE_MS 100
#define QUEUE_MAX 800
#define BATCH_MAX 64
#define NICK_MAX 40 #define NICK_MAX 40
#define MSG_MAX 10 #define MSG_MAX 10
#define INBUF_SIZE PAGESIZE
#define OUTBUF_SIZE PAGESIZE
#define GETOPTS "dvp:w:k:" #define GETOPTS "dvp:w:k:"
#define USAGE \ #define USAGE \
@ -111,6 +119,9 @@ Usage: turfwar.com [-dv] ARGS...\n\
SlicesEqualCase(S, strlen(S), HeaderData(H), HeaderLength(H)) SlicesEqualCase(S, strlen(S), HeaderData(H), HeaderLength(H))
#define UrlEqual(S) \ #define UrlEqual(S) \
SlicesEqual(inbuf + msg->uri.a, msg->uri.b - msg->uri.a, S, strlen(S)) SlicesEqual(inbuf + msg->uri.a, msg->uri.b - msg->uri.a, S, strlen(S))
#define UrlStartsWith(S) \
(msg->uri.b - msg->uri.a >= strlen(S) && \
!memcmp(inbuf + msg->uri.a, S, strlen(S)))
#if 1 #if 1
#define LOG(...) kprintf(__VA_ARGS__) #define LOG(...) kprintf(__VA_ARGS__)
@ -169,6 +180,7 @@ struct Asset {
char *path; char *path;
nsync_mu lock; nsync_mu lock;
const char *type; const char *type;
const char *cache;
struct Data data; struct Data data;
struct Data gzip; struct Data gzip;
struct timespec mtim; struct timespec mtim;
@ -180,20 +192,26 @@ int g_port = PORT;
int g_workers = WORKERS; int g_workers = WORKERS;
int g_keepalive = KEEPALIVE_MS; int g_keepalive = KEEPALIVE_MS;
struct tm g_nowish;
nsync_note g_shutdown; nsync_note g_shutdown;
nsync_mu g_nowish_lock;
struct Board { struct Recent {
nsync_mu mu; nsync_mu mu;
nsync_cv cv; nsync_cv cv;
} g_board; } g_recent;
struct Nowish {
nsync_mu lock;
struct timespec ts;
struct tm tm;
} g_nowish;
struct Assets { struct Assets {
struct Asset index; struct Asset index;
struct Asset about; struct Asset about;
struct Asset user; struct Asset user;
struct Asset board; struct Asset score;
struct Asset recent;
struct Asset favicon;
} g_asset; } g_asset;
struct Claims { struct Claims {
@ -204,8 +222,9 @@ struct Claims {
nsync_cv non_empty; nsync_cv non_empty;
struct Claim { struct Claim {
uint32_t ip; uint32_t ip;
int64_t created;
char name[NICK_MAX + 1]; char name[NICK_MAX + 1];
} data[CLAIM_MAX]; } data[QUEUE_MAX];
} g_claims; } g_claims;
bool CheckSys(const char *file, int line, long rc) { bool CheckSys(const char *file, int line, long rc) {
@ -229,6 +248,8 @@ bool CheckDb(const char *file, int line, int rc, sqlite3 *db) {
bool IsValidNick(const char *s, size_t n) { bool IsValidNick(const char *s, size_t n) {
size_t i; size_t i;
if (n == -1) n = strlen(s);
if (!n) return false;
if (n > NICK_MAX) return false; if (n > NICK_MAX) return false;
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
if (!(isalnum(s[i]) || // if (!(isalnum(s[i]) || //
@ -236,7 +257,10 @@ bool IsValidNick(const char *s, size_t n) {
s[i] == '/' || // s[i] == '/' || //
s[i] == ':' || // s[i] == ':' || //
s[i] == '.' || // s[i] == '.' || //
s[i] == '^' || //
s[i] == '+' || // s[i] == '+' || //
s[i] == '!' || //
s[i] == '-' || //
s[i] == '_' || // s[i] == '_' || //
s[i] == '*')) { s[i] == '*')) {
return false; return false;
@ -255,22 +279,21 @@ char *FormatUnixHttpDateTime(char *s, int64_t t) {
void UpdateNow(void) { void UpdateNow(void) {
int64_t secs; int64_t secs;
struct tm tm; struct tm tm;
struct timespec ts; clock_gettime(CLOCK_REALTIME, &g_nowish.ts);
clock_gettime(CLOCK_REALTIME, &ts); secs = g_nowish.ts.tv_sec;
secs = ts.tv_sec;
gmtime_r(&secs, &tm); gmtime_r(&secs, &tm);
//!//!//!//!//!//!//!//!//!//!//!//!//!/ //!//!//!//!//!//!//!//!//!//!//!//!//!/
nsync_mu_lock(&g_nowish_lock); nsync_mu_lock(&g_nowish.lock);
g_nowish = tm; g_nowish.tm = tm;
nsync_mu_unlock(&g_nowish_lock); nsync_mu_unlock(&g_nowish.lock);
//!//!//!//!//!//!//!//!//!//!//!//!//!/ //!//!//!//!//!//!//!//!//!//!//!//!//!/
} }
char *FormatDate(char *p) { char *FormatDate(char *p) {
//////////////////////////////////////// ////////////////////////////////////////
nsync_mu_rlock(&g_nowish_lock); nsync_mu_rlock(&g_nowish.lock);
p = FormatHttpDateTime(p, &g_nowish); p = FormatHttpDateTime(p, &g_nowish.tm);
nsync_mu_runlock(&g_nowish_lock); nsync_mu_runlock(&g_nowish.lock);
//////////////////////////////////////// ////////////////////////////////////////
return p; return p;
} }
@ -345,13 +368,57 @@ static bool GetNick(char *inbuf, struct HttpMessage *msg, struct Claim *v) {
return found; return found;
} }
void *NewSafeBuffer(size_t n) {
char *p;
size_t m = ROUNDUP(n, PAGESIZE);
_npassert((p = valloc(m + PAGESIZE)));
_npassert(!mprotect(p + m, PAGESIZE, PROT_NONE));
return p;
}
void FreeSafeBuffer(void *p) {
size_t n = malloc_usable_size(p);
size_t m = ROUNDDOWN(n, PAGESIZE);
_npassert(!mprotect(p, m, PROT_READ | PROT_WRITE));
free(p);
}
void OnlyRunOnCpu(int i) {
cpu_set_t cpus;
if (GetCpuCount() > i + 1) {
CPU_ZERO(&cpus);
CPU_SET(i, &cpus);
CHECK_EQ(0, pthread_setaffinity_np(pthread_self(), sizeof(cpus), &cpus));
}
}
void DontRunOnFirstCpus(int i) {
int n;
cpu_set_t cpus;
if ((n = GetCpuCount()) > 1) {
CPU_ZERO(&cpus);
for (; i < n; ++i) {
CPU_SET(i, &cpus);
}
CHECK_EQ(0, pthread_setaffinity_np(pthread_self(), sizeof(cpus), &cpus));
} else {
notpossible;
}
}
// thousands of threads for handling client connections // thousands of threads for handling client connections
void *HttpWorker(void *arg) { void *HttpWorker(void *arg) {
int server; int server;
int yes = 1; int yes = 1;
char name[16];
int id = (intptr_t)arg; int id = (intptr_t)arg;
struct HttpMessage *msg; char *inbuf = NewSafeBuffer(INBUF_SIZE);
char *outbuf = NewSafeBuffer(OUTBUF_SIZE);
struct HttpMessage *msg = _gc(xmalloc(sizeof(struct HttpMessage)));
STRACE("HttpWorker #%d started", id); STRACE("HttpWorker #%d started", id);
DontRunOnFirstCpus(2);
ksnprintf(name, sizeof(name), "HTTP #%d", id);
pthread_setname_np(pthread_self(), name);
// load balance incoming connections for port 8080 across all threads // load balance incoming connections for port 8080 across all threads
// hangup on any browser clients that lag for more than a few seconds // hangup on any browser clients that lag for more than a few seconds
@ -369,7 +436,6 @@ void *HttpWorker(void *arg) {
CHECK_NE(-1, bind(server, &addr, sizeof(addr))); CHECK_NE(-1, bind(server, &addr, sizeof(addr)));
CHECK_NE(-1, listen(server, 1)); CHECK_NE(-1, listen(server, 1));
msg = _gc(xmalloc(sizeof(struct HttpMessage)));
// connection loop // connection loop
while (!nsync_note_is_notified(g_shutdown)) { while (!nsync_note_is_notified(g_shutdown)) {
@ -381,10 +447,10 @@ void *HttpWorker(void *arg) {
ssize_t got, sent; ssize_t got, sent;
struct iovec iov[2]; struct iovec iov[2];
uint32_t ip, clientip; uint32_t ip, clientip;
char ipbuf[32], *p, *q;
uint32_t clientaddrsize; uint32_t clientaddrsize;
struct sockaddr_in clientaddr; struct sockaddr_in clientaddr;
int client, inmsglen, outmsglen; int client, inmsglen, outmsglen;
char inbuf[1500], outbuf[512], ipbuf[32], *p, *q;
// this slows the server down a lot but is needed on non-Linux to // this slows the server down a lot but is needed on non-Linux to
// react to keyboard ctrl-c // react to keyboard ctrl-c
@ -403,7 +469,7 @@ void *HttpWorker(void *arg) {
msgcount = 0; msgcount = 0;
do { do {
InitHttpMessage(msg, kHttpRequest); InitHttpMessage(msg, kHttpRequest);
if ((got = read(client, inbuf, sizeof(inbuf))) <= 0) break; if ((got = read(client, inbuf, INBUF_SIZE)) <= 0) break;
if ((inmsglen = ParseHttpMessage(msg, inbuf, got)) <= 0) break; if ((inmsglen = ParseHttpMessage(msg, inbuf, got)) <= 0) break;
if (msg->version != 11) break; // cloudflare won't send 0.9 or 1.0 if (msg->version != 11) break; // cloudflare won't send 0.9 or 1.0
@ -418,14 +484,18 @@ void *HttpWorker(void *arg) {
ksnprintf(ipbuf, sizeof(ipbuf), "%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16, ksnprintf(ipbuf, sizeof(ipbuf), "%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16,
ip >> 8, ip); ip >> 8, ip);
if (UrlEqual("/") || UrlEqual("/index.html")) { if (UrlEqual("/") || UrlStartsWith("/index.html")) {
a = &g_asset.index; a = &g_asset.index;
} else if (UrlEqual("/about.html")) { } else if (UrlStartsWith("/favicon.ico")) {
a = &g_asset.favicon;
} else if (UrlStartsWith("/about.html")) {
a = &g_asset.about; a = &g_asset.about;
} else if (UrlEqual("/user.html")) { } else if (UrlStartsWith("/user.html")) {
a = &g_asset.user; a = &g_asset.user;
} else if (UrlEqual("/board")) { } else if (UrlStartsWith("/score")) {
a = &g_asset.board; a = &g_asset.score;
} else if (UrlStartsWith("/recent")) {
a = &g_asset.recent;
} else { } else {
a = 0; a = 0;
} }
@ -433,7 +503,6 @@ void *HttpWorker(void *arg) {
if (a) { if (a) {
comp = HeaderHas(msg, inbuf, kHttpAcceptEncoding, "gzip", 4); comp = HeaderHas(msg, inbuf, kHttpAcceptEncoding, "gzip", 4);
p = stpcpy(outbuf, "HTTP/1.1 200 OK\r\n" STANDARD_RESPONSE_HEADERS p = stpcpy(outbuf, "HTTP/1.1 200 OK\r\n" STANDARD_RESPONSE_HEADERS
"Cache-Control: max-age=60, must-revalidate\r\n"
"Vary: Accept-Encoding\r\n" "Vary: Accept-Encoding\r\n"
"Date: "); "Date: ");
p = FormatDate(p); p = FormatDate(p);
@ -443,6 +512,8 @@ void *HttpWorker(void *arg) {
p = stpcpy(p, a->lastmod); p = stpcpy(p, a->lastmod);
p = stpcpy(p, "\r\nContent-Type: "); p = stpcpy(p, "\r\nContent-Type: ");
p = stpcpy(p, a->type); p = stpcpy(p, a->type);
p = stpcpy(p, "\r\nCache-Control: ");
p = stpcpy(p, a->cache);
if (comp) p = stpcpy(p, "\r\nContent-Encoding: gzip"); if (comp) p = stpcpy(p, "\r\nContent-Encoding: gzip");
p = stpcpy(p, "\r\nContent-Length: "); p = stpcpy(p, "\r\nContent-Length: ");
d = comp ? a->gzip : a->data; d = comp ? a->gzip : a->data;
@ -457,11 +528,11 @@ void *HttpWorker(void *arg) {
nsync_mu_runlock(&a->lock); nsync_mu_runlock(&a->lock);
//////////////////////////////////////// ////////////////////////////////////////
} else if (UrlEqual("/ip")) { } else if (UrlStartsWith("/ip")) {
if (!ipv6) { if (!ipv6) {
p = stpcpy(outbuf, "HTTP/1.1 200 OK\r\n" STANDARD_RESPONSE_HEADERS p = stpcpy(outbuf, "HTTP/1.1 200 OK\r\n" STANDARD_RESPONSE_HEADERS
"Content-Type: text/plain\r\n" "Content-Type: text/plain\r\n"
"Cache-Control: private\r\n" "Cache-Control: max-age=3600, private\r\n"
"Date: "); "Date: ");
p = FormatDate(p); p = FormatDate(p);
p = stpcpy(p, "\r\nContent-Length: "); p = stpcpy(p, "\r\nContent-Length: ");
@ -492,10 +563,9 @@ void *HttpWorker(void *arg) {
break; break;
} }
} else if (msg->uri.b - msg->uri.a > 6 && } else if (UrlStartsWith("/claim")) {
!memcmp(inbuf + msg->uri.a, "/claim", 6)) {
if (ipv6) goto Ipv6Warning; if (ipv6) goto Ipv6Warning;
struct Claim v = {.ip = ip}; struct Claim v = {.ip = ip, .created = g_nowish.ts.tv_sec};
if (GetNick(inbuf, msg, &v)) { if (GetNick(inbuf, msg, &v)) {
if (AddClaim( if (AddClaim(
&g_claims, &v, &g_claims, &v,
@ -561,7 +631,8 @@ void *HttpWorker(void *arg) {
} }
} else { } else {
LOG("%s: 400 not found\n", ipbuf); LOG("%s: 400 not found %#.*s\n", ipbuf, msg->uri.b - msg->uri.a,
inbuf + msg->uri.a);
q = "<!doctype html>\r\n" q = "<!doctype html>\r\n"
"<title>404 not found</title>\r\n" "<title>404 not found</title>\r\n"
"<h1>404 not found</h1>\r\n"; "<h1>404 not found</h1>\r\n";
@ -594,6 +665,8 @@ void *HttpWorker(void *arg) {
} }
STRACE("HttpWorker #%d exiting", id); STRACE("HttpWorker #%d exiting", id);
FreeSafeBuffer(outbuf);
FreeSafeBuffer(inbuf);
close(server); close(server);
return 0; return 0;
} }
@ -631,6 +704,7 @@ struct Asset LoadAsset(const char *path, const char *type) {
CHECK_EQ(0, stat(path, &st)); CHECK_EQ(0, stat(path, &st));
CHECK_NOTNULL((a.data.p = xslurp(path, &a.data.n))); CHECK_NOTNULL((a.data.p = xslurp(path, &a.data.n)));
a.type = type; a.type = type;
a.cache = "max-age=3600, must-revalidate";
a.path = xstrdup(path); a.path = xstrdup(path);
a.mtim = st.st_mtim; a.mtim = st.st_mtim;
a.gzip = Gzip(a.data); a.gzip = Gzip(a.data);
@ -651,6 +725,7 @@ bool ReloadAsset(struct Asset *a) {
if (_timespec_gt(st.st_mtim, a->mtim) && (data.p = malloc(st.st_size))) { if (_timespec_gt(st.st_mtim, a->mtim) && (data.p = malloc(st.st_size))) {
FormatUnixHttpDateTime(lastmod, st.st_mtim.tv_sec); FormatUnixHttpDateTime(lastmod, st.st_mtim.tv_sec);
CHECK_SYS((rc = read(fd, data.p, st.st_size))); CHECK_SYS((rc = read(fd, data.p, st.st_size)));
data.n = st.st_size;
if (rc != st.st_size) goto OnError; if (rc != st.st_size) goto OnError;
gzip = Gzip(data); gzip = Gzip(data);
//!//!//!//!//!//!//!//!//!//!//!//!//!/ //!//!//!//!//!//!//!//!//!//!//!//!//!/
@ -680,6 +755,10 @@ void FreeAsset(struct Asset *a) {
free(a->gzip.p); free(a->gzip.p);
} }
void OnCtrlC(int sig) {
nsync_note_notify(g_shutdown);
}
static void GetOpts(int argc, char *argv[]) { static void GetOpts(int argc, char *argv[]) {
int opt; int opt;
while ((opt = getopt(argc, argv, GETOPTS)) != -1) { while ((opt = getopt(argc, argv, GETOPTS)) != -1) {
@ -709,97 +788,182 @@ static void GetOpts(int argc, char *argv[]) {
} }
} }
void OnCtrlC(int sig) { void Update(struct Asset *a, bool gen(struct Asset *)) {
nsync_note_notify(g_shutdown); void *f[2];
struct Asset t;
if (gen(&t)) {
//!//!//!//!//!//!//!//!//!//!//!//!//!/
nsync_mu_lock(&a->lock);
f[0] = a->data.p;
f[1] = a->gzip.p;
a->data = t.data;
a->gzip = t.gzip;
a->mtim = t.mtim;
memcpy(a->lastmod, t.lastmod, 32);
nsync_mu_unlock(&a->lock);
//!//!//!//!//!//!//!//!//!//!//!//!//!/
free(f[0]);
free(f[1]);
}
} }
bool GenerateBoard(struct Asset *out) { bool GenerateScore(struct Asset *out) {
int rc;
char *sb = 0; char *sb = 0;
sqlite3 *db = 0; sqlite3 *db = 0;
size_t sblen = 0; size_t sblen = 0;
struct Asset a = {0}; struct Asset a = {0};
sqlite3_stmt *stmt = 0; sqlite3_stmt *stmt = 0;
DEBUG("GenerateBoard\n"); bool namestate = false;
char name1[NICK_MAX + 1] = {0};
char name2[NICK_MAX + 1];
DEBUG("GenerateScore\n");
a.type = "application/json"; a.type = "application/json";
a.cache = "max-age=60, must-revalidate";
CHECK_SYS(clock_gettime(CLOCK_REALTIME, &a.mtim)); CHECK_SYS(clock_gettime(CLOCK_REALTIME, &a.mtim));
FormatUnixHttpDateTime(a.lastmod, a.mtim.tv_sec); FormatUnixHttpDateTime(a.lastmod, a.mtim.tv_sec);
CHECK_SYS(appends(&a.data.p, "{\"leaders\":[\n")); CHECK_SYS(appends(&a.data.p, "{\n"));
CHECK_SYS(appendf(&a.data.p, "\"now\":[%ld,%ld],\n", a.mtim.tv_sec,
a.mtim.tv_nsec));
CHECK_SYS(appends(&a.data.p, "\"score\":{\n"));
CHECK_SQL(sqlite3_open("db.sqlite3", &db)); CHECK_SQL(sqlite3_open("db.sqlite3", &db));
CHECK_SQL(sqlite3_exec(db, "PRAGMA journal_mode=WAL", 0, 0, 0)); CHECK_SQL(sqlite3_exec(db, "PRAGMA journal_mode=WAL", 0, 0, 0));
CHECK_SQL(sqlite3_exec(db, "PRAGMA synchronous=NORMAL", 0, 0, 0)); CHECK_SQL(sqlite3_exec(db, "PRAGMA synchronous=NORMAL", 0, 0, 0));
CHECK_DB(sqlite3_prepare_v2(db, CHECK_DB(sqlite3_prepare_v2(db,
"SELECT nick AS name, COUNT(ip) AS count\n" "SELECT nick, (ip >> 24), COUNT(*)\n"
"FROM land\n" "FROM land\n"
"WHERE ip >= ?1 AND ip <= ?2\n" "GROUP BY nick, (ip >> 24)",
"GROUP BY nick\n"
"ORDER BY count DESC\n"
"LIMIT 1",
-1, &stmt, 0)); -1, &stmt, 0));
CHECK_SQL(sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0)); CHECK_SQL(sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0));
for (long i = 0; i < 256; ++i) { while ((rc = sqlite3_step(stmt)) != SQLITE_DONE) {
if (i) CHECK_SYS(appends(&a.data.p, ",\n")); if (rc != SQLITE_ROW) CHECK_SQL(rc);
CHECK_DB(sqlite3_bind_int64(stmt, 1, i * 0x1000000)); strlcpy(name2, (void *)sqlite3_column_text(stmt, 0), sizeof(name2));
CHECK_DB(sqlite3_bind_int64(stmt, 2, i * 0x1000000 + 0xFFFFFF)); if (!IsValidNick(name2, -1)) continue;
switch (sqlite3_step(stmt)) { if (strcmp(name1, name2)) {
case SQLITE_ROW: // name changed
CHECK_SYS(appendf( if (namestate) CHECK_SYS(appends(&a.data.p, "],\n"));
&a.data.p, "{\"name\":\"%s\",\"count\":%ld}", namestate = true;
EscapeJsStringLiteral(&sb, &sblen, CHECK_SYS(appendf(
(void *)sqlite3_column_text(stmt, 0), -1, 0), &a.data.p, "\"%s\":[\n",
sqlite3_column_int64(stmt, 1))); EscapeJsStringLiteral(&sb, &sblen, strcpy(name1, name2), -1, 0)));
break; } else {
case SQLITE_DONE: // name repeated
CHECK_SYS(appends(&a.data.p, "false")); CHECK_SYS(appends(&a.data.p, ",\n"));
break;
default:
kprintf("%s\n", sqlite3_errmsg(db));
abort();
} }
CHECK_DB(sqlite3_reset(stmt)); CHECK_SYS(appendf(&a.data.p, " [%ld,%ld]", sqlite3_column_int64(stmt, 1),
sqlite3_column_int64(stmt, 2)));
} }
CHECK_SQL(sqlite3_exec(db, "END TRANSACTION", 0, 0, 0)); CHECK_SQL(sqlite3_exec(db, "END TRANSACTION", 0, 0, 0));
CHECK_SYS(appends(&a.data.p, "\n]}\n")); if (namestate) CHECK_SYS(appends(&a.data.p, "]\n"));
CHECK_SYS(appends(&a.data.p, "}}\n"));
CHECK_DB(sqlite3_finalize(stmt)); CHECK_DB(sqlite3_finalize(stmt));
CHECK_SQL(sqlite3_close(db)); CHECK_SQL(sqlite3_close(db));
a.data.n = appendz(a.data.p).i; a.data.n = appendz(a.data.p).i;
a.gzip = Gzip(a.data); a.gzip = Gzip(a.data);
free(sb);
*out = a; *out = a;
return true; return true;
OnError: OnError:
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
sqlite3_close(db); sqlite3_close(db);
free(a.data.p); free(a.data.p);
free(sb);
return false; return false;
} }
// single thread for regenerating the leaderboard json // single thread for regenerating the user scores json
void *BoardWorker(void *arg) { void *ScoreWorker(void *arg) {
int rc; nsync_time deadline;
void *f[2]; LOG("ScoreWorker started\n");
struct Asset a; OnlyRunOnCpu(0);
LOG("BoardWorker started\n"); pthread_setname_np(pthread_self(), "ScoreWorker");
for (;;) { for (deadline = _timespec_real();;) {
nsync_mu_lock(&g_board.mu); deadline = _timespec_add(deadline, _timespec_frommillis(SCORE_UPDATE_MS));
rc = nsync_cv_wait_with_deadline(&g_board.cv, &g_board.mu, if (!nsync_note_wait(g_shutdown, deadline)) {
nsync_time_no_deadline, g_shutdown); Update(&g_asset.score, GenerateScore);
nsync_mu_unlock(&g_board.mu); } else {
if (rc == ECANCELED) break; break;
if (GenerateBoard(&a)) {
//!//!//!//!//!//!//!//!//!//!//!//!//!/
nsync_mu_lock(&g_asset.board.lock);
f[0] = g_asset.board.data.p;
f[1] = g_asset.board.gzip.p;
g_asset.board.data = a.data;
g_asset.board.gzip = a.gzip;
g_asset.board.mtim = a.mtim;
memcpy(g_asset.board.lastmod, a.lastmod, 32);
nsync_mu_unlock(&g_asset.board.lock);
//!//!//!//!//!//!//!//!//!//!//!//!//!/
free(f[0]);
free(f[1]);
} }
} }
LOG("BoardWorker exiting\n"); LOG("ScoreWorker exiting\n");
return 0;
}
bool GenerateRecent(struct Asset *out) {
int rc;
char *sb = 0;
sqlite3 *db = 0;
size_t sblen = 0;
bool once = false;
struct Asset a = {0};
sqlite3_stmt *stmt = 0;
DEBUG("GenerateRecent\n");
OnlyRunOnCpu(0);
pthread_setname_np(pthread_self(), "GenerateRecent");
a.type = "application/json";
a.cache = "max-age=0, must-revalidate";
CHECK_SYS(clock_gettime(CLOCK_REALTIME, &a.mtim));
FormatUnixHttpDateTime(a.lastmod, a.mtim.tv_sec);
CHECK_SYS(appends(&a.data.p, "{\n"));
CHECK_SYS(appendf(&a.data.p, "\"now\":[%ld,%ld],\n", a.mtim.tv_sec,
a.mtim.tv_nsec));
CHECK_SYS(appends(&a.data.p, "\"recent\":[\n"));
CHECK_SQL(sqlite3_open("db.sqlite3", &db));
CHECK_SQL(sqlite3_exec(db, "PRAGMA journal_mode=WAL", 0, 0, 0));
CHECK_SQL(sqlite3_exec(db, "PRAGMA synchronous=NORMAL", 0, 0, 0));
CHECK_DB(sqlite3_prepare_v2(db,
"SELECT ip, nick, created\n"
"FROM land\n"
"WHERE created NOT NULL\n"
"ORDER BY created DESC\n"
"LIMIT 50",
-1, &stmt, 0));
CHECK_SQL(sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0));
while ((rc = sqlite3_step(stmt)) != SQLITE_DONE) {
if (rc != SQLITE_ROW) CHECK_SQL(rc);
if (once) {
CHECK_SYS(appends(&a.data.p, ",\n"));
} else {
once = true;
}
CHECK_SYS(
appendf(&a.data.p, "[%ld,\"%s\",%ld]", sqlite3_column_int64(stmt, 0),
EscapeJsStringLiteral(
&sb, &sblen, (void *)sqlite3_column_text(stmt, 1), -1, 0),
sqlite3_column_int64(stmt, 2)));
}
CHECK_SQL(sqlite3_exec(db, "END TRANSACTION", 0, 0, 0));
CHECK_SYS(appends(&a.data.p, "]}\n"));
CHECK_DB(sqlite3_finalize(stmt));
CHECK_SQL(sqlite3_close(db));
a.data.n = appendz(a.data.p).i;
a.gzip = Gzip(a.data);
free(sb);
*out = a;
return true;
OnError:
sqlite3_finalize(stmt);
sqlite3_close(db);
free(a.data.p);
free(sb);
return false;
}
// thread for realtime json generation most recent successful claims
void *RecentWorker(void *arg) {
int rc;
OnlyRunOnCpu(1);
pthread_setname_np(pthread_self(), "RecentWorker");
LOG("RecentWorker started\n");
for (;;) {
nsync_mu_lock(&g_recent.mu);
rc = nsync_cv_wait_with_deadline(&g_recent.cv, &g_recent.mu,
nsync_time_no_deadline, g_shutdown);
nsync_mu_unlock(&g_recent.mu);
if (rc == ECANCELED) break;
Update(&g_asset.recent, GenerateRecent);
}
LOG("RecentWorker exiting\n");
return 0; return 0;
} }
@ -807,31 +971,36 @@ void *BoardWorker(void *arg) {
void *ClaimWorker(void *arg) { void *ClaimWorker(void *arg) {
int i, n, rc; int i, n, rc;
sqlite3 *db = 0; sqlite3 *db = 0;
struct Claim v[32];
sqlite3_stmt *stmt = 0; sqlite3_stmt *stmt = 0;
struct Claim *v = _gc(xcalloc(BATCH_MAX, sizeof(struct Claim)));
OnlyRunOnCpu(0);
pthread_setname_np(pthread_self(), "ClaimWorker");
StartOver: StartOver:
CHECK_SQL(sqlite3_open("db.sqlite3", &db)); CHECK_SQL(sqlite3_open("db.sqlite3", &db));
CHECK_SQL(sqlite3_exec(db, "PRAGMA journal_mode=WAL", 0, 0, 0)); CHECK_SQL(sqlite3_exec(db, "PRAGMA journal_mode=WAL", 0, 0, 0));
CHECK_SQL(sqlite3_exec(db, "PRAGMA synchronous=NORMAL", 0, 0, 0)); CHECK_SQL(sqlite3_exec(db, "PRAGMA synchronous=NORMAL", 0, 0, 0));
CHECK_DB(sqlite3_prepare_v2(db, CHECK_DB(sqlite3_prepare_v2(db,
"INSERT INTO land (ip, nick) VALUES (?1, ?2)\n" "INSERT INTO land (ip, nick, created)\n"
"ON CONFLICT (ip) DO UPDATE SET (nick) = (?2)\n" "VALUES (?1, ?2, ?3)\n"
"ON CONFLICT (ip) DO\n"
"UPDATE SET (nick, created) = (?2, ?3)\n"
"WHERE nick != ?2", "WHERE nick != ?2",
-1, &stmt, 0)); -1, &stmt, 0));
LOG("ClaimWorker started\n"); LOG("ClaimWorker started\n");
while ((n = GetClaims(&g_claims, v, ARRAYLEN(v), nsync_time_no_deadline))) { while ((n = GetClaims(&g_claims, v, BATCH_MAX, nsync_time_no_deadline))) {
CHECK_SQL(sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0)); CHECK_SQL(sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0));
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
CHECK_DB(sqlite3_bind_int64(stmt, 1, v[i].ip)); CHECK_DB(sqlite3_bind_int64(stmt, 1, v[i].ip));
CHECK_DB(sqlite3_bind_text(stmt, 2, v[i].name, -1, SQLITE_TRANSIENT)); CHECK_DB(sqlite3_bind_text(stmt, 2, v[i].name, -1, SQLITE_TRANSIENT));
CHECK_DB(sqlite3_bind_int64(stmt, 3, v[i].created));
CHECK_DB((rc = sqlite3_step(stmt)) == SQLITE_DONE ? SQLITE_OK : rc); CHECK_DB((rc = sqlite3_step(stmt)) == SQLITE_DONE ? SQLITE_OK : rc);
CHECK_DB(sqlite3_reset(stmt)); CHECK_DB(sqlite3_reset(stmt));
} }
CHECK_SQL(sqlite3_exec(db, "COMMIT TRANSACTION", 0, 0, 0)); CHECK_SQL(sqlite3_exec(db, "COMMIT TRANSACTION", 0, 0, 0));
DEBUG("Committed %d claims\n", n); DEBUG("Committed %d claims\n", n);
nsync_mu_lock(&g_board.mu); nsync_mu_lock(&g_recent.mu);
nsync_cv_signal(&g_board.cv); nsync_cv_signal(&g_recent.cv);
nsync_mu_unlock(&g_board.mu); nsync_mu_unlock(&g_recent.mu);
} }
CHECK_DB(sqlite3_finalize(stmt)); CHECK_DB(sqlite3_finalize(stmt));
CHECK_SQL(sqlite3_close(db)); CHECK_SQL(sqlite3_close(db));
@ -848,6 +1017,8 @@ OnError:
// single thread for computing HTTP Date header // single thread for computing HTTP Date header
void *NowWorker(void *arg) { void *NowWorker(void *arg) {
nsync_time deadline; nsync_time deadline;
OnlyRunOnCpu(0);
pthread_setname_np(pthread_self(), "NowWorker");
for (deadline = _timespec_real();;) { for (deadline = _timespec_real();;) {
deadline = _timespec_add(deadline, _timespec_frommillis(DATE_UPDATE_MS)); deadline = _timespec_add(deadline, _timespec_frommillis(DATE_UPDATE_MS));
if (!nsync_note_wait(g_shutdown, deadline)) { if (!nsync_note_wait(g_shutdown, deadline)) {
@ -862,6 +1033,8 @@ void *NowWorker(void *arg) {
// single thread for monitoring assets on disk // single thread for monitoring assets on disk
void *AssetWorker(void *arg) { void *AssetWorker(void *arg) {
nsync_time deadline; nsync_time deadline;
OnlyRunOnCpu(0);
pthread_setname_np(pthread_self(), "AssetWorker");
for (deadline = _timespec_real();;) { for (deadline = _timespec_real();;) {
deadline = _timespec_add(deadline, _timespec_frommillis(POLL_ASSETS_MS)); deadline = _timespec_add(deadline, _timespec_frommillis(POLL_ASSETS_MS));
if (!nsync_note_wait(g_shutdown, deadline)) { if (!nsync_note_wait(g_shutdown, deadline)) {
@ -876,7 +1049,7 @@ void *AssetWorker(void *arg) {
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
ShowCrashReports(); // ShowCrashReports();
GetOpts(argc, argv); GetOpts(argc, argv);
__enable_threads(); __enable_threads();
@ -888,6 +1061,7 @@ int main(int argc, char *argv[]) {
g_asset.index = LoadAsset("index.html", "text/html; charset=utf-8"); g_asset.index = LoadAsset("index.html", "text/html; charset=utf-8");
g_asset.about = LoadAsset("about.html", "text/html; charset=utf-8"); g_asset.about = LoadAsset("about.html", "text/html; charset=utf-8");
g_asset.user = LoadAsset("user.html", "text/html; charset=utf-8"); g_asset.user = LoadAsset("user.html", "text/html; charset=utf-8");
g_asset.favicon = LoadAsset("favicon.ico", "image/vnd.microsoft.icon");
CHECK_EQ(0, unveil("/opt/turfwar", "rwc")); CHECK_EQ(0, unveil("/opt/turfwar", "rwc"));
CHECK_EQ(0, unveil(0, 0)); CHECK_EQ(0, unveil(0, 0));
@ -895,9 +1069,12 @@ int main(int argc, char *argv[]) {
CHECK_EQ(0, pledge("stdio flock rpath wpath cpath inet", 0)); CHECK_EQ(0, pledge("stdio flock rpath wpath cpath inet", 0));
// create threads // create threads
pthread_t boarder; pthread_t scorer;
CHECK_EQ(1, GenerateBoard(&g_asset.board)); CHECK_EQ(1, GenerateScore(&g_asset.score));
CHECK_EQ(0, pthread_create(&boarder, 0, BoardWorker, 0)); CHECK_EQ(0, pthread_create(&scorer, 0, ScoreWorker, 0));
pthread_t recentr;
CHECK_EQ(1, GenerateRecent(&g_asset.recent));
CHECK_EQ(0, pthread_create(&recentr, 0, RecentWorker, 0));
pthread_t claimer; pthread_t claimer;
CHECK_EQ(0, pthread_create(&claimer, 0, ClaimWorker, 0)); CHECK_EQ(0, pthread_create(&claimer, 0, ClaimWorker, 0));
pthread_t nower; pthread_t nower;
@ -920,13 +1097,17 @@ int main(int argc, char *argv[]) {
CHECK_EQ(0, pthread_join(httper[i], 0)); CHECK_EQ(0, pthread_join(httper[i], 0));
} }
CHECK_EQ(0, pthread_join(claimer, 0)); CHECK_EQ(0, pthread_join(claimer, 0));
CHECK_EQ(0, pthread_join(boarder, 0)); CHECK_EQ(0, pthread_join(recentr, 0));
CHECK_EQ(0, pthread_join(scorer, 0));
CHECK_EQ(0, pthread_join(nower, 0)); CHECK_EQ(0, pthread_join(nower, 0));
// free memory // free memory
FreeAsset(&g_asset.user); FreeAsset(&g_asset.user);
FreeAsset(&g_asset.about); FreeAsset(&g_asset.about);
FreeAsset(&g_asset.index); FreeAsset(&g_asset.index);
FreeAsset(&g_asset.score);
FreeAsset(&g_asset.recent);
FreeAsset(&g_asset.favicon);
nsync_note_free(g_shutdown); nsync_note_free(g_shutdown);
// CheckForMemoryLeaks(); // CheckForMemoryLeaks();
} }