How best do I keep a long running Go program, running?
Asked Answered
L

6

75

I've a long running server written in Go. Main fires off several goroutines where the logic of the program executes. After that main does nothing useful. Once main exits, the program will quit. The method I am using right now to keep the program running is just a simple call to fmt.Scanln(). I'd like to know how others keep main from exiting. Below is a basic example. What ideas or best practices could be used here?

I considered creating a channel and delaying exit of main by receiving on said channel, but I think that could be problematic if all my goroutines become inactive at some point.

Side note: In my server (not the example), the program isn't actually running connected to a shell, so it doesn't really make sense to interact with the console anyway. For now it works, but I'm looking for the "correct" way, assuming there is one.

package main

import (
    "fmt"
    "time"
)

func main() {
    go forever()
    //Keep this goroutine from exiting
    //so that the program doesn't end.
    //This is the focus of my question.
    fmt.Scanln()
}

func forever() {
    for ; ; {
    //An example goroutine that might run
    //indefinitely. In actual implementation
    //it might block on a chanel receive instead
    //of time.Sleep for example.
        fmt.Printf("%v+\n", time.Now())
        time.Sleep(time.Second)
    }
}
Labor answered 3/3, 2012 at 5:45 Comment(2)
I found 6 ways to make it - pliutau.com/different-ways-to-block-go-runtime-foreverUreter
For anyone who is trying to deploy forever running go application in Marathon or DC/OS, do not use fmt.Scanln method, you will get container exit with code 0, i.e it somehow gets an input from the console. Use any of the methods mentioned in the answers below, life will be easier. #GO #DOCKER #MARATHON #DCOSFrankfrankalmoign
B
34

The current design of Go's runtime assumes that the programmer is responsible for detecting when to terminate a goroutine and when to terminate the program. The programmer needs to compute the termination condition for goroutines and also for the entire program. A program can be terminated in a normal way by calling os.Exit or by returning from the main() function.

Creating a channel and delaying exit of main() by immediately receiving on said channel is a valid approach of preventing main from exiting. But it does not solve the problem of detecting when to terminate the program.

If the number of goroutines cannot be computed before the main() function enters the wait-for-all-goroutines-to-terminate loop, you need to be sending deltas so that main function can keep track of how many goroutines are in flight:

// Receives the change in the number of goroutines
var goroutineDelta = make(chan int)

func main() {
    go forever()

    numGoroutines := 0
    for diff := range goroutineDelta {
        numGoroutines += diff
        if numGoroutines == 0 { os.Exit(0) }
    }
}

// Conceptual code
func forever() {
    for {
        if needToCreateANewGoroutine {
            // Make sure to do this before "go f()", not within f()
            goroutineDelta <- +1

            go f()
        }
    }
}

func f() {
    // When the termination condition for this goroutine is detected, do:
    goroutineDelta <- -1
}

An alternative approach is to replace the channel with sync.WaitGroup. A drawback of this approach is that wg.Add(int) needs to be called before calling wg.Wait(), so it is necessary to create at least 1 goroutine in main() while subsequent goroutines can be created in any part of the program:

var wg sync.WaitGroup

func main() {
    // Create at least 1 goroutine
    wg.Add(1)
    go f()

    go forever()
    wg.Wait()
}

// Conceptual code
func forever() {
    for {
        if needToCreateANewGoroutine {
            wg.Add(1)
            go f()
        }
    }
}

func f() {
    // When the termination condition for this goroutine is detected, do:
    wg.Done()
}
Brasserie answered 3/3, 2012 at 8:13 Comment(1)
Thorough answer and great explanation. I will likely go with a channel receive, as my go routines are buried in other packages that do the serving. I do not want to do goroutine counting everywhere. The channel will work well if I decide to add shutdown logic to the program using signals or some other IPC...Thanks!Labor
F
65

Block forever. For example,

package main

import (
    "fmt"
    "time"
)

func main() {
    go forever()
    select {} // block forever
}

func forever() {
    for {
        fmt.Printf("%v+\n", time.Now())
        time.Sleep(time.Second)
    }
}
Fanelli answered 3/3, 2012 at 9:48 Comment(4)
this approach no longer seems to work in latest go, throws following error goroutine 1 [select (no cases)]: main.main()Pressurize
@dark_ruby: It works on the latest version of Go: go version devel +13c44d2 Sat Jun 20 10:35:38 2015 +0000 linux/amd64. How can we reproduce your failure exactly? Be very precise, for example, "latest go" is too vague.Fanelli
@Fanelli yes you're right, i just happened to use select {} on its own, but that causes fatal error: all goroutines are asleep - deadlock!, I need to do more reading on how exactly it works, and why doesnt it cause deadlock in above code.Pressurize
Some more blog.sgmansfield.com/2016/06/how-to-block-forever-in-goZoolatry
C
41

Nobody mentioned signal.Notify(c chan<- os.Signal, sig ...os.Signal)

Example:

package main

import (
    "fmt"
    "time"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    go forever()

    quitChannel := make(chan os.Signal, 1)
    signal.Notify(quitChannel, syscall.SIGINT, syscall.SIGTERM)
    <-quitChannel
    //time for cleanup before exit
    fmt.Println("Adios!")
}

func forever() {
    for {
        fmt.Printf("%v+\n", time.Now())
        time.Sleep(time.Second)
    }
}
Carbone answered 3/12, 2020 at 13:42 Comment(2)
For a running service, this is exactly what I was looking for!Rechaba
Excellent answer! So simple.Labor
B
34

The current design of Go's runtime assumes that the programmer is responsible for detecting when to terminate a goroutine and when to terminate the program. The programmer needs to compute the termination condition for goroutines and also for the entire program. A program can be terminated in a normal way by calling os.Exit or by returning from the main() function.

Creating a channel and delaying exit of main() by immediately receiving on said channel is a valid approach of preventing main from exiting. But it does not solve the problem of detecting when to terminate the program.

If the number of goroutines cannot be computed before the main() function enters the wait-for-all-goroutines-to-terminate loop, you need to be sending deltas so that main function can keep track of how many goroutines are in flight:

// Receives the change in the number of goroutines
var goroutineDelta = make(chan int)

func main() {
    go forever()

    numGoroutines := 0
    for diff := range goroutineDelta {
        numGoroutines += diff
        if numGoroutines == 0 { os.Exit(0) }
    }
}

// Conceptual code
func forever() {
    for {
        if needToCreateANewGoroutine {
            // Make sure to do this before "go f()", not within f()
            goroutineDelta <- +1

            go f()
        }
    }
}

func f() {
    // When the termination condition for this goroutine is detected, do:
    goroutineDelta <- -1
}

An alternative approach is to replace the channel with sync.WaitGroup. A drawback of this approach is that wg.Add(int) needs to be called before calling wg.Wait(), so it is necessary to create at least 1 goroutine in main() while subsequent goroutines can be created in any part of the program:

var wg sync.WaitGroup

func main() {
    // Create at least 1 goroutine
    wg.Add(1)
    go f()

    go forever()
    wg.Wait()
}

// Conceptual code
func forever() {
    for {
        if needToCreateANewGoroutine {
            wg.Add(1)
            go f()
        }
    }
}

func f() {
    // When the termination condition for this goroutine is detected, do:
    wg.Done()
}
Brasserie answered 3/3, 2012 at 8:13 Comment(1)
Thorough answer and great explanation. I will likely go with a channel receive, as my go routines are buried in other packages that do the serving. I do not want to do goroutine counting everywhere. The channel will work well if I decide to add shutdown logic to the program using signals or some other IPC...Thanks!Labor
D
31

Go's runtime package has a function called runtime.Goexit that will do exactly what you want.

Calling Goexit from the main goroutine terminates that goroutine without func main returning. Since func main has not returned, the program continues execution of other goroutines. If all other goroutines exit, the program crashes.

Example in the playground

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    go func() {
        time.Sleep(time.Second)
        fmt.Println("Go 1")
    }()
    go func() {
        time.Sleep(time.Second * 2)
        fmt.Println("Go 2")
    }()

    runtime.Goexit()

    fmt.Println("Exit")
}
Debauchery answered 9/8, 2016 at 16:59 Comment(3)
That is very cool. But, I have to wonder, why they chose to make the program crash instead of having it exit gracefully. Is the idea that you'd have another goroutine handling the shutdown of the application and it will call exit when it receives a signal? I'm just wondering how one would properly use this function.Labor
@Labor that is correct. For example check out this code, Go routine 2 exits the program and the program does not crash. If Goexit crashes your program it's because all other Go routines have completed. I quite like how it crashes, let's me know it's not doing anything, unlike say select{} which would block forever even though nothing is happening.Debauchery
like this one betterIwo
W
16

Here is a simple block forever using channels

package main

import (
    "fmt"
    "time"
)

func main() {
    done := make(chan bool)
    go forever()
    <-done // Block forever
}

func forever() {
    for {
        fmt.Printf("%v+\n", time.Now())
        time.Sleep(time.Second)
    }
}
Wolfe answered 2/8, 2013 at 13:9 Comment(0)
R
0

You could daemonize the process using Supervisor (http://supervisord.org/). Your function forever would just be a process that it runs, and it would handle the part of your function main. You would use the supervisor control interface to start/shutdown/check on your process.

Rovelli answered 3/3, 2012 at 5:54 Comment(1)
An interesting system, but it appears it's an actual program, not Go code I would use. I'm going to edit my question, as I am specifically trying to figure out best practices for keeping main from exiting. Right now, I'm using fmt.Scanln(), I want to know if there is a better way...But thanks for the tip to Supervisor. I could see us investigating it further.Labor

© 2022 - 2024 — McMap. All rights reserved.