From 7a7ea01c65e8366af277b956dc8ccf0601727172 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 15 Oct 2016 16:56:51 +0100 Subject: [PATCH] syscall, net: make deadline changes affect blocked read/write calls on nacl Flesh out nacl's fake network system to match how all the other platforms work: all other systems' SetReadDeadline and SetWriteDeadline affect currently-blocked read & write calls. This was documented in golang.org/cl/30164 because it was the status quo and existing packages relied on it. (notably the net/http package) And add a test. Change-Id: I074a1054dcabcedc97b173dad5e827f8babf7cfc Reviewed-on: https://go-review.googlesource.com/31178 Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- src/net/net_test.go | 48 +++++++++++++++++++++++++++++++++++++++++ src/syscall/net_nacl.go | 44 ++++++++++++++++++++++++++++++++++--- 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 } -- 2.48.1