golang http тайм-аут и накопление горутин

Я использую горутины для достижения http.Get timeout, а затем я обнаружил, что число горутин неуклонно растет, и когда оно достигнет 1000 или около того, программа выйдет

Код:

package main

import (
        "errors"
        "io/ioutil"
        "log"
        "net"
        "net/http"
        "runtime"
        "time"
)

// timeout dialler
func timeoutDialler(timeout time.Duration) func(network, addr string) (net.Conn, error) {
        return func(network, addr string) (net.Conn, error) {
                return net.DialTimeout(network, addr, timeout)
        }
}

func timeoutHttpGet(url string) ([]byte, error) {
        // change dialler add timeout support && disable keep-alive
        tr := &http.Transport{
                Dial:              timeoutDialler(3 * time.Second),
                DisableKeepAlives: true,
        }

        client := &http.Client{Transport: tr}

        type Response struct {
                resp []byte
                err  error
        }

        ch := make(chan Response, 0)
        defer func() {
                close(ch)
                ch = nil
        }()

        go func() {
                resp, err := client.Get(url)
                if err != nil {
                        ch <- Response{[]byte{}, err}
                        return
                }
                defer resp.Body.Close()

                body, err := ioutil.ReadAll(resp.Body)
                if err != nil {
                        ch <- Response{[]byte{}, err}
                        return
                }

                tr.CloseIdleConnections()
                ch <- Response{body, err}
        }()

        select {
        case <-time.After(5 * time.Second):
                return []byte{}, errors.New("timeout")
        case response := <-ch:
                return response.resp, response.err
        }
}

func handler(w http.ResponseWriter, r *http.Request) {
        _, err := timeoutHttpGet("http://google.com")
        if err != nil {
                log.Println(err)
                return
        }
}

func main() {
        go func() {
                for {
                        log.Println(runtime.NumGoroutine())
                        time.Sleep(500 * time.Millisecond)
                }
        }()

        s := &http.Server{
                Addr:         ":8888",
                ReadTimeout:  15 * time.Second,
                WriteTimeout: 15 * time.Second,
        }

        http.HandleFunc("/", handler)
        log.Fatal(s.ListenAndServe())
}

http://play.golang.org/p/SzGTMMmZkI


person Specode    schedule 08.01.2014    source источник
comment
Пожалуйста, всегда вставляйте соответствующий код в свой вопрос. Ссылка на скрипку - это нормально, но в качестве дополнения.   -  person Denys Séguret    schedule 08.01.2014
comment
@dystroy ок, я изменился   -  person Specode    schedule 08.01.2014


Ответы (1)


Начните свой чан с 1 вместо 0:

ch := make(chan Response, 1)

И удалите блок defer, который закрывает и nils ch.

См. http://blog.golang.org/go-concurrency-patterns-timing-out-and

Вот что, я думаю, происходит:

  1. по истечении 5 секунд timeoutHttpGet возвращает
  2. запускается оператор defer, закрывающий ch и затем устанавливающий для него значение nil
  3. процедура go, которую она начала, выполняет фактическое завершение выборки и пытается отправить свои данные в ch
  4. но ch равен нулю, и поэтому ничего не получит, что не позволит завершить этот оператор и, таким образом, не допустит завершения процедуры go

Я предполагаю, что вы устанавливаете ch = nil, потому что до этого вы бы получили панику во время выполнения, потому что это происходит, когда вы пытаетесь писать в закрытый канал, как описано в спецификацию.

Предоставление ch буфера, равного 1, означает, что процедура fetch go может отправлять ему сообщения без использования получателя. Если обработчик вернулся из-за тайм-аута, позже все будет просто собрано мусором.

person mjibson    schedule 08.01.2014
comment
Спасибо большое! - person Specode; 08.01.2014