Is this func possible to cause goroutine leak - go

func startTimer(ctx context.Context, intervalTime int) {
intervalChan := make(chan bool)
go func() {
for {
select {
case <-ctx.Done():
return
case <-time.After(time.Second * time.Duration(intervalTime)):
intervalChan <- true
}
}
}()
for {
select {
case <-ctx.Done():
return
case <-intervalChan:
doSomething()
}
}
Hi,I write a func as above and want to know is it possible to cause goroutine leak.
For example, the first select statement sends a true to intervalChan, then the second select statement receives Done flag from ctx.Done() and return. Will the goroutine be block forever?

I cannot replicate this behaviour every time but could be some leak. If doSomething do some heavy computation, meanwhile goroutine is blocked on intervalChan <- true since it cannot push into the channel. After doSomething finish execution and context was cancelled, startTimer exists before goroutine and this will lead into blocked goroutine, because there isn't any consumer of intervalChan.
go version go1.8.3 darwin/amd64
package main
import (
"context"
"fmt"
"time"
)
func startTimer(ctx context.Context, intervalTime int) {
intervalChan := make(chan bool)
go func() {
for {
select {
case <-ctx.Done():
fmt.Println("Done from inside of goroutine.")
return
case <-time.After(time.Second * time.Duration(intervalTime)):
fmt.Println("Interval reached.")
intervalChan <- true
}
}
}()
for {
select {
case <-ctx.Done():
fmt.Println("Done from startTimer.")
return
case <-intervalChan:
time.Sleep(10 * time.Second)
fmt.Println("Done")
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
startTimer(ctx, 2)
}

The only place your first goroutine could be blocked indefinitely is in intervalChan <- true. Put it in another select block to be able to cancel that send:
go func() {
for {
select {
case <-ctx.Done():
return
case <-time.After(time.Second * time.Duration(intervalTime)):
select {
case <-ctx.Done():
return
case intervalChan <- true:
}
}
}
}()

Related

How To Stop Sending To A Channel Once Context Is Canceled

I face an error while working with channel and context in golang.
So my idea is,
I want to run 3 functions with goroutines and pass a channel into that function. If one of that functions is already sending a value into that channel, I want to stop other goroutines from sending it to the channel and close the channel.
My current problem is after a channel value is received, the other functions tried to send the value to that channel again
here is my code
package main
import (
"context"
"fmt"
"log"
"time"
)
func main() {
var err string
ctx, cancel := context.WithCancel(context.Background())
errChan := make(chan string)
go one(ctx, errChan, cancel)
go two(ctx, errChan, cancel)
go three(ctx, errChan, cancel)
err = <-errChan
fmt.Println("recevied:", err)
close(errChan)
time.Sleep(10 * time.Second)
}
func one(ctx context.Context, c chan<- string, done context.CancelFunc) {
go func() {
for {
select {
case <-ctx.Done():
log.Println("a goutines already sending to channel, one is lose.")
return
case c <- "from one":
done()
return
}
}
}()
}
func two(ctx context.Context, c chan<- string, done context.CancelFunc) {
go func() {
for {
select {
case <-ctx.Done():
log.Println("a goutines already sending to channel, two is lose.")
return
case c <- "from two":
done()
return
}
}
}()
}
func three(ctx context.Context, c chan<- string, done context.CancelFunc) {
go func() {
for {
select {
case <-ctx.Done():
log.Println("a goutines already sending to channel, three is lose.")
return
case c <- "from three":
done()
return
}
}
}()
}
What should I change from my code so I could only send one (exactly one) time to the channel and stop the whole operation?

context ctx.Done not being executed even though context was passed to the function in golang

I just don't understand why ctx.Done() is not being executed even though I am passing context and calling the cancel from the main? What am I doing wrong here?
var c = make(chan string)
func A(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("killing AAAA")
return // kill A at least
default:
fmt.Println("in A1.. .. again")
c <- "yesss"
}
}
}
//func B(ctx context.Context) {
func main() {
ctx, cancel := context.WithCancel(context.Background())
fmt.Println("BEFORE Number of active goroutines ", runtime.NumGoroutine())
go A(ctx)
time.Sleep(2 * time.Second)
valueReceived := <-c
cancel()
fmt.Println("AFTER Number of active goroutines ", runtime.NumGoroutine())
}
The goroutine executes the default branch twice and blocks on send to c. The <-ctx.Done() case is not executed because the goroutine is stuck in the default branch.
Fix the problem by sending from the select case instead of the branch statements.
func A(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("killing AAAA")
return // kill A at least
case c <- "yesss":
fmt.Println("in A1.. .. again")
}
}
}
You may not see the the killing AAAA with this change alone because the program can exit before the goroutine runs to completion.
Wait for the goroutine to complete to see the message:
var wg sync.WaitGroup
func A(ctx context.Context) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println("killing AAAA")
return // kill A at least
case c <- "yesss":
fmt.Println("in A1.. .. again")
}
}
}
...
wg.Add(1)
go A(ctx)
time.Sleep(2 * time.Second)
valueReceived := <-c
cancel()
wg.Wait()
Run it on the Go playground.

How can I completely terminate the running go func() when ctx times out?

When I want ctx timeout, what should I do to completely terminate the method that is executing longRunningCalculation()?
package main
import (
"context"
"log"
"time"
)
func longRunningCalculation(timeCost int) chan string {
result := make(chan string)
go func() {
time.Sleep(time.Second * (time.Duration(timeCost)))
log.Println("Still doing other things...") //Even if it times out, this goroutine is still doing other tasks.
result <- "Done"
log.Println(timeCost)
}()
return result
}
func jobWithTimeout() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select {
case <-ctx.Done():
log.Println(ctx.Err())
return
case result := <-longRunningCalculation(3):
log.Println(result)
}
}
func main() {
jobWithTimeout()
time.Sleep(time.Second * 5)
}
What did you expect to see?
2019/09/25 11:00:16 context deadline exceeded
What did you see instead?
2019/09/25 11:00:16 context deadline exceeded
2019/09/25 11:00:17 Still doing other things...
To stop the goroutine started by longRunningCalculation when the caller's context times out, you need to pass ctx into longRunningCalculation and explicitly handle the context timing out, the same way you do in jobWithTimeout
Doing things that way also means instead of calling time.Sleep, that time.Tick will be a better choice, so both timers are running at the same time. Like so:
package main
import (
"context"
"log"
"time"
)
func longRunningCalculation(ctx context.Context, timeCost int) chan string {
result := make(chan string)
go func() {
calcDone := time.Tick(time.Second * time.Duration(timeCost))
log.Printf("entering select (longRunningCalculation)")
select {
case <-ctx.Done():
result <- "Caller timed out"
return
case <-calcDone:
log.Println("Still doing other things...") //Even if it times out, this goroutine is still doing other tasks.
result <- "Done"
}
log.Println(timeCost)
}()
return result
}
func jobWithTimeout() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
result := longRunningCalculation(ctx, 3)
log.Printf("entering select (jobWithTimeout)")
select {
case <-ctx.Done():
log.Println(ctx.Err())
return
case res := <-result:
log.Println(res)
}
}
func main() {
jobWithTimeout()
}

context cancel does not exit

Expected: To be done after approx. 2 seconds
Actual: Runs indefinitely.
Don't understand what could be causing it to run indefinitely.
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for i := range generator(ctx) {
select {
case <-time.After(2 * time.Second):
cancel()
return
default:
fmt.Println(i)
}
}
}
func generator(ctx context.Context) <-chan int {
ch := make(chan int)
go func() {
count := 0
for {
select {
case <-ctx.Done():
return
case ch <- count:
count++
}
}
}()
return ch
}
The main issue is that your channel returned from generator(ctx) emits values almost as fast as you can read them.
The channel created by time.After(2 * time.Second) is discarded almost immediately, and you create a new timeout channel every iteration through the generator.
If you make one small change; create the timeout channel outside the loop, and then put it in the select clause you'll see it begin to work.
timeout := time.After(2 * time.Second)
for i := range generator(ctx) {
select {
case <-timeout:
cancel()
return
default:
fmt.Println(i)
}
}
https://play.golang.org/p/zb3wn5FJuK

What will happen to a golang blocking function if the invoker of this function has done?

I wonder what will happen if the interruptable_call is uninterruptable and return after the context is done. The call stack would have been already destroyed. What would the return action perform? How would select perform when one case return while another case is still running. Would that case function call be terminated? in what way?
package main
import (
"context"
"fmt"
"time"
)
func interruptable_call() <-chan time.Time {
return time.After(1 * time.Second)
}
func A(ctx context.Context) int {
for {
select {
case <-ctx.Done():
fmt.Println("func done")
return 1
case <-interruptable_call():
fmt.Println("blocking")
}
}
return 0
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
fmt.Println("go A")
go A(ctx)
fmt.Println("go A done")
select {
case <-ctx.Done():
fmt.Println("main done")
break
}
}
I am not sure what you mean by "synchronisation problem", since there is no synchronization here.. You need to synchronise your goroutines using channels, waitgroups or any other means if you want to be sure that your spawned goroutines performed their task.
It doesn't really matter what happens in the goroutine - if it's not synchronised with main gouroutine it will cease to exist after main exits.
Return value from function called asynchronously in goroutine won't be available to you anyway.
You can check out how blocking calls work for yourself:
package main
import (
"context"
"fmt"
"time"
)
func interruptable_call(sleep time.Duration) <-chan time.Time {
fmt.Println("sleeping for ", sleep*time.Second)
time.Sleep(sleep * time.Second)
return time.After(0 * time.Second)
}
func A(ctx context.Context) int {
for {
select {
case <-ctx.Done():
fmt.Println("func done")
return 1
case <-interruptable_call(2):
fmt.Println("blocking")
case <-interruptable_call(3):
fmt.Println("blocking")
case <-interruptable_call(4):
fmt.Println("blocking")
case <-interruptable_call(5):
fmt.Println("blocking")
}
}
return 0
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
fmt.Println("go A")
go A(ctx)
fmt.Println("go A done")
select {
case <-ctx.Done():
fmt.Println("main done")
break
}
}

Resources