パニックしないでください:Ergroup Goroutinesでパニックを捕えてください

13090 ワード


イメージバイNicosmos , パブリックドメイン, Wikimedia

TLドクターバージョン


StevenACoffman/errgroup ゴーの素晴らしい代わるドロップ sync/errgroup しかし、それはエラーにGoroutineパニックを変換します.

なぜあなたが欲しい


net/http Galoutineに対応する各リクエストに対してパニックハンドラをインストールします.
Goroutinesは、親Goroutinesからパニックハンドラを継承することはできません.
だからpanic() 子供Goroutinesのうちの1人で、プログラム全体を殺します.
だから生産では、いつでも使用するsync.errgroup , あなたは常にAを追加することを忘れないように規律を持っている
繰延するrecover() すべての新しいGoroutineの先頭に、任意のパニックをエラーに変換します.
            defer func() {
                if rec := recover(); rec != nil {
                    err = FromPanicValue(rec)
                }
            }()
これを見ることができますaction in the Go Playground here . また、スタックトレースを失わないように注意してください.The CollectStack 私がここで使用する関数は、簡単に簡略化されているので、スキップしないため、少しノイズが追加されますFromPanicValue and CollectStack フレーム.🤷‍♂️
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 そのboilerplateを避けるために(そして、必要な規律).彼の許可を得てlightly modified it to
より一般的なGOコミュニティのために我々の個人的な仕事倉庫からそれを持ち上げてください.StevenACoffman/errgroup ゴーの素晴らしい代わるドロップ
sync/errgroup それがエラーにGoroutineパニックを変えるという違いで.
あなたは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 これには別の見方があります.
いくつかの例外では、パニックのプログラムをクラッシュする必要があります.私は開発とテストでそれでOKです、しかし、夜にはむしろ眠ります.

先行技術


ただの検索だけで、私はいくつかの既存のオープンソースの例を見つけました.

Kratosグループ


マイクロサービスのためのKratos Goフレームワークは類似しているerrgroup
解決法

Sergey Alexandrovichによる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()
  }()
}
私はフィードバックや改善のための提案をいただければ幸いです!