Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to stop a long blocking function?

Tags:

go

I have a function that run for minutes and I'm trying to find a way to stop it using a channel.

I think I can't do it like I do in the following code since I think the select will only handle the stop case after the default is done.

package main

import (
    "fmt"
    "time"
)

func main() {
    stop := make(chan int)

    go func() {
        for {
            select {
            case <-stop:
                fmt.Println("return")
                return
            default:
                fmt.Println("block")
                time.Sleep(5 * time.Second) // simulate a long running function
                fmt.Println("unblock")
            }
        }
    }()

    time.Sleep(1 * time.Second)
    stop <- 1
}
like image 645
bbigras Avatar asked Dec 06 '13 17:12

bbigras


3 Answers

I reckon you can't do it: goroutines in Go are, in a sense, cooperative: until a goroutine actively tries to somehow figure out if it should exit, there's no way to force it to do so.

I'd say it's a feature in fact because if you could forcibly reap a long-running goroutine, you would be unable to be sure it exited cleanly, that is, properly released all resources it had acquired.

So either live with this (say, if your process wants to exit, just wait on that goroutine to finish) or restructure it so that it periodically checks whether it is signaled to quit. Or even consider offloading the task it performs to an external process (but note that while it's safe to kill a process with regard to releasing the resources it acquired from the OS, it's not safe with regard to external data that process might have been updating — such as files).

like image 157
kostix Avatar answered Oct 18 '22 04:10

kostix


I don't think you can end a goroutine but you can switch to a different one. You can timeout a function by wrapping it inside a goroutine that send data to a channel once complete. There then needs to be select waiting for the returned channel or the channel from a timeout.

Example:

package main

import (
    "fmt"
    "time"
)

func waitForMe(){
        time.Sleep(time.Second*5)
}
func main(){
        c1 := make(chan string, 1)
        go func(){
                waitForMe()
                c1 <- "waitForMe is done"
        }()
        select {
        case res := <-c1:
                fmt.Println(res)
        case <-time.After(time.Second*2):
                fmt.Println("timed out")
        }
}

Note every time you call select, time.After() or have a blocking channel, the goroutines switches to next available goroutine if possible.

Here's what's happening with your program.

Your program with Goroutine statements

package main

import (
    "fmt"
    "time"
)

func main() {
    stop := make(chan int)
    go func() {
       fmt.Println("Goroutine B before for loop")
        for {
            fmt.Println("Goroutine B inside for loop")
            select {
            case <-stop:
                fmt.Println("return")
                return
            default:
                fmt.Println("Goroutine B default case")
                fmt.Println("block")
                time.Sleep(5 * time.Second) // simulate a long running function
                fmt.Println("unblock")
            }
        }
    }()

    fmt.Println("Goroutine A before time.Sleep()")
    time.Sleep(1 * time.Second)
    fmt.Println("Goroutine A after sleep")
    stop <- 1
    fmt.Println("Goroutine A after stop")
}

Output

Goroutine A before time.Sleep()
Goroutine B before for loop
Goroutine B inside for loop
Goroutine B default case
block
Goroutine A after sleep
unblock
Goroutine B inside for loop
return
Goroutine A after stop

Useful link: https://gobyexample.com/timeouts

like image 6
Larry Battle Avatar answered Oct 18 '22 05:10

Larry Battle


You can only do this if you somehow make your function interruptible, i.e. you can't stop a blocking call on it's own. If you're writing the blocking function yourself, you can usually fashion a select with multiple cases and channels.

Your example would look like

package main

import (
    "fmt"
    "time"
)

func main() {
    stop := make(chan int)

    go func() {
        for {
            fmt.Println("block")
            select {
            case <-time.After(5 * time.Second):
                fmt.Println("unblock")
            case <-stop:
                fmt.Println("stopped")
                return
            }

        }
    }()

    time.Sleep(2 * time.Second)
    stop <- 1

    // this is just to give the goroutine time to write "stopped" before we exit
    select{}
}
like image 2
JimB Avatar answered Oct 18 '22 05:10

JimB