fix: stop parliament accept starvation on outbound dial (0.9.22)

Peer dial used per-step read timeouts so a stuck handshake could block the
single-threaded daemon for tens of seconds; inbound hellos piled up unread
(CLOSE-WAIT). Use a wall-clock dial budget, drain accepts before each dial,
skip self, and only one side initiates idle polls.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-10-29 13:19:03 +08:00
co-authored by Cursor
parent 2f96b050d6
commit de2eaf87f7
5 changed files with 129 additions and 45 deletions
+40 -23
View File
@@ -272,43 +272,59 @@ 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;
int tick = 0, idle = 0, dial_rr = 0;
while (!g_stop) {
cst_peer_session_t sess;
char herr[128];
/* Accept wait backs off when idle so a quiet seat barely wakes. */
int accept_ms = idle > 20 ? 2000 : idle > 5 ? 1000 : 400;
if (cst_peer_accept((intptr_t)ls, &cert, sk, realm.pk, accept_ms, &sess,
herr, sizeof(herr)) == 0) {
/* 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)
break;
sync_on_session(&sess);
cst_peer_close(&sess);
accepted++;
idle = 0;
} else {
idle++;
if (accepted >= 8)
break;
}
if (accepted == 0)
idle++;
int push = cst_parliament_sync_pending();
tick++;
/* Dial peers on push, or periodically (slower while idle). */
int period = idle > 20 ? 45 : idle > 5 ? 20 : 8;
/* 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;
if ((push || tick % period == 0) && disc) {
cst_peer_info_t peers[16];
int n = cst_discover_peers(disc, peers, 16);
if (n > 0 || push) {
for (int i = 0; i < n; i++) {
if (peers[i].port <= 0)
continue;
if (cst_peer_dial(peers[i].host, peers[i].port, &cert, sk, realm.pk,
3000, &sess, herr, sizeof(herr)) == 0) {
sync_on_session(&sess);
cst_peer_close(&sess);
idle = 0;
}
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 */
if (!push && strcmp(instance_id, peers[i].instance_id) > 0)
continue; /* lower id initiates idle polls */
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) {
sync_on_session(&sess);
cst_peer_close(&sess);
idle = 0;
}
tried = 1;
break; /* one dial then go back to accept */
}
if (push)
if (push && (tried || n == 0))
cst_parliament_sync_ack();
}
/* No extra sleep: accept_ms already paced the loop. */
}
if (disc)
cst_discover_stop(disc);
@@ -401,12 +417,13 @@ int cst_parliament_daemon_start(const char *password, const char *instance_id,
snprintf(err, err_sz, "service did not start");
return -1;
}
/* Child: detach. */
/* Child: detach fully so SSH/nohup callers are not held by open stdio. */
setsid();
int fd = open("/dev/null", O_RDWR);
if (fd >= 0) {
dup2(fd, 0);
/* Keep stderr for service banner until closed by operator logs. */
dup2(fd, 1);
dup2(fd, 2);
if (fd > 2)
close(fd);
}