Why when I timeout a function a defer is not called? - go

When I add a defer in a function I expect that it will be always called when the function ends.
I noticed that it does not happen when the function is times out.
package main
import (
"context"
"fmt"
"time"
)
func service1(ctx context.Context, r *Registry) {
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer func() {
r.Unset("service 1")
}()
r.Set("service 1")
go service2(ctx, r)
select {
case <-ctx.Done():
cancel()
break
}
}
func service2(ctx context.Context, r *Registry) {
defer func() {
r.Unset("service 2")
}()
r.Set("service 2")
time.Sleep(time.Millisecond * 300)
}
type Registry struct {
entries map[string]bool
}
func (r *Registry)Set(key string) {
r.entries[key] = true
}
func (r *Registry)Unset(key string) {
r.entries[key] = false
}
func (r *Registry)Print() {
for key, val := range r.entries {
fmt.Printf("%s -> %v\n", key, val)
}
}
func NewRegistry() *Registry {
r := Registry{}
r.entries = make(map[string]bool)
return &r
}
func main() {
r := NewRegistry()
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*200)
go service1(ctx, r)
// go service3(ctx, r)
select {
case <-ctx.Done():
fmt.Printf("context err: %s\n", ctx.Err())
cancel()
}
r.Print()
}
In the example above, the defer in service2() is never called and that's why the output is:
service 1 -> false
service 2 -> true
instead of
service 1 -> false
service 2 -> false
I understand that timeout means "stop executing" but it's reasonable to me execute deferred code. I could not find any explanation of this behavior.
And the second part of the question - how to modify the service or Registry to be resistant to such situations?

Answer of 1st part
Say you have a function f1() which uses defer to call f2(), i.e. defer f2() . The fact is that the f2 will be called if and only if f1 completes even if a run-time panic occurs. More specifically, look at go-defer.
Now our concern is about using defer in goroutine. We also have to remember that a go-routine exits if it's parent function completes of exits.
So if we use defer in a go-routine function, then if the parent fuction completes or exits, then go-routine function must exit. Since it exits (not completes) the defer statement will not execute. It will be clear we draw the state of your program.
As you see,
at 1st millisecond, service1() completes before others. So, service2() exits without executing defer statement and 'service 2' won't be set to false. Since service1() completes, it's defer will execute and 'service 1' will be set to false.
at 2nd millisecond, the main() completes and program finishes.
So we see how this program executes.
Answer of 2nd part
One possible solution i tried is increase time in service1() or decrease time in service2().

Related

Is it possible to cancel unfinished goroutines?

Consider a group of check works, each of which has independent logic, so they seem to be good to run concurrently, like:
type Work struct {
// ...
}
// This Check could be quite time-consuming
func (w *Work) Check() bool {
// return succeed or not
//...
}
func CheckAll(works []*Work) {
num := len(works)
results := make(chan bool, num)
for _, w := range works {
go func(w *Work) {
results <- w.Check()
}(w)
}
for i := 0; i < num; i++ {
if r := <-results; !r {
ReportFailed()
break;
}
}
}
func ReportFailed() {
// ...
}
When concerned about the results, if the logic is no matter which one work fails, we assert all works totally fail, the remaining values in the channel are useless. Let the remaining unfinished goroutines continue to run and send results to the channel is meaningless and waste, especially when w.Check() is quite time-consuming. The ideal effect is similar to:
for _, w := range works {
if !w.Check() {
ReportFailed()
break;
}
}
This only runs necessary check works then break, but is in sequential non-concurrent scenario.
So, is it possible to cancel these unfinished goroutines, or sending to channel?
Cancelling a (blocking) send
Your original question asked how to cancel a send operation. A send on a channel is basically "instant". A send on a channel blocks if the channel's buffer is full and there is no ready receiver.
You can "cancel" this send by using a select statement and a cancel channel which you close, e.g.:
cancel := make(chan struct{})
select {
case ch <- value:
case <- cancel:
}
Closing the cancel channel with close(cancel) on another goroutine will make the above select abandon the send on ch (if it's blocking).
But as said, the send is "instant" on a "ready" channel, and the send first evaluates the value to be sent:
results <- w.Check()
This first has to run w.Check(), and once it's done, its return value will be sent on results.
Cancelling a function call
So what you really need is to cancel the w.Check() method call. For that, the idiomatic way is to pass a context.Context value which you can cancel, and w.Check() itself must monitor and "obey" this cancellation request.
See Terminating function execution if a context is cancelled
Note that your function must support this explicitly. There is no implicit termination of function calls or goroutines, see cancel a blocking operation in Go.
So your Check() should look something like this:
// This Check could be quite time-consuming
func (w *Work) Check(ctx context.Context, workDuration time.Duration) bool {
// Do your thing and monitor the context!
select {
case <-ctx.Done():
return false
case <-time.After(workDuration): // Simulate work
return true
case <-time.After(2500 * time.Millisecond): // Simulate failure after 2.5 sec
return false
}
}
And CheckAll() may look like this:
func CheckAll(works []*Work) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
num := len(works)
results := make(chan bool, num)
wg := &sync.WaitGroup{}
for i, w := range works {
workDuration := time.Second * time.Duration(i)
wg.Add(1)
go func(w *Work) {
defer wg.Done()
result := w.Check(ctx, workDuration)
// You may check and return if context is cancelled
// so result is surely not sent, I omitted it here.
select {
case results <- result:
case <-ctx.Done():
return
}
}(w)
}
go func() {
wg.Wait()
close(results) // This allows the for range over results to terminate
}()
for result := range results {
fmt.Println("Result:", result)
if !result {
cancel()
break
}
}
}
Testing it:
CheckAll(make([]*Work, 10))
Output (try it on the Go Playground):
Result: true
Result: true
Result: true
Result: false
We get true printed 3 times (works that complete under 2.5 seconds), then the failure simulation kicks in, returns false, and terminates all other jobs.
Note that the sync.WaitGroup in the above example is not strictly needed as results has a buffer capable of holding all results, but in general it's still good practice (should you use a smaller buffer in the future).
See related: Close multiple goroutine if an error occurs in one in go
The short answer is: No.
You can not cancel or close any goroutine unless the goroutine itself reaches the return or end of its stack.
If you want to cancel something, the best approach is to pass a context.Context to them and listen to this context.Done() inside of the routine. Whenever context is canceled, you should return and the goroutine will automatically die after executing defers(if any).
package main
import "fmt"
type Work struct {
// ...
Name string
IsSuccess chan bool
}
// This Check could be quite time-consuming
func (w *Work) Check() {
// return succeed or not
//...
if len(w.Name) > 0 {
w.IsSuccess <- true
}else{
w.IsSuccess <- false
}
}
//堆排序
func main() {
works := make([]*Work,3)
works[0] = &Work{
Name: "",
IsSuccess: make(chan bool),
}
works[1] = &Work{
Name: "111",
IsSuccess: make(chan bool),
}
works[2] =&Work{
Name: "",
IsSuccess: make(chan bool),
}
for _,w := range works {
go w.Check()
}
for i,w := range works{
select {
case checkResult := <-w.IsSuccess :
fmt.Printf("index %d checkresult %t \n",i,checkResult)
}
}
}
enter image description here

Golang Channels - Help me get my head around this

Trying to get 2 Goroutines to play nicely within my project. Even though this is all sorta working, I am 100% sure it is being done very poorly... maybe even wrong...ly (is that even a word?).
Anyway, the basic concept of this project is to run some repeated work over a pre-selected amount of time while allowing the ability to abort the run before the time is up.
Here's the mess I have so far (I've only included the important parts for everyone's sanity... and to hide my horrible coding):
Two Goroutines:
BenchTimer() is a simple run-time countdown timer that does some repeating work for a set amount of time.
AbortTest() is a keyboard listener used to catch an 'ESC' (or whatever else I want) keypress from the keyboard to act as a "User Abort".
Each Goroutine, upon a successful run (i.e. BenchTimer() completes the countdown OR AbortTest() catches an abort keypress), sends a message down a common channel testAction. I use the one channel since this is an OR kinda thing (i.e. You can't get a completed countdown and an abort at the same time.). If BenchTimer() completes, then it sends "Complete" down the channel. If AbortTest() "completes" it sends "Abort" down the channel. [So far this all seems to be working...]
The next problem I ran into with this setup is how to kill the Goroutine that wasn't the "winner"... (i.e. If BenchTimer() completes normally, then I need to somehow kill AbortTest()... and vice-versa.) After a bunch of searching, I found that it isn't possible to kill a Goroutine externally, but it can be done internally... so I came up with using a second channel for each Goroutine to act as a sort of "kill signal" line: killAbortTest and killBenchTimer.
To tie this all together, I evaluate the result of the testAction channel. Because this channel will tell me which Goroutine "won", I can use this knowledge to send the correct (i.e. opposite) "kill signal" to have the "loser" Goroutine self-terminate.
Note: ... just means other code exists, but was removed due to not being needed for this post.
func main() {
...
testAction := make(chan string) // Action Result (Timer "Complete" or User "Abort")
killAbortTest := make(chan bool) // Kill AbortTest() Goroutine when BenchTimer() completes.
killBenchTimer := make(chan bool) // Kill BenchTimer() Goroutine when AbortTest() completes.
go BenchTimer(testAction, killBenchTimer) // Run BenchTimer() as Goroutine
go AbortTest(testAction, killAbortTest) // Run AbortTest() as Goroutine
// Program should wait here until it receives something on testAction channel.
actionVal := <-testAction
// Evaluate the testAction to kill the "loser" Goroutine
switch actionVal {
case "Abort":
killBenchTimer <- true // Abort received, signal BenchTimer() Goroutine to Quit
fmt.Println()
fmt.Println("Test Aborted")
case "Complete":
killAbortTest <- true // Countdown finished, signal AbortTest() Goroutine to Quit
fmt.Println()
fmt.Println("Test Completed")
}
...
}
// AbortTest - Listen for User Abort
func AbortTest(c chan<- string, k <-chan bool) {
if err := keyboard.Open(); err != nil {
panic(err)
}
defer func() {
_ = keyboard.Close()
}()
for {
select {
case <-k:
return
default:
_, key, err := keyboard.GetKey() // Poll for keypress
if err != nil {
panic(err)
}
if key == keyboard.KeyEsc { // ESC key was pressed
c <- "Abort"
return
}
}
}
}
// BenchTimer - Countdown Timer for BenchTest
func BenchTimer(c chan<- string, k <-chan bool) {
seconds := 0
switch testTime {
case "2-minute (fast)":
seconds = 120
case "5-minute (short)":
seconds = 300
case "10-minute (long)":
seconds = 600
case "20-minute (slow)":
seconds = 1200
}
ticker := time.Tick(time.Second)
for i := seconds; i >= 0; i-- {
select {
case <-k: // Kill Signal Received
return
default:
<-ticker
...
}
}
c <- "Complete"
}
There it is. My mess. There are many like it, but this one is my own. Like I said, it sorta works now, but I'm looking to make it better.
Am I just overthinking this whole process and making it way more complex than it needs to be?
Any help would be great.
This is the simplest example I can think of. I left out the keyboard part, but its basically the idea behind your code :
As mentioned by mh-cbon, this is safe :
package main
import (
"context"
"fmt"
"math/rand"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
rand.Seed(time.Now().UnixNano())
ctx, cancel := context.WithCancel(context.Background())
wg.Add(2)
go DoSomeTask(ctx, cancel)
go CancelTask(ctx, cancel)
wg.Wait()
}
func DoSomeTask(ctx context.Context, cancel func()) {
defer wg.Done()
defer cancel() // force cancellation
for i := 1; i < 10; i++ {
select {
case <-ctx.Done():
fmt.Println("context cancelled")
return
case <-time.After(time.Second):
}
fmt.Println("Done something!", i)
}
}
func CancelTask(ctx context.Context, cancel func()) {
defer wg.Done()
defer cancel() // force cancellation
duration := time.Second * time.Duration((rand.Intn(20-1) + 1))
fmt.Println("Will cancel in ", duration, " seconds!")
select {
case <-ctx.Done():
fmt.Println("context cancelled")
case <-time.After(duration):
}
}

Gorutine doesn't finish executing after returning from main. Why?

I am trying to understand context in golang. I copied an example from https://golang.org/pkg/context/#example_WithCancel and changed it a bit:
Playgroud: https://play.golang.org/p/Aczc2CqcVZR
package main
import (
"context"
"fmt"
"time"
)
func main() {
// gen generates integers in a separate goroutine and
// sends them to the returned channel.
// The callers of gen need to cancel the context once
// they are done consuming generated integers not to leak
// the internal goroutine started by gen.
gen := func(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
fmt.Println("DONE")
return // returning not to leak the goroutine
case dst <- n:
n++
}
}
fmt.Println("END")
}()
return dst
}
ctx, cancel := context.WithCancel(context.Background())
defer time.Sleep(1 * time.Second)
defer fmt.Println("Before cancel")
defer cancel() // cancel when we are finished consuming integers
defer fmt.Println("After cancel")
channel := gen(ctx)
for n := range channel {
fmt.Println(n)
if n == 5 {
break
}
}
fmt.Println( <-channel)
}
When commenting out the
defer time.Sleep(1 * time.Second)
the "DONE" never gets printed. Playgroud: (https://play.golang.org/p/K0OcyZaj_xK)
I would expect the go routine which was started in the anonymous function still to be active. Once cancel() is called due to being deferred, the select should no longer block as
case <-ctx.Done():
should be available. However it seems to just end, unless I wait for 1 second and give it time. This behavior seems very wrong.
This behavior seems very wrong.
It's not. That's how program execution is specified. After main and its deferred functions return, the program exits.
Program execution begins by initializing the main package and then invoking the function main. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.
https://golang.org/ref/spec#Program_execution

How to close a goroutine which runs the flow based logics?

I have some goroutine logics like this:
go func() {
do_things_0()
do_things_1()
do_things_2()
do_things_3()
...
...
} ()
When the service receives a request, it will create such goroutine. And the goroutine maybe memory consuming and needs to run more than 30 minutes.
Sometimes, the service may notice the lack of memory, and needs to terminate some goroutines.
My questions are:
How can I terminate the goroutine in the above example?
Is there any way to know the used memory of each goroutine?
Update
I read other SO answers that goroutine can't be killed outside
I suppose that send a signal to the channel handled by the goroutine to make the goroutine quit is only suitable for the for loop based logics.
I am looking for some best practice to close the goroutine for the flow based logics.
You should be able to adapt this easily to a for loop if you range over a list of the functions that are your steps:
package main
import (
"fmt"
"time"
)
func a() { fmt.Printf("a") }
func b() { fmt.Printf("b") }
func c() { fmt.Printf("c") }
func d() { fmt.Printf("d") }
func e() { fmt.Printf("e") }
func f(quit <-chan struct{}) {
for i := 0; i < 10000; i++ {
for _, fn := range []func(){a, b, c, d, e} {
select {
case _, _ = <-quit:
fmt.Println("quit f")
return
default:
fn()
time.Sleep(1 * time.Millisecond)
}
}
}
}
func main() {
quit := make(chan struct{})
fmt.Println("go f")
go f(quit)
fmt.Println("sleep")
time.Sleep(100 * time.Millisecond)
fmt.Println("\nquit")
close(quit)
time.Sleep(10 * time.Millisecond)
fmt.Println("exit")
}
Try it on the playground.
The outer loop is just there to repeat the steps for long enough that we can witness the quit command happening.

Go using timeouts with channels

I am using goroutines/channels to check if list of urls are reachable. Here is my code. This seems to always return true. Why is the timeout case not getting executed? The goal is to return false even if one of the urls is not reachable
import "fmt"
import "time"
func check(u string) bool {
time.Sleep(4 * time.Second)
return true
}
func IsReachable(urls []string) bool {
ch := make(chan bool, 1)
for _, url := range urls {
go func(u string) {
select {
case ch <- check(u):
case <-time.After(time.Second):
ch<-false
}
}(url)
}
return <-ch
}
func main() {
fmt.Println(IsReachable([]string{"url1"}))
}
check(u) will sleep in the current goroutine, i.e. the one that's running func. The select statement is only run properly once it returns, and by that time, both branches are runnable and the runtime can pick whichever one it pleases.
You can solve it by running check inside yet another goroutine:
package main
import "fmt"
import "time"
func check(u string, checked chan<- bool) {
time.Sleep(4 * time.Second)
checked <- true
}
func IsReachable(urls []string) bool {
ch := make(chan bool, 1)
for _, url := range urls {
go func(u string) {
checked := make(chan bool)
go check(u, checked)
select {
case ret := <-checked:
ch <- ret
case <-time.After(1 * time.Second):
ch <- false
}
}(url)
}
return <-ch
}
func main() {
fmt.Println(IsReachable([]string{"url1"}))
}
It seems you want to check reachability of a set of URLs, and return true if one of them is available. If the timeout is long compared to the time it takes to spin up a goroutine, you could simplify this by having just one timeout for all URLs together. But we need to make sure that the channel is large enough to hold the answers from all checks, or the ones that don't "win" will block forever:
package main
import "fmt"
import "time"
func check(u string, ch chan<- bool) {
time.Sleep(4 * time.Second)
ch <- true
}
func IsReachable(urls []string) bool {
ch := make(chan bool, len(urls))
for _, url := range urls {
go check(url, ch)
}
time.AfterFunc(time.Second, func() { ch <- false })
return <-ch
}
func main() {
fmt.Println(IsReachable([]string{"url1", "url2"}))
}
The reason this always returns true is you are calling check(u) within your select statement. You need to call it within a go routine and then use a select to either wait for the result or timeout.
In case you want to check the reachability of multiple URLs in parallel you need to restructure your code.
First create a function which checks the reachability of one URL:
func IsReachable(url string) bool {
ch := make(chan bool, 1)
go func() { ch <- check(url) }()
select {
case reachable := <-ch:
return reachable
case <-time.After(time.Second):
// call timed out
return false
}
}
Then call this function from a loop:
urls := []string{"url1", "url2", "url3"}
for _, url := range urls {
go func() { fmt.Println(IsReachable(url)) }()
}
Play
change the line
ch := make(chan bool, 1)
to
ch := make(chan bool)
You did open a asynchronous (= non blocking) channel, but you need a blocking channel to get it work.
The result of true being returned here is deterministic in this scenario, it's not a random one picked up by the runtime, because there's only true value available (however long it may take for it to become available!) being sent into the channel, the false result would never be available for the channel since the time.After() call statement would never get the chance to be executed in the first place!
In this select, the first executable line it sees is check(u) call, not the channel sending call in the first case branch, or any other call at all! And it's only after this first check(u) execution has returned here, would select branch cases get checked and called upon, by which point, the value of true is already to be pushed into the first branch case channel, so no channel blocking here to the select statement, the select can fulfil its purpose promptly here without needing to check its remaining branch cases!
so looks like it's the use of select here that wouldn't seem quite correct in this scenario.
the select branch cases are supposed to listen to channel sending and receiving values directly, or optionally with a default to escape the blocking when necessary.
so the fix is as some people pointed out here already, putting the long running task or process into a separate goroutine, and have it send result into channel,
and then in the main goroutine (or whichever other routine that needs that value off the channel), use the select branch cases to either listen on that specific channel for a value, or on the channel provided by the time.After(time.Second) call.
Basically, this line: case ch <- check(u) is correct in the sense of sending a value into a channel, but it's just not for its intended use (i.e. blocking this branch case), because the case channel<- is not being blocked there at all (the time check(u) spends on is all happening before the channel gets involved), since in a separate goroutine, aka, the main one: return <-ch, it's already ready to read that value whenever it gets pushed through. That is why time.After() call statement in the second case branch would never even get a chance to be evaluated, in the first instance!
see this example for a simple solution, ie. the correct use of a select in conjunction of separate goroutines: https://gobyexample.com/timeouts
In case it's useful, here's a generalised version of #Thomas 's answer, much simplified by #mh-cbon
func WithTimeout(delegate func() interface{}, timeout time.Duration) (ret interface{}, ok bool) {
ch := make(chan interface{}, 1) // buffered
go func() { ch <- delegate() }()
select {
case ret = <-ch:
return ret, true
case <-time.After(timeout):
}
return nil, false
}
Then you can call to 'timeout' any function
if value,ok := WithTimeout(myFunc, time.Second); ok {
// returned
} else {
// didn't return
}
Call like this to wait for a channel
if value,ok := WithTimeout(func()interface{}{return <- inbox}, time.Second); ok {
// returned
} else {
// didn't return
}
Like this to try sending
_,ok = WithTimeout(func()interface{}{outbox <- myValue; return nil}, time.Second)
if !ok{...

Resources