[RFC,v5,9/9] libcamera: process: Remove `ProcessManager` singleton
diff mbox series

Message ID 20250424114103.451395-10-barnabas.pocze@ideasonboard.com
State Superseded
Headers show
Series
  • libcamera: process: Remove `ProcessManager` singleton
Related show

Commit Message

Barnabás Pőcze April 24, 2025, 11:41 a.m. UTC
The `ProcessManager` is a singleton class to handle `SIGCHLD` signals
and report the exit status to the particular `Process` instance.

However, having a singleton in a library is not favourable and it is
even less favourable if it installs a signal handler.

Using pidfd it is possible to avoid the need for the signal handler;
and the `Process` objects can watch their pidfd themselves, eliminating
the need for the `ProcessManager` class altogether.

`P_PIDFD` for `waitid()` was introduced in Linux 5.4, so this change
raises the minimum supported kernel version. `clone3()`, `CLONE_PIDFD`,
`pidfd_send_signal()` were all introduced earlier.

Furthermore, the call to the `unshare()` system call can be removed
as those options can be passed to `clone3()` directly.

Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 include/libcamera/internal/camera_manager.h |   1 -
 include/libcamera/internal/process.h        |  34 +--
 meson.build                                 |   2 +-
 src/libcamera/process.cpp                   | 245 +++++---------------
 test/ipc/unixsocket_ipc.cpp                 |   2 -
 test/log/log_process.cpp                    |   2 -
 test/process/process_test.cpp               |   2 -
 7 files changed, 60 insertions(+), 228 deletions(-)

Comments

Kieran Bingham April 26, 2025, 1:27 p.m. UTC | #1
Quoting Barnabás Pőcze (2025-04-24 12:41:03)
> The `ProcessManager` is a singleton class to handle `SIGCHLD` signals
> and report the exit status to the particular `Process` instance.
> 
> However, having a singleton in a library is not favourable and it is
> even less favourable if it installs a signal handler.

Do we still have the segfault signal handler? I thought we had some
custom handler to print a backtrace somewhere...

I guess that's separate.

> Using pidfd it is possible to avoid the need for the signal handler;
> and the `Process` objects can watch their pidfd themselves, eliminating
> the need for the `ProcessManager` class altogether.
> 
> `P_PIDFD` for `waitid()` was introduced in Linux 5.4, so this change
> raises the minimum supported kernel version. `clone3()`, `CLONE_PIDFD`,
> `pidfd_send_signal()` were all introduced earlier.
> 
> Furthermore, the call to the `unshare()` system call can be removed
> as those options can be passed to `clone3()` directly.

I know we've had issues in CI tests before with unshare() so I think
that being removed sounds good, except I expect the same permission
requirements of unshare() on the clone3() so I guess it won't change
much.

Is there anything to explore here that the process we launch can have
custom restrictions/jails added? (or is that where we would have to
handle things in a separate daemon context?)

Anyway, that's a lot of code removed, which if it doesn't mean a loss of
functionality is a good thing.


> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  include/libcamera/internal/camera_manager.h |   1 -
>  include/libcamera/internal/process.h        |  34 +--
>  meson.build                                 |   2 +-
>  src/libcamera/process.cpp                   | 245 +++++---------------
>  test/ipc/unixsocket_ipc.cpp                 |   2 -
>  test/log/log_process.cpp                    |   2 -
>  test/process/process_test.cpp               |   2 -
>  7 files changed, 60 insertions(+), 228 deletions(-)
> 
> diff --git a/include/libcamera/internal/camera_manager.h b/include/libcamera/internal/camera_manager.h
> index 0150ca61f..5dfbe1f65 100644
> --- a/include/libcamera/internal/camera_manager.h
> +++ b/include/libcamera/internal/camera_manager.h
> @@ -65,7 +65,6 @@ private:
>         std::unique_ptr<DeviceEnumerator> enumerator_;
>  
>         std::unique_ptr<IPAManager> ipaManager_;
> -       ProcessManager processManager_;
>  };
>  
>  } /* namespace libcamera */
> diff --git a/include/libcamera/internal/process.h b/include/libcamera/internal/process.h
> index e55d11fa3..27d4b9258 100644
> --- a/include/libcamera/internal/process.h
> +++ b/include/libcamera/internal/process.h
> @@ -7,7 +7,6 @@
>  
>  #pragma once
>  
> -#include <signal.h>
>  #include <string>
>  
>  #include <libcamera/base/class.h>
> @@ -45,41 +44,14 @@ public:
>  private:
>         LIBCAMERA_DISABLE_COPY_AND_MOVE(Process)
>  
> -       int isolate();
> -       void died(int wstatus);
> +       void onPidfdNotify();
>  
>         pid_t pid_;
>         enum ExitStatus exitStatus_;
>         int exitCode_;
>  
> -       friend class ProcessManager;
> -};
> -
> -class ProcessManager
> -{
> -public:
> -       ProcessManager();
> -       ~ProcessManager();
> -
> -       void registerProcess(Process *proc);
> -
> -       static ProcessManager *instance();
> -
> -       int writePipe() const;
> -
> -       const struct sigaction &oldsa() const;
> -
> -private:
> -       static ProcessManager *self_;
> -
> -       void sighandler();
> -
> -       std::list<Process *> processes_;
> -
> -       struct sigaction oldsa_;
> -
> -       EventNotifier *sigEvent_;
> -       UniqueFD pipe_[2];
> +       UniqueFD pidfd_;
> +       std::unique_ptr<EventNotifier> pidfdNotify_;
>  };
>  
>  } /* namespace libcamera */
> diff --git a/meson.build b/meson.build
> index ae13727eb..3c895ae9e 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -265,7 +265,7 @@ subdir('Documentation')
>  subdir('test')
>  
>  if not meson.is_cross_build()
> -    kernel_version_req = '>= 5.0.0'
> +    kernel_version_req = '>= 5.4.0'
>      kernel_version = run_command('uname', '-r', check : true).stdout().strip()
>      if not kernel_version.version_compare(kernel_version_req)
>          warning('The current running kernel version @0@ is too old to run libcamera.'
> diff --git a/src/libcamera/process.cpp b/src/libcamera/process.cpp
> index 42b56374d..b01c78034 100644
> --- a/src/libcamera/process.cpp
> +++ b/src/libcamera/process.cpp
> @@ -10,15 +10,18 @@
>  #include <algorithm>
>  #include <dirent.h>
>  #include <fcntl.h>
> -#include <list>
>  #include <signal.h>
>  #include <string.h>
>  #include <sys/socket.h>
> +#include <sys/syscall.h>
>  #include <sys/types.h>
>  #include <sys/wait.h>
>  #include <unistd.h>
>  #include <vector>
>  
> +#include <linux/sched.h>
> +#include <linux/wait.h> /* glibc only provides `P_PIDFD` in `sys/wait.h` since 2.36 */
> +
>  #include <libcamera/base/event_notifier.h>
>  #include <libcamera/base/log.h>
>  #include <libcamera/base/utils.h>
> @@ -32,37 +35,8 @@ namespace libcamera {
>  
>  LOG_DEFINE_CATEGORY(Process)
>  
> -/**
> - * \class ProcessManager
> - * \brief Manager of processes
> - *
> - * The ProcessManager singleton keeps track of all created Process instances,
> - * and manages the signal handling involved in terminating processes.
> - */
> -
>  namespace {
>  
> -void sigact(int signal, siginfo_t *info, void *ucontext)
> -{
> -#pragma GCC diagnostic push
> -#pragma GCC diagnostic ignored "-Wunused-result"
> -       /*
> -        * We're in a signal handler so we can't log any message, and we need
> -        * to continue anyway.
> -        */
> -       char data = 0;
> -       write(ProcessManager::instance()->writePipe(), &data, sizeof(data));
> -#pragma GCC diagnostic pop
> -
> -       const struct sigaction &oldsa = ProcessManager::instance()->oldsa();
> -       if (oldsa.sa_flags & SA_SIGINFO) {
> -               oldsa.sa_sigaction(signal, info, ucontext);
> -       } else {
> -               if (oldsa.sa_handler != SIG_IGN && oldsa.sa_handler != SIG_DFL)
> -                       oldsa.sa_handler(signal);
> -       }
> -}
> -
>  void closeAllFdsExcept(Span<const int> fds)
>  {
>         std::vector<int> v(fds.begin(), fds.end());
> @@ -118,129 +92,6 @@ void closeAllFdsExcept(Span<const int> fds)
>  
>  } /* namespace */
>  
> -void ProcessManager::sighandler()
> -{
> -       char data;
> -       ssize_t ret = read(pipe_[0].get(), &data, sizeof(data));
> -       if (ret < 0) {
> -               LOG(Process, Error)
> -                       << "Failed to read byte from signal handler pipe";
> -               return;
> -       }
> -
> -       for (auto it = processes_.begin(); it != processes_.end();) {
> -               Process *process = *it;
> -
> -               int wstatus;
> -               pid_t pid = waitpid(process->pid_, &wstatus, WNOHANG);
> -               if (process->pid_ != pid) {
> -                       ++it;
> -                       continue;
> -               }
> -
> -               it = processes_.erase(it);
> -               process->died(wstatus);
> -       }
> -}
> -
> -/**
> - * \brief Register process with process manager
> - * \param[in] proc Process to register
> - *
> - * This function registers the \a proc with the process manager. It
> - * shall be called by the parent process after successfully forking, in
> - * order to let the parent signal process termination.
> - */
> -void ProcessManager::registerProcess(Process *proc)
> -{
> -       processes_.push_back(proc);
> -}
> -
> -ProcessManager *ProcessManager::self_ = nullptr;
> -
> -/**
> - * \brief Construct a ProcessManager instance
> - *
> - * The ProcessManager class is meant to only be instantiated once, by the
> - * CameraManager.
> - */
> -ProcessManager::ProcessManager()
> -{
> -       if (self_)
> -               LOG(Process, Fatal)
> -                       << "Multiple ProcessManager objects are not allowed";
> -
> -       sigaction(SIGCHLD, NULL, &oldsa_);
> -
> -       struct sigaction sa;
> -       memset(&sa, 0, sizeof(sa));
> -       sa.sa_sigaction = &sigact;
> -       memcpy(&sa.sa_mask, &oldsa_.sa_mask, sizeof(sa.sa_mask));
> -       sigaddset(&sa.sa_mask, SIGCHLD);
> -       sa.sa_flags = oldsa_.sa_flags | SA_SIGINFO;
> -
> -       sigaction(SIGCHLD, &sa, NULL);
> -
> -       int pipe[2];
> -       if (pipe2(pipe, O_CLOEXEC | O_DIRECT | O_NONBLOCK))
> -               LOG(Process, Fatal)
> -                       << "Failed to initialize pipe for signal handling";
> -
> -       pipe_[0] = UniqueFD(pipe[0]);
> -       pipe_[1] = UniqueFD(pipe[1]);
> -
> -       sigEvent_ = new EventNotifier(pipe_[0].get(), EventNotifier::Read);
> -       sigEvent_->activated.connect(this, &ProcessManager::sighandler);
> -
> -       self_ = this;
> -}
> -
> -ProcessManager::~ProcessManager()
> -{
> -       sigaction(SIGCHLD, &oldsa_, NULL);
> -
> -       delete sigEvent_;
> -
> -       self_ = nullptr;
> -}
> -
> -/**
> - * \brief Retrieve the Process manager instance
> - *
> - * The ProcessManager is constructed by the CameraManager. This function shall
> - * be used to retrieve the single instance of the manager.
> - *
> - * \return The Process manager instance
> - */
> -ProcessManager *ProcessManager::instance()
> -{
> -       return self_;
> -}
> -
> -/**
> - * \brief Retrieve the Process manager's write pipe
> - *
> - * This function is meant only to be used by the static signal handler.
> - *
> - * \return Pipe for writing
> - */
> -int ProcessManager::writePipe() const
> -{
> -       return pipe_[1].get();
> -}
> -
> -/**
> - * \brief Retrive the old signal action data
> - *
> - * This function is meant only to be used by the static signal handler.
> - *
> - * \return The old signal action data
> - */
> -const struct sigaction &ProcessManager::oldsa() const
> -{
> -       return oldsa_;
> -}
> -
>  /**
>   * \class Process
>   * \brief Process object
> @@ -291,8 +142,6 @@ int Process::start(const std::string &path,
>                    Span<const std::string> args,
>                    Span<const int> fds)
>  {
> -       int ret;
> -
>         if (pid_ > 0)
>                 return -EBUSY;
>  
> @@ -301,20 +150,29 @@ int Process::start(const std::string &path,
>                         return -EINVAL;
>         }
>  
> -       int childPid = fork();
> -       if (childPid == -1) {
> -               ret = -errno;
> +       clone_args cargs = {};
> +       int pidfd;
> +
> +       cargs.flags = CLONE_CLEAR_SIGHAND | CLONE_PIDFD | CLONE_NEWUSER | CLONE_NEWNET;
> +       cargs.pidfd = reinterpret_cast<uintptr_t>(&pidfd);
> +       cargs.exit_signal = SIGCHLD;
> +
> +       long childPid = syscall(SYS_clone3, &cargs, sizeof(cargs));
> +       if (childPid < 0) {
> +               int ret = -errno;
>                 LOG(Process, Error) << "Failed to fork: " << strerror(-ret);
>                 return ret;
> -       } else if (childPid) {
> +       }
> +
> +       if (childPid) {
>                 pid_ = childPid;
> -               ProcessManager::instance()->registerProcess(this);
> +               pidfd_ = UniqueFD(pidfd);
> +               pidfdNotify_ = std::make_unique<EventNotifier>(pidfd_.get(), EventNotifier::Type::Read);
> +               pidfdNotify_->activated.connect(this, &Process::onPidfdNotify);
>  
> -               return 0;
> +               LOG(Process, Debug) << this << "[" << childPid << ':' << pidfd << "]"
> +                                   << " forked";
>         } else {
> -               if (isolate())
> -                       _exit(EXIT_FAILURE);
> -
>                 closeAllFdsExcept(fds);
>  
>                 const char *file = utils::secure_getenv("LIBCAMERA_LOG_FILE");
> @@ -333,35 +191,44 @@ int Process::start(const std::string &path,
>  
>                 exit(EXIT_FAILURE);
>         }
> -}
> -
> -int Process::isolate()
> -{
> -       int ret = unshare(CLONE_NEWUSER | CLONE_NEWNET);
> -       if (ret) {
> -               ret = -errno;
> -               LOG(Process, Error) << "Failed to unshare execution context: "
> -                                   << strerror(-ret);
> -               return ret;
> -       }
>  
>         return 0;
>  }
>  
> -/**
> - * \brief SIGCHLD handler
> - * \param[in] wstatus The status as output by waitpid()
> - *
> - * This function is called when the process associated with Process terminates.
> - * It emits the Process::finished signal.
> - */
> -void Process::died(int wstatus)
> +void Process::onPidfdNotify()
>  {
> -       pid_ = -1;
> -       exitStatus_ = WIFEXITED(wstatus) ? NormalExit : SignalExit;
> -       exitCode_ = exitStatus_ == NormalExit ? WEXITSTATUS(wstatus) : -1;
> +       auto pidfdNotify = std::exchange(pidfdNotify_, {});
> +       auto pidfd = std::exchange(pidfd_, {});
> +       auto pid = std::exchange(pid_, -1);
> +
> +       ASSERT(pidfdNotify);
> +       ASSERT(pidfd.isValid());
> +       ASSERT(pid > 0);
> +
> +       siginfo_t info;
> +
> +       /*
> +        * `static_cast` is needed because `P_PIDFD` is not defined in `sys/wait.h` if the C standard library
> +        * is old enough. So `P_PIDFD` is taken from `linux/wait.h`, where it is just an integer #define.
> +        */
> +       if (waitid(static_cast<idtype_t>(P_PIDFD), pidfd.get(), &info, WNOHANG | WEXITED) >= 0) {
> +               ASSERT(info.si_pid == pid);
>  
> -       finished.emit(exitStatus_, exitCode_);
> +               LOG(Process, Debug)
> +                       << this << "[" << pid << ':' << pidfd.get() << "]"
> +                       << " code: " << info.si_code
> +                       << " status: " << info.si_status;
> +
> +               exitStatus_ = info.si_code == CLD_EXITED ? Process::NormalExit : Process::SignalExit;
> +               exitCode_ = info.si_code == CLD_EXITED ? info.si_status : -1;
> +
> +               finished.emit(exitStatus_, exitCode_);
> +       } else {
> +               int err = errno;
> +               LOG(Process, Warning)
> +                       << this << "[" << pid << ":" << pidfd.get() << "]"
> +                       << " waitid() failed: " << strerror(err);

I wonder if there's any extra way we can get custom messages from the
IPA's over to the main process to report /why/ they shut down in event
of failures.

So frequently when IPA's are isolated - do we get difficult to support
issues from users who's IPA isn't running - and we don't report in
libcamera any thing about /why/

Anyway - I don't see anything wrong with this patch so :

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

> +       }
>  }
>  
>  /**
> @@ -399,8 +266,8 @@ void Process::died(int wstatus)
>   */
>  void Process::kill()
>  {
> -       if (pid_ > 0)
> -               ::kill(pid_, SIGKILL);
> +       if (pidfd_.isValid())
> +               syscall(SYS_pidfd_send_signal, pidfd_.get(), SIGKILL, nullptr, 0);
>  }
>  
>  } /* namespace libcamera */
> diff --git a/test/ipc/unixsocket_ipc.cpp b/test/ipc/unixsocket_ipc.cpp
> index df7d9c2b4..f3e3c09ef 100644
> --- a/test/ipc/unixsocket_ipc.cpp
> +++ b/test/ipc/unixsocket_ipc.cpp
> @@ -209,8 +209,6 @@ protected:
>         }
>  
>  private:
> -       ProcessManager processManager_;
> -
>         unique_ptr<IPCPipeUnixSocket> ipc_;
>  };
>  
> diff --git a/test/log/log_process.cpp b/test/log/log_process.cpp
> index 9609e23d5..367b78803 100644
> --- a/test/log/log_process.cpp
> +++ b/test/log/log_process.cpp
> @@ -137,8 +137,6 @@ private:
>                 exitCode_ = exitCode;
>         }
>  
> -       ProcessManager processManager_;
> -
>         Process proc_;
>         Process::ExitStatus exitStatus_ = Process::NotExited;
>         string logPath_;
> diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp
> index a88d8fef1..fe76666e6 100644
> --- a/test/process/process_test.cpp
> +++ b/test/process/process_test.cpp
> @@ -86,8 +86,6 @@ private:
>                 exitCode_ = exitCode;
>         }
>  
> -       ProcessManager processManager_;
> -
>         Process proc_;
>         enum Process::ExitStatus exitStatus_;
>         int exitCode_;
> -- 
> 2.49.0
>
Laurent Pinchart April 26, 2025, 4:42 p.m. UTC | #2
On Sat, Apr 26, 2025 at 02:27:55PM +0100, Kieran Bingham wrote:
> Quoting Barnabás Pőcze (2025-04-24 12:41:03)
> > The `ProcessManager` is a singleton class to handle `SIGCHLD` signals
> > and report the exit status to the particular `Process` instance.
> > 
> > However, having a singleton in a library is not favourable and it is
> > even less favourable if it installs a signal handler.
> 
> Do we still have the segfault signal handler? I thought we had some
> custom handler to print a backtrace somewhere...

That's for ASSERT() and the fatal log level. We don't install a signal
handler.

> I guess that's separate.
> 
> > Using pidfd it is possible to avoid the need for the signal handler;
> > and the `Process` objects can watch their pidfd themselves, eliminating
> > the need for the `ProcessManager` class altogether.
> > 
> > `P_PIDFD` for `waitid()` was introduced in Linux 5.4, so this change
> > raises the minimum supported kernel version. `clone3()`, `CLONE_PIDFD`,
> > `pidfd_send_signal()` were all introduced earlier.
> > 
> > Furthermore, the call to the `unshare()` system call can be removed
> > as those options can be passed to `clone3()` directly.
> 
> I know we've had issues in CI tests before with unshare() so I think
> that being removed sounds good, except I expect the same permission
> requirements of unshare() on the clone3() so I guess it won't change
> much.
> 
> Is there anything to explore here that the process we launch can have
> custom restrictions/jails added? (or is that where we would have to
> handle things in a separate daemon context?)

It could be done here, but I think we should move it to a daemon.

> Anyway, that's a lot of code removed, which if it doesn't mean a loss of
> functionality is a good thing.
> 
> > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  include/libcamera/internal/camera_manager.h |   1 -
> >  include/libcamera/internal/process.h        |  34 +--
> >  meson.build                                 |   2 +-
> >  src/libcamera/process.cpp                   | 245 +++++---------------
> >  test/ipc/unixsocket_ipc.cpp                 |   2 -
> >  test/log/log_process.cpp                    |   2 -
> >  test/process/process_test.cpp               |   2 -
> >  7 files changed, 60 insertions(+), 228 deletions(-)
> > 
> > diff --git a/include/libcamera/internal/camera_manager.h b/include/libcamera/internal/camera_manager.h
> > index 0150ca61f..5dfbe1f65 100644
> > --- a/include/libcamera/internal/camera_manager.h
> > +++ b/include/libcamera/internal/camera_manager.h
> > @@ -65,7 +65,6 @@ private:
> >         std::unique_ptr<DeviceEnumerator> enumerator_;
> >  
> >         std::unique_ptr<IPAManager> ipaManager_;
> > -       ProcessManager processManager_;
> >  };
> >  
> >  } /* namespace libcamera */
> > diff --git a/include/libcamera/internal/process.h b/include/libcamera/internal/process.h
> > index e55d11fa3..27d4b9258 100644
> > --- a/include/libcamera/internal/process.h
> > +++ b/include/libcamera/internal/process.h
> > @@ -7,7 +7,6 @@
> >  
> >  #pragma once
> >  
> > -#include <signal.h>
> >  #include <string>
> >  
> >  #include <libcamera/base/class.h>
> > @@ -45,41 +44,14 @@ public:
> >  private:
> >         LIBCAMERA_DISABLE_COPY_AND_MOVE(Process)
> >  
> > -       int isolate();
> > -       void died(int wstatus);
> > +       void onPidfdNotify();
> >  
> >         pid_t pid_;
> >         enum ExitStatus exitStatus_;
> >         int exitCode_;
> >  
> > -       friend class ProcessManager;
> > -};
> > -
> > -class ProcessManager
> > -{
> > -public:
> > -       ProcessManager();
> > -       ~ProcessManager();
> > -
> > -       void registerProcess(Process *proc);
> > -
> > -       static ProcessManager *instance();
> > -
> > -       int writePipe() const;
> > -
> > -       const struct sigaction &oldsa() const;
> > -
> > -private:
> > -       static ProcessManager *self_;
> > -
> > -       void sighandler();
> > -
> > -       std::list<Process *> processes_;
> > -
> > -       struct sigaction oldsa_;
> > -
> > -       EventNotifier *sigEvent_;
> > -       UniqueFD pipe_[2];
> > +       UniqueFD pidfd_;
> > +       std::unique_ptr<EventNotifier> pidfdNotify_;
> >  };
> >  
> >  } /* namespace libcamera */
> > diff --git a/meson.build b/meson.build
> > index ae13727eb..3c895ae9e 100644
> > --- a/meson.build
> > +++ b/meson.build
> > @@ -265,7 +265,7 @@ subdir('Documentation')
> >  subdir('test')
> >  
> >  if not meson.is_cross_build()
> > -    kernel_version_req = '>= 5.0.0'
> > +    kernel_version_req = '>= 5.4.0'
> >      kernel_version = run_command('uname', '-r', check : true).stdout().strip()
> >      if not kernel_version.version_compare(kernel_version_req)
> >          warning('The current running kernel version @0@ is too old to run libcamera.'
> > diff --git a/src/libcamera/process.cpp b/src/libcamera/process.cpp
> > index 42b56374d..b01c78034 100644
> > --- a/src/libcamera/process.cpp
> > +++ b/src/libcamera/process.cpp
> > @@ -10,15 +10,18 @@
> >  #include <algorithm>
> >  #include <dirent.h>
> >  #include <fcntl.h>
> > -#include <list>
> >  #include <signal.h>
> >  #include <string.h>
> >  #include <sys/socket.h>
> > +#include <sys/syscall.h>
> >  #include <sys/types.h>
> >  #include <sys/wait.h>
> >  #include <unistd.h>
> >  #include <vector>
> >  
> > +#include <linux/sched.h>
> > +#include <linux/wait.h> /* glibc only provides `P_PIDFD` in `sys/wait.h` since 2.36 */
> > +
> >  #include <libcamera/base/event_notifier.h>
> >  #include <libcamera/base/log.h>
> >  #include <libcamera/base/utils.h>
> > @@ -32,37 +35,8 @@ namespace libcamera {
> >  
> >  LOG_DEFINE_CATEGORY(Process)
> >  
> > -/**
> > - * \class ProcessManager
> > - * \brief Manager of processes
> > - *
> > - * The ProcessManager singleton keeps track of all created Process instances,
> > - * and manages the signal handling involved in terminating processes.
> > - */
> > -
> >  namespace {
> >  
> > -void sigact(int signal, siginfo_t *info, void *ucontext)
> > -{
> > -#pragma GCC diagnostic push
> > -#pragma GCC diagnostic ignored "-Wunused-result"
> > -       /*
> > -        * We're in a signal handler so we can't log any message, and we need
> > -        * to continue anyway.
> > -        */
> > -       char data = 0;
> > -       write(ProcessManager::instance()->writePipe(), &data, sizeof(data));
> > -#pragma GCC diagnostic pop
> > -
> > -       const struct sigaction &oldsa = ProcessManager::instance()->oldsa();
> > -       if (oldsa.sa_flags & SA_SIGINFO) {
> > -               oldsa.sa_sigaction(signal, info, ucontext);
> > -       } else {
> > -               if (oldsa.sa_handler != SIG_IGN && oldsa.sa_handler != SIG_DFL)
> > -                       oldsa.sa_handler(signal);
> > -       }
> > -}
> > -
> >  void closeAllFdsExcept(Span<const int> fds)
> >  {
> >         std::vector<int> v(fds.begin(), fds.end());
> > @@ -118,129 +92,6 @@ void closeAllFdsExcept(Span<const int> fds)
> >  
> >  } /* namespace */
> >  
> > -void ProcessManager::sighandler()
> > -{
> > -       char data;
> > -       ssize_t ret = read(pipe_[0].get(), &data, sizeof(data));
> > -       if (ret < 0) {
> > -               LOG(Process, Error)
> > -                       << "Failed to read byte from signal handler pipe";
> > -               return;
> > -       }
> > -
> > -       for (auto it = processes_.begin(); it != processes_.end();) {
> > -               Process *process = *it;
> > -
> > -               int wstatus;
> > -               pid_t pid = waitpid(process->pid_, &wstatus, WNOHANG);
> > -               if (process->pid_ != pid) {
> > -                       ++it;
> > -                       continue;
> > -               }
> > -
> > -               it = processes_.erase(it);
> > -               process->died(wstatus);
> > -       }
> > -}
> > -
> > -/**
> > - * \brief Register process with process manager
> > - * \param[in] proc Process to register
> > - *
> > - * This function registers the \a proc with the process manager. It
> > - * shall be called by the parent process after successfully forking, in
> > - * order to let the parent signal process termination.
> > - */
> > -void ProcessManager::registerProcess(Process *proc)
> > -{
> > -       processes_.push_back(proc);
> > -}
> > -
> > -ProcessManager *ProcessManager::self_ = nullptr;
> > -
> > -/**
> > - * \brief Construct a ProcessManager instance
> > - *
> > - * The ProcessManager class is meant to only be instantiated once, by the
> > - * CameraManager.
> > - */
> > -ProcessManager::ProcessManager()
> > -{
> > -       if (self_)
> > -               LOG(Process, Fatal)
> > -                       << "Multiple ProcessManager objects are not allowed";
> > -
> > -       sigaction(SIGCHLD, NULL, &oldsa_);
> > -
> > -       struct sigaction sa;
> > -       memset(&sa, 0, sizeof(sa));
> > -       sa.sa_sigaction = &sigact;
> > -       memcpy(&sa.sa_mask, &oldsa_.sa_mask, sizeof(sa.sa_mask));
> > -       sigaddset(&sa.sa_mask, SIGCHLD);
> > -       sa.sa_flags = oldsa_.sa_flags | SA_SIGINFO;
> > -
> > -       sigaction(SIGCHLD, &sa, NULL);
> > -
> > -       int pipe[2];
> > -       if (pipe2(pipe, O_CLOEXEC | O_DIRECT | O_NONBLOCK))
> > -               LOG(Process, Fatal)
> > -                       << "Failed to initialize pipe for signal handling";
> > -
> > -       pipe_[0] = UniqueFD(pipe[0]);
> > -       pipe_[1] = UniqueFD(pipe[1]);
> > -
> > -       sigEvent_ = new EventNotifier(pipe_[0].get(), EventNotifier::Read);
> > -       sigEvent_->activated.connect(this, &ProcessManager::sighandler);
> > -
> > -       self_ = this;
> > -}
> > -
> > -ProcessManager::~ProcessManager()
> > -{
> > -       sigaction(SIGCHLD, &oldsa_, NULL);
> > -
> > -       delete sigEvent_;
> > -
> > -       self_ = nullptr;
> > -}
> > -
> > -/**
> > - * \brief Retrieve the Process manager instance
> > - *
> > - * The ProcessManager is constructed by the CameraManager. This function shall
> > - * be used to retrieve the single instance of the manager.
> > - *
> > - * \return The Process manager instance
> > - */
> > -ProcessManager *ProcessManager::instance()
> > -{
> > -       return self_;
> > -}
> > -
> > -/**
> > - * \brief Retrieve the Process manager's write pipe
> > - *
> > - * This function is meant only to be used by the static signal handler.
> > - *
> > - * \return Pipe for writing
> > - */
> > -int ProcessManager::writePipe() const
> > -{
> > -       return pipe_[1].get();
> > -}
> > -
> > -/**
> > - * \brief Retrive the old signal action data
> > - *
> > - * This function is meant only to be used by the static signal handler.
> > - *
> > - * \return The old signal action data
> > - */
> > -const struct sigaction &ProcessManager::oldsa() const
> > -{
> > -       return oldsa_;
> > -}
> > -
> >  /**
> >   * \class Process
> >   * \brief Process object
> > @@ -291,8 +142,6 @@ int Process::start(const std::string &path,
> >                    Span<const std::string> args,
> >                    Span<const int> fds)
> >  {
> > -       int ret;
> > -
> >         if (pid_ > 0)
> >                 return -EBUSY;
> >  
> > @@ -301,20 +150,29 @@ int Process::start(const std::string &path,
> >                         return -EINVAL;
> >         }
> >  
> > -       int childPid = fork();
> > -       if (childPid == -1) {
> > -               ret = -errno;
> > +       clone_args cargs = {};
> > +       int pidfd;
> > +
> > +       cargs.flags = CLONE_CLEAR_SIGHAND | CLONE_PIDFD | CLONE_NEWUSER | CLONE_NEWNET;
> > +       cargs.pidfd = reinterpret_cast<uintptr_t>(&pidfd);
> > +       cargs.exit_signal = SIGCHLD;
> > +
> > +       long childPid = syscall(SYS_clone3, &cargs, sizeof(cargs));
> > +       if (childPid < 0) {
> > +               int ret = -errno;
> >                 LOG(Process, Error) << "Failed to fork: " << strerror(-ret);
> >                 return ret;
> > -       } else if (childPid) {
> > +       }
> > +
> > +       if (childPid) {
> >                 pid_ = childPid;
> > -               ProcessManager::instance()->registerProcess(this);
> > +               pidfd_ = UniqueFD(pidfd);
> > +               pidfdNotify_ = std::make_unique<EventNotifier>(pidfd_.get(), EventNotifier::Type::Read);
> > +               pidfdNotify_->activated.connect(this, &Process::onPidfdNotify);
> >  
> > -               return 0;
> > +               LOG(Process, Debug) << this << "[" << childPid << ':' << pidfd << "]"
> > +                                   << " forked";
> >         } else {
> > -               if (isolate())
> > -                       _exit(EXIT_FAILURE);
> > -
> >                 closeAllFdsExcept(fds);
> >  
> >                 const char *file = utils::secure_getenv("LIBCAMERA_LOG_FILE");
> > @@ -333,35 +191,44 @@ int Process::start(const std::string &path,
> >  
> >                 exit(EXIT_FAILURE);
> >         }
> > -}
> > -
> > -int Process::isolate()
> > -{
> > -       int ret = unshare(CLONE_NEWUSER | CLONE_NEWNET);
> > -       if (ret) {
> > -               ret = -errno;
> > -               LOG(Process, Error) << "Failed to unshare execution context: "
> > -                                   << strerror(-ret);
> > -               return ret;
> > -       }
> >  
> >         return 0;
> >  }
> >  
> > -/**
> > - * \brief SIGCHLD handler
> > - * \param[in] wstatus The status as output by waitpid()
> > - *
> > - * This function is called when the process associated with Process terminates.
> > - * It emits the Process::finished signal.
> > - */
> > -void Process::died(int wstatus)
> > +void Process::onPidfdNotify()
> >  {
> > -       pid_ = -1;
> > -       exitStatus_ = WIFEXITED(wstatus) ? NormalExit : SignalExit;
> > -       exitCode_ = exitStatus_ == NormalExit ? WEXITSTATUS(wstatus) : -1;
> > +       auto pidfdNotify = std::exchange(pidfdNotify_, {});
> > +       auto pidfd = std::exchange(pidfd_, {});
> > +       auto pid = std::exchange(pid_, -1);
> > +
> > +       ASSERT(pidfdNotify);
> > +       ASSERT(pidfd.isValid());
> > +       ASSERT(pid > 0);
> > +
> > +       siginfo_t info;
> > +
> > +       /*
> > +        * `static_cast` is needed because `P_PIDFD` is not defined in `sys/wait.h` if the C standard library
> > +        * is old enough. So `P_PIDFD` is taken from `linux/wait.h`, where it is just an integer #define.
> > +        */
> > +       if (waitid(static_cast<idtype_t>(P_PIDFD), pidfd.get(), &info, WNOHANG | WEXITED) >= 0) {
> > +               ASSERT(info.si_pid == pid);
> >  
> > -       finished.emit(exitStatus_, exitCode_);
> > +               LOG(Process, Debug)
> > +                       << this << "[" << pid << ':' << pidfd.get() << "]"
> > +                       << " code: " << info.si_code
> > +                       << " status: " << info.si_status;
> > +
> > +               exitStatus_ = info.si_code == CLD_EXITED ? Process::NormalExit : Process::SignalExit;
> > +               exitCode_ = info.si_code == CLD_EXITED ? info.si_status : -1;
> > +
> > +               finished.emit(exitStatus_, exitCode_);
> > +       } else {
> > +               int err = errno;
> > +               LOG(Process, Warning)
> > +                       << this << "[" << pid << ":" << pidfd.get() << "]"
> > +                       << " waitid() failed: " << strerror(err);
> 
> I wonder if there's any extra way we can get custom messages from the
> IPA's over to the main process to report /why/ they shut down in event
> of failures.
> 
> So frequently when IPA's are isolated - do we get difficult to support
> issues from users who's IPA isn't running - and we don't report in
> libcamera any thing about /why/
> 
> Anyway - I don't see anything wrong with this patch so :
> 
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> > +       }
> >  }
> >  
> >  /**
> > @@ -399,8 +266,8 @@ void Process::died(int wstatus)
> >   */
> >  void Process::kill()
> >  {
> > -       if (pid_ > 0)
> > -               ::kill(pid_, SIGKILL);
> > +       if (pidfd_.isValid())
> > +               syscall(SYS_pidfd_send_signal, pidfd_.get(), SIGKILL, nullptr, 0);
> >  }
> >  
> >  } /* namespace libcamera */
> > diff --git a/test/ipc/unixsocket_ipc.cpp b/test/ipc/unixsocket_ipc.cpp
> > index df7d9c2b4..f3e3c09ef 100644
> > --- a/test/ipc/unixsocket_ipc.cpp
> > +++ b/test/ipc/unixsocket_ipc.cpp
> > @@ -209,8 +209,6 @@ protected:
> >         }
> >  
> >  private:
> > -       ProcessManager processManager_;
> > -
> >         unique_ptr<IPCPipeUnixSocket> ipc_;
> >  };
> >  
> > diff --git a/test/log/log_process.cpp b/test/log/log_process.cpp
> > index 9609e23d5..367b78803 100644
> > --- a/test/log/log_process.cpp
> > +++ b/test/log/log_process.cpp
> > @@ -137,8 +137,6 @@ private:
> >                 exitCode_ = exitCode;
> >         }
> >  
> > -       ProcessManager processManager_;
> > -
> >         Process proc_;
> >         Process::ExitStatus exitStatus_ = Process::NotExited;
> >         string logPath_;
> > diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp
> > index a88d8fef1..fe76666e6 100644
> > --- a/test/process/process_test.cpp
> > +++ b/test/process/process_test.cpp
> > @@ -86,8 +86,6 @@ private:
> >                 exitCode_ = exitCode;
> >         }
> >  
> > -       ProcessManager processManager_;
> > -
> >         Process proc_;
> >         enum Process::ExitStatus exitStatus_;
> >         int exitCode_;

Patch
diff mbox series

diff --git a/include/libcamera/internal/camera_manager.h b/include/libcamera/internal/camera_manager.h
index 0150ca61f..5dfbe1f65 100644
--- a/include/libcamera/internal/camera_manager.h
+++ b/include/libcamera/internal/camera_manager.h
@@ -65,7 +65,6 @@  private:
 	std::unique_ptr<DeviceEnumerator> enumerator_;
 
 	std::unique_ptr<IPAManager> ipaManager_;
-	ProcessManager processManager_;
 };
 
 } /* namespace libcamera */
diff --git a/include/libcamera/internal/process.h b/include/libcamera/internal/process.h
index e55d11fa3..27d4b9258 100644
--- a/include/libcamera/internal/process.h
+++ b/include/libcamera/internal/process.h
@@ -7,7 +7,6 @@ 
 
 #pragma once
 
-#include <signal.h>
 #include <string>
 
 #include <libcamera/base/class.h>
@@ -45,41 +44,14 @@  public:
 private:
 	LIBCAMERA_DISABLE_COPY_AND_MOVE(Process)
 
-	int isolate();
-	void died(int wstatus);
+	void onPidfdNotify();
 
 	pid_t pid_;
 	enum ExitStatus exitStatus_;
 	int exitCode_;
 
-	friend class ProcessManager;
-};
-
-class ProcessManager
-{
-public:
-	ProcessManager();
-	~ProcessManager();
-
-	void registerProcess(Process *proc);
-
-	static ProcessManager *instance();
-
-	int writePipe() const;
-
-	const struct sigaction &oldsa() const;
-
-private:
-	static ProcessManager *self_;
-
-	void sighandler();
-
-	std::list<Process *> processes_;
-
-	struct sigaction oldsa_;
-
-	EventNotifier *sigEvent_;
-	UniqueFD pipe_[2];
+	UniqueFD pidfd_;
+	std::unique_ptr<EventNotifier> pidfdNotify_;
 };
 
 } /* namespace libcamera */
diff --git a/meson.build b/meson.build
index ae13727eb..3c895ae9e 100644
--- a/meson.build
+++ b/meson.build
@@ -265,7 +265,7 @@  subdir('Documentation')
 subdir('test')
 
 if not meson.is_cross_build()
-    kernel_version_req = '>= 5.0.0'
+    kernel_version_req = '>= 5.4.0'
     kernel_version = run_command('uname', '-r', check : true).stdout().strip()
     if not kernel_version.version_compare(kernel_version_req)
         warning('The current running kernel version @0@ is too old to run libcamera.'
diff --git a/src/libcamera/process.cpp b/src/libcamera/process.cpp
index 42b56374d..b01c78034 100644
--- a/src/libcamera/process.cpp
+++ b/src/libcamera/process.cpp
@@ -10,15 +10,18 @@ 
 #include <algorithm>
 #include <dirent.h>
 #include <fcntl.h>
-#include <list>
 #include <signal.h>
 #include <string.h>
 #include <sys/socket.h>
+#include <sys/syscall.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
 #include <vector>
 
+#include <linux/sched.h>
+#include <linux/wait.h> /* glibc only provides `P_PIDFD` in `sys/wait.h` since 2.36 */
+
 #include <libcamera/base/event_notifier.h>
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
@@ -32,37 +35,8 @@  namespace libcamera {
 
 LOG_DEFINE_CATEGORY(Process)
 
-/**
- * \class ProcessManager
- * \brief Manager of processes
- *
- * The ProcessManager singleton keeps track of all created Process instances,
- * and manages the signal handling involved in terminating processes.
- */
-
 namespace {
 
-void sigact(int signal, siginfo_t *info, void *ucontext)
-{
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-result"
-	/*
-	 * We're in a signal handler so we can't log any message, and we need
-	 * to continue anyway.
-	 */
-	char data = 0;
-	write(ProcessManager::instance()->writePipe(), &data, sizeof(data));
-#pragma GCC diagnostic pop
-
-	const struct sigaction &oldsa = ProcessManager::instance()->oldsa();
-	if (oldsa.sa_flags & SA_SIGINFO) {
-		oldsa.sa_sigaction(signal, info, ucontext);
-	} else {
-		if (oldsa.sa_handler != SIG_IGN && oldsa.sa_handler != SIG_DFL)
-			oldsa.sa_handler(signal);
-	}
-}
-
 void closeAllFdsExcept(Span<const int> fds)
 {
 	std::vector<int> v(fds.begin(), fds.end());
@@ -118,129 +92,6 @@  void closeAllFdsExcept(Span<const int> fds)
 
 } /* namespace */
 
-void ProcessManager::sighandler()
-{
-	char data;
-	ssize_t ret = read(pipe_[0].get(), &data, sizeof(data));
-	if (ret < 0) {
-		LOG(Process, Error)
-			<< "Failed to read byte from signal handler pipe";
-		return;
-	}
-
-	for (auto it = processes_.begin(); it != processes_.end();) {
-		Process *process = *it;
-
-		int wstatus;
-		pid_t pid = waitpid(process->pid_, &wstatus, WNOHANG);
-		if (process->pid_ != pid) {
-			++it;
-			continue;
-		}
-
-		it = processes_.erase(it);
-		process->died(wstatus);
-	}
-}
-
-/**
- * \brief Register process with process manager
- * \param[in] proc Process to register
- *
- * This function registers the \a proc with the process manager. It
- * shall be called by the parent process after successfully forking, in
- * order to let the parent signal process termination.
- */
-void ProcessManager::registerProcess(Process *proc)
-{
-	processes_.push_back(proc);
-}
-
-ProcessManager *ProcessManager::self_ = nullptr;
-
-/**
- * \brief Construct a ProcessManager instance
- *
- * The ProcessManager class is meant to only be instantiated once, by the
- * CameraManager.
- */
-ProcessManager::ProcessManager()
-{
-	if (self_)
-		LOG(Process, Fatal)
-			<< "Multiple ProcessManager objects are not allowed";
-
-	sigaction(SIGCHLD, NULL, &oldsa_);
-
-	struct sigaction sa;
-	memset(&sa, 0, sizeof(sa));
-	sa.sa_sigaction = &sigact;
-	memcpy(&sa.sa_mask, &oldsa_.sa_mask, sizeof(sa.sa_mask));
-	sigaddset(&sa.sa_mask, SIGCHLD);
-	sa.sa_flags = oldsa_.sa_flags | SA_SIGINFO;
-
-	sigaction(SIGCHLD, &sa, NULL);
-
-	int pipe[2];
-	if (pipe2(pipe, O_CLOEXEC | O_DIRECT | O_NONBLOCK))
-		LOG(Process, Fatal)
-			<< "Failed to initialize pipe for signal handling";
-
-	pipe_[0] = UniqueFD(pipe[0]);
-	pipe_[1] = UniqueFD(pipe[1]);
-
-	sigEvent_ = new EventNotifier(pipe_[0].get(), EventNotifier::Read);
-	sigEvent_->activated.connect(this, &ProcessManager::sighandler);
-
-	self_ = this;
-}
-
-ProcessManager::~ProcessManager()
-{
-	sigaction(SIGCHLD, &oldsa_, NULL);
-
-	delete sigEvent_;
-
-	self_ = nullptr;
-}
-
-/**
- * \brief Retrieve the Process manager instance
- *
- * The ProcessManager is constructed by the CameraManager. This function shall
- * be used to retrieve the single instance of the manager.
- *
- * \return The Process manager instance
- */
-ProcessManager *ProcessManager::instance()
-{
-	return self_;
-}
-
-/**
- * \brief Retrieve the Process manager's write pipe
- *
- * This function is meant only to be used by the static signal handler.
- *
- * \return Pipe for writing
- */
-int ProcessManager::writePipe() const
-{
-	return pipe_[1].get();
-}
-
-/**
- * \brief Retrive the old signal action data
- *
- * This function is meant only to be used by the static signal handler.
- *
- * \return The old signal action data
- */
-const struct sigaction &ProcessManager::oldsa() const
-{
-	return oldsa_;
-}
-
 /**
  * \class Process
  * \brief Process object
@@ -291,8 +142,6 @@  int Process::start(const std::string &path,
 		   Span<const std::string> args,
 		   Span<const int> fds)
 {
-	int ret;
-
 	if (pid_ > 0)
 		return -EBUSY;
 
@@ -301,20 +150,29 @@  int Process::start(const std::string &path,
 			return -EINVAL;
 	}
 
-	int childPid = fork();
-	if (childPid == -1) {
-		ret = -errno;
+	clone_args cargs = {};
+	int pidfd;
+
+	cargs.flags = CLONE_CLEAR_SIGHAND | CLONE_PIDFD | CLONE_NEWUSER | CLONE_NEWNET;
+	cargs.pidfd = reinterpret_cast<uintptr_t>(&pidfd);
+	cargs.exit_signal = SIGCHLD;
+
+	long childPid = syscall(SYS_clone3, &cargs, sizeof(cargs));
+	if (childPid < 0) {
+		int ret = -errno;
 		LOG(Process, Error) << "Failed to fork: " << strerror(-ret);
 		return ret;
-	} else if (childPid) {
+	}
+
+	if (childPid) {
 		pid_ = childPid;
-		ProcessManager::instance()->registerProcess(this);
+		pidfd_ = UniqueFD(pidfd);
+		pidfdNotify_ = std::make_unique<EventNotifier>(pidfd_.get(), EventNotifier::Type::Read);
+		pidfdNotify_->activated.connect(this, &Process::onPidfdNotify);
 
-		return 0;
+		LOG(Process, Debug) << this << "[" << childPid << ':' << pidfd << "]"
+				    << " forked";
 	} else {
-		if (isolate())
-			_exit(EXIT_FAILURE);
-
 		closeAllFdsExcept(fds);
 
 		const char *file = utils::secure_getenv("LIBCAMERA_LOG_FILE");
@@ -333,35 +191,44 @@  int Process::start(const std::string &path,
 
 		exit(EXIT_FAILURE);
 	}
-}
-
-int Process::isolate()
-{
-	int ret = unshare(CLONE_NEWUSER | CLONE_NEWNET);
-	if (ret) {
-		ret = -errno;
-		LOG(Process, Error) << "Failed to unshare execution context: "
-				    << strerror(-ret);
-		return ret;
-	}
 
 	return 0;
 }
 
-/**
- * \brief SIGCHLD handler
- * \param[in] wstatus The status as output by waitpid()
- *
- * This function is called when the process associated with Process terminates.
- * It emits the Process::finished signal.
- */
-void Process::died(int wstatus)
+void Process::onPidfdNotify()
 {
-	pid_ = -1;
-	exitStatus_ = WIFEXITED(wstatus) ? NormalExit : SignalExit;
-	exitCode_ = exitStatus_ == NormalExit ? WEXITSTATUS(wstatus) : -1;
+	auto pidfdNotify = std::exchange(pidfdNotify_, {});
+	auto pidfd = std::exchange(pidfd_, {});
+	auto pid = std::exchange(pid_, -1);
+
+	ASSERT(pidfdNotify);
+	ASSERT(pidfd.isValid());
+	ASSERT(pid > 0);
+
+	siginfo_t info;
+
+	/*
+	 * `static_cast` is needed because `P_PIDFD` is not defined in `sys/wait.h` if the C standard library
+	 * is old enough. So `P_PIDFD` is taken from `linux/wait.h`, where it is just an integer #define.
+	 */
+	if (waitid(static_cast<idtype_t>(P_PIDFD), pidfd.get(), &info, WNOHANG | WEXITED) >= 0) {
+		ASSERT(info.si_pid == pid);
 
-	finished.emit(exitStatus_, exitCode_);
+		LOG(Process, Debug)
+			<< this << "[" << pid << ':' << pidfd.get() << "]"
+			<< " code: " << info.si_code
+			<< " status: " << info.si_status;
+
+		exitStatus_ = info.si_code == CLD_EXITED ? Process::NormalExit : Process::SignalExit;
+		exitCode_ = info.si_code == CLD_EXITED ? info.si_status : -1;
+
+		finished.emit(exitStatus_, exitCode_);
+	} else {
+		int err = errno;
+		LOG(Process, Warning)
+			<< this << "[" << pid << ":" << pidfd.get() << "]"
+			<< " waitid() failed: " << strerror(err);
+	}
 }
 
 /**
@@ -399,8 +266,8 @@  void Process::died(int wstatus)
  */
 void Process::kill()
 {
-	if (pid_ > 0)
-		::kill(pid_, SIGKILL);
+	if (pidfd_.isValid())
+		syscall(SYS_pidfd_send_signal, pidfd_.get(), SIGKILL, nullptr, 0);
 }
 
 } /* namespace libcamera */
diff --git a/test/ipc/unixsocket_ipc.cpp b/test/ipc/unixsocket_ipc.cpp
index df7d9c2b4..f3e3c09ef 100644
--- a/test/ipc/unixsocket_ipc.cpp
+++ b/test/ipc/unixsocket_ipc.cpp
@@ -209,8 +209,6 @@  protected:
 	}
 
 private:
-	ProcessManager processManager_;
-
 	unique_ptr<IPCPipeUnixSocket> ipc_;
 };
 
diff --git a/test/log/log_process.cpp b/test/log/log_process.cpp
index 9609e23d5..367b78803 100644
--- a/test/log/log_process.cpp
+++ b/test/log/log_process.cpp
@@ -137,8 +137,6 @@  private:
 		exitCode_ = exitCode;
 	}
 
-	ProcessManager processManager_;
-
 	Process proc_;
 	Process::ExitStatus exitStatus_ = Process::NotExited;
 	string logPath_;
diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp
index a88d8fef1..fe76666e6 100644
--- a/test/process/process_test.cpp
+++ b/test/process/process_test.cpp
@@ -86,8 +86,6 @@  private:
 		exitCode_ = exitCode;
 	}
 
-	ProcessManager processManager_;
-
 	Process proc_;
 	enum Process::ExitStatus exitStatus_;
 	int exitCode_;