From 32321ab1e98bd3c3e3a1ce2540fb560efa81a105 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Mon, 3 Oct 2022 15:05:33 -0700 Subject: [PATCH] Further improve ipv4.games server --- libc/calls/pledge-linux.c | 4 +- libc/calls/pledge.c | 4 +- libc/calls/sched_getaffinity.c | 2 +- libc/calls/sched_setaffinity.c | 2 +- libc/thread/posixthread.internal.h | 3 + libc/thread/pthread_exit.c | 3 +- libc/thread/pthread_join.c | 2 +- libc/thread/pthread_self.c | 9 + net/turfwar/turfwar.c | 391 +++++++++++++++++++++-------- 9 files changed, 307 insertions(+), 113 deletions(-) diff --git a/libc/calls/pledge-linux.c b/libc/calls/pledge-linux.c index dd7faa889..31679c8b9 100644 --- a/libc/calls/pledge-linux.c +++ b/libc/calls/pledge-linux.c @@ -554,6 +554,8 @@ static const uint16_t kPledgeStdio[] = { __NR_linux_set_robust_list, // __NR_linux_get_robust_list, // __NR_linux_prlimit | STDIO, // + __NR_linux_sched_getaffinity, // + __NR_linux_sched_setaffinity, // }; static const uint16_t kPledgeFlock[] = { @@ -705,8 +707,6 @@ static const uint16_t kPledgeProc[] = { __NR_linux_sched_setscheduler, // __NR_linux_sched_get_priority_min, // __NR_linux_sched_get_priority_max, // - __NR_linux_sched_getaffinity, // - __NR_linux_sched_setaffinity, // __NR_linux_sched_getparam, // __NR_linux_sched_setparam, // }; diff --git a/libc/calls/pledge.c b/libc/calls/pledge.c index bc14afce5..ed8721053 100644 --- a/libc/calls/pledge.c +++ b/libc/calls/pledge.c @@ -19,11 +19,11 @@ #include "libc/calls/calls.h" #include "libc/calls/pledge.internal.h" #include "libc/calls/state.internal.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/promises.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/runtime/runtime.h" #include "libc/sysv/errfuns.h" @@ -110,7 +110,7 @@ * fcntl(F_SETFD), fcntl(F_GETFL), fcntl(F_SETFL), sched_yield, * epoll_create, epoll_create1, epoll_ctl, epoll_wait, epoll_pwait, * 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), * openat(O_RDONLY), stat, fstat, lstat, fstatat, access, faccessat, diff --git a/libc/calls/sched_getaffinity.c b/libc/calls/sched_getaffinity.c index 45a8e3071..96e843be8 100644 --- a/libc/calls/sched_getaffinity.c +++ b/libc/calls/sched_getaffinity.c @@ -77,6 +77,6 @@ int sched_getaffinity(int tid, size_t size, cpu_set_t *bitset) { } 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; } diff --git a/libc/calls/sched_setaffinity.c b/libc/calls/sched_setaffinity.c index 4e56a7a24..e7aedc4eb 100644 --- a/libc/calls/sched_setaffinity.c +++ b/libc/calls/sched_setaffinity.c @@ -85,6 +85,6 @@ int sched_setaffinity(int tid, size_t size, const cpu_set_t *bitset) { } else { 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; } diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index 25bc94125..ea0b26640 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -52,6 +52,9 @@ enum PosixThreadStatus { // - kPosixThreadZombie -> _pthread_free() will happen whenever // convenient, e.g. pthread_create() entry or atexit handler. kPosixThreadZombie, + + // special main thread + kPosixThreadMain, }; struct PosixThread { diff --git a/libc/thread/pthread_exit.c b/libc/thread/pthread_exit.c index 60cb72a1e..14189ff35 100644 --- a/libc/thread/pthread_exit.c +++ b/libc/thread/pthread_exit.c @@ -36,7 +36,8 @@ */ wontreturn void pthread_exit(void *rc) { struct PosixThread *pt; - if ((pt = (struct PosixThread *)__get_tls()->tib_pthread)) { + pt = (struct PosixThread *)pthread_self(); + if (pt->status != kPosixThreadMain) { pt->rc = rc; _gclongjmp(pt->exiter, 1); } else { diff --git a/libc/thread/pthread_join.c b/libc/thread/pthread_join.c index 7ef7acec4..9d54c799b 100644 --- a/libc/thread/pthread_join.c +++ b/libc/thread/pthread_join.c @@ -29,7 +29,7 @@ */ int pthread_join(pthread_t thread, void **value_ptr) { struct PosixThread *pt; - if (thread == __get_tls()->tib_pthread) { + if (thread == pthread_self()) { return EDEADLK; } if (!(pt = (struct PosixThread *)thread) || // diff --git a/libc/thread/pthread_self.c b/libc/thread/pthread_self.c index 358679b61..2bc6fee4d 100644 --- a/libc/thread/pthread_self.c +++ b/libc/thread/pthread_self.c @@ -16,6 +16,8 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" @@ -25,3 +27,10 @@ pthread_t pthread_self(void) { 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; +} diff --git a/net/turfwar/turfwar.c b/net/turfwar/turfwar.c index 5a67e79d5..679dfcc49 100644 --- a/net/turfwar/turfwar.c +++ b/net/turfwar/turfwar.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/calls/pledge.h" #include "libc/calls/struct/iovec.h" @@ -37,6 +38,8 @@ #include "libc/nexgen32e/crc32.h" #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" +#include "libc/runtime/stack.h" +#include "libc/runtime/sysconf.h" #include "libc/sock/sock.h" #include "libc/sock/struct/pollfd.h" #include "libc/sock/struct/sockaddr.h" @@ -48,12 +51,14 @@ #include "libc/sysv/consts/clock.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/poll.h" +#include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/so.h" #include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/sol.h" #include "libc/sysv/consts/tcp.h" #include "libc/thread/thread.h" +#include "libc/thread/thread2.h" #include "libc/time/struct/tm.h" #include "libc/x/x.h" #include "libc/x/xasprintf.h" @@ -75,16 +80,19 @@ */ #define PORT 8080 -#define WORKERS 2000 +#define WORKERS 9001 #define HEARTBEAT 2000 -#define KEEPALIVE_MS 2000 -#define DATE_UPDATE_MS 500 +#define KEEPALIVE_MS 1000 #define POLL_ASSETS_MS 250 -#define BOARD_GENERATE_MS 10000 -#define CLAIM_DEADLINE_MS 1000 -#define CLAIM_MAX 200 +#define DATE_UPDATE_MS 500 +#define SCORE_UPDATE_MS 15000 +#define CLAIM_DEADLINE_MS 100 +#define QUEUE_MAX 800 +#define BATCH_MAX 64 #define NICK_MAX 40 #define MSG_MAX 10 +#define INBUF_SIZE PAGESIZE +#define OUTBUF_SIZE PAGESIZE #define GETOPTS "dvp:w:k:" #define USAGE \ @@ -111,6 +119,9 @@ Usage: turfwar.com [-dv] ARGS...\n\ SlicesEqualCase(S, strlen(S), HeaderData(H), HeaderLength(H)) #define UrlEqual(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 #define LOG(...) kprintf(__VA_ARGS__) @@ -169,6 +180,7 @@ struct Asset { char *path; nsync_mu lock; const char *type; + const char *cache; struct Data data; struct Data gzip; struct timespec mtim; @@ -180,20 +192,26 @@ int g_port = PORT; int g_workers = WORKERS; int g_keepalive = KEEPALIVE_MS; -struct tm g_nowish; nsync_note g_shutdown; -nsync_mu g_nowish_lock; -struct Board { +struct Recent { nsync_mu mu; nsync_cv cv; -} g_board; +} g_recent; + +struct Nowish { + nsync_mu lock; + struct timespec ts; + struct tm tm; +} g_nowish; struct Assets { struct Asset index; struct Asset about; struct Asset user; - struct Asset board; + struct Asset score; + struct Asset recent; + struct Asset favicon; } g_asset; struct Claims { @@ -204,8 +222,9 @@ struct Claims { nsync_cv non_empty; struct Claim { uint32_t ip; + int64_t created; char name[NICK_MAX + 1]; - } data[CLAIM_MAX]; + } data[QUEUE_MAX]; } g_claims; 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) { size_t i; + if (n == -1) n = strlen(s); + if (!n) return false; if (n > NICK_MAX) return false; for (i = 0; i < n; ++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] == '*')) { return false; @@ -255,22 +279,21 @@ char *FormatUnixHttpDateTime(char *s, int64_t t) { void UpdateNow(void) { int64_t secs; struct tm tm; - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - secs = ts.tv_sec; + clock_gettime(CLOCK_REALTIME, &g_nowish.ts); + secs = g_nowish.ts.tv_sec; gmtime_r(&secs, &tm); //!//!//!//!//!//!//!//!//!//!//!//!//!/ - nsync_mu_lock(&g_nowish_lock); - g_nowish = tm; - nsync_mu_unlock(&g_nowish_lock); + nsync_mu_lock(&g_nowish.lock); + g_nowish.tm = tm; + nsync_mu_unlock(&g_nowish.lock); //!//!//!//!//!//!//!//!//!//!//!//!//!/ } char *FormatDate(char *p) { //////////////////////////////////////// - nsync_mu_rlock(&g_nowish_lock); - p = FormatHttpDateTime(p, &g_nowish); - nsync_mu_runlock(&g_nowish_lock); + nsync_mu_rlock(&g_nowish.lock); + p = FormatHttpDateTime(p, &g_nowish.tm); + nsync_mu_runlock(&g_nowish.lock); //////////////////////////////////////// return p; } @@ -345,13 +368,57 @@ static bool GetNick(char *inbuf, struct HttpMessage *msg, struct Claim *v) { 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 void *HttpWorker(void *arg) { int server; int yes = 1; + char name[16]; 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); + 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 // 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, listen(server, 1)); - msg = _gc(xmalloc(sizeof(struct HttpMessage))); // connection loop while (!nsync_note_is_notified(g_shutdown)) { @@ -381,10 +447,10 @@ void *HttpWorker(void *arg) { ssize_t got, sent; struct iovec iov[2]; uint32_t ip, clientip; + char ipbuf[32], *p, *q; uint32_t clientaddrsize; struct sockaddr_in clientaddr; 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 // react to keyboard ctrl-c @@ -403,7 +469,7 @@ void *HttpWorker(void *arg) { msgcount = 0; do { 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 (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, ip >> 8, ip); - if (UrlEqual("/") || UrlEqual("/index.html")) { + if (UrlEqual("/") || UrlStartsWith("/index.html")) { 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; - } else if (UrlEqual("/user.html")) { + } else if (UrlStartsWith("/user.html")) { a = &g_asset.user; - } else if (UrlEqual("/board")) { - a = &g_asset.board; + } else if (UrlStartsWith("/score")) { + a = &g_asset.score; + } else if (UrlStartsWith("/recent")) { + a = &g_asset.recent; } else { a = 0; } @@ -433,7 +503,6 @@ void *HttpWorker(void *arg) { if (a) { comp = HeaderHas(msg, inbuf, kHttpAcceptEncoding, "gzip", 4); 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" "Date: "); p = FormatDate(p); @@ -443,6 +512,8 @@ void *HttpWorker(void *arg) { p = stpcpy(p, a->lastmod); p = stpcpy(p, "\r\nContent-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"); p = stpcpy(p, "\r\nContent-Length: "); d = comp ? a->gzip : a->data; @@ -457,11 +528,11 @@ void *HttpWorker(void *arg) { nsync_mu_runlock(&a->lock); //////////////////////////////////////// - } else if (UrlEqual("/ip")) { + } else if (UrlStartsWith("/ip")) { if (!ipv6) { p = stpcpy(outbuf, "HTTP/1.1 200 OK\r\n" STANDARD_RESPONSE_HEADERS "Content-Type: text/plain\r\n" - "Cache-Control: private\r\n" + "Cache-Control: max-age=3600, private\r\n" "Date: "); p = FormatDate(p); p = stpcpy(p, "\r\nContent-Length: "); @@ -492,10 +563,9 @@ void *HttpWorker(void *arg) { break; } - } else if (msg->uri.b - msg->uri.a > 6 && - !memcmp(inbuf + msg->uri.a, "/claim", 6)) { + } else if (UrlStartsWith("/claim")) { 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 (AddClaim( &g_claims, &v, @@ -561,7 +631,8 @@ void *HttpWorker(void *arg) { } } 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 = "\r\n" "404 not found\r\n" "

404 not found

\r\n"; @@ -594,6 +665,8 @@ void *HttpWorker(void *arg) { } STRACE("HttpWorker #%d exiting", id); + FreeSafeBuffer(outbuf); + FreeSafeBuffer(inbuf); close(server); return 0; } @@ -631,6 +704,7 @@ struct Asset LoadAsset(const char *path, const char *type) { CHECK_EQ(0, stat(path, &st)); CHECK_NOTNULL((a.data.p = xslurp(path, &a.data.n))); a.type = type; + a.cache = "max-age=3600, must-revalidate"; a.path = xstrdup(path); a.mtim = st.st_mtim; 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))) { FormatUnixHttpDateTime(lastmod, st.st_mtim.tv_sec); CHECK_SYS((rc = read(fd, data.p, st.st_size))); + data.n = st.st_size; if (rc != st.st_size) goto OnError; gzip = Gzip(data); //!//!//!//!//!//!//!//!//!//!//!//!//!/ @@ -680,6 +755,10 @@ void FreeAsset(struct Asset *a) { free(a->gzip.p); } +void OnCtrlC(int sig) { + nsync_note_notify(g_shutdown); +} + static void GetOpts(int argc, char *argv[]) { int opt; while ((opt = getopt(argc, argv, GETOPTS)) != -1) { @@ -709,97 +788,182 @@ static void GetOpts(int argc, char *argv[]) { } } -void OnCtrlC(int sig) { - nsync_note_notify(g_shutdown); +void Update(struct Asset *a, bool gen(struct Asset *)) { + 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; sqlite3 *db = 0; size_t sblen = 0; struct Asset a = {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.cache = "max-age=60, must-revalidate"; CHECK_SYS(clock_gettime(CLOCK_REALTIME, &a.mtim)); 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_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 nick AS name, COUNT(ip) AS count\n" + "SELECT nick, (ip >> 24), COUNT(*)\n" "FROM land\n" - "WHERE ip >= ?1 AND ip <= ?2\n" - "GROUP BY nick\n" - "ORDER BY count DESC\n" - "LIMIT 1", + "GROUP BY nick, (ip >> 24)", -1, &stmt, 0)); CHECK_SQL(sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0)); - for (long i = 0; i < 256; ++i) { - if (i) CHECK_SYS(appends(&a.data.p, ",\n")); - CHECK_DB(sqlite3_bind_int64(stmt, 1, i * 0x1000000)); - CHECK_DB(sqlite3_bind_int64(stmt, 2, i * 0x1000000 + 0xFFFFFF)); - switch (sqlite3_step(stmt)) { - case SQLITE_ROW: - CHECK_SYS(appendf( - &a.data.p, "{\"name\":\"%s\",\"count\":%ld}", - EscapeJsStringLiteral(&sb, &sblen, - (void *)sqlite3_column_text(stmt, 0), -1, 0), - sqlite3_column_int64(stmt, 1))); - break; - case SQLITE_DONE: - CHECK_SYS(appends(&a.data.p, "false")); - break; - default: - kprintf("%s\n", sqlite3_errmsg(db)); - abort(); + while ((rc = sqlite3_step(stmt)) != SQLITE_DONE) { + if (rc != SQLITE_ROW) CHECK_SQL(rc); + strlcpy(name2, (void *)sqlite3_column_text(stmt, 0), sizeof(name2)); + if (!IsValidNick(name2, -1)) continue; + if (strcmp(name1, name2)) { + // name changed + if (namestate) CHECK_SYS(appends(&a.data.p, "],\n")); + namestate = true; + CHECK_SYS(appendf( + &a.data.p, "\"%s\":[\n", + EscapeJsStringLiteral(&sb, &sblen, strcpy(name1, name2), -1, 0))); + } else { + // name repeated + CHECK_SYS(appends(&a.data.p, ",\n")); } - 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_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_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; } -// single thread for regenerating the leaderboard json -void *BoardWorker(void *arg) { - int rc; - void *f[2]; - struct Asset a; - LOG("BoardWorker started\n"); - for (;;) { - nsync_mu_lock(&g_board.mu); - rc = nsync_cv_wait_with_deadline(&g_board.cv, &g_board.mu, - nsync_time_no_deadline, g_shutdown); - nsync_mu_unlock(&g_board.mu); - if (rc == ECANCELED) 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]); +// single thread for regenerating the user scores json +void *ScoreWorker(void *arg) { + nsync_time deadline; + LOG("ScoreWorker started\n"); + OnlyRunOnCpu(0); + pthread_setname_np(pthread_self(), "ScoreWorker"); + for (deadline = _timespec_real();;) { + deadline = _timespec_add(deadline, _timespec_frommillis(SCORE_UPDATE_MS)); + if (!nsync_note_wait(g_shutdown, deadline)) { + Update(&g_asset.score, GenerateScore); + } else { + break; } } - 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; } @@ -807,31 +971,36 @@ void *BoardWorker(void *arg) { void *ClaimWorker(void *arg) { int i, n, rc; sqlite3 *db = 0; - struct Claim v[32]; sqlite3_stmt *stmt = 0; + struct Claim *v = _gc(xcalloc(BATCH_MAX, sizeof(struct Claim))); + OnlyRunOnCpu(0); + pthread_setname_np(pthread_self(), "ClaimWorker"); StartOver: 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, - "INSERT INTO land (ip, nick) VALUES (?1, ?2)\n" - "ON CONFLICT (ip) DO UPDATE SET (nick) = (?2)\n" + "INSERT INTO land (ip, nick, created)\n" + "VALUES (?1, ?2, ?3)\n" + "ON CONFLICT (ip) DO\n" + "UPDATE SET (nick, created) = (?2, ?3)\n" "WHERE nick != ?2", -1, &stmt, 0)); 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)); for (i = 0; i < n; ++i) { 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_int64(stmt, 3, v[i].created)); CHECK_DB((rc = sqlite3_step(stmt)) == SQLITE_DONE ? SQLITE_OK : rc); CHECK_DB(sqlite3_reset(stmt)); } CHECK_SQL(sqlite3_exec(db, "COMMIT TRANSACTION", 0, 0, 0)); DEBUG("Committed %d claims\n", n); - nsync_mu_lock(&g_board.mu); - nsync_cv_signal(&g_board.cv); - nsync_mu_unlock(&g_board.mu); + nsync_mu_lock(&g_recent.mu); + nsync_cv_signal(&g_recent.cv); + nsync_mu_unlock(&g_recent.mu); } CHECK_DB(sqlite3_finalize(stmt)); CHECK_SQL(sqlite3_close(db)); @@ -848,6 +1017,8 @@ OnError: // single thread for computing HTTP Date header void *NowWorker(void *arg) { nsync_time deadline; + OnlyRunOnCpu(0); + pthread_setname_np(pthread_self(), "NowWorker"); for (deadline = _timespec_real();;) { deadline = _timespec_add(deadline, _timespec_frommillis(DATE_UPDATE_MS)); if (!nsync_note_wait(g_shutdown, deadline)) { @@ -862,6 +1033,8 @@ void *NowWorker(void *arg) { // single thread for monitoring assets on disk void *AssetWorker(void *arg) { nsync_time deadline; + OnlyRunOnCpu(0); + pthread_setname_np(pthread_self(), "AssetWorker"); for (deadline = _timespec_real();;) { deadline = _timespec_add(deadline, _timespec_frommillis(POLL_ASSETS_MS)); if (!nsync_note_wait(g_shutdown, deadline)) { @@ -876,7 +1049,7 @@ void *AssetWorker(void *arg) { } int main(int argc, char *argv[]) { - ShowCrashReports(); + // ShowCrashReports(); GetOpts(argc, argv); __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.about = LoadAsset("about.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(0, 0)); @@ -895,9 +1069,12 @@ int main(int argc, char *argv[]) { CHECK_EQ(0, pledge("stdio flock rpath wpath cpath inet", 0)); // create threads - pthread_t boarder; - CHECK_EQ(1, GenerateBoard(&g_asset.board)); - CHECK_EQ(0, pthread_create(&boarder, 0, BoardWorker, 0)); + pthread_t scorer; + CHECK_EQ(1, GenerateScore(&g_asset.score)); + 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; CHECK_EQ(0, pthread_create(&claimer, 0, ClaimWorker, 0)); 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(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)); // free memory FreeAsset(&g_asset.user); FreeAsset(&g_asset.about); FreeAsset(&g_asset.index); + FreeAsset(&g_asset.score); + FreeAsset(&g_asset.recent); + FreeAsset(&g_asset.favicon); nsync_note_free(g_shutdown); // CheckForMemoryLeaks(); }