访问实现错误接口的Golang结构会导致函数永不返回

Aks*_*jan 1 struct go

背景

我正在将HTTP API中的JSON数据解组为以下Golang结构:

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"`
}
Run Code Online (Sandbox Code Playgroud)

我已经实现了错误接口,Error如下所示:

func (e Error) Error() string {
    return fmt.Sprintf("Error: %+v", e)
}
Run Code Online (Sandbox Code Playgroud)

到目前为止的相关代码:

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
}
Run Code Online (Sandbox Code Playgroud)

问题

以下代码无限期挂起成功通话而不会发生恐慌:

// `body` here is just any old struct
result, err := Request("generateIntegers", body)
if err != nil {
    return []int{}, err  // hangs here
}
Run Code Online (Sandbox Code Playgroud)

实际上,当我调用时,代码总是挂起err.没有引起恐慌并且没有返回错误 - 它只是被冻结[1].

[1]严格地说,它会导致堆栈溢出错误,但那是因为该函数永远不会返回,因此递延resp.Body.Close()Request不会被调用.

它变得更奇怪了.添加以下调试行:

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
Run Code Online (Sandbox Code Playgroud)

工作,但改变这些行只是

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
Run Code Online (Sandbox Code Playgroud)

导致Request函数本身挂起此调试语句.

我试过的事情

  • 运行跟踪go test -trace以查看所调用的内容.这会失败,因为go tool trace无法解析生成的跟踪文件.
  • 将我的签名转换为返回*error而不是常规error.这没有用.

为什么这段代码片段挂起了?

注意:我正在运行Go 1.7

abh*_*ink 5

问题在于Error界面的定义.当您尝试使用值时Error() string,Error类型上的函数会以递归方式调用自身:fmt.Sprintfe

func (e Error) Error() string {
    return fmt.Sprintf("Error: %+v", e) // calls e.Error again
}
Run Code Online (Sandbox Code Playgroud)

尝试通过Error明确访问类型的成员来返回错误:

func (e Error) Error() string {
    return fmt.Sprintf("Error: %+v", e.Message)
}
Run Code Online (Sandbox Code Playgroud)