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) {