From 997e4c8d939374c1dbd54d7402915f8b3b8a6d7c Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 18 Aug 2026 13:59:13 +0900 Subject: [PATCH] srv, ibus: a note the daemon lives through keeps its endpoints srvnote and addrnote unlinked on any note at all, and plan9port marks SIGPIPE Ignore: notify.c:59 lists it, and signotify runs the handler chain first and only then finds the Ignore flag and returns. So one broken pipe took the IPC socket and the IBus address file away from a daemon that went on running -- measured on a private runtime dir, a single kill -PIPE left the process in state Ssl with both files gone, so every client that focused a widget afterwards silently had no input method and only a restart brought it back. libxcb writes with writev, so the note is a broken X connection away; today xim.c's die() masks it by taking the daemon down on the same event, which is exactly why the two must not depend on each other. Co-Authored-By: Claude Opus 5 (1M context) --- dat.h | 7 +++++++ ibus.c | 5 +++-- srv.c | 4 ++-- tests/daemon_restart_test.c | 36 +++++++++++++++++++++++++++++++++++- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/dat.h b/dat.h index 71066a2..b1f4c12 100644 --- a/dat.h +++ b/dat.h @@ -7,6 +7,13 @@ #define min(a, b) ((a) < (b) ? (a) : (b)) #define max(a, b) ((a) > (b) ? (a) : (b)) +/* + * plan9port runs the note handlers for a note it goes on to ignore, and + * the process lives through it, so what strans announced must outlive + * such a note. A daemon that forks nothing gets only the one. + */ +#define notefatal(note) (strcmp(note, "sys: write on closed pipe") != 0) + /* A language id is the control code of the Ctrl key that selects it. */ enum { diff --git a/ibus.c b/ibus.c index 6a8e1b0..6c291af 100644 --- a/ibus.c +++ b/ibus.c @@ -83,9 +83,10 @@ unlinkaddr(void) } static int -addrnote(void*, char*) +addrnote(void*, char *note) { - unlinkaddr(); + if(notefatal(note)) + unlinkaddr(); return 0; } diff --git a/srv.c b/srv.c index 79f822f..1cd9b86 100644 --- a/srv.c +++ b/srv.c @@ -26,8 +26,8 @@ static int srvnote(void *v, char *note) { USED(v); - USED(note); - srvunlink(); + if(notefatal(note)) + srvunlink(); return 0; } diff --git a/tests/daemon_restart_test.c b/tests/daemon_restart_test.c index d4080c4..599bfd4 100644 --- a/tests/daemon_restart_test.c +++ b/tests/daemon_restart_test.c @@ -140,6 +140,38 @@ openpersistent(Test *t) return 1; } +/* + * plan9port ignores a broken pipe: the note handlers run and the daemon + * goes on serving, so the endpoints it announced must still be there. + */ +static int +survivenote(Test *t) +{ + struct stat st; + int fd, ok; + + if(kill(t->firstpid, SIGPIPE) < 0) + return fail("send a broken pipe to the first daemon: %s", + strerror(errno)); + pausems(200); + if(!daemonalive(&t->first)) + return fail("first daemon died of a broken pipe"); + if(lstat(t->l.socket, &st) < 0) + return fail("IPC socket taken by a note the daemon survived: %s", + strerror(errno)); + if(lstat(t->l.addrfile, &st) < 0) + return fail("IBus address taken by a note the daemon survived: %s", + strerror(errno)); + fd = connectsocket(t->l.socket, nowms() + Calltimeout); + if(fd < 0) + return fail("connect after a broken pipe: %s", strerror(errno)); + ok = ipcprobe(fd, "after a broken pipe"); + if(close(fd) < 0) + return fail("close the client opened after a broken pipe: %s", + strerror(errno)); + return ok; +} + static int hardcrash(Test *t) { @@ -329,6 +361,8 @@ runrestart(Test *t, char *program, char *mapdir) if(!waitready(&t->l, &t->first, t->firstaddress, sizeof t->firstaddress) || !privateaddress(t->firstaddress, t->firstpid) || !openpersistent(t)) return 0; + if(!survivenote(t)) + return 0; if(!hardcrash(t) || !waitipcclosed(t) || !waitbusclosed(t) || !checkstale(t) || !rejectold(t, "after hard crash")) return 0; @@ -367,6 +401,6 @@ main(int argc, char **argv) showerrors(&test.second); return 1; } - printf("daemon hard-crash endpoint recovery: ok\n"); + printf("daemon endpoints across a note and a hard crash: ok\n"); return 0; }