Alomerry Wu @ alomerry.com

恐慌与恢复

Jul 15, 2023 · 15min · 2.8k · · updated at 8 months ago

panic

结构

// A _panic holds information about an active panic.
//
// A _panic value must only ever live on the stack.
//
// The argp and link fields are stack pointers, but don't need special
// handling during stack growth: because they are pointer-typed and
// _panic values only live on the stack, regular stack pointer
// adjustment takes care of them.
type _panic struct {
  argp      unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
  arg       any            // argument to panic
  link      *_panic        // link to earlier panic
  pc        uintptr        // where to return to in runtime if this panic is bypassed
  sp        unsafe.Pointer // where to return to in runtime if this panic is bypassed
  recovered bool           // whether this panic is over
  aborted   bool           // the panic was aborted
  goexit    bool
}
  • argp 是指向 defer 调用时参数的指针;
  • arg 是调用 panic 时传入的参数;
  • link 指向了更早调用的 runtime._panic 结构;
  • recovered 表示当前 runtime._panic 是否被 recover 恢复;
  • borted 表示当前的 panic 是否被强行终止;

== 结构体中的 pc、sp 和 goexit 三个字段都是为了修复 runtime.Goexit 带来的问题引入的。runtime.Goexit 能够只结束调用该函数的 Goroutine 而不影响其他的 Goroutine,但是该函数会被 defer 中的 panic 和 recover 取消2,引入这三个字段就是为了保证该函数的一定会生效。 ==

在编译简介 walkexpr [1] 将 panic 转成 gopanic[^gopanic],gopanic 首先进行一些判断,然后生成一个 _panic 结构插入到当前协程上,给 runningPanicDefers 增加 1,然后调用 addOneOpenDeferFrame[2] 针对开放编码的 defer 执行栈扫描(开放编码的 defer 虽然通过内联减小了 defer 的开销,但是却增加了 panic 的开销,因为开放编码优化的 defer 不会在协程上插入 defer 链,所以 addOneOpenDeferFrame 需要查找内联的 defer 并插入 1 个开发编码的 defer 到协程的 defer 链中),在 addOneOpenDeferFrame 函数的注释中也有相应描述:(addOneOpenDeferFrame 通过获取 openCodedDeferInfo 来获取 funcdata)

INFO

// addOneOpenDeferFrame scans the stack (in gentraceback order, from inner frames to // outer frames) for the first frame (if any) with open-coded defers. If it finds // one, it adds a single entry to the defer chain for that frame. The entry added // represents all the defers in the associated open defer frame, and is sorted in // order with respect to any non-open-coded defers. // // addOneOpenDeferFrame stops (possibly without adding a new entry) if it encounters // an in-progress open defer entry. An in-progress open defer entry means there has // been a new panic because of a defer in the associated frame. addOneOpenDeferFrame // does not add an open defer entry past a started entry, because that started entry // still needs to finished, and addOneOpenDeferFrame will be called when that started // entry is completed. The defer removal loop in gopanic() similarly stops at an // in-progress defer entry. Together, addOneOpenDeferFrame and the defer removal loop // ensure the invariant that there is no open defer entry further up the stack than // an in-progress defer, and also that the defer removal loop is guaranteed to remove // all not-in-progress open defer entries from the defer chain. // // If sp is non-nil, addOneOpenDeferFrame starts the stack scan from the frame // specified by sp. If sp is nil, it uses the sp from the current defer record (which // has just been finished). Hence, it continues the stack scan from the frame of the // defer that just finished. It skips any frame that already has a (not-in-progress) // open-coded _defer record in the defer chain. // // Note: All entries of the defer chain (including this new open-coded entry) have // their pointers (including sp) adjusted properly if the stack moves while // running deferred functions. Also, it is safe to pass in the sp arg (which is // the direct result of calling getcallersp()), because all pointer variables // (including arguments) are adjusted as needed during stack copies.

接下来循环获取协程的 defer 链依次执行,

首先标记 _defer.started 为 true,(// Mark defer as started, but keep on list, so that traceback // can find and update the defer’s argument frame if stack growth // or a garbage collection happens before executing d.fn.)????

然后将 _defer._panic 设置为当前 panic

然后根据 defer 是否是开放编码执行两种逻辑:

  • 如果是开放编码,调用 runOpenDeferFrame,从 deferBits 上从高位到低位依次执行,每执行一位清空该位的 deferBits
    • defer 中没有 recover
    • defer 中有 recover

执行完则结束后调用

preprintpanics 提前记录参数(为什么?)// ran out of deferred calls - old-school panic now // Because it is unsafe to call arbitrary user code after freezing // the world, we call preprintpanics to invoke all necessary Error // and String methods to prepare the panic strings before startpanic.

然后调用 fatalpanic 通过递归(printpanics)从最 _panic 链尾部至头部依次输出错误信息打印错误和栈信息后终止进程(为什么要在系统栈???防止栈增长???)

recover

[3]

首先有 defer 就会在当前协程 defer 链前插入

  • 没有 panic 时,会依次执行协程的 defer 链
  • 有 panic 时
    • 在协程 panic 链前插入 panic,并依次执行协程 defer 链,执行时,会将 panic 关联到执行的 defer,标记已开始
      • 执行完一个 defer 就移除,同时检测 panic 是否 recover
        • 如果 recover 了,就会移除当前 panic,继续执行 defer 链
        • 如果没有 recover,就会继续执行 defer 链,最后输出堆栈信息
      • 如果 panic 后执行 defer 时又产生了新的 panic,则在 panic 链头新增一个 panic,然后执行 defer 链
        • 则执行到已开始的 defer,会将前面的 panic 标记为已终止

Codes

func addOneOpenDeferFrame(gp *g, pc uintptr, sp unsafe.Pointer) {
  var prevDefer *_defer
  if sp == nil {
    prevDefer = gp._defer
    pc = prevDefer.framepc
    sp = unsafe.Pointer(prevDefer.sp)
  }
  systemstack(func() {
    gentraceback(pc, uintptr(sp), 0, gp, 0, nil, 0x7fffffff,
      func(frame *stkframe, unused unsafe.Pointer) bool {
        if prevDefer != nil && prevDefer.sp == frame.sp {
          // Skip the frame for the previous defer that
          // we just finished (and was used to set
          // where we restarted the stack scan)
          return true
        }
        f := frame.fn
        fd := funcdata(f, _FUNCDATA_OpenCodedDeferInfo)
        if fd == nil {
          return true
        }
        // Insert the open defer record in the
        // chain, in order sorted by sp.
        d := gp._defer
        var prev *_defer
        for d != nil {
          dsp := d.sp
          if frame.sp < dsp {
            break
          }
          if frame.sp == dsp {
            if !d.openDefer {
              throw("duplicated defer entry")
            }
            // Don't add any record past an
            // in-progress defer entry. We don't
            // need it, and more importantly, we
            // want to keep the invariant that
            // there is no open defer entry
            // passed an in-progress entry (see
            // header comment).
            if d.started {
              return false
            }
            return true
          }
          prev = d
          d = d.link
        }
        if frame.fn.deferreturn == 0 {
          throw("missing deferreturn")
        }

        d1 := newdefer()
        d1.openDefer = true
        d1._panic = nil
        // These are the pc/sp to set after we've
        // run a defer in this frame that did a
        // recover. We return to a special
        // deferreturn that runs any remaining
        // defers and then returns from the
        // function.
        d1.pc = frame.fn.entry() + uintptr(frame.fn.deferreturn)
        d1.varp = frame.varp
        d1.fd = fd
        // Save the SP/PC associated with current frame,
        // so we can continue stack trace later if needed.
        d1.framepc = frame.pc
        d1.sp = frame.sp
        d1.link = d
        if prev == nil {
          gp._defer = d1
        } else {
          prev.link = d1
        }
        // Stop stack scanning after adding one open defer record
        return false
      },
      nil, 0)
  })
}
  1. func walkExpr1(n ir.Node, init *ir.Nodes) ir.Node {
      switch n.Op() {
      ...
      case ir.OPANIC:
        n := n.(*ir.UnaryExpr)
        return mkcall("gopanic", nil, init, n.X)
      ...
    }
    ↩︎
  2. ↩︎
  3. func walkExpr1(n ir.Node, init *ir.Nodes) ir.Node {
      switch n.Op() {
      ...
      case ir.ORECOVERFP:
        return walkRecoverFP(n.(*ir.CallExpr), init)
      ...
    }
    func walkRecoverFP(nn *ir.CallExpr, init *ir.Nodes) ir.Node {
      return mkcall("gorecover", nn.Type(), init, walkExpr(nn.Args[0], init))
    }
    ↩︎
 
 comment..
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.0.1
Theme by antfu
2018 - Present © Alomerry Wu