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

问题描述 投票:1回答:2

Background

我正在将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"`
}

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

func (e Error) Error() string {
    return fmt.Sprintf("Error: %+v", e)
}

到目前为止的相关代码:

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

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

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

事实上,当我调用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

工作,但改变这些行只是

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

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

Things I've Tried

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

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

注意:我正在运行Go 1.7

go struct
2个回答
5
投票

问题在于Error界面的定义。当你尝试Error() stringError时,fmt.Sprintf类型的函数e递归调用自身:

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

尝试通过显式访问qazxsw poi类型成员来返回错误:

Error

2
投票

问题是递归,因为func (e Error) Error() string { return fmt.Sprintf("Error: %+v", e.Message) } fmt再次调用fmt。

经典解决方案是将您的错误类型转换为Error(),然后使用标准机制。这样您就拥有了所有格式化工具。喜欢:

string

游乐场:type E string func (e E) Error() string { return fmt.Sprintf(string(e)) }

来自https://play.golang.org/p/NI5JL3H4g7Y fmt的摘录(不幸的是不能直接链接到该段):

  1. 如果操作数实现了错误接口,则将调用Error方法将对象转换为字符串,然后根据动词的需要对其进行格式化(如果有)。
  2. 如果操作数实现方法String()字符串,则将调用该方法将对象转换为字符串,然后根据动词(如果有)格式化该字符串。

避免在诸如此类的情况下递归

documentation

在重复之前转换值:

type X string 

func (x X) String() string { return Sprintf("<%s>", x) }

无限递归也可以由自引用数据结构触发,例如,如果该类型具有String方法,则将自身包含为元素。然而,这种病症是罕见的,并且包装不能防止它们。

打印结构时,fmt不能,因此不会在未导出的字段上调用错误或字符串等格式化方法。

© www.soinside.com 2019 - 2024. All rights reserved.