aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/net/net_test.go48
-rw-r--r--src/syscall/net_nacl.go44
2 files changed, 89 insertions, 3 deletions
diff --git a/src/net/net_test.go b/src/net/net_test.go
index f58b93e74b..640bcac808 100644
--- a/src/net/net_test.go
+++ b/src/net/net_test.go
@@ -5,6 +5,8 @@
package net
import (
+ "errors"
+ "fmt"
"io"
"net/internal/socktest"
"os"
@@ -449,3 +451,49 @@ func withTCPConnPair(t *testing.T, peer1, peer2 func(c *TCPConn) error) {
}
}
}
+
+// Tests that a blocked Read is interrupted by a concurrent SetReadDeadline
+// modifying that Conn's read deadline to the past.
+// See golang.org/cl/30164 which documented this. The net/http package
+// depends on this.
+func TestReadTimeoutUnblocksRead(t *testing.T) {
+ serverDone := make(chan struct{})
+ server := func(cs *TCPConn) error {
+ defer close(serverDone)
+ errc := make(chan error, 1)
+ go func() {
+ defer close(errc)
+ go func() {
+ // TODO: find a better way to wait
+ // until we're blocked in the cs.Read
+ // call below. Sleep is lame.
+ time.Sleep(100 * time.Millisecond)
+
+ // Interrupt the upcoming Read, unblocking it:
+ cs.SetReadDeadline(time.Unix(123, 0)) // time in the past
+ }()
+ var buf [1]byte
+ n, err := cs.Read(buf[:1])
+ if n != 0 || err == nil {
+ errc <- fmt.Errorf("Read = %v, %v; want 0, non-nil", n, err)
+ }
+ }()
+ select {
+ case err := <-errc:
+ return err
+ case <-time.After(5 * time.Second):
+ buf := make([]byte, 2<<20)
+ buf = buf[:runtime.Stack(buf, true)]
+ println("Stacks at timeout:\n", string(buf))
+ return errors.New("timeout waiting for Read to finish")
+ }
+
+ }
+ // Do nothing in the client. Never write. Just wait for the
+ // server's half to be done.
+ client := func(*TCPConn) error {
+ <-serverDone
+ return nil
+ }
+ withTCPConnPair(t, client, server)
+}
diff --git a/src/syscall/net_nacl.go b/src/syscall/net_nacl.go
index 1a0122c4b6..9dc5d0ca0b 100644
--- a/src/syscall/net_nacl.go
+++ b/src/syscall/net_nacl.go
@@ -6,6 +6,8 @@
// The simulation is not particularly tied to NaCl,
// but other systems have real networks.
+// All int64 times are UnixNanos.
+
package syscall
import (
@@ -50,6 +52,22 @@ func (t *timer) stop() {
stopTimer(&t.r)
}
+func (t *timer) reset(q *queue, deadline int64) {
+ if t.r.f != nil {
+ t.stop()
+ }
+ if deadline == 0 {
+ return
+ }
+ if t.r.f == nil {
+ t.q = q
+ t.r.f = timerExpired
+ t.r.arg = t
+ }
+ t.r.when = deadline
+ startTimer(&t.r)
+}
+
func timerExpired(i interface{}, seq uintptr) {
t := i.(*timer)
go func() {
@@ -233,9 +251,11 @@ type queue struct {
sync.Mutex
canRead sync.Cond
canWrite sync.Cond
- r int // total read index
- w int // total write index
- m int // index mask
+ rtimer *timer // non-nil if in read
+ wtimer *timer // non-nil if in write
+ r int // total read index
+ w int // total write index
+ m int // index mask
closed bool
}
@@ -259,9 +279,11 @@ func (q *queue) waitRead(n int, deadline int64) (int, error) {
}
var t timer
t.start(q, deadline)
+ q.rtimer = &t
for q.w-q.r == 0 && !q.closed && !t.expired {
q.canRead.Wait()
}
+ q.rtimer = nil
t.stop()
m := q.w - q.r
if m == 0 && t.expired {
@@ -281,9 +303,11 @@ func (q *queue) waitWrite(n int, deadline int64) (int, error) {
}
var t timer
t.start(q, deadline)
+ q.wtimer = &t
for q.w-q.r > q.m && !q.closed && !t.expired {
q.canWrite.Wait()
}
+ q.wtimer = nil
t.stop()
m := q.m + 1 - (q.w - q.r)
if m == 0 && t.expired {
@@ -871,6 +895,13 @@ func SetReadDeadline(fd int, t int64) error {
return err
}
atomic.StoreInt64(&f.rddeadline, t)
+ if bq := f.rd; bq != nil {
+ bq.Lock()
+ if timer := bq.rtimer; timer != nil {
+ timer.reset(&bq.queue, t)
+ }
+ bq.Unlock()
+ }
return nil
}
@@ -884,6 +915,13 @@ func SetWriteDeadline(fd int, t int64) error {
return err
}
atomic.StoreInt64(&f.wrdeadline, t)
+ if bq := f.wr; bq != nil {
+ bq.Lock()
+ if timer := bq.wtimer; timer != nil {
+ timer.reset(&bq.queue, t)
+ }
+ bq.Unlock()
+ }
return nil
}