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:
+40
-23
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user