服务器和客户端之间的明显死锁

I have a test function which both creates a server and spawns a goroutine acting as a client. Now, simply sending a message from the client to the server works, but if I want to create an exchange, they seem to deadlock since the test never runs to completion (if no r/w deadlines are set). For example, I want the client to send a message to the server, the server to copy that message and send it back to the client, and then the client to verify that the received message was identical. Here is my test code:

func TestSendAwait(t *testing.T) {
    m := "Hello World"

    go func() {
        conn, err := net.Dial("tcp", testingAddr)
        if err != nil {
            t.Fatal(err)
        }
        defer conn.Close()
        t.Log("client connected to server") // DEBUG

        conn.SetDeadline(time.Now().Add(2 * time.Second))
        conn.Write([]byte(m))

        conn.SetDeadline(time.Now().Add(2 * time.Second))
        buf, err := ioutil.ReadAll(conn)
        if err != nil {
            t.Fatal(err)
        }
        t.Log(string(buf))
    }()

    ln, err := net.Listen("tcp", testingAddr)
    if err != nil {
        t.Fatal(err)
    }
    defer ln.Close()
    t.Log("server started") // DEBUG

    conn, err := ln.Accept()
    if err != nil {
        t.Fatal(err)
    }
    defer conn.Close()
    t.Log("server received connection") // DEBUG

    buf, err := ioutil.ReadAll(conn)
    if err != nil {
        t.Fatal(err)
    }
    t.Logf("server read buffer: %v", buf) // DEBUG

    _, err = conn.Write(buf)
    if err != nil {
        t.Fatal(err)
    }
    t.Log("server wrote to connection") // DEBUG
}

The deadlines are set on the connection because otherwise the deadlock would be indefinite. The output is as follows:

    transmission_test.go:42: server started
    transmission_test.go:24: client connected to server
    transmission_test.go:49: server received connection
    transmission_test.go:32: read tcp 127.0.0.1:41164->127.0.0.1:9090: i/o timeout
    transmission_test.go:55: server read buffer: [72 101 108 108 111 32 87 111 114 108 100]
    transmission_test.go:61: server wrote to connection

Process finished with exit code 1

I don't understand why the client is unable to read and exits, and only then the server decides to send data down the socket? This happens even if I increase the read deadline in the client.

The program blocks on the call to ioutil.ReadAll. This function reads until io.EOF or some other error is returned.

One fix is to shutdown write after writing data to the connection. This will cause read on the peer to return io.EOF and for ioutil.ReadAll to return successfully.

    conn.Write(data)
    cw, ok := conn.(interface{ CloseWrite() error })
    if !ok {
        // handle error
    }
    cw.CloseWrite()

playground example

The program in the question does not guarantee that the listener is opened before the connection is dialed or that client will print print the received message. The playground example corrects these issues.

Another approach is to frame the messages in some way:

  • Write newline or some other byte sequence not allowed in message after message. Read until this byte sequence is found.
  • Write message length before message. Read length and then specified number of bytes.