Skip to content

Commit c043fc4

Browse files
os: don't let sendFile put a pipe into blocking mode
Use SyscallConn to avoid calling the Fd method in sendFile on Unix systems, since Fd has the side effect of putting the descriptor into blocking mode. Fixes #28330 Change-Id: If093417a225fe44092bd2c0dbbc3937422e98c0b Reviewed-on: https://go-review.googlesource.com/c/155137 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent b115207 commit c043fc4

File tree

3 files changed

+130
-2
lines changed

3 files changed

+130
-2
lines changed

src/net/sendfile_linux.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,19 @@ func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) {
3232
return 0, nil, false
3333
}
3434

35-
written, err = poll.SendFile(&c.pfd, int(f.Fd()), remain)
35+
sc, err := f.SyscallConn()
36+
if err != nil {
37+
return 0, nil, false
38+
}
39+
40+
var werr error
41+
err = sc.Read(func(fd uintptr) bool {
42+
written, werr = poll.SendFile(&c.pfd, int(fd), remain)
43+
return true
44+
})
45+
if werr == nil {
46+
werr = err
47+
}
3648

3749
if lr != nil {
3850
lr.N = remain - written

src/net/sendfile_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ import (
1212
"encoding/hex"
1313
"fmt"
1414
"io"
15+
"io/ioutil"
1516
"os"
17+
"runtime"
18+
"sync"
1619
"testing"
20+
"time"
1721
)
1822

1923
const (
@@ -210,3 +214,103 @@ func TestSendfileSeeked(t *testing.T) {
210214
t.Error(err)
211215
}
212216
}
217+
218+
// Test that sendfile doesn't put a pipe into blocking mode.
219+
func TestSendfilePipe(t *testing.T) {
220+
switch runtime.GOOS {
221+
case "nacl", "plan9", "windows":
222+
// These systems don't support deadlines on pipes.
223+
t.Skipf("skipping on %s", runtime.GOOS)
224+
}
225+
226+
t.Parallel()
227+
228+
ln, err := newLocalListener("tcp")
229+
if err != nil {
230+
t.Fatal(err)
231+
}
232+
defer ln.Close()
233+
234+
r, w, err := os.Pipe()
235+
if err != nil {
236+
t.Fatal(err)
237+
}
238+
defer w.Close()
239+
defer r.Close()
240+
241+
copied := make(chan bool)
242+
243+
var wg sync.WaitGroup
244+
wg.Add(1)
245+
go func() {
246+
// Accept a connection and copy 1 byte from the read end of
247+
// the pipe to the connection. This will call into sendfile.
248+
defer wg.Done()
249+
conn, err := ln.Accept()
250+
if err != nil {
251+
t.Error(err)
252+
return
253+
}
254+
defer conn.Close()
255+
_, err = io.CopyN(conn, r, 1)
256+
if err != nil {
257+
t.Error(err)
258+
return
259+
}
260+
// Signal the main goroutine that we've copied the byte.
261+
close(copied)
262+
}()
263+
264+
wg.Add(1)
265+
go func() {
266+
// Write 1 byte to the write end of the pipe.
267+
defer wg.Done()
268+
_, err := w.Write([]byte{'a'})
269+
if err != nil {
270+
t.Error(err)
271+
}
272+
}()
273+
274+
wg.Add(1)
275+
go func() {
276+
// Connect to the server started two goroutines up and
277+
// discard any data that it writes.
278+
defer wg.Done()
279+
conn, err := Dial("tcp", ln.Addr().String())
280+
if err != nil {
281+
t.Error(err)
282+
return
283+
}
284+
defer conn.Close()
285+
io.Copy(ioutil.Discard, conn)
286+
}()
287+
288+
// Wait for the byte to be copied, meaning that sendfile has
289+
// been called on the pipe.
290+
<-copied
291+
292+
// Set a very short deadline on the read end of the pipe.
293+
if err := r.SetDeadline(time.Now().Add(time.Microsecond)); err != nil {
294+
t.Fatal(err)
295+
}
296+
297+
wg.Add(1)
298+
go func() {
299+
// Wait for much longer than the deadline and write a byte
300+
// to the pipe.
301+
defer wg.Done()
302+
time.Sleep(50 * time.Millisecond)
303+
w.Write([]byte{'b'})
304+
}()
305+
306+
// If this read does not time out, the pipe was incorrectly
307+
// put into blocking mode.
308+
_, err = r.Read(make([]byte, 1))
309+
if err == nil {
310+
t.Error("Read did not time out")
311+
} else if !os.IsTimeout(err) {
312+
t.Errorf("got error %v, expected a time out", err)
313+
}
314+
315+
wg.Wait()
316+
}

src/net/sendfile_unix_alt.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,19 @@ func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) {
5858
return 0, err, false
5959
}
6060

61-
written, err = poll.SendFile(&c.pfd, int(f.Fd()), pos, remain)
61+
sc, err := f.SyscallConn()
62+
if err != nil {
63+
return 0, nil, false
64+
}
65+
66+
var werr error
67+
err = sc.Read(func(fd uintptr) bool {
68+
written, werr = poll.SendFile(&c.pfd, int(fd), pos, remain)
69+
return true
70+
})
71+
if werr == nil {
72+
werr = err
73+
}
6274

6375
if lr != nil {
6476
lr.N = remain - written

0 commit comments

Comments
 (0)