Home > Software design >  Using `Context` to implement timeout
Using `Context` to implement timeout

Time:08-24

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 <- true
    }()

    for {
        select {
        case <-ctx.Done():
            {
                return nil, errors.New("Cancelled")
            }
        case <-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?

CodePudding user response:

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:

Terminating function execution if a context is cancelled

Is it possible to cancel unfinished goroutines?

Stopping running function using context timeout in Golang

cancel a blocking operation in Go

  • Related