Description
Proposal
Add a new WithHandle
method for os.Process
, which, if a handle is available, calls a specified callback function with the process handle as an argument. On Linux, handle is a pidfd (file descriptor referring to the process). On Windows, handle is a handle to the process.
// WithHandle calls a supplied function f with a valid process handle as an argument.
// The handle is guaranteed to refer to process p until f returns, even if p terminates.
// If no process handle is available, it returns ErrNoHandle.
//
// Currently, process handles are only available on Linux 5.4 and later
// (where it is known as pidfd) and Windows.
func (p *Process) WithHandle(f func(handle uintptr)) error
var ErrNoHandle = errors.New("process handle unavailable")
Context
This is a continuation of #62654, in particular its item 6, described in there as:
- (Optional) Add
(*Process).Handle() uintptr
method to return process handle on Windows and pidfd on Linux. This might be useful for low-level operations that require handle/pidfd (e.g. pidfd_getfd on Linux), or to ensure that pidfd (rather than pid) is being used for kill/wait.
Also, a similar thing was proposed earlier here by @prattmic:
A new
os.Process.Fd()
could return the pid FD for additional direct use.
Use cases
1. Check if pidfd is being used on Linux.
Since Go 1.23, pidfd is used for os.Process-related operations instead of pid, if supported by the Linux kernel. This includes os.StartProcess
and os.FindProcess
(they obtain a pidfd), as well as (*Process).Wait
, (*Process).Signal
, and (*Process).Kill
(they use pidfd). The main benefit of pidfd in the use cases above is a guarantee we're referring to the same process (i.e. there's no pid reuse issue).
However, since this is done in a fully transparent way, there is no way for a user to know if pidfd is being used or not. Some programs implement some protection against pid reuse (for example, runc
and cri-o
obtain and check process start time from /proc/<pid>/stat
). They can benefit from being able to know if Go is using pidfd internally.
Another example is containerd which relies on Go 1.23 using pidfd internally, but since there's no way to check they had to recreate all the functionality checking for pidfd support here (which is still not 100% correct since the checks are slightly different from those in Go's checkPidfd, and Go checks may change over time ). Cc @fuweid.
With the proposed interface, a user can easily check if pidfd is being used:
p, err := os.FindProcess()
pidfdUsed := false
p.WithHandle(func(_ uintptr) {
pidfdUsed = true
})
2. Use the existing pidfd directly.
Aside from use cases already covered by existing os.Process
methods, pidfd can also be used to:
- obtain a duplicate of a file descriptor of another process (pidfd_getfd(2));
- select/poll/epoll on a pidfd to know when a process is terminated;
- move a process/thread into one or more of the same namespaces as the process referred to by the file descriptor (setns(2)).
Other use cases may emerge in the future.
Currently, the only way to obtain a pidfd on Linux is to execute a new process (via os.StartProcess
or os/exec
) with process' Attr.SysAttr.PidFD
field set. This works if we're starting the process, but not in any other case (someone else starts a process for us, or it is already running).
Questions / discussion points
1. What are (could be) the additional direct use cases of Windows process handle?
A few are listed here. Apparently some git grep (GetPriorityClass, SetPriorityClass, AssignProcessToJobObject) are implemented in golang.org/x/sys/windows.
2. Should a duplicate of a handle be used, or the original handle?
Use the original one.
Arguments against duplicated handle:
- a duplicated pidfd makes the "check if pidfd is being used" use case above more complicated as the user will need to close the returned pidfd;
- a user can always use dupfd if/when needed;
- returned handle won't leak as it is still part of os.Process struct, which have a
Release
method and a proper finalizer; - os.File.Fd returns the original underlying fd, pidfd is similar.
Arguments for duplicated handle:
- cleaner separation of responsibilities(?);
- a Windows process handle can be duplicated;
3. Should WithHandle
provide *os.File
rather than uintptr
?
Raw handle makes more sense in this case, and *os.File won't work with Windows process handle as it is not a file descriptor.
4. Should this be Linux-specific?
Probably not. Since we have a boolean flag returned, we can implement it for all platforms and do nothing for those that do not support process handle (other than Windows and Linux).
Metadata
Metadata
Assignees
Type
Projects
Status