commit:     28df3ab5d6336d8db66c4ea9209120ba35d86b2c
Author:     Michał Górny <mgorny <AT> gentoo <DOT> org>
AuthorDate: Sat Nov 22 16:19:23 2025 +0000
Commit:     Michał Górny <mgorny <AT> gentoo <DOT> org>
CommitDate: Sat Nov 22 16:19:23 2025 +0000
URL:        https://gitweb.gentoo.org/proj/steve.git/commit/?id=28df3ab5

Use pidfd API to handle exiting processes immediately

Thanks to amonakov for the suggestion!

Signed-off-by: Michał Górny <mgorny <AT> gentoo.org>

 steve.cxx | 84 ++++++++++++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 61 insertions(+), 23 deletions(-)

diff --git a/steve.cxx b/steve.cxx
index 2ab4e15..4cbb2e9 100644
--- a/steve.cxx
+++ b/steve.cxx
@@ -22,6 +22,7 @@
 #include <unordered_map>
 
 #include <sys/poll.h>
+#include <sys/syscall.h>
 #include <unistd.h>
 
 #include <event2/event.h>
@@ -61,7 +62,14 @@ struct steve_poll_waiter {
 };
 
 struct steve_process {
+       int pid_fd;
        unsigned int tokens_held{0};
+       std::unique_ptr<struct event, std::function<void(struct event*)>> event;
+
+       ~steve_process() {
+               if (pid_fd != 0)
+                       close(pid_fd);
+       }
 };
 
 struct steve_state {
@@ -71,6 +79,7 @@ struct steve_state {
        std::deque<steve_read_waiter> read_waiters;
        std::deque<steve_poll_waiter> poll_waiters;
        std::unordered_map<uint64_t, steve_process> processes;
+       struct event_base *evb;
 };
 
 enum steve_arg_keys {
@@ -137,19 +146,19 @@ static void steve_wake_waiters(steve_state *state)
        }
 }
 
-static void steve_check_processes(steve_state *state)
-{
-       for (auto it = state->processes.begin(); it != state->processes.end();)
-       {
-               if (kill(it->first, 0) == -1 && errno == ESRCH) {
+static void steve_handle_pidfd(evutil_socket_t pid_fd, short, void *userdata) {
+       steve_state *state = static_cast<steve_state *>(userdata);
+
+       for (auto it = state->processes.begin(); it != state->processes.end(); 
++it) {
+               if (it->second.pid_fd == pid_fd) {
                        state->tokens += it->second.tokens_held;
-                       fprintf(stderr, "Process %ld disappeared while holding 
%d tokens, "
+                       fprintf(stderr, "Process %ld exited while holding %d 
tokens, "
                                        "%d tokens available after returning 
them\n",
                                        it->first, it->second.tokens_held, 
state->tokens);
-                       it = state->processes.erase(it);
+                       state->processes.erase(it);
                        steve_wake_waiters(state);
-               } else
-                       ++it;
+                       break;
+               }
        }
 }
 
@@ -176,10 +185,36 @@ static void steve_open(fuse_req_t req, struct 
fuse_file_info *fi)
 
        /* pid is not available in release, so store it here */
        fi->fh = context->pid;
-       fuse_reply_open(req, fi);
+
+       int pid_fd = syscall(SYS_pidfd_open, context->pid, 0);
+       if (pid_fd == -1) {
+               perror("unable to open pidfd, rejecting to open");
+               fuse_reply_err(req, EIO);
+               return;
+       }
+
+       std::unique_ptr<struct event, std::function<void(struct event*)>>
+               pidfd_event{event_new(state->evb, pid_fd, EV_READ|EV_PERSIST, 
steve_handle_pidfd, state), event_free};
+       if (!pidfd_event) {
+               fprintf(stderr, "unable to allocate event for pidfd");
+               close(pid_fd);
+               fuse_reply_err(req, EIO);
+               return;
+       }
+       if (event_add(pidfd_event.get(), nullptr) == -1) {
+               fprintf(stderr, "failed to enable pidfd handler");
+               close(pid_fd);
+               fuse_reply_err(req, EIO);
+               return;
+       }
+
+       state->processes[fi->fh].pid_fd = pid_fd;
+       state->processes[fi->fh].event = std::move(pidfd_event);
 
        if (state->verbose)
-               printf("Device open by PID %ld\n", fi->fh);
+               printf("Device open by PID %ld (pidfd %d)\n", fi->fh, pid_fd);
+
+       fuse_reply_open(req, fi);
 }
 
 static void steve_release(fuse_req_t req, struct fuse_file_info *fi)
@@ -221,10 +256,6 @@ static void steve_read(
                return;
        }
 
-       /* check if we have tokens to reclaim */
-       if (state->tokens == 0)
-               steve_check_processes(state);
-
        /* no need to support reading more than one token at a time */
        if (state->tokens > 0) {
                state->tokens--;
@@ -290,9 +321,6 @@ static void steve_poll(
                printf("PID %ld requested poll, %d tokens available, %d tokens 
held by process\n",
                                fi->fh, state->tokens, 
state->processes[fi->fh].tokens_held);
 
-       /* check if we have tokens to reclaim */
-       if (state->tokens == 0)
-               steve_check_processes(state);
        /* POLLOUT is always possible, POLLIN only if we have any tokens */
        if (state->tokens == 0) {
                state->poll_waiters.emplace_back(ph, fi->fh);
@@ -314,11 +342,10 @@ static const struct cuse_lowlevel_ops steve_ops = {
 
 static void steve_handle_sigusr1(evutil_socket_t, short, void *userdata) {
        steve_state *state = static_cast<steve_state *>(userdata);
-       steve_check_processes(state);
 
        printf("steve: currently %d tokens available out of %d\n",
                        state->tokens, state->jobs);
-       for (auto it : state->processes) {
+       for (auto &it : state->processes) {
                printf("PID %ld holds %d tokens\n", it.first, 
it.second.tokens_held);
        }
 }
@@ -341,6 +368,7 @@ int main(int argc, char **argv)
                fprintf(stderr, "failed to initialize libevent\n");
                return 1;
        }
+       state.evb = evb.get();
 
        struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
        if (fuse_opt_parse(&args, &state, steve_opts, steve_process_arg)) {
@@ -381,17 +409,27 @@ int main(int argc, char **argv)
                fuse_opt_free_args(&args);
                return 1;
        }
-       event_add(cuse_event.get(), nullptr);
+       if (event_add(cuse_event.get(), nullptr) == -1) {
+               fprintf(stderr, "failed to enable CUSE handler");
+               close(cuse_fd);
+               fuse_opt_free_args(&args);
+               return 1;
+       }
 
        std::unique_ptr<struct event, std::function<void(struct event*)>>
                sigusr1_event{evsignal_new(evb.get(), SIGUSR1, 
steve_handle_sigusr1, &state), event_free};
        if (!sigusr1_event) {
-               fprintf(stderr, "failed to initialize signal handler");
+               fprintf(stderr, "failed to initialize SIGUSR1 handler");
+               close(cuse_fd);
+               fuse_opt_free_args(&args);
+               return 1;
+       }
+       if (event_add(sigusr1_event.get(), nullptr) == -1) {
+               fprintf(stderr, "failed to enable SIGUSR1 handler");
                close(cuse_fd);
                fuse_opt_free_args(&args);
                return 1;
        }
-       event_add(sigusr1_event.get(), nullptr);
 
        std::string mountpoint = "/dev/fd/" + std::to_string(cuse_fd);
        if (fuse_session_mount(session.get(), mountpoint.c_str()) == -1) {

Reply via email to