Cześć, myślę, że dość proste pytanie, ale chciałem potwierdzić czy dobrze myślę. Może opowiecie czy robicie podobnie, czy spotkaliście z tym itd. No więc sytuacja wygląda tak, że jak tworzymy w Go serwer http, to większość bibliotek/paczek, które to ułatwiają od razu wołają dla nas recover tak, aby serwer nie padł jeśli coś pójdzie nie tak. Przykładowo:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// Create a new Gin router
router := gin.Default()
// Define a route with a handler function
router.GET("/hello", func(c *gin.Context) {
panic("oops, something went wrong")
c.JSON(http.StatusOK, gin.H{
"message": "Hello, Gin!",
})
})
// Run the server on port 8090
router.Run(":8090")
}
Jeśli w kodzie wystąpi panic, to gin posiada middleware, który “złapie” nam tęgo panica i serwer nie padnie. Ma to sens. Nie chcemy, żeby na produkcji popadały nam serwisy przez błąd w kodzie (który w końcu komuś się przytrafi).
Sytuacja lekko się komplikuje gdy w kodzie stworzymy nową goroutine. Jest dość częsty przypadek. Dla przykładu mój serwis żeby zrealizować zapytanie musi wysłać zapytanie do dwóch innych serwisów. Żeby trwało to szybciej niż wolniej, to mogę zawołać to na dwóch osobnych goroutine i poczekać, aż pozostałe dwa serwisy zwrócą odpowiedź (zamiast robić to sekwencyjnie i czekać na odpowiedź po kolei). Jednak, w tym przypadku domyślny panic recover middleware (albo nie domyślny, ale taki, który sami sobie napiszemy) już nie “złapie” potencjalnego panica. Wypadałoby więc przy wołaniu nowej gorutyny samemu łapać panica wołając “recover”. W innym wypadku możemy mieć w kodzie błąd, który może potencjalnie położyć wszystkie nasze serwisy.
Na postawie przykładu powyżej, kod bez dodatkowego recovery, ale z tworzeniem nowej gorotuine:
package main
import (
"net/http"
"sync"
"github.com/gin-gonic/gin"
)
func main() {
// Create a new Gin router
router := gin.Default()
// Define a route with a handler function
router.GET("/hello", func(c *gin.Context) {
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
panic("oops, something went wrong")
}()
wg.Wait()
c.JSON(http.StatusOK, gin.H{
"message": "Hello, Gin!",
})
})
// Run the server on port 8090
router.Run(":8090")
}
W tym przypadku, jeśli zawołam endpoint 'hello' to serwis padnie tzn. przestanie działać. Jeśli jest uruchomiony w kontenerze w jakimś kubernetesie albo czymś podobnym, to zostanie uruchomiony ponownie.
Kod z recovery, który zapobiegnie padnięciu serwera:
package main
import (
"fmt"
"net/http"
"sync"
"github.com/gin-gonic/gin"
)
func main() {
// Create a new Gin router
router := gin.Default()
// Define a route with a handler function
router.GET("/hello", func(c *gin.Context) {
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered on another goroutine")
}
}()
defer wg.Done()
panic("oops, something went wrong")
}()
wg.Wait()
c.JSON(http.StatusOK, gin.H{
"message": "Hello, Gin!",
})
})
// Run the server on port 8090
router.Run(":8090")
}
Pytanie: czy dobrze rozumuje? Czy tak robicie/piszecie/pamiętacie o tym?