Skip to content

Commit b950cc8

Browse files
ianlancetaylorgopherbot
authored andcommitted
net, os: net.Conn.File.Fd should return a blocking descriptor
Historically net.Conn.File.Fd has returned a descriptor in blocking mode. That was broken by CL 495079, which changed the behavior for os.OpenFile and os.NewFile without intending to affect net.Conn.File.Fd. Use a hidden os entry point to preserve the historical behavior, to ensure backward compatibility. Change-Id: I8d14b9296070ddd52bb8940cb88c6a8b2dc28c27 Reviewed-on: https://go-review.googlesource.com/c/go/+/496080 Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: Bryan Mills <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]>
1 parent d694046 commit b950cc8

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed

src/net/fd_unix.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ func (fd *netFD) accept() (netfd *netFD, err error) {
190190
return netfd, nil
191191
}
192192

193+
// Defined in os package.
194+
func newUnixFile(fd uintptr, name string) *os.File
195+
193196
func (fd *netFD) dup() (f *os.File, err error) {
194197
ns, call, err := fd.pfd.Dup()
195198
if err != nil {
@@ -199,5 +202,5 @@ func (fd *netFD) dup() (f *os.File, err error) {
199202
return nil, err
200203
}
201204

202-
return os.NewFile(uintptr(ns), fd.name()), nil
205+
return newUnixFile(uintptr(ns), fd.name()), nil
203206
}

src/net/file_unix_test.go

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build unix
6+
7+
package net
8+
9+
import (
10+
"internal/syscall/unix"
11+
"testing"
12+
)
13+
14+
// For backward compatibility, opening a net.Conn, turning it into an os.File,
15+
// and calling the Fd method should return a blocking descriptor.
16+
func TestFileFdBlocks(t *testing.T) {
17+
ls := newLocalServer(t, "unix")
18+
defer ls.teardown()
19+
20+
errc := make(chan error, 1)
21+
done := make(chan bool)
22+
handler := func(ls *localServer, ln Listener) {
23+
server, err := ln.Accept()
24+
errc <- err
25+
if err != nil {
26+
return
27+
}
28+
defer server.Close()
29+
<-done
30+
}
31+
if err := ls.buildup(handler); err != nil {
32+
t.Fatal(err)
33+
}
34+
defer close(done)
35+
36+
client, err := Dial(ls.Listener.Addr().Network(), ls.Listener.Addr().String())
37+
if err != nil {
38+
t.Fatal(err)
39+
}
40+
defer client.Close()
41+
42+
if err := <-errc; err != nil {
43+
t.Fatalf("server error: %v", err)
44+
}
45+
46+
// The socket should be non-blocking.
47+
rawconn, err := client.(*UnixConn).SyscallConn()
48+
if err != nil {
49+
t.Fatal(err)
50+
}
51+
err = rawconn.Control(func(fd uintptr) {
52+
nonblock, err := unix.IsNonblock(int(fd))
53+
if err != nil {
54+
t.Fatal(err)
55+
}
56+
if !nonblock {
57+
t.Fatal("unix socket is in blocking mode")
58+
}
59+
})
60+
if err != nil {
61+
t.Fatal(err)
62+
}
63+
64+
file, err := client.(*UnixConn).File()
65+
if err != nil {
66+
t.Fatal(err)
67+
}
68+
69+
// At this point the descriptor should still be non-blocking.
70+
rawconn, err = file.SyscallConn()
71+
if err != nil {
72+
t.Fatal(err)
73+
}
74+
err = rawconn.Control(func(fd uintptr) {
75+
nonblock, err := unix.IsNonblock(int(fd))
76+
if err != nil {
77+
t.Fatal(err)
78+
}
79+
if !nonblock {
80+
t.Fatal("unix socket as os.File is in blocking mode")
81+
}
82+
})
83+
if err != nil {
84+
t.Fatal(err)
85+
}
86+
87+
fd := file.Fd()
88+
89+
// Calling Fd should have put the descriptor into blocking mode.
90+
nonblock, err := unix.IsNonblock(int(fd))
91+
if err != nil {
92+
t.Fatal(err)
93+
}
94+
if nonblock {
95+
t.Error("unix socket through os.File.Fd is non-blocking")
96+
}
97+
}

src/os/file_unix.go

+17
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"io/fs"
1313
"runtime"
1414
"syscall"
15+
_ "unsafe" // for go:linkname
1516
)
1617

1718
const _UTIME_OMIT = unix.UTIME_OMIT
@@ -113,6 +114,22 @@ func NewFile(fd uintptr, name string) *File {
113114
return f
114115
}
115116

117+
// net_newUnixFile is a hidden entry point called by net.conn.File.
118+
// This is used so that a nonblocking network connection will become
119+
// blocking if code calls the Fd method. We don't want that for direct
120+
// calls to NewFile: passing a nonblocking descriptor to NewFile should
121+
// remain nonblocking if you get it back using Fd. But for net.conn.File
122+
// the call to NewFile is hidden from the user. Historically in that case
123+
// the Fd method has returned a blocking descriptor, and we want to
124+
// retain that behavior because existing code expects it and depends on it.
125+
//
126+
//go:linkname net_newUnixFile net.newUnixFile
127+
func net_newUnixFile(fd uintptr, name string) *File {
128+
f := newFile(fd, name, kindNonBlock)
129+
f.nonblock = true // tell Fd to return blocking descriptor
130+
return f
131+
}
132+
116133
// newFileKind describes the kind of file to newFile.
117134
type newFileKind int
118135

0 commit comments

Comments
 (0)