به وحشت بروید و بازیابی کنید: یک شیرجه عمیق در کار با خطا

Leapcell: پلت فرم بدون سرور نسل بعدی برای میزبانی Golang ، کارهای Async و Redis
در زبان Go ، دو کلمه کلیدی وجود دارد که اغلب به صورت جفت ظاهر می شوند – وحشت و بازیابی. این دو کلمه کلیدی از نزدیک با تعویق ارتباط دارند. آنها هر دو توابع داخلی در زبان GO هستند و عملکردهای مکمل را ارائه می دهند.
I. عملکردهای اساسی وحشت و بازیابی
- وحشت: می تواند جریان کنترل برنامه را تغییر دهد. پس از فراخوانی وحشت ، کد باقیمانده عملکرد فعلی بلافاصله از اجرای آن متوقف می شود و تعویق تماس گیرنده به صورت بازگشتی در گوروتین فعلی اجرا می شود.
- بهبودی پیدا کردن: این می تواند جلوی تصادف برنامه ناشی از وحشت را بگیرد. این تابعی است که فقط می تواند در تعویق عمل کند. فراخوانی آن در دامنه های دیگر هیچ تاثیری نخواهد داشت.
ii. پدیده هنگام استفاده از وحشت و بهبودی
(i) وحشت فقط باعث ایجاد گوروتین فعلی می شود
کد زیر این پدیده را نشان می دهد:
func main() {
defer println("in main")
go func() {
defer println("in goroutine")
panic("")
}()
time.Sleep(1 * time.Second)
}
نتیجه در حال اجرا به شرح زیر است:
$ go run main.go
in goroutine
panic:
...
هنگام اجرای این کد ، مشخص می شود که بیانیه تعویق در عملکرد اصلی اجرا نمی شود و فقط معوق در گوروتین فعلی اجرا می شود. از آنجا که Runtime.deferproc مربوط به کلمه کلیدی Defer ، عملکرد تماس معوق را با گوروتین که در آن تماس گیرنده در آن قرار دارد مرتبط می کند ، بنابراین وقتی برنامه خراب می شود ، فقط عملکرد تماس معوق از گوروتین فعلی فراخوانی می شود.
(ب) بهبودی فقط هنگامی که در تعویق نامیده می شود ، عملی می شود
کد زیر این ویژگی را منعکس می کند:
func main() {
defer fmt.Println("in main")
if err := recover(); err != nil {
fmt.Println(err)
}
panic("unknown err")
}
نتیجه در حال اجرا:
$ go run main.go
in main
panic: unknown err
goroutine 1 [running]:
main.main()
...
exit status 2
با تجزیه و تحلیل دقیق این فرآیند ، می توان تشخیص داد که بهبودی تنها در صورت فراخوانی پس از وقوع وحشت ، عملی می شود. با این حال ، در جریان کنترل فوق ، بهبودی قبل از وحشت خوانده می شود ، که شرایط لازم برای تأثیرگذاری را برآورده نمی کند. بنابراین ، کلمه کلیدی بازیابی باید در تعویق استفاده شود.
(iii) وحشت اجازه می دهد تا چندین تماس تو در تو در تعویق بیفتد
کد زیر نحوه تماس با وحشت را چندین بار در یک عملکرد تعویق نشان می دهد:
func main() {
defer fmt.Println("in main")
defer func() {
defer func() {
panic("panic again and again")
}()
panic("panic again")
}()
panic("panic once")
}
نتیجه در حال اجرا به شرح زیر است:
$ go run main.go
in main
panic: panic once
panic: panic again
panic: panic again and again
goroutine 1 [running]:
...
exit status 2
از نتیجه خروجی برنامه فوق ، می توان مشخص کرد که تماس های متعدد برای وحشت در برنامه بر اجرای عادی عملکرد Defer تأثیر نمی گذارد. بنابراین ، به طور کلی استفاده از تعویق برای کار نهایی بی خطر است.
iii ساختار داده وحشت
کلمه کلیدی وحشت در کد منبع زبان GO توسط ساختار داده Runtime._Panic نشان داده شده است. هر بار که وحشت خوانده می شود ، ساختار داده ای مانند موارد زیر برای ذخیره اطلاعات مربوطه ایجاد می شود:
type _panic struct {
argp unsafe.Pointer
arg interface{}
link *_panic
recovered bool
aborted bool
pc uintptr
sp unsafe.Pointer
goexit bool
}
- argp: این یک اشاره گر به پارامتر است که به تعویق می افتد.
- جنجال: این پارامتر منتقل شده در هنگام فراخوانی وحشت است.
- پیوند: این به ساختار قبلی Runtime._Panic اشاره دارد.
- بهبود یافته: این نشان می دهد که آیا زمان اجرا فعلی ._ اسپانیک با بهبودی بازیابی شده است.
- سقط شده: این نشان می دهد که آیا وحشت فعلی به زور خاتمه یافته است یا خیر.
از قسمت Link در ساختار داده ، می توان نتیجه گرفت که عملکرد وحشت را می توان چندین بار به طور مداوم نامید و می توانند از طریق پیوند یک لیست مرتبط تشکیل دهند.
Three Fields PC ، SP و Goexit در ساختار همگی برای رفع مشکلات ناشی از Runtime.goexit معرفی شده اند. Runtime.GoExit فقط می تواند به Goroutine پایان دهد که این عملکرد را بدون تأثیر سایر گوروها می نامد. با این حال ، این عملکرد با وحشت لغو می شود و در تعویق بهبود می یابد. معرفی این سه زمینه اطمینان از عملکرد این عملکرد است.
IV اصل سقوط برنامه
کامپایلر وحشت کلمه کلیدی را به زمان اجرا. gopanic تبدیل می کند. فرآیند اجرای این عملکرد شامل مراحل زیر است:
- یک زمان اجرا جدید ایجاد کنید.
- به طور مداوم زمان اجرا را بدست آورید ._دفر از لیست _defer پیوند شده از goroutine فعلی در یک حلقه و تماس با زمان اجرا.
- برای سقط جنین کل برنامه ، زمان اجرا را فراخوانی کنید.
func gopanic(e interface{}) {
gp := getg()
...
var p _panic
p.arg = e
p.link = gp._panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
for {
d := gp._defer
if d == nil {
break
}
d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
d._panic = nil
d.fn = nil
gp._defer = d.link
freedefer(d)
if p.recovered {
...
}
}
fatalpanic(gp._panic)
*(*int)(nil) = 0
}
لازم به ذکر است که سه بخش نسبتاً مهم کد در عملکرد فوق حذف شده اند:
- کد موجود در شاخه بازیابی برای بازیابی برنامه.
- کد برای بهینه سازی عملکرد فراخوانی معوق از طریق Inlining.
- کد برای رفع وضعیت غیر طبیعی زمان اجرا. GOEXIT.
در نسخه 1.14 ، زبان GO درگیری بین وحشت بازگشتی و بهبودی و زمان اجرا را حل کرد. از طریق ارسال زمان اجرا: اطمینان حاصل کنید که GoExit نمی تواند با وحشت بازگشتی/بازیابی سقط شود.
Runtime.Fatalpanic یک تصادف برنامه را اجرا می کند که قابل بازیابی نیست. قبل از سقط جنین ، تمام پیام های وحشت و پارامترهای منتقل شده در طول تماس از طریق Runtime.printpanics را چاپ می کند:
func fatalpanic(msgs *_panic) {
pc := getcallerpc()
sp := getcallersp()
gp := getg()
if startpanic_m() && msgs != nil {
atomic.Xadd(&runningPanicDefers, -1)
printpanics(msgs)
}
if dopanic_m(gp, pc, sp) {
crash()
}
exit(2)
}
پس از چاپ پیام CRASC ، برای خروج از برنامه فعلی و بازگشت کد خطا ، با زمان اجرا تماس می گیرد.
V. اصل بازیابی تصادف
کامپایلر بازیابی کلمه کلیدی را به زمان اجرا تبدیل می کند. gorecover:
func gorecover(argp uintptr) interface{} {
gp := getg()
p := gp._panic
if p != nil &&!p.recovered && argp == uintptr(p.argp) {
p.recovered = true
return p.arg
}
return nil
}
اجرای این عملکرد بسیار ساده است. اگر Goroutine فعلی وحشت نامیده نشده باشد ، این عملکرد مستقیماً صفر را برمی گرداند ، به همین دلیل است که بازیابی تصادف در هنگام نامشخص در غیر اعدام شکست می خورد. در شرایط عادی ، زمینه بازیابی شده از زمان اجرا را اصلاح می کند ._ اسپانیک ، و بازیابی برنامه توسط زمان اجرا انجام می شود.
func gopanic(e interface{}) {
...
for {
// Execute the deferred call function, which may set p.recovered = true
...
pc := d.pc
sp := unsafe.Pointer(d.sp)
...
if p.recovered {
gp._panic = p.link
for gp._panic != nil && gp._panic.aborted {
gp._panic = gp._panic.link
}
if gp._panic == nil {
gp.sig = 0
}
gp.sigcode0 = uintptr(sp)
gp.sigcode1 = pc
mcall(recovery)
throw("recovery failed")
}
}
...
}
کد فوق بهینه سازی متناقض از تعقیب را حذف می کند. این برنامه Counter PC و STACK POINTER SP را از زمان اجرا می کند. قبل از برنامه ریزی ، SP ، PC و مقدار بازگشت عملکرد را آماده می کند:
func recovery(gp *g) {
sp := gp.sigcode0
pc := gp.sigcode1
gp.sched.sp = sp
gp.sched.pc = pc
gp.sched.lr = 0
gp.sched.ret = 1
gogo(&gp.sched)
}
هنگامی که کلمه کلیدی Defer نامیده می شود ، STAP POINTER SP و برنامه Counter PC در زمان تماس قبلاً در ساختار Runtime._defer ذخیره شده است. عملکرد Runtime.gogo در اینجا به موقعیتی که کلمه کلیدی تعویض نامیده می شود ، باز می گردد.
Runtime.Recovery مقدار بازده عملکرد را در طی فرآیند برنامه ریزی به 1 تنظیم می کند. از نظرات Runtime.DeferProc ، می توان دریافت که وقتی مقدار بازگشت عملکرد Runtime.DeferProc 1 است ، کد تولید شده توسط کامپایلر قبل از بازگشت عملکرد تماس گیرنده مستقیماً به آن می پرید و اجرا می شود. DeferReturn:
func deferproc(siz int32, fn *funcval) {
...
return0()
}
پس از پرش به عملکرد Runtime.deferreturn ، این برنامه از وحشت بازیابی شده و منطق عادی را اجرا می کند ، و عملکرد Runtime.gorecover همچنین می تواند پارامتر ARG را هنگام تماس با وحشت از زمان اجرا به دست آورد و آن را برگرداند و آن را برگرداند. به تماس گیرنده
vi خلاصه
تجزیه و تحلیل روند سقوط و بازیابی برنامه بسیار دشوار است و درک کد به خصوص آسان نیست. در اینجا خلاصه ای از برنامه سقوط و روند بازیابی برنامه وجود دارد:
- کامپایلر مسئول کار تبدیل کلمات کلیدی است. این وحشت را تبدیل می کند و به ترتیب به ترتیب اجرا می شود. به ترتیب. gopanic و runtime.gorecover ، معوق را به عملکرد Runtime.deferproc تبدیل می کند ، و عملکرد Runtime.deferreturn را در انتهای عملکردی که Defer می نامد ، فراخوانی می کند.
- هنگام مواجهه با روش Runtime.gopanic در طی فرآیند در حال اجرا ، ساختار Runtime._defer را از لیست مرتبط Goroutine به طور پی در پی استفاده می کند و آن را اجرا می کند.
- اگر Runtime.gorecover هنگام فراخوانی عملکرد اجرای معوق روبرو شود ، _panic.ecoured را به عنوان درست مشخص می کند و پارامتر وحشت را باز می گرداند.
- پس از پایان این تماس ، Runtime.gopanic برنامه Counter PC و STACK POINTER SP را از ساختار Run._defer خارج می کند و برای بازگرداندن برنامه با عملکرد Runtime.recovery تماس می گیرد.
- Runtime.Recovery با توجه به رایانه شخصی و SP به زمان اجرا می شود. DeferProc.
- کد تولید شده به طور خودکار توسط کامپایلر متوجه می شود که مقدار بازده Runtime.deferproc 0 نیست. در این زمان ، به زمان اجرا باز می گردد. Deferreturn و بازگرداندن به جریان اجرای عادی.
- اگر Runtime.gorecover با آن روبرو نشوید ، تمام زمان اجرا را طی می کند. به نوبه خود به نوبه خود ، و در نهایت با Runtime.fatalpanic تماس بگیرید تا برنامه را سقط کنید ، پارامترهای وحشت را چاپ کنید و کد خطا 2 را برگردانید.
فرایند تجزیه و تحلیل شامل دانش زیادی در سطح اساسی زبان است و کد منبع نیز برای خواندن نسبتاً مبهم است. پر از جریان کنترل غیر متعارف است و از طریق پیشخوان برنامه به جلو و عقب می پرید. با این حال ، هنوز هم برای درک جریان اجرای برنامه بسیار مفید است.
سرانجام ، من می خواهم مناسب ترین بستر استقرار را توصیه کنم: جهش
1. پشتیبانی چند زبانه
- با JavaScript ، Python ، Go یا Rust توسعه دهید.
5. پروژه های نامحدود را به صورت رایگان مستقر کنید
- فقط برای استفاده پرداخت کنید – بدون درخواست ، بدون هزینه.
3. راندمان هزینه بی نظیر
- پرداخت به عنوان شما بدون هیچ گونه هزینه بیکار.
- مثال: 25 دلار از درخواست های 6.94M در زمان پاسخ متوسط 60ms پشتیبانی می کند.
4. تجربه توسعه دهنده ساده
- UI بصری برای راه اندازی بی دردسر.
- خطوط لوله CI/CD کاملاً خودکار و ادغام GITOPS.
- معیارهای زمان واقعی و ورود به سیستم برای بینش های عملی.
5. مقیاس پذیری بی دردسر و عملکرد بالا
- مقیاس خودکار برای رسیدگی به همزمانی بالا با سهولت.
- صفر سربار عملیاتی – فقط روی ساختمان تمرکز کنید.
در اسناد بیشتر کاوش کنید!
توییتر Leapcell: https://x.com/leapcellhq