یک مشکل: Goroutines کودک باعث تصادفات سرویس می شود

پیشینه
اخیراً ، در طول توسعه میکروسرویس ، با یک مسئله بسیار خطرناک روبرو شدم. ما استفاده می کردیم echo
چارچوب ، و در handler
، ما یک goroutine را از طریق راه اندازی کردیم errgroup
بشر با این حال ، هنگامی که یک وحشت در داخل آن گوروتین رخ داد ، حتی اگر ما اضافه کرده بودیم Recover
Middleware برای محافظت از فرایند خدمات ، کل سرویس هنوز خراب شد، باعث می شود همه کاربران به اشتراک گذاری همان سرور درخواست های خود را قطع کنند.
این یک مشکل بسیار رایج اما به راحتی نادیده گرفته شده است:
defer recover
نمی توان وحشت را در داخل گوروهای کودک انجام داد.
تولید مثل
شما می توانید با اجرای این کد این رفتار را بازتولید کنید:
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
func TestPanicRecovered() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
panic("panic")
}
func TestPanicRecoverFailed() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
g, _ := errgroup.WithContext(context.TODO())
g.Go(func() error {
panic("panic")
})
err := g.Wait()
fmt.Println(err)
}
func main() {
TestPanicRecovered()
TestPanicRecoverFailed()
}
در TestPanicRecovered
با defer-recover
با موفقیت وحشت را جلب می کند. با این حال ، در TestPanicRecoverFailed
، حتی با recover
در Goroutine والدین قرار داده شده ، وحشت در داخل گوروتین کودک هنوز باعث تصادف می شودبشر
چرا Echo's Recover Middleware کار نمی کند؟
بیایید نگاهی به اجرای Middleware Recover Echo (کد منبع) بیندازیم:
// RecoverWithConfig returns a Recover middleware with config.
// See: `Recover()`.
func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultRecoverConfig.Skipper
}
if config.StackSize == 0 {
config.StackSize = DefaultRecoverConfig.StackSize
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (returnErr error) {
if config.Skipper(c) {
return next(c)
}
defer func() {
if r := recover(); r != nil {
if r == http.ErrAbortHandler {
panic(r)
}
err, ok := r.(error)
if !ok {
err = fmt.Errorf("%v", r)
}
...
همانطور که می بینید ، فقط ضبط می شود وحشت هایی که در داخل Goroutine درخواست HTTP فعلی رخ می دهدبشر وحشت هایی که در گوروتین های کودک رخ می دهد کاملاً دور از دسترس میان افزار است.
با توجه به مستندات رسمی GO:
The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes.
یک وحشت فقط در همان پشته گوروتین که در آن رخ داده است قابل بازیابی است. در غیر این صورت ، وحشت به سمت بالا پخش می شود و در نهایت باعث سقوط کل برنامه می شود.
راه حل
-
از 28 آوریل 2025 ، آخرین
errgroup
هنوز هم به طور خودکار از وحشت در توابع موجود بهبود نمی یابد ، هنوز هم توصیه می شود goroutines را از طریق مدیریت کنیدerrgroup
(یا مکانیسم های مشابه) در کنترل کننده خود. به این ترتیب ، می توانید به راحتی هر کار را با مکانیسم بازیابی بپیچید و از رسیدگی به خطای برازنده اطمینان حاصل کنید. -
خطای رسمی پیش از این این مسئله را در شعبه مستر برطرف کرده است ، اما رفع آن است رسما آزاد نشده است هنوز
-
در ضمن ، شما می توانید Supegroup من را بررسی کنید. به طور خودکار هر کار را با یک ایمان می بندد
recover
مکانیسم و یک روش ایمن برای کنترل فراهم می کندpanic
خطا