4
votes

Staring a goroutine which runs a recursive function, I want to send a signal to stop those recursive functions. This is the function (the functionality is not important):

func RecursiveFunc(x int, depth int, quit chan bool) int {

    if depth == 0 {
        return 1
    }

    if quit != nil {
        select {
        case <-quit:
            return 0
        default:
        }
    }

    total := 0

    for i := 0; i < x; i++ {

        y := RecursiveFunc(x, depth - 1, quit)

        if y > 0 {
            total += y
        }

    }

    return total
}

This function may take a long time to be done and I want stop it after sending a quit signal and use the result (whatever it is). To run it:

import (
    "fmt"
    "time"
    "sync"
)

func main() {

    quit := make(chan bool)
    wg := &sync.WaitGroup{}
    result := -1

    go func() {
        defer wg.Done()
        wg.Add(1)
        result = RecursiveFunc(5, 20, quit)
    }()

    time.Sleep(10 * time.Millisecond)

    close(quit) // Using `quit <- true` doesn't work

    wg.Wait()

    fmt.Println(result)
}

To stop the goroutine, I'm using a channel say quit and after closing it, the program works well, however I don't want really close the channel and I want just send a signal quit <- true. However, quit <- true doesn't work and I probably quits only one instance of recursion.

How can I stop all instances of recursive function by sending a quit signal?

3
In Go we have context.Context to create hierarchy of dependent goroutines; see stackoverflow.com/questions/42516717/how-to-stop-goroutine/… - Kaveh Shahbazian
Why don't you want to close the channel? It is the simplest way to what you describe, so it would be good to know what other restrictions you have. - djd
If you use a single channel, the you are stopping all goroutines in your app. Or if you want use channels for this specific purpose, you have to manage them all manually. And if you just need to stop a portion of active goroutines in your app, how would you do that? Besides, using context.Context is an idiomatic pattern for managing goroutines in Go. - Kaveh Shahbazian
@djd: Imagine the caller side is in a loop, after each iteration I need to reuse the channel, how can I reopen the channel? - masoud
@deepmax: just make a fresh channel for each loop. It's an inexpensive operation, and you need to do something similar. Basically, with this approach, each unique channel represents a set of goroutines/function calls that will be cancelled at once. - djd

3 Answers

6
votes

You can do the what you are going to do using context.

You can pass a context.Context object as the first parameter to the function which you need to stop from outside, and call the corresponding cancel function to send a "cancellation signal" to the function, which will cause the Done() channel of the context.Context to be closed, and the called function will thus be notified of the cancellation signal in a select statement.

Here is how the function handles the cancellation signal using context.Context:

func RecursiveFunc(ctx context.Context, x int, depth int) int {

    if depth == 0 {
        return 1
    }

    select {
    case <-ctx.Done():
        return 0
    default:
    }

    total := 0

    for i := 0; i < x; i++ {

        y := RecursiveFunc(ctx, x, depth-1)

        if y > 0 {
            total += y
        }

    }

    return total
}

And here is how you can call the function with the new signature:

func main() {

    wg := &sync.WaitGroup{}
    result := -1

    ctx, cancel := context.WithCancel(context.Background())

    go func() {
        defer wg.Done()
        wg.Add(1)
        result = RecursiveFunc(ctx, 5, 20)
    }()

    time.Sleep(10 * time.Millisecond)

    cancel()

    wg.Wait()

    fmt.Println(result)
}
0
votes

I was in a similar situation recently and like your case, the quit signal was consumed by one of the recursion branch leaving the other branches without a signal. I solved this by forwarding the stop signal to the channel before returning from the function.

For example, you can modify the select inside the recursive function to be:

if quit != nil {
    select {
    case <-quit:
        quit <- true // forward the signal
        return 0
    default:
    }
}
-1
votes

Try to add flag to continue execution, but it can be not thread safe.

var finishIt bool

func RecursiveFunc(x int, depth int, quit chan bool) int {
   if finishIt {
    return 0
   }
//other code here
}


//some code here, but than we decide to stop it
finishIt = true