golang源码学习之panic

ihornet · · 828 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

这里会涉及到一些defer的知识,有兴趣可以看我的另一篇关于defer的文章 https://www.jianshu.com/p/fec11caadaf6

数据结构

//runtime/runtime2.go
type _panic struct {
    // 调用defer时入参的指针
    argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
    // panic 的参数
    arg interface{} // argument to panic
    // 指向更早的panic, 新的panic添加到链表头
    link *_panic // link to earlier panic
    // 当前panic是否被recover
    recovered bool // whether this panic is over
    // 当前panic是否被强制终止
    aborted bool // the panic was aborted
}

这里会涉及到两个函数: panic和recover, 它们分别对应gopanic、gorecover

gopanic

//runtime/panic.go
func gopanic(e interface{}) {
    gp := getg()

    ...
    
    // 初始化panic
    var p _panic
    p.arg = e
    p.link = gp._panic
    gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

    atomic.Xadd(&runningPanicDefers, 1)

    // 遍历 G 的defer链表
    for {
        d := gp._defer
        if d == nil {
            break
        }

        // If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
        // take defer off list. The earlier panic or Goexit will not continue running.
        // defer已经被调用过, 跳过继续循环
        if d.started {
            if d._panic != nil {
                d._panic.aborted = true
            }
            d._panic = nil
            d.fn = nil
            gp._defer = d.link
            freedefer(d)
            continue
        }

        d.started = true // 标识被调用


        d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

        p.argp = unsafe.Pointer(getargp(0))
        //调用defer后面的函数。如果函数中包含了recover,那么会调用gorecover
        reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
        p.argp = nil

        // reflectcall did not panic. Remove d.
        if gp._defer != d {
            throw("bad defer entry in panic")
        }
        d._panic = nil
        d.fn = nil
        gp._defer = d.link

        // trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic
        //GC()

        pc := d.pc
        sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
        // 将 defer 放回缓存
        freedefer(d)
        // 已经有recover被调用
        if p.recovered {
            atomic.Xadd(&runningPanicDefers, -1)

            gp._panic = p.link
            // Aborted panics are marked but remain on the g.panic list.
            // Remove them from the list.
            // 移除被终止的panic
            for gp._panic != nil && gp._panic.aborted {
                gp._panic = gp._panic.link
            }
            if gp._panic == nil { // must be done with signal
                gp.sig = 0
            }
            // Pass information about recovering frame to recovery.
            gp.sigcode0 = uintptr(sp)
            gp.sigcode1 = pc
            // 恢复,调用gorecovery
            mcall(recovery)
            throw("recovery failed") // mcall should not return
        }
    }

    preprintpanics(gp._panic)

    // 终止程序
    fatalpanic(gp._panic) // should not return
    *(*int)(nil) = 0      // not reached
}

gorecover

//runtime/panic.go
func gorecover(argp uintptr) interface{} {
    gp := getg()
    p := gp._panic
    if p != nil && !p.recovered && argp == uintptr(p.argp) {
        // 将recovered设为true,说明已经defer后面的函数包含recover
        p.recovered = true 
        return p.arg
    }
    return nil
}

recovery

//runtime/panic.go
func recovery(gp *g) {

    ...
    
    // 这里会跳转到deferreturn
    gogo(&gp.sched) 
}

fatalpanic

//runtime/panic.go
func fatalpanic(msgs *_panic) {
    
    ...
    
    systemstack(func() {
        if startpanic_m() && msgs != nil {

            atomic.Xadd(&runningPanicDefers, -1)

            // 打印错误消息
            printpanics(msgs)
        }

        docrash = dopanic_m(gp, pc, sp)
    })

    systemstack(func() {
        exit(2) // 退出程序
    })

    *(*int)(nil) = 0 // not reached
}

总结:panic、recover和defer十分密切,所以在这里就一起总结了。编译阶段会将defer转换成deferproc,并在return前插入deferreturn。deferproc会从两级缓存中获取defer或者重新生成defer,defer会添加到G的defer链表头部。deferreturn时会遍历defer链表(后进先出)。当发生panic时, 会遍历G的defer链表,如发现defer后面的函数中包含recover,则会跳转到deferreturn,否则退出程序。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:ihornet

查看原文:golang源码学习之panic

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

828 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传