Skip to content

Conversation

@rata
Copy link
Member

@rata rata commented Mar 14, 2025

This PR adds two helpers:

  • RetryOnEINTR(): This helper takes a function that returns an error and retries until that error is not EINTR
  • RetryOnEINTR2(): This helper returns an int and an error. This is useful for functions like unix.Open(). While we could handle them with the previous helper using go closures, when coding that it doesn't look nice: we need to declar the vars before and it gets kind of long. With this helper, we can just use the synta: fd, err := utils.RetryOnEINTR2()... and declare the vars on assignment, which is much nicer.

Then, the rest of the commits use them for unix.Open and unix.Dup*(), that were not properly handled and switches the existing code with open-coded versions to use the helpers.

Please note the comment on the commit about unix.Open on why I didn't try to switch some cases to os.OpenFile(). I'd like to tackle that later, on another PR.

@rata rata force-pushed the handle-more-EINTR branch from 9a82ada to 90d4a06 Compare March 14, 2025 17:48
Comment on lines 30 to 35
fd, err := utils.RetryOnEINTR2(func() (int, error) {
return unix.Open(slavePath, unix.O_RDWR, 0)
})
if err != nil {
return &os.PathError{
Op: "open",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I would go one step further and create libcontainer/internal/unix (or list internal/unix) and implement open like this:

func OpenAt(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
   fd, err := retryOnEINTR2(unix.Openat(dirfd, path, flags, mode))
   if err != nil {
     op := "open"
     if dirfd != unix.AT_FDCWD {
       op = "openat"
       path = filepath.Join("/proc/self/fd/", strconv.Itoa(fd), path)
     }
     return -1, &os.PathError{Op: op, Path: path, Err: err}
   }
   return fd, nil
}

func Open(path string, flags int, mode uint32) (fd int, err error) {
  return OpenAt(unix.AT_FDCWD, path, flags, mode)
}

and then use these everywhere.

Or, just switch the code that uses unix.Open to os.Open and f.Fd() (and only add/use OpenAt).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And for openat, we can actually explore using os.Root (https://pkg.go.dev/os@master#Root) and its Open (which properly uses openat(2) under the hood) -- but this needs to wait as it is only there since Go 1.24.

I mean, except for cases when we have openat2 with fancy flags.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to do this. But we already have an openAt implementation in utils:

flags |= unix.O_CLOEXEC

And it's different, see that line that adds the close on exec, for example. It also returns an os.File instead of an fd, as the function you put here does.

IMHO it seems better in this PR to keep it about handling EINTR properly and we can do another PR later that tries to use os.Open or even unify to use an internal implementation of Open/OpenAt that handles retries, maybe adds close on exec, etc.

What do you think?

Copy link
Contributor

@kolyshkin kolyshkin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, in general, I think it would be better to create our own wrappers for functions that we think should handle retry on EINTR, to keep the retry internal (and the code easier).

Copy link
Contributor

@kolyshkin kolyshkin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it's beneficial to move some stuff that you touch from libcontainer/system and libcontainer/utils to libcontainer/internal/whatever (and keep a backward-compatible alias/wrapper marked as deprecated for some time).

Copy link
Contributor

@kolyshkin kolyshkin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meant to "request changes" but I guess my hand slipped :)

@rata rata force-pushed the handle-more-EINTR branch from 90d4a06 to 7de268b Compare March 18, 2025 17:32
@rata
Copy link
Member Author

rata commented Mar 18, 2025

Thanks! I fixed some issues, will ping for more reviews when I fix them all

rata added 2 commits March 24, 2025 12:58
Adding them to internal to not expose new functions in libct. We can
tackle moving more code to internal in other PRs, where we discuss
exactly for which functions we want to provide deprecated wrappers and
for which ones we don't.

Signed-off-by: Rodrigo Campos <rodrigoca@microsoft.com>
unix.Open() can be interrupted, so let's retry on EINTR.

In future PRs I'd explore using os.OpenFile and friends, but let's add a
retry for now because:
	* os.OpenFile() means the GC can close the fd and that can cause
	  a regression
	* We don't have many tests for some of these calls
	* If we cause a regression and we have to revert, we should
	  revert to an option that retries on EINTR.

For those reasons, let's just add a retry on EINTR for now.

Signed-off-by: Rodrigo Campos <rodrigoca@microsoft.com>
@kolyshkin kolyshkin marked this pull request as draft March 24, 2025 19:05
@kolyshkin
Copy link
Contributor

Converted to draft; @rata please change it back to non-draft once it's ready for review.

rata added 2 commits March 25, 2025 12:14
Signed-off-by: Rodrigo Campos <rodrigoca@microsoft.com>
Let's move the existing code to the new helper. There are two places
that are not moved, because they currently use an unbounded for loop for
other reasons and the code seems cleaner keeping it as it is.

Signed-off-by: Rodrigo Campos <rodrigoca@microsoft.com>
@rata rata force-pushed the handle-more-EINTR branch from 7de268b to e9920e7 Compare March 25, 2025 11:43
@rata rata marked this pull request as ready for review March 25, 2025 11:54
@rata rata requested review from cyphar and kolyshkin March 25, 2025 11:54
@rata
Copy link
Member Author

rata commented Mar 25, 2025

@kolyshkin PTAL

// RetryOnEINTR takes a function that returns an error and calls it until it the error returned is
// not EINTR.
func RetryOnEINTR(fn func() error) error {
var err error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: no need to instantiate this, you can use err := inside the loop, or have a named return variable.

@kolyshkin
Copy link
Contributor

Closing in favor of #4697

@kolyshkin kolyshkin closed this Mar 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants