diff --git a/src/libcmd/include/nix/cmd/unix-socket-server.hh b/src/libcmd/include/nix/cmd/unix-socket-server.hh index 3aeee5b55..7a0d9fa79 100644 --- a/src/libcmd/include/nix/cmd/unix-socket-server.hh +++ b/src/libcmd/include/nix/cmd/unix-socket-server.hh @@ -53,6 +53,19 @@ struct ServeUnixSocketOptions * Mode for the created socket file. */ mode_t socketMode = 0666; + +#ifndef _WIN32 + /** + * Additional file descriptor to poll. Useful for doing a self-pipe trick + * https://cr.yp.to/docs/selfpipe.html. + */ + Descriptor auxiliaryFd = INVALID_DESCRIPTOR; + + /** + * Optional callback invoked on POLLIN event for auxiliaryFd. + */ + std::function onAuxiliaryFdPollin = nullptr; +#endif }; /** diff --git a/src/libcmd/unix/unix-socket-server.cc b/src/libcmd/unix/unix-socket-server.cc index 8cc2bd713..e33f4317d 100644 --- a/src/libcmd/unix/unix-socket-server.cc +++ b/src/libcmd/unix/unix-socket-server.cc @@ -9,6 +9,8 @@ #include "nix/util/unix-domain-socket.hh" #include "nix/util/util.hh" +#include + #include #include #include @@ -83,6 +85,9 @@ PeerInfo getPeerInfo(Descriptor remote) for (auto & i : listeningSockets) fds.push_back({.fd = i.get(), .events = POLLIN}); + if (options.auxiliaryFd != INVALID_DESCRIPTOR) + fds.push_back({.fd = options.auxiliaryFd, .events = POLLIN}); + // Loop accepting connections. while (1) { try { @@ -95,7 +100,11 @@ PeerInfo getPeerInfo(Descriptor remote) throw SysError("polling for incoming connections"); } - for (auto & fd : fds) { + if (options.auxiliaryFd != INVALID_DESCRIPTOR && options.onAuxiliaryFdPollin && fds.back().revents & POLLIN) + /* Useful for reaping children. */ + options.onAuxiliaryFdPollin(); + + for (auto & fd : std::views::take(fds, listeningSockets.size())) { if (!fd.revents) continue; diff --git a/src/nix/unix/daemon.cc b/src/nix/unix/daemon.cc index 7fce2e8f6..3faaba642 100644 --- a/src/nix/unix/daemon.cc +++ b/src/nix/unix/daemon.cc @@ -120,13 +120,20 @@ static ssize_t splice(int fd_in, void * off_in, int fd_out, void * off_out, size } #endif +static Pipe sigChldPipe; + static void sigChldHandler(int sigNo) { // Ensure we don't modify errno of whatever we've interrupted auto saved_errno = errno; - // Reap all dead children. - while (waitpid(-1, 0, WNOHANG) > 0) - ; + /* Write to the self-pipe that gets polled in the accept loop. Pipe + is non-blocking. https://man7.org/tlpi/code/online/dist/altio/self_pipe.c.html */ + auto res = ::write(sigChldPipe.writeSide.get(), "x", 1); + if (res == -1 && errno != EAGAIN) { + /* Something is deeply wrong. Can't call std::terminate here because our terminate + handler is not safe for that. */ + abort(); + } errno = saved_errno; } @@ -243,7 +250,13 @@ static void daemonLoop(ref storeConfig, std::optional storeConfig, std::optional buf; + + /* Drain the self-pipe. */ + while (true) { + if (::read(sigChldPipe.readSide.get(), buf.data(), buf.size()) == -1) { + if (errno == EAGAIN) + break; + else + throw SysError("reading from self-pipe for SIGCHLD"); + } + } + + /* Reap all dead children. */ + pid_t pid = -1; + int status; + while (pid = ::waitpid(/*pid (any child process)=*/-1, &status, WNOHANG), pid > 0) + printInfo("reaped child process %1%, status = %2%", pid, statusToString(status)); + }, }, [&](AutoCloseFD remote, std::function closeListeners) { unix::closeOnExec(remote.get());