Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net/rpc: strange behavior in rpc using default codec #8997

Open
gopherbot opened this issue Oct 25, 2014 · 6 comments
Open

net/rpc: strange behavior in rpc using default codec #8997

gopherbot opened this issue Oct 25, 2014 · 6 comments
Milestone

Comments

@gopherbot
Copy link

@gopherbot gopherbot commented Oct 25, 2014

by HongF.Yue:

go version:go version go1.3.3 linux/amd64

I discovered a strange behavior related to 0 value in reply structure. The codes below
will produce this problem.
---------------------------------------------------
```go
//rpc server
package main

import (
    "fmt"
    "net"
    "net/rpc"
    "os"
)

type Args struct {
    A int
}

type Rsp struct {
    A int
}

type Echoer int

var isFirst = true

func (t *Echoer) Echo(args *Args, reply *Rsp) error {
    if isFirst {
        reply.A = args.A
        isFirst = false
    } else {
        reply.A = 0
    }
    fmt.Println("reply-----", reply.A)
    return nil
}

func main() {
    echoer := new(Echoer)
    rpc.Register(echoer)

    tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
    checkError(err)

    listener, err := net.ListenTCP("tcp", tcpAddr)
    checkError(err)

    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        rpc.ServeConn(conn)
    }

}

func checkError(err error) {
    if err != nil {
        fmt.Println("Fatal error ", err.Error())
        os.Exit(1)
    }
}
```
---------------------------------------------------
```go
//rpc client
package main

import (
    "fmt"
    "log"
    "net/rpc"
    "os"
    "time"
)

type Args struct {
    A int
}

type Rsp struct {
    A int
}

func main() {
    if len(os.Args) != 2 {
        fmt.Println("Usage: ", os.Args[0], "server:port")
        os.Exit(1)
    }
    service := os.Args[1]
    client, err := rpc.Dial("tcp", service)
    if err != nil {
        log.Fatal("dialing:", err)
    }
    args := Args{3}
    var rsp Rsp
    for {
        err = client.Call("Echoer.Echo", args, &rsp)
        if err != nil {
            log.Fatal("arith error:", err)
        }
        fmt.Printf("Echoer: A=%d\n", rsp.A)
        time.Sleep(time.Second * 2)
    }
}
```
------------------------------------------
The output of server:
reply----- 3
reply----- 0
reply----- 0
reply----- 0

The output of client:
Echoer: A=3
Echoer: A=3         //why is not A=0
Echoer: A=3
Echoer: A=3

The client should print A=3 on the first time and others would be A=0. More test code
showed that this behavior related to zero values in the RPC reply. When server reply
zero values, client print last reply which is not zero. 

When I change codec to msgpack, this problem doesn't happen. I also found that in rpc
client code, when I move ```var rsp Rsp``` to inner of for loop, everything goes well. 

Is this a bug? or I misunderstand somewhere of golang?
@davecheney
Copy link
Contributor

@davecheney davecheney commented Oct 25, 2014

Comment 1:

Drive by comment, have you run your server program under the go race detector?
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Oct 25, 2014

Comment 2:

Labels changed: added repo-main, release-none.

@gopherbot
Copy link
Author

@gopherbot gopherbot commented Oct 26, 2014

Comment 3 by HongF.Yue:

I rebuild and run server code in -race flag, and edit rpc server code as below:
----------------------------------------
//rpc server
package main
import (
    "fmt"
    "net"
    "net/rpc"
    "os"
)
type Args struct {
    A int
}
type Rsp struct {
    A int
}
type Echoer int
var isFirst = true
func (t *Echoer) Echo(args *Args, reply *Rsp) error {
    if isFirst {
        reply.A = args.A
        isFirst = false
    } else {
        reply.A = 0
    }
    fmt.Println("reply-----", reply.A)
    return nil
}
func main() {
    echoer := new(Echoer)
    rpc.Register(echoer)
    tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
    checkError(err)
    listener, err := net.ListenTCP("tcp", tcpAddr)
    checkError(err)
    conn, err := listener.Accept()
    checkError(err)
    rpc.ServeConn(conn)
}
func checkError(err error) {
    if err != nil {
        fmt.Println("Fatal error ", err.Error())
        os.Exit(1)
    }
}
----------------------------------
The output of both client and server have nothing different from original. And sending
Ctrl-C to client, server exit with no warning or error. 
The Processor of this server is only one, I don't think this problem is related to
concurrent or race condition. Maybe some strange behavior of gob codec (zero value) is
the answer.
@bradfitz bradfitz removed the new label Dec 18, 2014
@boydc2014
Copy link

@boydc2014 boydc2014 commented Mar 5, 2015

Running to the same issue here. When reply value is 0(for int type), it seems that the decoder just overlook this field.

A simpler way to reproduce is,
let "rsp := {A:3}" and let the function make "reply.A = 0", you can see what happens.

@rsc rsc added this to the Unplanned milestone Apr 10, 2015
@mljli
Copy link

@mljli mljli commented Oct 10, 2016

I also found that in rpc client code, when I move var rsp Rsp to inner of for loop, everything goes well.

Still have the same issue with Go 1.7.1. When will this be fixed?

@weijunji
Copy link

@weijunji weijunji commented Feb 21, 2021

This is because net/rpc use encoding/gob as default codec. And if a field has the zero value for its type, it is omitted from the transmission. So you should start with a clean, zeroed item before decoding.
See https://golang.org/pkg/encoding/gob/#hdr-Encoding_Details and #21929

func main() {
    if len(os.Args) != 2 {
        fmt.Println("Usage: ", os.Args[0], "server:port")
        os.Exit(1)
    }
    service := os.Args[1]
    client, err := rpc.Dial("tcp", service)
    if err != nil {
        log.Fatal("dialing:", err)
    }
    args := Args{3}
    //  var rsp Rsp
    for {
        var rsp Rsp // make it clean
        err = client.Call("Echoer.Echo", args, &rsp)
        if err != nil {
            log.Fatal("arith error:", err)
        }
        fmt.Printf("Echoer: A=%d\n", rsp.A)
        time.Sleep(time.Second * 2)
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
8 participants