Stop goroutine execution on timeout - go

I want to stop goroutine execution on timeout. But it seems like it is not working for me. I am using iris framework.
type Response struct {
data interface{}
status bool
}
func (s *CicService) Find() (interface{}, bool) {
ch := make(chan Response, 1)
go func() {
time.Sleep(10 * time.Second)
fmt.Println("test")
fmt.Println("test1")
ch <- Response{data: "data", status: true}
}()
select {
case <-ch:
fmt.Println("Read from ch")
res := <-ch
return res.data, res.status
case <-time.After(50 * time.Millisecond):
return "Timed out", false
}
}
Output:
Timed out
test
test1
Expected Output:
Timed out
Can somebody point out what is missing here? It does timeout but still runs goroutine to print test and test1. I just want to stop the execution of goroutine as soon as there is timeout.

There's no good way to "interrupt" the execution of a goroutine in the middle of it's execution.
Go uses a fork-join model of concurrency, this means that you "fork" creating a new goroutine and then have no control over how that goroutine is scheduled until you get to a "join point". A join point is some kind of synchronisation between more than one goroutine. e.g. sending a value on a channel.
Taking your specific example, this line:
ch <- Response{data: "data", status: true}
... will be able to send the value, no matter what because it's a buffered channel. But the timeout's you've created:
case <-time.After(50 * time.Millisecond):
return "Timed out", false
These timeouts are on the "receiver" or "reader" of the channel, and not on the "sender". As mentioned at the top of this answer, there's no way to interrupt the execution of a goroutine without using some synchronisation techniques.
Because the timeouts are on the goroutine "reading" from the channel, there's nothing to stop the execution of the goroutine that send on the channel.

Best way to control your goroutine processing is context (std go library).
You can cancel something inside goroutine and stop execution without possible goroutine leak.
Here simple example with cancel by timeout for your case.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ch := make(chan Response, 1)
go func() {
time.Sleep(1 * time.Second)
select {
default:
ch <- Response{data: "data", status: true}
case <-ctx.Done():
fmt.Println("Canceled by timeout")
return
}
}()
select {
case <-ch:
fmt.Println("Read from ch")
case <-time.After(500 * time.Millisecond):
fmt.Println("Timed out")
}

You have a gouroutine leaks, you must handle some done action to return the goroutine before timeout like this:
func (s *CicService) Find() (interface{}, bool) {
ch := make(chan Response, 1)
done := make(chan struct{})
go func() {
select {
case <-time.After(10 * time.Second):
case <-done:
return
}
fmt.Println("test")
fmt.Println("test1")
ch <- Response{data: "data", status: true}
}()
select {
case res := <-ch:
return res.data, res.status
case <-time.After(50 * time.Millisecond):
done <- struct{}{}
return "Timed out", false
}
}

Related

Why is the following channel operation deadlocking? i.e. downstream <- <- upstream

I have two channels, upstream and downstream. My objective is to read data from upstream and pass them to downstream. However, when context is cancelled, I'd like to exit gracefully without deadlock.
I was trying to be "smart" and did something like the following.
func main() {
upstream := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())
go func() {
<-time.After(5 * time.Second)
cancel()
}()
// Buffered downstream ensures no blocking in this scenario
downstream := make(chan struct{}, 1)
select {
case <-ctx.Done():
log.Println("context is killed")
case downstream <- <-upstream:
log.Println("transferred value from upstream to downstream")
}
}
Then I got myself a deadlock. However, if I stop being lazy and do the following,
func main() {
upstream := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())
go func() {
<-time.After(5 * time.Second)
cancel()
}()
// Buffered downstream ensures no blocking in this scenario
downstream := make(chan struct{}, 1)
select {
case <-ctx.Done():
log.Println("context is killed")
case val := <-upstream:
downstream <-val
log.Println("transferred value from upstream to downstream")
}
}
It exited perfectly fine with no deadlock. Can you please enlighten me, what is the key difference between
downstream <- <-upstream
and
val := <-upstream
downstream <-val
The select statement isn't operating on the <-upstream receive statement, it's operating on the downstream <- send statement.
Before the select case can determine if the downstream <- send statement is ready, it first has to evaluate the argument expression, which is <-upstream. Because nothing is ever sent to upstream, that evaluation is blocked. This means you never get to the select cases at all.
The equivalent multi-line code would look like this, which makes it very apparent why it doesn't work.
val := <-upstream
select {
case <-ctx.Done():
log.Println("context is killed")
case downstream <- val:
log.Println("transferred value from upstream to downstream")
}

Go program sleeps forever even after passing short time.Duration

I'm trying to build some short of semaphore in Go. Although when when the channel receives the signal it just sleeps forever.
I've tried changing the way to sleep and the duration to sleep, but it stills just stopping forever.
Here a representation of what I've tried:
func main() {
backOffChan := make(chan struct{})
go func() {
time.Sleep(2)
backOffChan <- struct{}{}
}()
for {
select {
case <-backOffChan:
d := time.Duration(5 * time.Second)
log.Println("reconnecting in %s", d)
select {
case <-time.After(d):
log.Println("reconnected after %s", d)
return
}
default:
}
}
}
I expect that it just returns after printing the log message and returning.
Thanks!
This code has a number of problems, mainly a tight loop using for/select that may not allow the other goroutine to ever get to send on the channel. Since the default case is empty and the select has only one case, the whole select is unnecessary. The following code works correctly:
backOffChan := make(chan struct{})
go func() {
time.Sleep(1 * time.Millisecond)
backOffChan <- struct{}{}
}()
for range backOffChan {
d := time.Duration(10 * time.Millisecond)
log.Printf("reconnecting in %s", d)
select {
case <-time.After(d):
log.Printf("reconnected after %s", d)
return
}
}
This will wait until the backOffChan gets a message without burning a tight loop.
(Note that this code also addresses issues using log.Println with formatting directives - these were corrected to log.Printf).
See it in action here: https://play.golang.org/p/ksAzOq5ekrm

How to gracefully shutdown chained goroutines in an idiomatic way

Creating multiple goroutines which will have nested goroutines while processing in a multilevel manner (Imagine a tree of goroutines each level can have many leaves).
What is the idiomatic way to gracefully shutdown these goroutines in order and wait for them to come back? Order is the bottom top (deepest child first) and also assume I dont know how many goroutines I will launch beforehand (dynamic).
the example below just gracefully shuts them down in an non ordered manner.
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
//level1
go func() {
fmt.Println("level1 started")
//level2
go func() {
fmt.Println("level2 started")
//level3
go func() {
fmt.Println("level3 started")
select {
case <-ctx.Done():
fmt.Println("Done called on level3")
case <-time.After(5* time.Second):
fmt.Println("After called on level3")
}
}()
select {
case <-ctx.Done():
fmt.Println("Done called on level2")
case <-time.After(7* time.Second):
fmt.Println("After called on level2")
}
}()
select {
case <-ctx.Done():
fmt.Println("Done called on level1")
case <-time.After(10* time.Second):
fmt.Println("After called on level1")
}
}()
time.Sleep(1*time.Second)
cancel()
time.Sleep(1 * time.Second)
}
To wait for a group of goroutines, sync.WaitGroup is the idiomatic solution. You can add 1 to its counter when you launch a new goroutine (WaitGroup.Add()), and the goroutine can signal that it's done with WaitGroup.Done(). The parent goroutine may call WaitGroup.Wait() to wait all its children to finish.
You may do the same on each level. Create a WaitGroup on each level where child goroutines are launched, and only return when Wait() of that goroutine returns.
Here's how it's applied on your example:
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
//level1
wg1 := &sync.WaitGroup{}
wg1.Add(1)
go func() {
defer wg1.Done()
fmt.Println("level1 started")
//level2
wg2 := &sync.WaitGroup{}
wg2.Add(1)
go func() {
defer wg2.Done()
fmt.Println("level2 started")
//level3
wg3 := &sync.WaitGroup{}
wg3.Add(1)
go func() {
defer wg3.Done()
fmt.Println("level3 started")
select {
case <-ctx.Done():
fmt.Println("Done called on level3")
case <-time.After(5 * time.Second):
fmt.Println("After called on level3")
}
fmt.Println("Level 3 ended.")
}()
select {
case <-ctx.Done():
fmt.Println("Done called on level2")
case <-time.After(7 * time.Second):
fmt.Println("After called on level2")
}
wg3.Wait()
fmt.Println("Level 2 ended.")
}()
select {
case <-ctx.Done():
fmt.Println("Done called on level1")
case <-time.After(10 * time.Second):
fmt.Println("After called on level1")
}
wg2.Wait()
fmt.Println("Level 1 ended.")
}()
time.Sleep(1 * time.Second)
cancel()
wg1.Wait()
fmt.Println("Main ended.")
This outputs (try it on the Go Playground):
level1 started
level2 started
level3 started
Done called on level1
Done called on level3
Level 3 ended.
Done called on level2
Level 2 ended.
Level 1 ended.
Parent ended.
What's important from the output:
Level 3 ended.
Level 2 ended.
Level 1 ended.
Main ended.
Levels end in descending level order (from bottom-up), closing with "Main ended.".
One possible, also I'd say idiomatic, way to do this is by passing a channel of strict{}. Whenever you'd like the said goroutine to terminate just write an empty struct to this channel: shutdown <- struct{}{}. This should do the job.
Alternatively you could close the channel, you'll recognise this by having false as the second return value of <-, but I'd suggest using this only if you need to share this channel with multiple goroutines. In general I find this approach a bit shoddy and error prone.
On a side note: the way the shutdown of the goroutines is done in your example, once the context has been cancelled, all goroutines will return. Don't know whether this has much benefit in the general case. Maybe in your case it does.

A question about main routine and a child routine listening on the same channel simultaneously

func main() {
c := make(chan os.Signal, 1)
signal.Notify(c)
ticker := time.NewTicker(time.Second)
stop := make(chan bool)
go func() {
defer func() { stop <- true }()
for {
select {
case <-ticker.C:
fmt.Println("Tick")
case <-stop:
fmt.Println("Goroutine closing")
return
}
}
}()
<-c
ticker.Stop()
stop <- true
<-stop
fmt.Println("Application stopped")
}
No matter how many times I run the code above, I got the same result. That is, "Goroutine closing" is always printed before "Application stopped" after I press Ctrl+C.
I think, theoretically, there is a chance that "Goroutine closing" won't be printed at all. Am I right? Unfortunately, I never get this theoretical result.
BTW: I know reading and writing a channel in one routine should be avoided. Ignore that temporarily.
In your case, Goroutine closing will always be executed and it will always be printed before Application stopped, because your stop channel is not buffered. This means that the sending will block until the result is received.
In your code, the stop <- true in your main will block until the goroutine has received the value, causing the channel to be empty again. Then the <-stop in your main will block until another value is sent to the channel, which happens when your goroutine returns after printing Goroutine closing.
If you would initialize your channel in a buffered fashion
stop := make(chan bool, 1)
then Goroutine closing might not be executed. To see this, you can add a time.Sleep right after printing Tick, as this makes this case more likely (it will occur everytime you press Ctrl+C during the sleep).
Using a sync.WaitGroup to wait for goroutines to finish is a good alternative, especially if you have to wait for more than one goroutine. You can also use a context.Context to stop goroutines. Reworking your code to use these two methods could look something like this:
func main() {
c := make(chan os.Signal, 1)
signal.Notify(c)
ticker := time.NewTicker(time.Second)
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer func() { wg.Done() }()
for {
select {
case <-ctx.Done():
fmt.Println("Goroutine closing")
return
case <-ticker.C:
fmt.Println("Tick")
time.Sleep(time.Second)
}
}
}()
<-c
ticker.Stop()
cancel()
wg.Wait()
fmt.Println("Application stopped")
}

How to implement a timeout when using sync.WaitGroup.wait? [duplicate]

This question already has answers here:
Timeout for WaitGroup.Wait()
(10 answers)
Closed 7 months ago.
I have come across a situation that i want to trace some goroutine to sync on a specific point, for example when all the urls are fetched. Then, we can put them all and show them in specific order.
I think this is the barrier comes in. It is in go with sync.WaitGroup. However, in real situation that we can not make sure that all the fetch operation will succeed in a short time. So, i want to introduce a timeout when wait for the fetch operations.
I am a newbie to Golang, so can someone give me some advice?
What i am looking for is like this:
wg := &sync.WaigGroup{}
select {
case <-wg.Wait():
// All done!
case <-time.After(500 * time.Millisecond):
// Hit timeout.
}
I know Wait do not support Channel.
If all you want is your neat select, you can easily convert blocking function to a channel by spawning a routine which calls a method and closes/sends on channel once done.
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
// All done!
case <-time.After(500 * time.Millisecond):
// Hit timeout.
}
Send your results to a buffered channel enough to take all results, without blocking, and read them in for-select loop in the main thread:
func work(msg string, d time.Duration, ret chan<- string) {
time.Sleep(d) // Work emulation.
select {
case ret <- msg:
default:
}
}
// ...
const N = 2
ch := make(chan string, N)
go work("printed", 100*time.Millisecond, ch)
go work("not printed", 1000*time.Millisecond, ch)
timeout := time.After(500 * time.Millisecond)
loop:
for received := 0; received < N; received++ {
select {
case msg := <-ch:
fmt.Println(msg)
case <-timeout:
fmt.Println("timeout!")
break loop
}
}
Playground: http://play.golang.org/p/PxeEEJo2dz.
See also: Go Concurrency Patterns: Timing out, moving on.
Another way to do it would be to monitor it internally, your question is limited but I'm going to assume you're starting your goroutines through a loop even if you're not you can refactor this to work for you but you could do one of these 2 examples, the first one will timeout each request to timeout individually and the second one will timeout the entire batch of requests and move on if too much time has passed
var wg sync.WaitGroup
wg.Add(1)
go func() {
success := make(chan struct{}, 1)
go func() {
// send your request and wait for a response
// pretend response was received
time.Sleep(5 * time.Second)
success <- struct{}{}
// goroutine will close gracefully after return
fmt.Println("Returned Gracefully")
}()
select {
case <-success:
break
case <-time.After(1 * time.Second):
break
}
wg.Done()
// everything should be garbage collected and no longer take up space
}()
wg.Wait()
// do whatever with what you got
fmt.Println("Done")
time.Sleep(10 * time.Second)
fmt.Println("Checking to make sure nothing throws errors after limbo goroutine is done")
Or if you just want a general easy way to timeout ALL requests you could do something like
var wg sync.WaitGroup
waiter := make(chan int)
wg.Add(1)
go func() {
success := make(chan struct{}, 1)
go func() {
// send your request and wait for a response
// pretend response was received
time.Sleep(5 * time.Second)
success <- struct{}{}
// goroutine will close gracefully after return
fmt.Println("Returned Gracefully")
}()
select {
case <-success:
break
case <-time.After(1 * time.Second):
// control the timeouts for each request individually to make sure that wg.Done gets called and will let the goroutine holding the .Wait close
break
}
wg.Done()
// everything should be garbage collected and no longer take up space
}()
completed := false
go func(completed *bool) {
// Unblock with either wait
wg.Wait()
if !*completed {
waiter <- 1
*completed = true
}
fmt.Println("Returned Two")
}(&completed)
go func(completed *bool) {
// wait however long
time.Sleep(time.Second * 5)
if !*completed {
waiter <- 1
*completed = true
}
fmt.Println("Returned One")
}(&completed)
// block until it either times out or .Wait stops blocking
<-waiter
// do whatever with what you got
fmt.Println("Done")
time.Sleep(10 * time.Second)
fmt.Println("Checking to make sure nothing throws errors after limbo goroutine is done")
This way your WaitGroup will stay in sync and you won't have any goroutines left in limbo
http://play.golang.org/p/g0J_qJ1BUT try it here you can change the variables around to see it work differently
Edit: I'm on mobile If anybody could fix the formatting that would be great thanks.
If you would like to avoid mixing concurrency logic with business logic, I wrote this library https://github.com/shomali11/parallelizer to help you with that. It encapsulates the concurrency logic so you do not have to worry about it.
So in your example:
package main
import (
"github.com/shomali11/parallelizer"
"fmt"
)
func main() {
urls := []string{ ... }
results = make([]*HttpResponse, len(urls)
options := &Options{ Timeout: time.Second }
group := parallelizer.NewGroup(options)
for index, url := range urls {
group.Add(func(index int, url string, results *[]*HttpResponse) {
return func () {
...
results[index] = &HttpResponse{url, response, err}
}
}(index, url, &results))
}
err := group.Run()
fmt.Println("Done")
fmt.Println(fmt.Sprintf("Results: %v", results))
fmt.Printf("Error: %v", err) // nil if it completed, err if timed out
}

Resources