Home > Mobile >  How to terminate long running function after a timeout
How to terminate long running function after a timeout

Time:09-06

So I a attempting to shut down a long running function if something takes too long, maybe is just a solution to treating the symptoms rather than cause, but in any case for my situation it didn't really worked out.

I did it like this:

func foo(abort <- chan struct{}) {
for {
  select{
    case <-abort:
      return
    default:
    ///long running code
  }
}
}

And in separate function I have which after some time closes the passed chain, which it does, if I cut the body returns the function. However if there is some long running code, it does not affect the outcome it simply continues the work as if nothing has happened.

I am pretty new to GO, but it feels like it should work, but it does not. Is there anything I am missing. After all routers frameworks have timeout function, after which whatever is running is terminated. So maybe this is just out of curiosity, but I would really want how to od it.

CodePudding user response:

your code only checks whether the channel was closed once per iteration, before executing the long running code. There's no opportunity to check the abort chan after the long running code starts, so it will run to completion.

You need to occasionally check whether to exit early in the body of the long running code, and this is more idiomatically accomplished using context.Context and WithTimeout for example: https://pkg.go.dev/context#example-WithTimeout

CodePudding user response:

In your "long running code" you have to periodically check that abort channel.

The usual approach to implement that "periodically" is to split the code into chunks each of which completes in a reasonably short time frame (given that the system the process runs on is not overloaded).

After executing each such chunk you check whether the termination condition holds and then terminate execution if it is.
The idiomatic approach to perform such a check is "select with default":

select {
case <-channel:
  // terminate processing
default:
}

Here, the default no-op branch is immediately taken if channel is not ready to be received from (or closed).

Some alogrithms make such chunking easier because they employ a loop where each iteration takes roughly the same time to execute. If your algorithm is not like this, you'd have to chunk it manually; in this case, it's best to create a separate function (or a method) for each chunk.

Further points.

  1. Consider using contexts: they provide a useful framework to solve the style of problems like the one you're solving.

    What's better, the fact they can "inherit" one another allow one to easily implement two neat things:

    • You can combine various ways to cancel contexts: say, it's possible to create a context which is cancelled either when some timeout passes or explicitly by some other code.
    • They make it possible to create "cancellation trees" — when cancelling the root context propagate this signal to all the inheriting contexts — making them cancel what other goroutines are doing.
  2. Sometimes, when people say "long-running code" they do not mean code actually crunching numbers on a CPU all that time, but rather the code which performs requests to slow entities — such as databases, HTTP servers etc.

    If your case is this, note that all good Go packages (and all packages of the Go standard library which deal with networked services) accept contexts in those functions of their APIs which actually make calls to those slow entities, and this means that if you make your function to accept a context, you can (actually should) pass this context down the stack of calls where applicable — so that all the code you call can be cancelled in the same way as yours.


Further reading:

  •  Tags:  
  • go
  • Related