perf: accept thread + dial backoff; join/peers.json (0.9.23)
Run inbound peer handling on its own thread so outbound dials cannot starve :9100. Failed peers cool down exponentially. Also land --join (member seat) and realm/peers.json seed discovery for Tailscale meshes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+147
-32
@@ -20,8 +20,10 @@
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
@@ -72,7 +74,6 @@ static int read_meta(daemon_meta_t *m) {
|
||||
} else if (!strncmp(line, "port=", 5))
|
||||
m->port = atoi(line + 5);
|
||||
else if (m->pid <= 0) {
|
||||
/* Legacy single-line pidfile. */
|
||||
int pid = atoi(line);
|
||||
if (pid > 0)
|
||||
m->pid = pid;
|
||||
@@ -165,6 +166,20 @@ static void sleep_ms(int ms) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static int64_t now_ms(void) {
|
||||
#ifdef _WIN32
|
||||
return (int64_t)GetTickCount64();
|
||||
#else
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (int64_t)tv.tv_sec * 1000 + (int64_t)tv.tv_usec / 1000;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
static pthread_mutex_t g_sync_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
#endif
|
||||
|
||||
static int load_local_bundle(cst_peer_sync_bundle_t *b) {
|
||||
memset(b, 0, sizeof(*b));
|
||||
cst_parliament_meta_t m;
|
||||
@@ -186,16 +201,27 @@ static int apply_if_newer(cst_peer_sync_bundle_t *b, unsigned long before) {
|
||||
b->chair_instance, b->context);
|
||||
}
|
||||
|
||||
/* Short sync budget: accept thread must not stall the listen backlog. */
|
||||
static int sync_on_session(cst_peer_session_t *sess) {
|
||||
cst_peer_sync_bundle_t local;
|
||||
if (load_local_bundle(&local) != 0)
|
||||
#ifndef _WIN32
|
||||
pthread_mutex_lock(&g_sync_mu);
|
||||
#endif
|
||||
if (load_local_bundle(&local) != 0) {
|
||||
#ifndef _WIN32
|
||||
pthread_mutex_unlock(&g_sync_mu);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
unsigned long before = local.revision;
|
||||
char err[128];
|
||||
int rc = cst_peer_sync(sess, &local, 15000, err, sizeof(err));
|
||||
int rc = cst_peer_sync(sess, &local, 3000, err, sizeof(err));
|
||||
if (rc == 0)
|
||||
apply_if_newer(&local, before);
|
||||
free(local.context);
|
||||
#ifndef _WIN32
|
||||
pthread_mutex_unlock(&g_sync_mu);
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -208,6 +234,72 @@ static int unlock_realm(const char *password, cst_realm_keys_t *keys) {
|
||||
return cst_realm_load(dir, keys);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
os_socket_t ls;
|
||||
cst_realm_cert_t cert;
|
||||
uint8_t sk[64];
|
||||
uint8_t realm_pk[32];
|
||||
} accept_ctx_t;
|
||||
|
||||
#ifndef _WIN32
|
||||
static void *accept_main(void *arg) {
|
||||
accept_ctx_t *ac = arg;
|
||||
while (!g_stop) {
|
||||
cst_peer_session_t sess;
|
||||
char herr[128];
|
||||
if (cst_peer_accept((intptr_t)ac->ls, &ac->cert, ac->sk, ac->realm_pk, 400,
|
||||
&sess, herr, sizeof(herr)) != 0)
|
||||
continue;
|
||||
sync_on_session(&sess);
|
||||
cst_peer_close(&sess);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Per-peer dial cooldown so stuck remotes do not dominate the dial loop. */
|
||||
typedef struct {
|
||||
char iid[64];
|
||||
int fails;
|
||||
int64_t cool_until_ms;
|
||||
} dial_cool_t;
|
||||
|
||||
static dial_cool_t *cool_find(dial_cool_t *tab, int *n, int cap,
|
||||
const char *iid) {
|
||||
for (int i = 0; i < *n; i++) {
|
||||
if (!strcmp(tab[i].iid, iid))
|
||||
return &tab[i];
|
||||
}
|
||||
if (*n >= cap)
|
||||
return NULL;
|
||||
dial_cool_t *c = &tab[(*n)++];
|
||||
memset(c, 0, sizeof(*c));
|
||||
snprintf(c->iid, sizeof(c->iid), "%s", iid);
|
||||
return c;
|
||||
}
|
||||
|
||||
static int cool_blocked(dial_cool_t *c, int64_t now) {
|
||||
return c && c->cool_until_ms > now;
|
||||
}
|
||||
|
||||
static void cool_fail(dial_cool_t *c, int64_t now) {
|
||||
if (!c)
|
||||
return;
|
||||
c->fails = c->fails < 8 ? c->fails + 1 : 8;
|
||||
/* 4s, 8s, 16s, … capped at 120s */
|
||||
int64_t wait = 4000LL << (c->fails - 1);
|
||||
if (wait > 120000)
|
||||
wait = 120000;
|
||||
c->cool_until_ms = now + wait;
|
||||
}
|
||||
|
||||
static void cool_ok(dial_cool_t *c) {
|
||||
if (!c)
|
||||
return;
|
||||
c->fails = 0;
|
||||
c->cool_until_ms = 0;
|
||||
}
|
||||
|
||||
int cst_parliament_daemon_run(const char *password, const char *instance_id,
|
||||
int peer_port, char *err, size_t err_sz) {
|
||||
if (err && err_sz)
|
||||
@@ -272,65 +364,93 @@ int cst_parliament_daemon_run(const char *password, const char *instance_id,
|
||||
signal(SIGHUP, on_sig);
|
||||
#endif
|
||||
g_stop = 0;
|
||||
int tick = 0, idle = 0, dial_rr = 0;
|
||||
|
||||
accept_ctx_t ac;
|
||||
memset(&ac, 0, sizeof(ac));
|
||||
ac.ls = ls;
|
||||
ac.cert = cert;
|
||||
memcpy(ac.sk, sk, 64);
|
||||
memcpy(ac.realm_pk, realm.pk, 32);
|
||||
|
||||
#ifndef _WIN32
|
||||
pthread_t ath;
|
||||
int have_ath = pthread_create(&ath, NULL, accept_main, &ac) == 0;
|
||||
#else
|
||||
int have_ath = 0;
|
||||
#endif
|
||||
|
||||
int tick = 0, dial_rr = 0;
|
||||
dial_cool_t cool[24];
|
||||
int ncool = 0;
|
||||
|
||||
while (!g_stop) {
|
||||
cst_peer_session_t sess;
|
||||
char herr[128];
|
||||
/* Prefer accept: a blocking outbound dial used to starve inbound peers
|
||||
* (CLOSE-WAIT + unread hello ~385B on :9100). Drain the backlog first. */
|
||||
int accepted = 0;
|
||||
for (;;) {
|
||||
int accept_ms = accepted > 0 ? 50 : (idle > 20 ? 800 : idle > 5 ? 400 : 200);
|
||||
if (cst_peer_accept((intptr_t)ls, &cert, sk, realm.pk, accept_ms, &sess,
|
||||
herr, sizeof(herr)) != 0)
|
||||
|
||||
#ifdef _WIN32
|
||||
/* No accept thread: keep prefer-accept single-threaded path. */
|
||||
for (int a = 0; a < 4; a++) {
|
||||
if (cst_peer_accept((intptr_t)ls, &cert, sk, realm.pk, 200, &sess, herr,
|
||||
sizeof(herr)) != 0)
|
||||
break;
|
||||
sync_on_session(&sess);
|
||||
cst_peer_close(&sess);
|
||||
accepted++;
|
||||
idle = 0;
|
||||
if (accepted >= 8)
|
||||
break;
|
||||
}
|
||||
if (accepted == 0)
|
||||
idle++;
|
||||
#endif
|
||||
|
||||
int push = cst_parliament_sync_pending();
|
||||
tick++;
|
||||
/* At most one outbound dial per loop turn, short wall budget, so accept
|
||||
* keeps pace. Lexicographic rule avoids A↔B mutual-dial deadlock unless
|
||||
* we have a local push to deliver. */
|
||||
int period = idle > 20 ? 30 : idle > 5 ? 12 : 6;
|
||||
int period = push ? 1 : 8;
|
||||
if ((push || tick % period == 0) && disc) {
|
||||
cst_peer_info_t peers[16];
|
||||
int n = cst_discover_peers(disc, peers, 16);
|
||||
int64_t now = now_ms();
|
||||
int tried = 0;
|
||||
for (int k = 0; k < n; k++) {
|
||||
int i = (dial_rr + k) % n;
|
||||
if (peers[i].port <= 0)
|
||||
continue;
|
||||
if (!strcmp(peers[i].instance_id, instance_id))
|
||||
continue; /* never dial ourselves */
|
||||
continue;
|
||||
if (!push && strcmp(instance_id, peers[i].instance_id) > 0)
|
||||
continue; /* lower id initiates idle polls */
|
||||
continue;
|
||||
dial_cool_t *c = cool_find(cool, &ncool, 24, peers[i].instance_id);
|
||||
if (!push && cool_blocked(c, now))
|
||||
continue;
|
||||
dial_rr = (i + 1) % n;
|
||||
if (cst_peer_dial(peers[i].host, peers[i].port, &cert, sk, realm.pk,
|
||||
2500, &sess, herr, sizeof(herr)) == 0) {
|
||||
/* Dial budget stays short; accept thread covers inbound meanwhile. */
|
||||
int ok = cst_peer_dial(peers[i].host, peers[i].port, &cert, sk,
|
||||
realm.pk, 2000, &sess, herr, sizeof(herr)) == 0;
|
||||
if (ok) {
|
||||
sync_on_session(&sess);
|
||||
cst_peer_close(&sess);
|
||||
idle = 0;
|
||||
cool_ok(c);
|
||||
} else {
|
||||
cool_fail(c, now);
|
||||
}
|
||||
tried = 1;
|
||||
break; /* one dial then go back to accept */
|
||||
break;
|
||||
}
|
||||
if (push && (tried || n == 0))
|
||||
cst_parliament_sync_ack();
|
||||
}
|
||||
/* Pace dial loop; accept runs concurrently on Unix. */
|
||||
sleep_ms(have_ath ? 150 : 50);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
if (have_ath) {
|
||||
/* Wake accept's poll by shutting down listen sock after stop. */
|
||||
os_net_shutdown(ls);
|
||||
pthread_join(ath, NULL);
|
||||
}
|
||||
#endif
|
||||
if (disc)
|
||||
cst_discover_stop(disc);
|
||||
os_net_close(ls);
|
||||
clear_pid();
|
||||
crypto_wipe(sk, sizeof(sk));
|
||||
crypto_wipe(ac.sk, sizeof(ac.sk));
|
||||
crypto_wipe(realm.seed, sizeof(realm.seed));
|
||||
crypto_wipe(realm.sk, sizeof(realm.sk));
|
||||
return 0;
|
||||
@@ -380,13 +500,10 @@ int cst_parliament_daemon_start(const char *password, const char *instance_id,
|
||||
(cur_port <= 0 || cur_port == want_port);
|
||||
if (same)
|
||||
return 0;
|
||||
/* Different seat or port: take over. */
|
||||
(void)cst_parliament_daemon_stop();
|
||||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
/* Windows: run in-process foreground loop under a detached console is
|
||||
* deferred; callers should use --service in a service manager. */
|
||||
if (err && err_sz)
|
||||
snprintf(err, err_sz, "background daemon requires --service on Windows");
|
||||
return -1;
|
||||
@@ -403,7 +520,6 @@ int cst_parliament_daemon_start(const char *password, const char *instance_id,
|
||||
return -1;
|
||||
}
|
||||
if (pid > 0) {
|
||||
/* Parent: wait briefly for matching pidfile. */
|
||||
for (int i = 0; i < 50; i++) {
|
||||
int cur_pid = 0, cur_port = 0;
|
||||
char cur_id[64] = {0};
|
||||
@@ -417,7 +533,6 @@ int cst_parliament_daemon_start(const char *password, const char *instance_id,
|
||||
snprintf(err, err_sz, "service did not start");
|
||||
return -1;
|
||||
}
|
||||
/* Child: detach fully so SSH/nohup callers are not held by open stdio. */
|
||||
setsid();
|
||||
int fd = open("/dev/null", O_RDWR);
|
||||
if (fd >= 0) {
|
||||
|
||||
Reference in New Issue
Block a user