Don't Panic: Errgroup 고루틴에서 패닉 잡기

13117 단어 go


Nicosmos의 이미지, 퍼블릭 도메인, Wikimedia Commons를 통해

TL;DR 버전



StevenACoffman/errgroup은 Go의 훌륭함 sync/errgroup 에 대한 드롭인 대안이지만 고루틴 패닉을 오류로 변환합니다.

이것을 원하는 이유


net/http는 각 요청 제공 고루틴과 함께 패닉 핸들러를 설치하지만,
고루틴은 부모 고루틴에서 패닉 핸들러를 상속하지 않으며 상속할 수도 없습니다.
따라서 하위 고루틴 중 하나에 있는 panic()는 전체 프로그램을 종료합니다.

따라서 프로덕션에서 sync.errgroup 를 사용할 때마다
지연recover()하여 모든 새 고루틴의 시작 부분으로 이동하고 모든 패닉을 오류로 전환합니다.

            defer func() {
                if rec := recover(); rec != nil {
                    err = FromPanicValue(rec)
                }
            }()


action in the Go Playground here에서 확인할 수 있습니다. 또한 스택 추적을 잃지 않도록 주의해야 합니다. 여기서 사용한 CollectStack 함수는 지나치게 단순화되어 FromPanicValueCollectStack 프레임을 건너뛰지 않기 때문에 약간의 노이즈가 추가됩니다. 🤷‍♂️

func FromPanicValue(i interface{}) error {
    switch value := i.(type) {
    case nil:
        return nil
    case string:
        return fmt.Errorf("panic: %v\n%s", value, CollectStack())
    case error:
        return fmt.Errorf("panic in errgroup goroutine %w\n%s", value, CollectStack())
    default:
        return fmt.Errorf("unknown panic: %+v\n%s", value, CollectStack())
    }
}

func CollectStack() []byte {
    buf := make([]byte, 64<<10)
    buf = buf[:runtime.Stack(buf, false)]
    return buf
}



광산 동료Ben Kraft의 동료는 상용구(및 필요한 규율)를 피하기 위해 편리한 래퍼 코드sync/errgroup를 작성했습니다. 그의 허락을 받아 나lightly modified it
보다 일반적인 Go 커뮤니티를 위해 개인 작업 저장소에서 가져옵니다.
StevenACoffman/errgroup는 Go의 훌륭한 대안입니다.
sync/errgroup 고루틴 패닉을 오류로 변환한다는 차이점이 있습니다.

see it in use in the playground 또는 여기에서 할 수 있습니다.

package main

import (
    "fmt"

    "github.com/StevenACoffman/errgroup"
)

func main() {
    g := new(errgroup.Group)
    var urls = []string{
        "http://www.golang.org/",
        "http://www.google.com/",
        "http://www.somestupidname.com/",
    }
    for i := range urls {
        // Launch a goroutine to fetch the URL.
        i := i // https://golang.org/doc/faq#closures_and_goroutines
        g.Go(func() error {

            // deliberate index out of bounds triggered
            fmt.Println("Fetching:", i, urls[i+1])

            return nil
        })
    }
    // Wait for all HTTP fetches to complete.
    err := g.Wait()
    if err == nil {
        fmt.Println("Successfully fetched all URLs.")
    } else {
        fmt.Println(err)
    }
}


대위법



대안적인 견해를 가진 an interesting discussion이 있습니다.
몇 가지 예외를 제외하고 패닉이 발생하면 프로그램이 중단됩니다. 개발 및 테스트에서는 괜찮지만 밤에는 푹 자고 싶습니다.

선행 기술



대략적인 검색만으로 몇 가지 기존 오픈 소스 예제를 찾았습니다.

Kratos errgroup



마이크로서비스용 Kratos Go 프레임워크는 유사합니다errgroup.
해결책.

PanicGroup 세르게이 알렉산드로비치



기사Errors in Go:
From denial to acceptance
에서 ,
(패닉 기반 흐름 제어를 옹호합니다 😱), 대략적으로 동일한 PanicGroup가 있습니다.

type PanicGroup struct {
  wg      sync.WaitGroup
  errOnce sync.Once
  err     error
}

func (g *PanicGroup) Wait() error {
  g.wg.Wait()
  return g.err
}

func (g *PanicGroup) Go(f func()) {
  g.wg.Add(1)

  go func() {
    defer g.wg.Done()
    defer func(){
      if r := recover(); r != nil {
        if err, ok := r.(error); ok {
          // We need only the first error, sync.Once is useful here.
          g.errOnce.Do(func() {
            g.err = err
          })
        } else {
          panic(r)
        }
      }
    }()

    f()
  }()
}


개선을 위한 피드백이나 제안을 부탁드립니다!

좋은 웹페이지 즐겨찾기