Go – 实现超时的最佳方式

huangapple go评论77阅读模式
英文:

Go - The most optimal way to implement a timeout

问题

我有一个异步部署的服务,我需要等待一段指定的时间,直到它上线。如果指定的时间过去了,我们仍然无法找到该服务,那么就会出错。在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
}

这段代码的作用是,在指定的时间内等待服务上线。它使用了一个循环来检查服务是否已经上线,每隔10秒钟检查一次。如果超过指定的时间仍然没有找到服务,就会返回一个错误。如果找到了服务,就会跳出循环并返回nil。

你提到了使用context.WithTimeout,这是一个用于设置超时的函数。你可以在调用GetService的地方使用context.WithTimeout来设置超时时间,如果超时了就会返回一个错误。这样可以避免使用循环和time.Sleep来等待服务上线。

以下是使用context.WithTimeout的示例代码:

func (c *Client) WaitForServiceToComeAlive(ctx context.Context, name string, timeout time.Duration) error {
	
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	mysvc, err := c.GetService(ctx, name)
	if err != nil {
		return err
	}

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

	return nil
}

这样,如果超过指定的时间仍然没有找到服务,GetService函数会返回一个超时错误。

英文:

I have a service that is deployed asynchronously and I need to wait a specified amount of time for it to come online. If the specified amount of time elapses and we still aren't able to find the service, then we error out. What is the most optimal way of writing this in go? I was looking into using context.WithTimeout but not sure exactly how this would work. Thanks for the help!

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

得分: 2

永远不要使用time.Sleep,尤其是在长时间间隔时,因为它是不可中断的。为什么这很重要?如果它在一个goroutine中,并且该任务不会完成(即上下文被取消),那么你宁愿立即中止。

因此,要创建一个带有取消功能的轮询等待:

select {
case <-ctx.Done():               // 如果上下文被取消,则提前取消
    return ctx.Err()
case <-time.After(pollInterval): // 等待pollInterval的时间
}

将较长的超时时间放在输入上下文中:

ctx := context.TODO() // <- 外部请求上下文放在这里或者使用context.Background()

// 使用超时时间包装上下文
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel() // 避免泄漏

err := c.WaitForServiceToComeAlive(ctx, "job", 10*time.Second /* 轮询间隔 */)

然后你的服务等待函数简化为:

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) // <- 如果可能的话,这里也应该接收一个上下文,以便提前取消
        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

英文:

Never use time.Sleep especially if it's a long period - as it's uninterruptible. Why does this matter? If its in a goroutine and that task is not going to complete (i.e. context was canceled), then you would rather abort immediately.

So to create a polling-wait with cancelation:

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

Put your larger timeout within the input context:

ctx := context.TODO() // &lt;- 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, &quot;job&quot;, 10*time.Second /* poll interval */)

then your service-wait function simplifies to:

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) // &lt;- this should take a ctx too - if possible for early cancelation
		if err != nil {
			return err
		}
		if mysvc != nil {
			return nil
		}

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

}

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

huangapple
  • 本文由 发表于 2021年11月11日 07:06:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/69921271.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定