Cancel further operations on context cancellation - go

I have a func that receives context and does some cpubound operations like bellow.
func DoSomeOperation(ctx context.Context){
CPUBoundWork1()
CPUBoundWork2()
CPUBoundWork3()
CPUBoundWork4()
}
What I want to do is to check if Context has been cancelled or not before making each CPUBound func call. If cancelled I want to return immidiately without making next func call. Is there any way to do this?

Use ctx.Err()
if ctx.Err() == context.Canceled {
return
}
You also can use select statement with slice of functions.
For Example:
ctx := ...
executors := []func(){...}
Loop:
for _,executor := range executors{
select {
case <-ctx.Done():
if ctx.Err() == context.Canceled {
break Loop
}
if ctx.Err() == context.DeadlineExceeded {
//do something else
}
default:
executor()
}
}
PLAYGROUND

Related

What is the effects for this empty select-case-default code block?

I'm trying to understand a pool library codes, and when instancing a pool struct, call a function named startCleanerLocked(t Duration), in this function, there's one empty select...case...default... code block, I cann't understand what is the effect for this code block.
Pool Interface is:
// Pool interface.
type Pool interface {
Get(ctx context.Context) (io.Closer, error)
Put(ctx context.Context, c io.Closer, forceClose bool) error
Close() error
}
List Struct implement Pool Interface,
type List struct {
// New is an application supplied function for creating and configuring a
// item.
//
// The item returned from new must not be in a special state
// (subscribed to pubsub channel, transaction started, ...).
New func(ctx context.Context) (io.Closer, error)
// mu protects fields defined below.
mu sync.Mutex
cond chan struct{}
closed bool
active int
// clean stale items
cleanerCh chan struct{}
// Stack of item with most recently used at the front.
idles list.List
// Config pool configuration
conf *Config
}
when Create a new pool, startCleanerLocked(t Duration) function be called:
// NewList creates a new pool.
func NewList(c *Config) *List {
// check Config
if c == nil || c.Active < c.Idle {
panic("config nil or Idle Must <= Active")
}
// new pool
p := &List{conf: c}
p.cond = make(chan struct{})
p.startCleanerLocked(time.Duration(c.IdleTimeout))
return p
}
and in startCleanerLocked(t Duration), there is a select...case...default:
// startCleanerLocked
func (p *List) startCleanerLocked(d time.Duration) {
if d <= 0 {
// if set 0, staleCleaner() will return directly
return
}
if d < time.Duration(p.conf.IdleTimeout) && p.cleanerCh != nil {
select {
case p.cleanerCh <- struct{}{}:
default:
}
}
// run only one, clean stale items.
if p.cleanerCh == nil {
p.cleanerCh = make(chan struct{}, 1)
go p.staleCleaner()
}
}
what's the effect for this code block:
select {
case p.cleanerCh <- struct{}{}:
default:
}
Seems it's nothing to do...
and in staleCleaner(), there is a same empty select..case...case, also cannot undestand its effect:
// staleCleaner clean stale items proc.
func (p *List) staleCleaner() {
ticker := time.NewTicker(100 * time.Millisecond)
for {
select {
case <-ticker.C:
case <-p.cleanerCh: // maxLifetime was changed or db was closed.
}
p.mu.Lock()
if p.closed || p.conf.IdleTimeout <= 0 {
p.mu.Unlock()
return
}
for i, n := 0, p.idles.Len(); i < n; i++ {
e := p.idles.Back()
if e == nil {
// no possible
break
}
ic := e.Value.(item)
if !ic.expired(time.Duration(p.conf.IdleTimeout)) {
// not need continue.
break
}
p.idles.Remove(e)
p.release()
p.mu.Unlock()
ic.c.Close()
p.mu.Lock()
}
p.mu.Unlock()
}
}
select {
case p.cleanerCh <- struct{}{}:
default:
}
This is a non-blocking select statement. (because there is a default: case)
If there is a receiver goroutine at the other end of the p.cleanerCh channel, i.e. there is a goroutine that's currently "waiting" at a receive operation i.e. <-p.cleanerCh, then the case p.cleanerCh <- struct{}{} is executed immediately which effectively unblocks the receive operation <-p.cleanerCh and then the goroutine can proceed to execute whatever statements follow.
If there is no receiver goroutine then the default: case is immediately executed and the surrounding startCleanerLocked function can proceed to execute whatever statement follow the select statement.
select {
case <-ticker.C:
case <-p.cleanerCh: // maxLifetime was changed or db was closed.
}
This is a blocking select statement. (because there is no default: case)
This select statement blocks the for loop until one of the two communication cases is ready to receive.

Is there a way to ask time.After() for an infinite amount of time?

Is there a way to ask time.After() for an infinite amount of time?
Motivation: I have a service that a caller can request a message from, with an optional timeout. The obvious way to do this would be:
func service(timeout *time.Duration) SomeType {
var timeout_value time.Duration
if timeout != nil {
timeout_value = *timeout
} else {
timeout_value = time.Forever /* or something */
}
select {
case value <- some_channel:
return value
case <- time.After(timeout_value):
return nil
}
}
Except I don't know if there's a way to say time.Forever.
There is no "forever" duration, but there is max duration:
const maxDuration time.Duration = 1<<63 - 1
maxDuration is about 292 years. It should be enough for the lifetime of a single app. But instead I propose the below solution which doesn't use it:
Note that if "forever" is the intended max wait time, it's simpler and more efficient to omit time.After() and use a simple receive:
func service(timeout *time.Duration) SomeType {
if timeout == nil {
return <-some_channel
}
select {
case value := <-some_channel:
return value
case <-time.After(*timeout):
return nil
}
}
You indicated that your actual code is much more complex and contains more cases.
In that case, I'd move the timeout channel creation outside of the select statement, and initialize accordingly. When timeout is nil, just leave the channel nil (its zero value), which will never deliver any value, so receiving from a nil channel literally takes "forever":
func service(timeout *time.Duration) SomeType {
var timeoutCh <-chan time.Time
if timeout != nil {
timeoutCh = time.After(*timeout)
}
select {
case value := <-some_channel:
return value
case <-timeoutCh:
return nil
}
}
Instead of the duration you could accept a context.Context in your function, which I think is quite idiomatic in Go code.
Then the caller can call the function with a Context.Background or with a Context.WithTimeout as needed. The service function selects on the context's Done(), which in case of the background context never ends (the chan is effectively nil).
Done may return nil if this context can never be canceled. [...] Done is provided for use in select statements
func callerNoTimeout() {
foo := service(context.Background())
}
func callerTimeout() {
foo := service(context.WithTimeout(context.Background(), timeOut))
}
func service(ctx context.Context) SomeType {
select {
case value <-some_channel:
return value
case <-ctx.Done():
return nil
}
}
Firstly, it's common practice to use a time.Duration of 0 (or negative) to indicate no timeout - so it's not necessary to pass a pointer.
Secondly, just check for this zero-value when enforcing a timeout or not:
func service(timeout time.Duration) SomeType {
if timeout <= 0 {
return <- some_channel
}
select {
case value <- some_channel:
return value
case <- time.After(timeout):
return nil
}
}

Use context to break out of a loop

Consider this (https://play.golang.org/p/zvDiwul9QR0):
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
for {
select {
case <-ctx.Done():
fmt.Println("Done")
break
default:
for {
fmt.Println("loop")
time.Sleep(500 * time.Millisecond)
}
}
}
}
So here the contexts returns a "Done()" channel after 2 seconds. And I want to catch this and cancel my infinite for loop. The code example above does not do this, it never exits the loop.
How can I achieve this?
Context cancelation is not magic - they are just a signal mechanism. To abort work, you need to monitor the state of the context from your worker goroutine:
for {
fmt.Println("loop")
select {
case <-time.After(500 * time.Millisecond):
case <-ctx.Done():
return
}
}
https://play.golang.org/p/L6-nDpo9chb
also as Eli pointed out, break will only break out of the select statement - so you need something more precise to break out of a loop. Refactoring into functions make return's much more intuitive for task abortion.
Following up from comments. I would refactor your task like so:
// any potentially blocking task should take a context
// style: context should be the first passed in parameter
func myTask(ctx context.Context, poll time.Duration) error {
for {
fmt.Println("loop")
select {
case <-time.After(poll):
case <-ctx.Done():
return ctx.Err()
}
}
}
https://play.golang.org/p/I3WDVd1uHbz

Stop for loop by passing empty struct down channel Go

I am attempting to create a poller in Go that spins up and every 24 hours executes a function.
I want to also be able to stop the polling, I'm attempting to do this by having a done channel and passing down an empty struct to stop the for loop.
In my tests, the for just loops infinitely and I can't seem to stop it, am I using the done channel incorrectly? The ticker case works as expected.
Poller struct {
HandlerFunc HandlerFunc
interval *time.Ticker
done chan struct{}
}
func (p *Poller) Start() error {
for {
select {
case <-p.interval.C:
err := p.HandlerFunc()
if err != nil {
return err
}
case <-p.done:
return nil
}
}
}
func (p *Poller) Stop() {
p.done <- struct{}{}
}
Here is the test that's exeuting the code and causing the infinite loop.
poller := poller.NewPoller(
testHandlerFunc,
time.NewTicker(1*time.Millisecond),
)
err := poller.Start()
assert.Error(t, err)
poller.Stop()
Seems like problem is in your use case, you calling poller.Start() in blocking maner, so poller.Stop() is never called. It's common, in go projects to call goroutine inside of Start/Run methods, so, in poller.Start(), i would do something like that:
func (p *Poller) Start() <-chan error {
errc := make(chan error, 1 )
go func() {
defer close(errc)
for {
select {
case <-p.interval.C:
err := p.HandlerFunc()
if err != nil {
errc <- err
return
}
case <-p.done:
return
}
}
}
return errc
}
Also, there's no need to send empty struct to done channel. Closing channel like close(p.done) is more idiomatic for go.
There is no explicit way in Go to broadcast an event to go routines for something like cancellation. Instead its idiomatic to create a channel that when closed signifies a message such as cancelling any work it has to do. Something like this is a viable pattern:
var done = make(chan struct{})
func cancelled() bool {
select {
case <-done:
return true
default:
return false
}
}
Go-routines can call cancelled to poll for a cancellation.
Then your main loop can respond to such an event but make sure you drain any channels that might cause go-routines to block.
for {
select {
case <-done:
// Drain whatever channels you need to.
for range someChannel { }
return
//.. Other cases
}
}

Go Gin middleware events

I'm trying to make middleware for the Gin server to handle events during a single request because I need things like IP address from context and to not pass the whole context I'd rather pass my listener which makes my method not dependent on gin.Context.
I set up a server
func main() {
router := gin.New()
router.Use(gin.Recovery())
api := router.Group("api", middleware())
{
api.GET("test/:id", endpoint)
}
router.Run("localhost:8080")
}
and made a middleware
func middleware() gin.HandlerFunc {
return func(c *gin.Context) {
listener := make(chan int)
c.Set("EventListener", listener)
go func() {
select {
case <-listener:
fmt.Println("event called")
default:
fmt.Println("default")
}
}()
c.Next()
}
}
then I got an endpoint where I get my listener and then pass it to any function I want to
func endpoint(c *gin.Context) {
listener := c.MustGet("EventListener").(chan int)
id := c.Param("id")
idInt, err := strconv.ParseInt(id, 10, 64)
if err != nil {
fmt.Println(err)
return
}
doSomething(listener, int(idInt))
}
and doSomething function to show that no context is used later on
func doSomething(listener chan int, id int) {
if id == 1 {
fmt.Println("ERROR: cause event and exit")
listener <- int(id)
return
}
if id == 2 {
fmt.Println("WARN: cause event but continue")
listener <- int(id)
}
fmt.Println("OK: everything is fine")
}
And the way it works now is:
when you call GET http://localhost:8080/api/test/1 it will trigger event and exit
when you call GET http://localhost:8080/api/test/2 it will trigger event and keep working
when you call GET http://localhost:8080/api/test/3 it will not trigger event because all is ok.
So everything is working but only for one event along request. You can not call another one, because select already passed so my question is how to fix it and allow to trigger event multiple times.
I know I can make it like
for {
select {
case <-listener:
fmt.Println("event called")
}
}
but what would be stop condition of this loop?
I know there is something like c.Done() which is chan but have no idea how to use it in my case because I can make it like
for {
select {
case <-listener:
fmt.Println("event called")
case <-c.Done():
return
}
}
but how to pass that c.Done()? This goroutine does not stop.
I found out that there is also c.Request.Context().Done() which works now, stopping goroutine and allowing to handle multiple events
go func() {
for {
select {
case <-listener:
fmt.Println("event called")
case <-c.Request.Context().Done():
return
}
}
}()

Resources