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_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, //
};

View file

@ -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,

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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) || //

View file

@ -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;
}

View file

@ -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 = "<!doctype html>\r\n"
"<title>404 not found</title>\r\n"
"<h1>404 not found</h1>\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();
}