Skip to content

Eliminate busy-loop in psutil.wait_procs() #2712

@giampaolo

Description

@giampaolo

In #2706 we eliminated the busy loop from Process.wait() by adopting an event-driven approach. psutil.wait_procs() (which waits for multiple PIDs, see doc) could benefit from a similar improvement.

Linux

select.poll() can efficiently wait for multiple PIDs at once. However, the downside is that each call to os.pidfd_open() consumes one file descriptor per monitored PID. If the number of PIDs is large, the process can hit the per-process FD limit (typically 1024) and fail with EMFILE. This limit can be increased (assuming the hard limit allows it) with:

import resource

soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
new_soft = min(soft * 4, hard)
if new_soft > soft:
    resource.setrlimit(resource.RLIMIT_NOFILE, (new_soft, hard))

...still, it's not something we can rely on. Possible solutions:

  • use a fallback mechanism (use pidfd_open() first, else fallback to the busy loop approach)
  • like above (use a fallback) and, in addition, determine the file descriptor limit first (e.g. 1024) and only use the fast path if the number of PIDs is less than half that limit (512); otherwise fall back, to avoid exhausting the process’s file descriptors.
  • use AF_LINK + PROC_EVENT_EXIT to monitor gone PIDs, see partial idea in An API for monitoring new PIDs / processes #2348. Drawback: it could actually be worse in case many PIDs are created while waiting.

BSD / macOS

The easy one. We can simply use kqueue() + KQ_FILTER_PROC + KQ_NOTE_EXIT as we're doing right now, we'll just pass multiple PIDs instead of 1.

Windows

We can use WaitForMultipleObjects().

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions