Timeout multiple goroutines

Hello,
I am trying to implement timeout in golang with context.

However I am unable to get it work. Even if I pass timeout as 1 second still I don’t get any errors also response from go routines is received after 5-6 seconds.

In ideal scenario I want to stop the process and return an empty results if timeout is reached.

Can you please help me to understand what am I missing here. ?

func GetResponse() {
  ctx, cancel := context.WithTimeout(context.Background(), time.Duration(searchData.TimeOut)*time.Second)
  defer cancel()
  InfoList := info.GetList()
  count := len(InfoList)
  call_status := make(chan bool)
  for _, info := range InfoList {
     go info.call_first(call_status, ctx,info)
     go info.call_second(call_status, ctx,info)
     go info.call_third(call_status, ctx,info)
     go info.call_n(call_status, ctx,info)
 }
 for i := 0; i < count; i++ {
	<-call_status
 }
}

Goroutine functions are as below :

func call_first(call_status, ctx,info){
 /* some processing here */
 call_status <- true
}

Coroutines aren’t automatically canceled when the context is canceled. You have to add the code yourself that checks if the context is canceled and returned. Something like:

func call_first(ctx context.Context, callInfo chan bool, info Info) {
    if err := ctx.Err(); err != nil {
        // The context is canceled or otherwise in an error state.  Do something
        // with the error.
    }

    /* some processing here */
    call_status <- true
}
3 Likes

In addition to @skillian’s solution, if the goroutine contains a long-running loop, you can also look for a timeout inside the loop, through a select statement that checks the output of the context’s “done” channel.

func call_first(ctx context.Context, call_status chan bool, info Info) {
    ... 
    /* long-running loop */
    for /* some condition */ {
        select {
        case <-ctx.Done():
            fmt.Println(ctx.Err()) // handle the error somehow
            return // return, or alternatively break out of the loop
        default: // needed so that the select statement does not block
        }
        /* no timeout or error yet -> do some processing */
    }
    call_status <- true
}

Thank you @skillian , @christophberger .
Do I need to check context in GetResponse() as well?

I think it depends. Let’s assume the timeout triggers. If the goroutines then send a status into call_status before exiting, then GetResponse() can read all statuses and will terminate properly. Otherwise, GetResponse() would indeed need to check for the timeout by itself.

I tried this in a loop which is in call_first(). However, I am getting context deadline exceeded all the time. It doen’t process the data and doesn’t return desired response. If I comment select then I am getting response. I tried increasing timeout also.

func call_first(ctx context.Context, call_status chan bool, info Info) {
	if err := ctx.Err(); err != nil {
		log.Println(err)
		call_status <- true
	}
	for _, value := range rows {
		select {
		case <-ctx.Done():
			log.Println(ctx.Err())
			call_status <- true
		default:
			
		}
		processdata(Info)
	}
}

Can you share the full runnable code? (Preferably via a share link from play.golang.org)

You seem to have pasted an invalid version of call_first by accident. The function as pasted here would not even compile because rows is not defined, value is not used and processdata(Info) should read processdata(info) (with a lowercase i).

I couldn’t share the exact application code. But I have created similar code https://play.golang.org/p/wRcn7CtNxbj

In main() I am passing 5 as timeout to context. In call_first()
and call_second() there is a loop of 5 and a call to processdata().

In processdata() I have added sleep timeout to emulate delayed response and expecting that I will get an error context deadline exceeded in call_second(). But I am not getting any error now.

Can you please advise if I am missing anything here? Thank you.

I would guess that main() exits too early. The loop that collects bools from call_status iterates over the array lengths, yet each of the two goroutines loops 5 times over the processdata() call. Hence main() exits too early.

defer cancel() runs when main exits but the goroutines may not have the time to react to the canceling as they stop hard along with the exit of main().

(When I run the playground code repeatedly, I get the error messages occasionally, though.)