Hi everyone,
I have been playing with Go's net package and observed the following issue.
According to Go doc, "SetReadDeadline sets the deadline for future Read calls". If my understanding is correct, if we set a deadline while an
earlier call is pending, the new deadline should not affect the behavior of the pending call. However, I did the following test and found it
behaved differently. I'm wondering if it is my misunderstanding or it is a potential bug in the implementation.
The following test reproduces the wrong behavior. It is expected that request 1 should timeout, but it didn't because request 2 resets the
deadline while request 1 is pending. Comment out the code for request 2 and then request 1 times out correctly.
// TestServer is a mock server that does nothing more than returning the input
// after sleeping for 20 seconds.
type TestServer struct {
}
// Sleep is the RPC call that sleeps for 20 seconds before returning the value.
func (s TestServer) Sleep(req int, reply *int) error {
fmt.Printf("Received request %d\n", req)
time.Sleep(20 * time.Second)
*reply = req
return nil
}
func TestDeadline(t *testing.T) {
// Start the RPC server.
srvListener, err := net.Listen("tcp", "localhost:0")
if nil != err {
t.Fatal(err)
}
server := rpc.NewServer()
server.Register(TestServer{})
go server.Accept(srvListener)
// Create a RPC client.
srvAddr, err := net.ResolveTCPAddr("tcp", srvListener.Addr().String())
if nil != err {
t.Fatal(err)
}
cliConn, err := net.DialTCP("tcp", nil, srvAddr)
if nil != err {
t.Fatal(err)
}
client := rpc.NewClient(cliConn)
// Set a deadline that is longer than the sleep time.
cliConn.SetReadDeadline(time.Now().Add(30 * time.Second))
// Send a request and it should succeed before timeout.
var reply int
if err := client.Call("TestServer.Sleep", 0, &reply); nil != err {
t.Fatal(err)
} else {
fmt.Printf("Processed request %d\n", reply)
}
// NOTE: Now we're going to see the issue with concurrent requests.
var wg sync.WaitGroup
// Set a deadline that is shorter than the sleep time.
cliConn.SetReadDeadline(time.Now().Add(10 * time.Second))
// Send the first request and it should timeout.
wg.Add(1)
go func() {
var reply int
if err := client.Call("TestServer.Sleep", 1, &reply); nil == err {
fmt.Printf("Processed request %d\n", reply)
t.Error("request 1 should timeout")
}
wg.Done()
}()
// Sleep for a while to make sure that the go scheduler schedules the
// first request.
time.Sleep(2 * time.Second)
// Set the deadline that is longer than the sleep time.
// NOTE: This should not affect the first request per Go doc --
// SetDeadline should only affect future I/O.
cliConn.SetReadDeadline(time.Now().Add(40 * time.Second))
// Send the second request and it should succeed before timeout.
wg.Add(1)
go func() {
var reply int
if err := client.Call("TestServer.Sleep", 2, &reply); nil != err {
t.Errorf("expected no error but got %s", err)
} else {
fmt.Printf("Processed request %d\n", reply)
}
wg.Done()
}()
// Wait for the requests to complete.
wg.Wait()
}