Go — наиболее оптимальный способ реализации тайм-аута

# #go #asynchronous #timeout #polling

Вопрос:

У меня есть служба, которая развертывается асинхронно, и мне нужно подождать определенное количество времени, пока она подключится к сети. Если по истечении указанного времени мы все еще не можем найти службу, то мы выдаем ошибку. Каков наиболее оптимальный способ записи этого в go? Я изучал использование context.WithTimeout , но не был уверен, как именно это будет работать. Спасибо за помощь!

 func (c *Client) WaitForServiceToComeAlive(ctx context.Context, name string, timeout time.Duration) error {
    
    var mysvc *Service
    var err error

    endtime := time.Now().Add(timeout)

    for time.Now().Before(endtime) {
        mysvc, err = c.GetService(ctx, name)
        if err != nil {
            return err
        }

        if mysvc != nil {
            break
        }

        time.Sleep(time.Second * 10)
    }

    if mysvc == nil {
        return fmt.Errorf("svc %s did not register", name)
    }

    return nil
}
 

Ответ №1:

Никогда не используйте time.Sleep , особенно если это длительный период, поскольку он не прерывается. Почему это имеет значение? Если он находится в подпрограмме и эта задача не будет завершена (т. Е. Контекст был отменен), тогда вы предпочли бы прервать ее немедленно.

Итак, чтобы создать опрос-подождите с отменой:

 select {
case <-ctx.Done():               // cancel early if context is canceled
    return ctx.Err()
case <-time.After(pollInterval): // wait for pollInterval duration
}
 

Поместите больший тайм-аут во входной контекст:

 ctx := context.TODO() // <- outer request context goes here or context.Background()

// wrap context with a timeout
ctx, cancel := context.WithTimeout(ctx, 1 * time.Minute)
defer cancel() // avoid leaks

err := c.WaitForServiceToComeAlive(ctx, "job", 10*time.Second /* poll interval */)
 

тогда ваша функция ожидания обслуживания упрощается до:

 func (c *Client) WaitForServiceToComeAlive(ctx context.Context, name string, pollInterval time.Duration) error {

    var mysvc *Service
    var err error

    for {
        mysvc, err = c.GetService(name) // <- this should take a ctx too - if possible for early cancelation
        if err != nil {
            return err
        }
        if mysvc != nil {
            return nil
        }

        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-time.After(pollInterval):
        }
    }

}
 

https://play.golang.org/p/JwH5CMyY0I2