使用`Context`实现超时功能

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

Using `Context` to implement timeout

问题

假设我有一个函数,用于向API端点发送网络请求,我想为客户端添加超时功能,以便如果调用时间过长,操作会中断,要么返回错误,要么中断当前线程。

另一个假设是,客户端函数(发送网络请求的函数)来自一个库,并且以同步方式实现。

让我们来看一下客户端函数的签名:

func Send(params map[string]string) (*http.Response, error)

我想写一个包装器来在这个函数周围添加超时机制。为此,我可以这样做:

func SendWithTimeout(ctx context.Context, params map[string]string) (*http.Response, error) {
    completed := make(chan bool)

    go func() {
        res, err := Send(params)
        _ = res
        _ = err
        completed <- true
    }()

    for {
        select {
        case <-ctx.Done():
            {
                return nil, errors.New("Cancelled")
            }
        case <-completed:
            {
                return nil, nil // just to test how this method works
            }
        }
    }
}

现在,当我调用这个新函数并传递一个可取消的上下文时,我成功地得到一个取消错误,但是运行原始Send函数的goroutine会一直运行到结束。

由于该函数进行API调用,意味着在后台实际上涉及建立套接字/TCP连接,将一个长时间运行的API留在后台是不好的做法。

是否有任何标准的方法在context.Done()触发时中断原始的Send函数?

英文:

Assuming that I have a function that sends web requests to an API endpoint, I would like to add a timeout to the client so that if the call is taking too long, the operation breaks either by returning an error or panicing the current thread.

Another assumption is that, the client function (the function that sends web requests) comes from a library and it has been implemented in a synchronous way.

Let's have a look at the client function's signature:

func Send(params map[string]string) (*http.Response, error)

I would like to write a wrapper around this function to add a timeout mechanism. To do that, I can do:

func SendWithTimeout(ctx context.Context, params map[string]string) (*http.Response, error) {
	completed := make(chan bool)

	go func() {
		res, err := Send(params)
		_ = res
		_ = err
		completed &lt;- true
	}()

	for {
		select {
		case &lt;-ctx.Done():
			{
				return nil, errors.New(&quot;Cancelled&quot;)
			}
		case &lt;-completed:
			{
				return nil, nil // just to test how this method works
			}
		}
	}
}

Now when I call the new function and pass a cancellable context, I successfully get a cancellation error, but the goroutine that is running the original Send function keeps on running to the end.

Since, the function makes an API call meaning that establishing socket/TCP connections are actually involved in the background, it is not a good practice to leave a long-running API behind the scene.

Is there any standard way to interrupt the original Send function when the context.Done() is hit?

答案1

得分: 3

这是一个“不好”的设计选择,将上下文支持添加到之前不支持上下文的现有API/实现中。应该将上下文支持添加到现有的Send()实现中,该实现使用它/监视它,并将其重命名为SendWithTimeout(),并提供一个新的Send()函数,该函数不接受上下文,并调用SendWithTimeout()并使用context.TODO()context.Background()

例如,如果你的Send()函数发起一个出站的HTTP调用,可以使用http.NewRequest()后跟Client.Do()来实现。在新的、支持上下文的版本中,使用http.NewRequestWithContext()

如果你有一个无法更改的Send()函数,那么你就“没办法”了。函数本身必须支持上下文或取消。你不能从外部中止它。

参考链接:

https://stackoverflow.com/questions/62002729/terminating-function-execution-if-a-context-is-cancelled/62002965#62002965

https://stackoverflow.com/questions/71855079/is-it-possible-to-cancel-unfinished-goroutines/71855127#71855127

https://stackoverflow.com/questions/61042141/stopping-running-function-using-context-timeout-in-golang/61042203#61042203

https://stackoverflow.com/questions/28240133/cancel-a-blocking-operation-in-go/28240299#28240299

英文:

This is a "poor" design choice to add context support to an existing API / implementation that did not support it earlier. Context support should be added to the existing Send() implementation that uses it / monitors it, renaming it to SendWithTimeout(), and provide a new Send() function that takes no context, and calls SendWithTimeout() with context.TODO() or context.Background().

For example if your Send() function makes an outgoing HTTP call, that may be achieved by using http.NewRequest() followed by Client.Do(). In the new, context-aware version use http.NewRequestWithContext().

If you have a Send() function which you cannot change, then you're "out of luck". The function itself has to support the context or cancellation. You can't abort it from the outside.

See related:

https://stackoverflow.com/questions/62002729/terminating-function-execution-if-a-context-is-cancelled/62002965#62002965

https://stackoverflow.com/questions/71855079/is-it-possible-to-cancel-unfinished-goroutines/71855127#71855127

https://stackoverflow.com/questions/61042141/stopping-running-function-using-context-timeout-in-golang/61042203#61042203

https://stackoverflow.com/questions/28240133/cancel-a-blocking-operation-in-go/28240299#28240299

huangapple
  • 本文由 发表于 2022年8月22日 15:13:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/73441392.html
匿名

发表评论

匿名网友

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

确定