I'm unmarshalling JSON data from an HTTP API to the following Golang structs:
type ResponseBody struct {
Version string `json:"jsonrpc"`
Result Result `json:"result"`
Error Error `json:"error"`
Id int `json:"id"`
}
type Result struct {
Random struct {
Data interface{} `json:"data"`
CompletionTime string `json:"completionTime"`
} `json:"random"`
BitsUsed int `json:"bitsUsed"`
BitsLeft int `json:"bitsLeft"`
RequestsLeft int `json:"requestsLeft"`
AdvisoryDelay int `json:"advisoryDelay"`
}
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Data []int `json:"data,omitempty"`
}
and I've implemented the error interface for Error
as follows:
func (e Error) Error() string {
return fmt.Sprintf("Error: %+v", e)
}
Relevant code so far:
func Request(method string, params interface{}) (Result, error) {
// `error` in return types is _not_ a typo
body, err := json.Marshal(RequestBody{Version: "2.0", Params: params, Method: method, Id: 1})
if err != nil {
return Result{}, err
}
resp, err := http.Post(endpoint, "application/json-rpc", bytes.NewReader(body))
if err != nil {
return Result{}, fmt.Errorf("Request failed, error was %s", err)
}
defer resp.Body.Close()
text, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Result{}, fmt.Errorf("Failed to read response into memory, error was: %s", err)
}
if resp.StatusCode != 200 {
var errorMessage Error
if err := json.Unmarshal(text, &errorMessage); err != nil {
return Result{}, Error{
Code: 409,
Message: fmt.Sprintf("Client could not decode JSON error response, received %s, error was: %s", text, err),
Data: []int{},
}
}
return Result{}, errorMessage
}
response := ResponseBody{}
if err := json.Unmarshal(text, &response); err != nil {
return Result{}, fmt.Errorf("Failed to JSON-decode response body, received %s from source, error was: %s", text, err)
}
return response.Result, response.Error
}
The following code hangs indefinitely on a successful call without panicking:
// `body` here is just any old struct
result, err := Request("generateIntegers", body)
if err != nil {
return []int{}, err // hangs here
}
In fact, the code always hangs when I invoke err
. No panic is raised and no error is returned - it is simply frozen[1].
[1] Strictly speaking, it causes a stack overflow error, but that's because the function never returns and so the deferred resp.Body.Close()
in Request
never gets called.
It gets weirder. Adding the following debugging lines:
response := ResponseBody{}
if err := json.Unmarshal(text, &response); err != nil {
return Result{}, fmt.Errorf("Failed to JSON-decode response body, received %s from source, error was: %s", text, err)
}
fmt.Println(response.Result.BitsUsed)
fmt.Println(response.Result) // this prints!
return response.Result, response.Error
works, but changing these lines to just
response := ResponseBody{}
if err := json.Unmarshal(text, &response); err != nil {
return Result{}, fmt.Errorf("Failed to JSON-decode response body, received %s from source, error was: %s", text, err)
}
fmt.Println(response.Result) // this no longer prints! :O
return response.Result, response.Error
causes Request
function itself to hang at this debug statement.
go test -trace
to view what's being called. This fails because go tool trace
cannot parse the generated trace file.*error
instead of regular error
. This did not help.Why is this code snippet hanging?
Note: I am running Go 1.7
The problem is with the definition of Error
interface. The function Error() string
on Error
type is recursively calling itself when you try to fmt.Sprintf
the value e
:
func (e Error) Error() string {
return fmt.Sprintf("Error: %+v", e) // calls e.Error again
}
Try to return error by accessing Error
type's members explicitely:
func (e Error) Error() string {
return fmt.Sprintf("Error: %+v", e.Message)
}
The problem is recursion because fmt
calls Error()
what calls fmt again.
Classical solutions is to convert your error type into string
and then use standard mechanics. This way you have all the formatting tools. Like:
type E string
func (e E) Error() string {
return fmt.Sprintf(string(e))
}
Playground: https://play.golang.org/p/NI5JL3H4g7Y
Excerpt from fmt
documentation (unfortunatelly cannot make direct link to the paragraph):
If an operand implements the error interface, the Error method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any).
If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any).
To avoid recursion in cases such as
type X string func (x X) String() string { return Sprintf("<%s>", x) }
convert the value before recurring:
func (x X) String() string { return Sprintf("<%s>", string(x)) }
Infinite recursion can also be triggered by self-referential data structures, such as a slice that contains itself as an element, if that type has a String method. Such pathologies are rare, however, and the package does not protect against them.
When printing a struct, fmt cannot and therefore does not invoke formatting methods such as Error or String on unexported fields.