برنامه نویسی

یک مشکل: 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.
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

یک وحشت فقط در همان پشته گوروتین که در آن رخ داده است قابل بازیابی است. در غیر این صورت ، وحشت به سمت بالا پخش می شود و در نهایت باعث سقوط کل برنامه می شود.


راه حل

  1. از 28 آوریل 2025 ، آخرین errgroup هنوز هم به طور خودکار از وحشت در توابع موجود بهبود نمی یابد ، هنوز هم توصیه می شود goroutines را از طریق مدیریت کنید errgroup (یا مکانیسم های مشابه) در کنترل کننده خود. به این ترتیب ، می توانید به راحتی هر کار را با مکانیسم بازیابی بپیچید و از رسیدگی به خطای برازنده اطمینان حاصل کنید.

  2. خطای رسمی پیش از این این مسئله را در شعبه مستر برطرف کرده است ، اما رفع آن است رسما آزاد نشده است هنوز

  3. در ضمن ، شما می توانید Supegroup من را بررسی کنید. به طور خودکار هر کار را با یک ایمان می بندد recover مکانیسم و ​​یک روش ایمن برای کنترل فراهم می کند panic خطا

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا