Home > Net >  Killing all routines when one failed
Killing all routines when one failed

Time:08-13

I need to create a program which starts multiple servers each on different ports and workers each to different queue. All servers and workers has be running or in case if at least one fails other services should be shutted down

Currently I'm listening on a channel and if error would occur just execute log.Fatal which would execute os.Exit

  • Is it proper way of killing all routines when one failed which doesn't occur any unexpected behaviours?
  • Is there a better solution for this problem?

My simplified program looks like this:

package main

import (
    "log"
    "net/http"
)

func startWorker(queue string) error {
    // other logic which 
    // could return an 
    // error goes here
    
    // simulate worker work
    for {
        // get items from `queue`
        // and do something with it
        continue
    }
}

func startServer(port string) error {
    // other logic which 
    // could return an 
    // error goes here
    return http.ListenAndServe(port, nil)
}

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
    channel := make(chan error)

    go func() {
        channel <- startServer(":3000")
    }()

    go func() {
        channel <- startServer(":3001")
    }()

    go func() {
        channel <- startWorker("first")
    }()

    go func() {
        channel <- startWorker("second")
    }()

    if err := <-channel; err != nil {
        log.Fatal(err)
    }
}

CodePudding user response:

In my opinion os.Exit() and log.Fatal() should only be used for development purposes or extremely unexpected errors.

There is a way to handle server errors gracefully:

package main

import (
    "context"
    "fmt"
    "net/http"
    "time"
)

func startServer(server *http.Server) error {
    // other logic which
    // could return an
    // error goes here
    return server.ListenAndServe()
}

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })

    server3000 := &http.Server{Addr: ":3000", Handler: nil}
    server3001 := &http.Server{Addr: ":3001", Handler: nil}

    closeAll := func() {
        server3000.Shutdown(context.Background())
        server3001.Shutdown(context.Background())
    }

    go func() {
        err := startServer(server3000)
        fmt.Printf("Got error: %s\n", err)
        if err != nil {
            closeAll()
        }
    }()

    go func() {
        err := startServer(server3001)
        fmt.Printf("Got error: %s\n", err)
        if err != nil {
            closeAll()
        }
    }()

    timer := time.NewTimer(time.Second)
    <-timer.C
    server3000.Close() // close one of the servers

    timer2 := time.NewTimer(time.Second)
    <-timer2.C // wait for both servers to shutdown
}

I omitted the closing of the workers because it depends on their implementation but they can also be handled in the closeAll() function.

Hope this helps

CodePudding user response:

try to communicate between goroutines. it's a powerful tool for Golang concurrency. you can send a channel to goroutines and change wait until sending an error form any goroutine. I fix your code and you see "queue" in the terminal and after 2 seconds app stopped by an error.

package main

import (
    "errors"
    "fmt"
    "log"
    "net/http"
    "time"
)

func startWorker(queue string, ch chan error) error {
    // other logic which
    // could return an
    // error goes here

    // simulate worker work
    for {
        // get items from `queue`
        // and do something with it
        fmt.Println("queue")
        continue
    }
    return nil
}

func startServer(port string, ch chan error) error {
    // other logic which
    // could return an
    // error goes here
    if port == ":3000" {
        time.Sleep(2 * time.Second)
        ch <- errors.New("error")
    }

    return http.ListenAndServe(port, nil)
}

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
    channel := make(chan error)
    ch := make(chan error)

    go func() {
        channel <- startServer(":3000", ch)
    }()

    go func() {
        channel <- startServer(":3001", ch)
    }()

    go startWorker("first", ch)

    go startWorker("second", ch)

    if err := <-ch; err != nil {
        log.Fatal(err)
    }
}
  •  Tags:  
  • go
  • Related