2017-08-18 44 views
1

在我的程序中发现泄漏之后,我已经解决了这个问题。但是,现在我试图找出如何“测试”在Go测试中泄漏连接?这是我的问题。如何在Go测试中测试泄漏连接?

我试图改变在我的测试中的请求数量,没关系。无论我做什么,测试中的当前TCP连接数保持不变。

func TestLeakingConnections(t *testing.T) { 
    getter := myhttp.New() 

    s := newServer(ok) 
    defer s.Close() 

    cur := tcps(t) 
    for i := 0; i < 1000; i++ { 
     r, _ := getter.GetWithTimeout(s.URL, time.Millisecond*10) 
     r.Body.Close() 
    } 

    for tries := 10; tries >= 0; tries-- { 
     growth := tcps(t) - cur 
     if growth > 5 { 
      t.Error("leaked") 
      return 
     } 
    } 
} 

// find tcp connections 
func tcps(t *testing.T) (conns int) { 
    lsof, err := exec.Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output() 
    if err != nil { 
     t.Skip("skipping test; error finding or running lsof") 
    } 

    for _, ls := range strings.Split(string(lsof), "\n") { 
     if strings.Contains(ls, "TCP") { 
      conns++ 
     } 
    } 
    return 
} 

func newServer(f http.HandlerFunc) *httptest.Server { 
    return httptest.NewServer(http.HandlerFunc(f)) 
} 

func ok(w http.ResponseWriter, r *http.Request) { 
    w.Header().Add("Content-Type", "application/xml") 
    io.WriteString(w, "<xml></xml>") 
} 

// myhttp package 

// ...other code omitted for clarification 

func (g *Getter) GetWithTimeout(
    url string, 
    timeout time.Duration, 
) (
    *http.Response, error, 
) { 
    // this is the leaking part 
    // moving this out of here will stop leaks 
    transport := http.Transport{ 
     DialContext: (&net.Dialer{ 
      Timeout: timeout, 
     }).DialContext, 
     TLSHandshakeTimeout: timeout, 
     ResponseHeaderTimeout: timeout, 
     ExpectContinueTimeout: timeout, 
    } 

    client := http.Client{ 
     Timeout: timeout, 
     Transport: &transport, 
    } 

    return client.Get(url) 
} 

// fixture worker package 

// some outside code injects getter into fixture_worker like this: 
getter := myhttp.New() 

// NewWithTimeout creates a new fetcher with timeout threshold 
func NewWithTimeout(
    getter myhttp.HTTPGetter, 
    fetchURL string, 
    timeout time.Duration, 
) *Fetcher { 
    return &Fetcher{getter, fetchURL, timeout} 
} 

// Fetch fetches fixture xml 
func (f *Fetcher) Fetch() (*parser.FixtureXML, error) { 
    res, err := f.getter.GetWithTimeout(f.fetchURL, f.timeout) 
    if err != nil { 
     if res != nil && res.Body != nil { 
      res.Body.Close() 
     } 
     return nil, errors.Wrap(err, ErrFetch.Error()) 
    } 
    defer res.Body.Close() 

    ioutil.ReadAll(res.Body) 

    return &parser.FixtureXML{}, nil 
} 

leaking conns gif


夹具工人lsof的输出:https://pastebin.com/fDUkpYsE

测试的输出:https://pastebin.com/uGpK0czn

测试从来没有泄漏,而它泄漏灯具的工人。

Fixture Worker正在使用与测试相同的代码来请求http获取使用myhttp包。

+0

你根本没有在读身体,http客户端通过完全抛弃连接来保存你的连接(这不是保证行为,也不能被依赖)。 'GetWithTimeout'也打破了关闭身体的记录模式,在有合法的响应时添加一个错误值。这意味着调用者无法知道机构是否需要关闭或不出错,而不会以某种方式检查“ErrStatus”,可能导致更多的泄漏。我不明白'dialFunc'的原因,你现在应该使用'DialContext'。 – JimB

+0

谢谢你的提示,我会研究这些。但是,这并不能回答我的问题:我如何在测试中测试泄漏? –

回答

1

如果你想让你的测试代表现实,你需要以与测试之外相同的方式使用它。在这种情况下,您不会读取任何响应,并且运输恰好可以防止丢失连接,因为它们会尽快丢弃它们,因为它们不能被重用。

读取响应将使用连接,并使其进入“泄漏”状态。您还需要在所有情况下妥善处理错误,并且您始终需要响应正文。该模式来处理HTTP响应,并确保它是封闭的很简单,并不一定需要测试(见What could happen if I don't close response.Body in golang?

resp, err := GetWithTimeout(s.URL) 
    if err != nil { 
     t.Fatal(err) 
    } 
    ioutil.ReadAll(resp.Body) 
    resp.Body.Close() 

这无疑是有限的用处,因为最常见的错误会导致不当错误和响应处理,而且您没有测试,因为测试需要首先正确执行。

这里剩下的问题是,你的GetWithTimeout方法返回一个错误值一个有效的HTTP响应,这违背了HTTP包文档以及大多数用户的期望。如果你要插入一个错误,最好还是在同一点处理响应,以确保关闭和丢弃正文。

最后,大多数GetWithTimeout是多余的,因为不仅每次创建传输都不正确,而且创建一个http.Client通常是不必要的,因为它们意味着被重用并且对于并发使用是安全的。

+0

确实很棒的提示,非常感谢你的所有。我正在考虑所有这些。但是,我仍然无法找到在我的集成测试中测试泄漏连接的方法。我很好奇如何测试这种行为。 –

+0

@InancGumus:你正在做的'lsof'检查工作正常(尽管你可能想过滤TIME_WAIT之类的东西,并且只查找客户端连接),并且在你的测试中修复代码将显示预期的结果。你想要测试什么其他行为? – JimB

+0

是的,我明白了。但是,正如在我的问题中的代码,目前,测试无法检测到泄漏。然而,当我自己在CLI中运行'lsof'时,我可以看到泄漏(_你可以在我的问题中看到附加的gif)。 –