]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: for kqueue treat EVFILT_READ with EV_EOF as permitting a write
authorIan Lance Taylor <iant@golang.org>
Thu, 19 Oct 2017 23:01:43 +0000 (16:01 -0700)
committerIan Lance Taylor <iant@golang.org>
Fri, 20 Oct 2017 22:26:30 +0000 (22:26 +0000)
On systems that use kqueue, we always register descriptors for both
EVFILT_READ and EVFILT_WRITE. On at least FreeBSD and OpenBSD, when
the write end of a pipe is registered for EVFILT_READ and EVFILT_WRITE
events, and the read end of the pipe is closed, kqueue reports an
EVFILT_READ event with EV_EOF set, but does not report an EVFILT_WRITE
event. Since the write to the pipe is waiting for an EVFILT_WRITE
event, closing the read end of a pipe can cause the write end to hang
rather than attempt another write which will fail with EPIPE.

Fix this by treating EVFILT_READ with EV_EOF set as making both reads
and writes ready to proceed.

The real test for this is in CL 71770, which tests using various
timeouts with pipes.

Updates #22114

Change-Id: Ib23fbaaddbccd8eee77bdf18f27a7f0aa50e2742
Reviewed-on: https://go-review.googlesource.com/71973
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
22 files changed:
src/internal/poll/fd_unix.go
src/net/write_unix_test.go [new file with mode: 0644]
src/runtime/defs1_netbsd_386.go
src/runtime/defs1_netbsd_amd64.go
src/runtime/defs1_netbsd_arm.go
src/runtime/defs_darwin.go
src/runtime/defs_darwin_386.go
src/runtime/defs_darwin_amd64.go
src/runtime/defs_darwin_arm.go
src/runtime/defs_darwin_arm64.go
src/runtime/defs_dragonfly.go
src/runtime/defs_dragonfly_amd64.go
src/runtime/defs_freebsd.go
src/runtime/defs_freebsd_386.go
src/runtime/defs_freebsd_amd64.go
src/runtime/defs_freebsd_arm.go
src/runtime/defs_netbsd.go
src/runtime/defs_openbsd.go
src/runtime/defs_openbsd_386.go
src/runtime/defs_openbsd_amd64.go
src/runtime/defs_openbsd_arm.go
src/runtime/netpoll_kqueue.go

index c51370a682470b399dfdd1c6efec247e3da23ac2..3ac69273370e1e6371999b8200776609199d46fb 100644 (file)
@@ -411,6 +411,15 @@ func (fd *FD) WaitWrite() error {
        return fd.pd.waitWrite(fd.isFile)
 }
 
+// WriteOnce is for testing only. It makes a single write call.
+func (fd *FD) WriteOnce(p []byte) (int, error) {
+       if err := fd.writeLock(); err != nil {
+               return 0, err
+       }
+       defer fd.writeUnlock()
+       return syscall.Write(fd.Sysfd, p)
+}
+
 // RawControl invokes the user-defined function f for a non-IO
 // operation.
 func (fd *FD) RawControl(f func(uintptr)) error {
diff --git a/src/net/write_unix_test.go b/src/net/write_unix_test.go
new file mode 100644 (file)
index 0000000..6d8cb6a
--- /dev/null
@@ -0,0 +1,66 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
+
+package net
+
+import (
+       "bytes"
+       "syscall"
+       "testing"
+       "time"
+)
+
+// Test that a client can't trigger an endless loop of write system
+// calls on the server by shutting down the write side on the client.
+// Possibility raised in the discussion of https://golang.org/cl/71973.
+func TestEndlessWrite(t *testing.T) {
+       t.Parallel()
+       c := make(chan bool)
+       server := func(cs *TCPConn) error {
+               cs.CloseWrite()
+               <-c
+               return nil
+       }
+       client := func(ss *TCPConn) error {
+               // Tell the server to return when we return.
+               defer close(c)
+
+               // Loop writing to the server. The server is not reading
+               // anything, so this will eventually block, and then time out.
+               b := bytes.Repeat([]byte{'a'}, 8192)
+               cagain := 0
+               for {
+                       n, err := ss.conn.fd.pfd.WriteOnce(b)
+                       if n > 0 {
+                               cagain = 0
+                       }
+                       switch err {
+                       case nil:
+                       case syscall.EAGAIN:
+                               if cagain == 0 {
+                                       // We've written enough data to
+                                       // start blocking. Set a deadline
+                                       // so that we will stop.
+                                       ss.SetWriteDeadline(time.Now().Add(5 * time.Millisecond))
+                               }
+                               cagain++
+                               if cagain > 20 {
+                                       t.Error("looping on EAGAIN")
+                                       return nil
+                               }
+                               if err = ss.conn.fd.pfd.WaitWrite(); err != nil {
+                                       t.Logf("client WaitWrite: %v", err)
+                                       return nil
+                               }
+                       default:
+                               // We expect to eventually get an error.
+                               t.Logf("client WriteOnce: %v", err)
+                               return nil
+                       }
+               }
+       }
+       withTCPConnPair(t, client, server)
+}
index 66f07ce5a5132d0760b1b58393c5678f851a639c..c26f417a02e0a537ab3255e79887e389b3d074c4 100644 (file)
@@ -79,6 +79,7 @@ const (
        _EV_CLEAR     = 0x20
        _EV_RECEIPT   = 0
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = 0x0
        _EVFILT_WRITE = 0x1
 )
index 9e314718f3a5d8de666f736fbcdd5414b2d881fd..0704cd4fb3f129aba89d01e587c11b39bc64589c 100644 (file)
@@ -79,6 +79,7 @@ const (
        _EV_CLEAR     = 0x20
        _EV_RECEIPT   = 0
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = 0x0
        _EVFILT_WRITE = 0x1
 )
index db8e4c63fc77d4d9a5ea522f00a5e7aca7d65a9d..d2a13ad4b0267c5c295e7541eb2c939e9c935c7a 100644 (file)
@@ -79,6 +79,7 @@ const (
        _EV_CLEAR     = 0x20
        _EV_RECEIPT   = 0
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = 0x0
        _EVFILT_WRITE = 0x1
 )
index 78df4e7ac88570c829a7d16da65629dbd860619d..f7d65e700d03fd5ba94e6f3f2c6d9f1bfea95e5d 100644 (file)
@@ -139,6 +139,7 @@ const (
        EV_CLEAR     = C.EV_CLEAR
        EV_RECEIPT   = C.EV_RECEIPT
        EV_ERROR     = C.EV_ERROR
+       EV_EOF       = C.EV_EOF
        EVFILT_READ  = C.EVFILT_READ
        EVFILT_WRITE = C.EVFILT_WRITE
 )
index 1a5967b24ba8ebe41346a058596c112d8afec395..f6dbcc519cac0da1717c53f371714366f7f21ab9 100644 (file)
@@ -118,6 +118,7 @@ const (
        _EV_CLEAR     = 0x20
        _EV_RECEIPT   = 0x40
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = -0x1
        _EVFILT_WRITE = -0x2
 )
index a4ab090d51d36ec2af5c1fff7b8445f5f06bf3ad..245fe158c71bde5047f435507cc9422b5455af38 100644 (file)
@@ -118,6 +118,7 @@ const (
        _EV_CLEAR     = 0x20
        _EV_RECEIPT   = 0x40
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = -0x1
        _EVFILT_WRITE = -0x2
 )
index 3f8dbbf25457a1e2e59b8768ed0b7c9fdecef2cd..f89aee6775ed6e27fb5929fddbce0143485d2574 100644 (file)
@@ -120,6 +120,7 @@ const (
        _EV_CLEAR     = 0x20
        _EV_RECEIPT   = 0x40
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = -0x1
        _EVFILT_WRITE = -0x2
 )
index c25a41b749f73398e5d981b25e8bac30f9355ca7..a0ca7f17038bc1dd5b1baef47749ebf8c06df24a 100644 (file)
@@ -118,6 +118,7 @@ const (
        _EV_CLEAR     = 0x20
        _EV_RECEIPT   = 0x40
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = -0x1
        _EVFILT_WRITE = -0x2
 )
index ed00be0f443b386da022f02ab9c35bf74c2f976e..95014fe6e7127be93a41f9edb626a7c2c0448152 100644 (file)
@@ -103,6 +103,7 @@ const (
        EV_DELETE    = C.EV_DELETE
        EV_CLEAR     = C.EV_CLEAR
        EV_ERROR     = C.EV_ERROR
+       EV_EOF       = C.EV_EOF
        EVFILT_READ  = C.EVFILT_READ
        EVFILT_WRITE = C.EVFILT_WRITE
 )
index fc70103286c5aa4cac7601e5f4acc827777f33d5..c30da805ccb1736253ae40d424c766123a2d0f31 100644 (file)
@@ -82,6 +82,7 @@ const (
        _EV_DELETE    = 0x2
        _EV_CLEAR     = 0x20
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = -0x1
        _EVFILT_WRITE = -0x2
 )
index 0a11d09db20312436bde0353d2ea180b851aa068..9d55111786a47f860d86c2978580eff7755e9643 100644 (file)
@@ -125,6 +125,7 @@ const (
        EV_CLEAR     = C.EV_CLEAR
        EV_RECEIPT   = C.EV_RECEIPT
        EV_ERROR     = C.EV_ERROR
+       EV_EOF       = C.EV_EOF
        EVFILT_READ  = C.EVFILT_READ
        EVFILT_WRITE = C.EVFILT_WRITE
 )
index 92b05503a3321a48dbaa18bf53206c36c2321551..49bcbb12a24c80d1b2d85a60624a5c232f865387 100644 (file)
@@ -95,6 +95,7 @@ const (
        _EV_CLEAR     = 0x20
        _EV_RECEIPT   = 0x40
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = -0x1
        _EVFILT_WRITE = -0x2
 )
index 645e2053f2b1106fc9b7837f752d2f98961e419e..0e1c6752d6bbfccdd15c9518ce2df8a978a9500d 100644 (file)
@@ -95,6 +95,7 @@ const (
        _EV_CLEAR     = 0x20
        _EV_RECEIPT   = 0x40
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = -0x1
        _EVFILT_WRITE = -0x2
 )
index c8a198fb4aaee4f0ac71795977391d6ca206ee42..71684fe9f80731468e92d8198aab7107617fc6e9 100644 (file)
@@ -95,6 +95,7 @@ const (
        _EV_CLEAR     = 0x20
        _EV_RECEIPT   = 0x40
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = -0x1
        _EVFILT_WRITE = -0x2
 )
index 56db1f015972eb0106313c0ab11f84e04b737616..41aa07af98ef14dfc0646506624155ae73b81ae0 100644 (file)
@@ -105,6 +105,7 @@ const (
        EV_CLEAR     = C.EV_CLEAR
        EV_RECEIPT   = 0
        EV_ERROR     = C.EV_ERROR
+       EV_EOF       = C.EV_EOF
        EVFILT_READ  = C.EVFILT_READ
        EVFILT_WRITE = C.EVFILT_WRITE
 )
index 7e721504e65601135ea5e8d1793a93306f32cf0e..9ff13dfcbf51a5358539214811758e5c8a595207 100644 (file)
@@ -100,6 +100,7 @@ const (
        EV_DELETE    = C.EV_DELETE
        EV_CLEAR     = C.EV_CLEAR
        EV_ERROR     = C.EV_ERROR
+       EV_EOF       = C.EV_EOF
        EVFILT_READ  = C.EVFILT_READ
        EVFILT_WRITE = C.EVFILT_WRITE
 )
index ce08111deab3efa9f4e8a89e488fca6738af4951..1185530964639c08bb0bfa2ee71063be7a8cf0b2 100644 (file)
@@ -80,6 +80,7 @@ const (
        _EV_DELETE    = 0x2
        _EV_CLEAR     = 0x20
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = -0x1
        _EVFILT_WRITE = -0x2
 )
index ea0709809a399560e7ed8222e26af8c38a3c0c87..4bb8eac08f1b1771a9d21b9ee2541ca21290ad79 100644 (file)
@@ -80,6 +80,7 @@ const (
        _EV_DELETE    = 0x2
        _EV_CLEAR     = 0x20
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = -0x1
        _EVFILT_WRITE = -0x2
 )
index b0fb639c72168a8372f2d3e376e7794de4fcfe15..38b77c92d0d30facdd927d2cfbda1f87f6572fcb 100644 (file)
@@ -80,6 +80,7 @@ const (
        _EV_DELETE    = 0x2
        _EV_CLEAR     = 0x20
        _EV_ERROR     = 0x4000
+       _EV_EOF       = 0x8000
        _EVFILT_READ  = -0x1
        _EVFILT_WRITE = -0x2
 )
index 71de98bcd663499cda302963acf583a53c1159ec..4d5d1a4ea87044ba1c9ac320100075ccc4d47e32 100644 (file)
@@ -88,10 +88,23 @@ retry:
        for i := 0; i < int(n); i++ {
                ev := &events[i]
                var mode int32
-               if ev.filter == _EVFILT_READ {
+               switch ev.filter {
+               case _EVFILT_READ:
                        mode += 'r'
-               }
-               if ev.filter == _EVFILT_WRITE {
+
+                       // On some systems when the read end of a pipe
+                       // is closed the write end will not get a
+                       // _EVFILT_WRITE event, but will get a
+                       // _EVFILT_READ event with EV_EOF set.
+                       // Note that setting 'w' here just means that we
+                       // will wake up a goroutine waiting to write;
+                       // that goroutine will try the write again,
+                       // and the appropriate thing will happen based
+                       // on what that write returns (success, EPIPE, EAGAIN).
+                       if ev.flags&_EV_EOF != 0 {
+                               mode += 'w'
+                       }
+               case _EVFILT_WRITE:
                        mode += 'w'
                }
                if mode != 0 {