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: zero values not transmitted using encoding/gob #8997

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

net/rpc: zero values not transmitted using encoding/gob #8997

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

Comments

@gopherbot
Copy link

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

Comment 1:

Drive by comment, have you run your server program under the go race detector?

@ianlancetaylor
Copy link
Contributor

Comment 2:

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

@gopherbot
Copy link
Author

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

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 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

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)
    }
}

@seankhliao seankhliao changed the title net/rpc: strange behavior in rpc using default codec net/rpc: zero values not transmitted using encoding/gob Dec 9, 2021
@jtroo
Copy link

jtroo commented May 11, 2022

Just got bit by this bug. I wanted to use a pointer in a struct to differentiate between the value existing vs. the value being the zero value, but gob doesn't support this use case correctly.

Reading through the spec of gob encoding, this shouldn't be the intended behaviour.

If a field has the zero value for its type (except for arrays; see above), it is omitted from the transmission.

The zero value for *T is nil, not the zero value of T.

@jtroo
Copy link

jtroo commented May 27, 2022

I recently discovered that the JSON encoding omitempty option behaves correctly with respect to pointers and nil vs. zero values. So there is precedent for this behaviour to be fixed.

https://pkg.go.dev/encoding/json#Marshal

The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

A non-nil pointer to a boolean that is false still gets encoded as false for JSON even with omitempty.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants