douxian3170
douxian3170
2018-07-07 12:07

Goroutine超时

已采纳
type Response struct {
  data   interface{}
  status bool
}

func Find() (interface{}, bool) {
  ch := make(chan Response, 1)

  go func() {
    data, status := findCicCode()
    ch <- Response{data: data, status: status}
  }()

  select {
  case response := <-ch:
    return response.data, response.status
  case <-time.After(50 * time.Millisecond):
    return "Request timed out", false
  }
}

So, I have above function. Basically findCicCode() function call makes 3 http calls internally to external services. I have added combined timeout here for those 3 http calls. Can't put individual timeout in my case. But It still makes api calls in background if it exceeds timeout.

I am not sure if there is goroutine leak here. Is there a way to cancel those https requests if there is timeout?

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

2条回答

  • douxiangshi6568 douxiangshi6568 3年前

    You control cancelation of http requests with a context.Context.

    // create a timeout or cancelation context to suit your requirements
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    
    req, err := http.NewRequest("GET", location, nil)
    
    // add the context to each request and they will be canceled in unison
    resp, err := http.Do(req.WithContext(ctx))
    
    点赞 评论 复制链接分享
  • duangenshi9836 duangenshi9836 3年前

    If you want to, you can create your own timeout system for arbitrary work by having a single receive operation on the channel (in the main goroutine), and whichever other goroutine reaches its send operation first -- the time.Sleep or the one doing actual work -- wins.

    Here's a complete runnable example/simulation. Adjust timeout and delay values to simulate different scenarios. The channel is unbuffered, and is closed after a single value is read to allow the other goroutine to exit on send.

    package main
    
    import(
        "fmt"
        "time"
    )
    
    type Response struct {
        Data        []byte
        Status      int
    }
    
    func Wait(s int) {
        time.Sleep(time.Duration(s) * time.Second)
    }
    
    func FindWrapper(ch chan Response, delay int) {
        // Put real find stuff here...
    
        // Dummy response after wait for testing purposes
        Wait(delay)
        ch <- Response{[]byte("Some data..."), 200}
    }
    
    func main() {
        timeout := 3
        delay := 4
        ch := make(chan Response)
    
        // whoever sends to ch first wins...
        go func() {
            Wait(timeout)
            ch <- Response{}
        }()
        go FindWrapper(ch, delay)
    
        r := <-ch
        close(ch)
        if r.Data == nil {
            r.Status = 500 // or whatever you want for timeout status
        }
        fmt.Printf("Data: %s  Status: %d
    ", string(r.Data), r.Status)
    }
    

    A buffered channel would work too. And you could accomplish the same thing with a sync.WaitGroup where you only call Add once and then close the channel after wg.Wait().

    That said, I suggest trying JimB's solution of using a Context timeout, as it probably works for your use case and is a less complicated solution.

    点赞 评论 复制链接分享

为你推荐