mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
os,internal/poll: don't call IsNonblock for consoles and Stdin
windows.IsNonblock can block for synchronous handles that have an outstanding I/O operation. Console handles are always synchronous, so we should not call IsNonblock for them. Stdin is often a pipe, and almost always a synchronous handle, so we should not call IsNonblock for it either. This avoids potential deadlocks during os package initialization, which calls NewFile(syscall.Stdin). Fixes #75949 Updates #76391 Cq-Include-Trybots: luci.golang.try:gotip-windows-amd64-longtest,gotip-windows-amd64-race,gotip-windows-arm64 Change-Id: I1603932b0a99823019aa0cad960f94cee9996505 Reviewed-on: https://go-review.googlesource.com/c/go/+/724640 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Damien Neil <dneil@google.com> Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
parent
71f8f031b2
commit
437d2362ce
5 changed files with 95 additions and 13 deletions
|
|
@ -451,6 +451,10 @@ func (fd *FD) Init(net string, pollable bool) error {
|
||||||
fd.isFile = fd.kind != kindNet
|
fd.isFile = fd.kind != kindNet
|
||||||
fd.isBlocking = !pollable
|
fd.isBlocking = !pollable
|
||||||
|
|
||||||
|
if !pollable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// It is safe to add overlapped handles that also perform I/O
|
// It is safe to add overlapped handles that also perform I/O
|
||||||
// outside of the runtime poller. The runtime poller will ignore
|
// outside of the runtime poller. The runtime poller will ignore
|
||||||
// I/O completion notifications not initiated by us.
|
// I/O completion notifications not initiated by us.
|
||||||
|
|
|
||||||
|
|
@ -44,20 +44,60 @@ func (file *File) fd() uintptr {
|
||||||
return uintptr(file.pfd.Sysfd)
|
return uintptr(file.pfd.Sysfd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newFileKind describes the kind of file to newFile.
|
||||||
|
type newFileKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// kindNewFile means that the descriptor was passed to us via NewFile.
|
||||||
|
kindNewFile newFileKind = iota
|
||||||
|
// kindOpenFile means that the descriptor was opened using
|
||||||
|
// Open, Create, or OpenFile.
|
||||||
|
kindOpenFile
|
||||||
|
// kindPipe means that the descriptor was opened using Pipe.
|
||||||
|
kindPipe
|
||||||
|
// kindSock means that the descriptor is a network file descriptor
|
||||||
|
// that was created from net package and was opened using net_newUnixFile.
|
||||||
|
kindSock
|
||||||
|
// kindConsole means that the descriptor is a console handle.
|
||||||
|
kindConsole
|
||||||
|
)
|
||||||
|
|
||||||
// newFile returns a new File with the given file handle and name.
|
// newFile returns a new File with the given file handle and name.
|
||||||
// Unlike NewFile, it does not check that h is syscall.InvalidHandle.
|
// Unlike NewFile, it does not check that h is syscall.InvalidHandle.
|
||||||
// If nonBlocking is true, it tries to add the file to the runtime poller.
|
// If nonBlocking is true, it tries to add the file to the runtime poller.
|
||||||
func newFile(h syscall.Handle, name string, kind string, nonBlocking bool) *File {
|
func newFile(h syscall.Handle, name string, kind newFileKind, nonBlocking bool) *File {
|
||||||
if kind == "file" {
|
typ := "file"
|
||||||
|
switch kind {
|
||||||
|
case kindNewFile, kindOpenFile:
|
||||||
t, err := syscall.GetFileType(h)
|
t, err := syscall.GetFileType(h)
|
||||||
if err != nil || t == syscall.FILE_TYPE_CHAR {
|
if err != nil || t == syscall.FILE_TYPE_CHAR {
|
||||||
var m uint32
|
var m uint32
|
||||||
if syscall.GetConsoleMode(h, &m) == nil {
|
if syscall.GetConsoleMode(h, &m) == nil {
|
||||||
kind = "console"
|
typ = "console"
|
||||||
|
// Console handles are always blocking.
|
||||||
|
break
|
||||||
}
|
}
|
||||||
} else if t == syscall.FILE_TYPE_PIPE {
|
} else if t == syscall.FILE_TYPE_PIPE {
|
||||||
kind = "pipe"
|
typ = "pipe"
|
||||||
}
|
}
|
||||||
|
// NewFile doesn't know if a handle is blocking or non-blocking,
|
||||||
|
// so we try to detect that here. This call may block/ if the handle
|
||||||
|
// is blocking and there is an outstanding I/O operation.
|
||||||
|
//
|
||||||
|
// Avoid doing this for Stdin, which is almost always blocking and might
|
||||||
|
// be in use by other process when the "os" package is initializing.
|
||||||
|
// See go.dev/issue/75949 and go.dev/issue/76391.
|
||||||
|
if kind == kindNewFile && h != syscall.Stdin {
|
||||||
|
nonBlocking, _ = windows.IsNonblock(h)
|
||||||
|
}
|
||||||
|
case kindPipe:
|
||||||
|
typ = "pipe"
|
||||||
|
case kindSock:
|
||||||
|
typ = "file+net"
|
||||||
|
case kindConsole:
|
||||||
|
typ = "console"
|
||||||
|
default:
|
||||||
|
panic("newFile with unknown kind")
|
||||||
}
|
}
|
||||||
|
|
||||||
f := &File{&file{
|
f := &File{&file{
|
||||||
|
|
@ -72,13 +112,13 @@ func newFile(h syscall.Handle, name string, kind string, nonBlocking bool) *File
|
||||||
|
|
||||||
// Ignore initialization errors.
|
// Ignore initialization errors.
|
||||||
// Assume any problems will show up in later I/O.
|
// Assume any problems will show up in later I/O.
|
||||||
f.pfd.Init(kind, nonBlocking)
|
f.pfd.Init(typ, nonBlocking)
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// newConsoleFile creates new File that will be used as console.
|
// newConsoleFile creates new File that will be used as console.
|
||||||
func newConsoleFile(h syscall.Handle, name string) *File {
|
func newConsoleFile(h syscall.Handle, name string) *File {
|
||||||
return newFile(h, name, "console", false)
|
return newFile(h, name, kindConsole, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newFileFromNewFile is called by [NewFile].
|
// newFileFromNewFile is called by [NewFile].
|
||||||
|
|
@ -87,8 +127,7 @@ func newFileFromNewFile(fd uintptr, name string) *File {
|
||||||
if h == syscall.InvalidHandle {
|
if h == syscall.InvalidHandle {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
nonBlocking, _ := windows.IsNonblock(syscall.Handle(fd))
|
return newFile(h, name, kindNewFile, false)
|
||||||
return newFile(h, name, "file", nonBlocking)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// net_newWindowsFile is a hidden entry point called by net.conn.File.
|
// net_newWindowsFile is a hidden entry point called by net.conn.File.
|
||||||
|
|
@ -100,7 +139,7 @@ func net_newWindowsFile(h syscall.Handle, name string) *File {
|
||||||
if h == syscall.InvalidHandle {
|
if h == syscall.InvalidHandle {
|
||||||
panic("invalid FD")
|
panic("invalid FD")
|
||||||
}
|
}
|
||||||
return newFile(h, name, "file+net", true)
|
return newFile(h, name, kindSock, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func epipecheck(file *File, e error) {
|
func epipecheck(file *File, e error) {
|
||||||
|
|
@ -121,7 +160,7 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
|
||||||
return nil, &PathError{Op: "open", Path: name, Err: err}
|
return nil, &PathError{Op: "open", Path: name, Err: err}
|
||||||
}
|
}
|
||||||
nonblocking := flag&windows.O_FILE_FLAG_OVERLAPPED != 0
|
nonblocking := flag&windows.O_FILE_FLAG_OVERLAPPED != 0
|
||||||
return newFile(r, name, "file", nonblocking), nil
|
return newFile(r, name, kindOpenFile, nonblocking), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openDirNolog(name string) (*File, error) {
|
func openDirNolog(name string) (*File, error) {
|
||||||
|
|
@ -235,7 +274,7 @@ func Pipe() (r *File, w *File, err error) {
|
||||||
return nil, nil, NewSyscallError("pipe", e)
|
return nil, nil, NewSyscallError("pipe", e)
|
||||||
}
|
}
|
||||||
// syscall.Pipe always returns a non-blocking handle.
|
// syscall.Pipe always returns a non-blocking handle.
|
||||||
return newFile(p[0], "|0", "pipe", false), newFile(p[1], "|1", "pipe", false), nil
|
return newFile(p[0], "|0", kindPipe, false), newFile(p[1], "|1", kindPipe, false), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var useGetTempPath2 = sync.OnceValue(func() bool {
|
var useGetTempPath2 = sync.OnceValue(func() bool {
|
||||||
|
|
|
||||||
|
|
@ -2288,3 +2288,42 @@ func TestOpenFileTruncateNamedPipe(t *testing.T) {
|
||||||
}
|
}
|
||||||
f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewFileStdinBlocked(t *testing.T) {
|
||||||
|
// See https://go.dev/issue/75949.
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Use a subprocess to test that os.NewFile on a blocked stdin works.
|
||||||
|
// Can't do it in the same process because os.NewFile would close
|
||||||
|
// stdin for the whole test process once the test ends.
|
||||||
|
if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
|
||||||
|
// In the child process, just exit.
|
||||||
|
// If we get here, the os package successfully initialized.
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
name := pipeName()
|
||||||
|
stdin := newBytePipe(t, name, false)
|
||||||
|
file := newFileOverlapped(t, name, false)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Go(func() {
|
||||||
|
// Block stdin on a read.
|
||||||
|
if _, err := stdin.Read(make([]byte, 1)); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond) // Give time for the read to start.
|
||||||
|
cmd := testenv.CommandContext(t, t.Context(), testenv.Executable(t), fmt.Sprintf("-test.run=^%s$", t.Name()))
|
||||||
|
cmd.Env = cmd.Environ()
|
||||||
|
cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
|
||||||
|
cmd.Stdin = stdin
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Unblock the read to let the goroutine exit.
|
||||||
|
if _, err := file.Write(make([]byte, 1)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wg.Wait() // Don't leave goroutines behind.
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,5 @@ func isErrNoFollow(err error) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDirFile(fd syscall.Handle, name string) (*File, error) {
|
func newDirFile(fd syscall.Handle, name string) (*File, error) {
|
||||||
return newFile(fd, name, "file", false), nil
|
return newFile(fd, name, kindOpenFile, false), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File,
|
||||||
return nil, &PathError{Op: "openat", Path: name, Err: err}
|
return nil, &PathError{Op: "openat", Path: name, Err: err}
|
||||||
}
|
}
|
||||||
// openat always returns a non-blocking handle.
|
// openat always returns a non-blocking handle.
|
||||||
return newFile(fd, joinPath(root.Name(), name), "file", false), nil
|
return newFile(fd, joinPath(root.Name(), name), kindOpenFile, false), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openat(dirfd syscall.Handle, name string, flag uint64, perm FileMode) (syscall.Handle, error) {
|
func openat(dirfd syscall.Handle, name string, flag uint64, perm FileMode) (syscall.Handle, error) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue