Subject: [RFC PATCH 2/2] daemon: poor man's pidfd like POC

From: Carlo Marcelo Arenas Belón <[email-masked]>
Date: Thu, 26 Jun 2025 14:22:34 -0700

Create a pipe for each child, and let the write side leak into
the children, then add the read side to the poll and wait for
the pipe to close (presumed to happen when the child is done).

There is no need to change the children code, since the OS (at
least in *NIX) will close all fds on termination.

This implementation is suboptimal, as it shouldn't need to
scan all children once a notification is received, but does so
to minimize changes.

Signed-off-by: Carlo Marcelo Arenas Belón <[email-masked]>
---
 daemon.c | 74 +++++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 52 insertions(+), 22 deletions(-)

diff --git a/daemon.c b/daemon.c
index d1be61fd57..c78556be97 100644
--- a/daemon.c
+++ b/daemon.c
@@ -811,9 +811,19 @@ static struct child {
 	struct sockaddr_storage address;
 } *firstborn;
 
-static void add_child(struct child_process *cld, struct sockaddr *addr, socklen_t addrlen)
+static void add_child(nfds_t *nfds, struct pollfd **pfd,
+			struct child_process *cld,
+			struct sockaddr *addr, socklen_t addrlen)
 {
 	struct child *newborn, **cradle;
+	struct pollfd newfd;
+	nfds_t size = *nfds;
+
+	newfd.fd = cld->parent_ipc_in;
+	newfd.events = POLL_HUP;
+	*nfds = size + 1;
+	REALLOC_ARRAY(*pfd, *nfds);
+	memcpy(*pfd + size, &newfd, sizeof(newfd));
 
 	CALLOC_ARRAY(newborn, 1);
 	live_children++;
@@ -846,10 +856,11 @@ static void kill_some_child(void)
 		}
 }
 
-static void check_dead_children(void)
+static void check_dead_children(nfds_t *nfds, struct pollfd *pfd, nfds_t base)
 {
 	int status;
 	pid_t pid;
+	nfds_t size = *nfds;
 
 	struct child **cradle, *blanket;
 	for (cradle = &firstborn; (blanket = *cradle);)
@@ -859,6 +870,20 @@ static void check_dead_children(void)
 				dead = " (with error)";
 			loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead);
 
+			for (nfds_t i = base; i < size; i++)
+				if (pfd[i].fd == blanket->cld.parent_ipc_in) {
+					close(blanket->cld.parent_ipc_in);
+					size--;
+					if (size - i) {
+						MOVE_ARRAY(pfd + i,
+								pfd + i + 1,
+								size - i);
+					}
+
+					*nfds = size;
+					break;
+				}
+
 			/* remove the child */
 			*cradle = blanket->next;
 			live_children--;
@@ -869,14 +894,16 @@ static void check_dead_children(void)
 }
 
 static struct strvec cld_argv = STRVEC_INIT;
-static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)
+static void handle(int incoming, nfds_t *nfds, struct pollfd **pfd, nfds_t base,
+			struct sockaddr *addr, socklen_t addrlen)
 {
+	int ipc[2] = { -1, -1 };
 	struct child_process cld = CHILD_PROCESS_INIT;
 
 	if (max_connections && live_children >= max_connections) {
 		kill_some_child();
 		sleep(1);  /* give it some time to die */
-		check_dead_children();
+		check_dead_children(nfds, *pfd, base);
 		if (live_children >= max_connections) {
 			close(incoming);
 			logerror("Too many children, dropping connection");
@@ -902,24 +929,22 @@ static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)
 #endif
 	}
 
+#ifndef GIT_WINDOWS_NATIVE
+	pipe(ipc);
+	cld.parent_ipc_in = ipc[0];
+#endif
+
 	strvec_pushv(&cld.args, cld_argv.v);
 	cld.in = incoming;
 	cld.out = dup(incoming);
 
 	if (start_command(&cld))
 		logerror("unable to fork");
-	else
-		add_child(&cld, addr, addrlen);
-}
-
-static void child_handler(int signo UNUSED)
-{
-	/*
-	 * Otherwise empty handler because systemcalls will get interrupted
-	 * upon signal receipt
-	 * SysV needs the handler to be rearmed
-	 */
-	signal(SIGCHLD, child_handler);
+	else {
+		if (ipc[1] != -1)
+			close(ipc[1]);
+		add_child(nfds, pfd, &cld, addr, addrlen);
+	}
 }
 
 static int set_reuse_addr(int sockfd)
@@ -1121,6 +1146,7 @@ static void socksetup(struct string_list *listen_addr, int listen_port, struct s
 static int service_loop(struct socketlist *socklist)
 {
 	struct pollfd *pfd;
+	nfds_t nfds = socklist->nr;
 
 	CALLOC_ARRAY(pfd, socklist->nr);
 
@@ -1129,12 +1155,10 @@ static int service_loop(struct socketlist *socklist)
 		pfd[i].events = POLLIN;
 	}
 
-	signal(SIGCHLD, child_handler);
-
 	for (;;) {
-		check_dead_children();
+		size_t i;
 
-		if (poll(pfd, socklist->nr, -1) < 0) {
+		if (poll(pfd, nfds, -1) < 0) {
 			if (errno != EINTR) {
 				logerror("Poll failed, resuming: %s",
 				      strerror(errno));
@@ -1143,7 +1167,12 @@ static int service_loop(struct socketlist *socklist)
 			continue;
 		}
 
-		for (size_t i = 0; i < socklist->nr; i++) {
+		for (i = socklist->nr; i < nfds; i++) {
+			if (pfd[i].revents & POLLHUP)
+				check_dead_children(&nfds, pfd, socklist->nr);
+		}
+
+		for (i = 0; i < socklist->nr; i++) {
 			if (pfd[i].revents & POLLIN) {
 				union {
 					struct sockaddr sa;
@@ -1164,7 +1193,8 @@ static int service_loop(struct socketlist *socklist)
 						die_errno("accept returned");
 					}
 				}
-				handle(incoming, &ss.sa, sslen);
+				handle(incoming, &nfds, &pfd, socklist->nr,
+					&ss.sa, sslen);
 			}
 		}
 	}
-- 
2.39.5 (Apple Git-154)