Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing Golang struct that implements error interface causes function to never return

Tags:

struct

go

Background

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
}

Issue

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.

Things I've Tried

  • Running a trace with go test -trace to view what's being called. This fails because go tool trace cannot parse the generated trace file.
  • Converting my signature to return *error instead of regular error. This did not help.

Why is this code snippet hanging?

Note: I am running Go 1.7

like image 477
Akshat Mahajan Avatar asked Feb 15 '26 03:02

Akshat Mahajan


1 Answers

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)
}
like image 95
abhink Avatar answered Feb 16 '26 18:02

abhink