揭秘Crashpad系統(tǒng)如何幫助Dropbox這樣復(fù)雜的桌面程序捕獲并報(bào)告崩潰,且兼容Python的多種語言。
維護(hù)像Dropbox這樣的復(fù)雜桌面應(yīng)用程序最大挑戰(zhàn)之一就是同時(shí)處理數(shù)億次的安裝,一個(gè)小小的錯(cuò)誤就會(huì)影響到大量的用戶。
這些錯(cuò)誤會(huì)攻擊程序,雖然應(yīng)用程序大多數(shù)情況下都可以恢復(fù),但有時(shí)也會(huì)導(dǎo)致程序終止。這樣的終止或“崩潰”對程序具有很高的破壞性:當(dāng)Dropbox程序終止時(shí),程序就無法同步了。為了確保我們的用戶可以不間斷的同步,我們會(huì)自動(dòng)檢測并報(bào)告所有崩潰,同時(shí)采取措施重新啟動(dòng)程序。
2016年,隨著逐步的過渡到Python 3,我們開始著手改進(jìn)我們檢測和報(bào)告崩潰的方式。目前,對于我們的桌面團(tuán)隊(duì)來說,我們的崩潰報(bào)告流程無論在報(bào)告的數(shù)量還是在質(zhì)量上都是非常可靠的。在本文中,我們將深入探討我們是如何設(shè)計(jì)這個(gè)新系統(tǒng)的。
Python不會(huì)崩潰,真是這樣的嗎?
部分Dropbox程序是用Python編寫的,雖然Python是一種安全的高級(jí)語言,但它還是會(huì)崩潰。大多數(shù)出現(xiàn)在Python中的崩潰(即未處理的異常)很容易處理,但很多異常來自“底層“:非Python代碼、解釋器代碼本身中,或在Python的擴(kuò)展中。這些“原始”的崩潰并不是什么新鮮事:例如,幾十年來錯(cuò)誤的內(nèi)存操作一直困擾著開發(fā)者們。
隨著我們的應(yīng)用程序變得越來越復(fù)雜,我們開始使用其他編程語言來構(gòu)建我們的一些功能。在與操作系統(tǒng)集成時(shí)尤其如此,其中最簡單的路徑往往是使用平臺(tái)特定的工具和語言(例如,Windows上的COM和macOS上的Objective-C)。這增加了我們的代碼庫中非Python代碼的比例,這就不可避免的帶來懸空指針、內(nèi)存錯(cuò)誤、數(shù)據(jù)競爭和未經(jīng)檢查的數(shù)組訪問的風(fēng)險(xiǎn),所有這些都可能導(dǎo)致Dropbox被暴力終結(jié)。結(jié)果就是,一個(gè)崩潰報(bào)告的堆棧軌跡中會(huì)包含Python,C ++,Objective-C和C多種代碼!
早期的做法
幾年前,我們使用簡單的進(jìn)程內(nèi)崩潰檢測機(jī)制:信號(hào)處理程序。我們能夠“捕獲”各種UNIX系統(tǒng)信號(hào),當(dāng)遇到致命信號(hào)(即SIGFPE)時(shí),我們的信號(hào)處理程序?qū)L試以下操作:
捕獲每個(gè)線程的Python堆棧軌跡(使用faulthandler模塊)
捕獲該線程的本機(jī)堆棧軌跡(通常使用libc的backtrace和backtrace_symbols函數(shù))
然后,我們會(huì)將這些數(shù)據(jù)安全地上傳到Dropbox的服務(wù)器。
雖然做到這些已經(jīng)足矣,但有一些基本問題會(huì)影響程序的可靠性或限制其在調(diào)試中的實(shí)用性:
如果問題發(fā)生在設(shè)置處理程序之前,那我們會(huì)收不到任何報(bào)告。這通常是由導(dǎo)入庫錯(cuò)誤或安裝錯(cuò)誤引起的。這些基本的“啟動(dòng)錯(cuò)誤”是最嚴(yán)重的,因?yàn)樗鼈儗?dǎo)致用戶無法啟動(dòng)應(yīng)用程序,這是一個(gè)無法接受的狀況,因?yàn)檫@時(shí)我們根本無法捕捉這些錯(cuò)誤。出現(xiàn)這樣問題時(shí),我們的工程師只能通過客戶支持系統(tǒng)獲取相關(guān)報(bào)告。雖然我們構(gòu)建了一個(gè)的錯(cuò)誤對話框來幫助完成這一過程,但這仍然會(huì)使我們的團(tuán)隊(duì)在干預(yù)啟動(dòng)/早期代碼方面增加了風(fēng)險(xiǎn)。
信號(hào)處理程序穩(wěn)定性不足。處理程序不僅負(fù)責(zé)捕獲狀態(tài),還負(fù)責(zé)將其發(fā)送到我們的服務(wù)器上。隨著時(shí)間的推移,我們意識(shí)到盡管能夠成功地生成報(bào)告,但它仍有可能無法完成發(fā)送。此外,特別嚴(yán)重的崩潰可能導(dǎo)致無法在崩潰時(shí)正確提取出狀態(tài)。例如,如果解釋器狀態(tài)本身就已經(jīng)損壞了,則可能會(huì)阻止我們進(jìn)行Python堆棧跟蹤,或者更糟糕,整個(gè)處理過程可能會(huì)破壞。
其中一個(gè)根本原因是信號(hào)處理程序本身的特性導(dǎo)致的:幸運(yùn)的是,Python的信號(hào)模塊考慮了大部分情況,而且還增加了一些限制。例如,信號(hào)只能從主線程調(diào)用,并且可能無法同步運(yùn)行。這種異步性意味著一些最常見的SIGSEGV通常不會(huì)被Python困住!
Crashpad大顯神通
通過在主進(jìn)程外部提取報(bào)告器可以構(gòu)建更可靠的崩潰報(bào)告機(jī)制。這很容易實(shí)現(xiàn),因?yàn)閃indows和MacOS都提供了系統(tǒng)工具來捕獲進(jìn)程外的崩潰。Chromium項(xiàng)目開發(fā)了一個(gè)全面的崩潰捕獲/報(bào)告解決方案,該解決方案利用了可獨(dú)立使用的工具庫:Crashpad。
Crashpad作為一個(gè)小的幫助程序進(jìn)程監(jiān)視你的應(yīng)用程序,當(dāng)出現(xiàn)崩潰的信號(hào)時(shí),它就會(huì)捕獲有用的信息,包括:
1.進(jìn)程崩潰的原因和導(dǎo)致崩潰的線程;
2.所有線程的堆棧軌跡;
3.堆的部分內(nèi)容;
4.開發(fā)人員添加到應(yīng)用程序的額外注釋(可靈活使用)。
以上這些都是在minidump有效負(fù)載中捕獲的,它是一種最初微軟開發(fā)的在Windows上使用編寫格式,有點(diǎn)類似于Unix風(fēng)格的核心轉(zhuǎn)儲(chǔ)。這種格式是開源的,并且有優(yōu)秀的服務(wù)器端工具(主要來自Google和Mozilla)來處理這些數(shù)據(jù)。
下圖概述了Crashpad的基本架構(gòu):
應(yīng)用程序通過實(shí)例化一個(gè)進(jìn)程內(nèi)對象(稱為“客戶端”)來使用Crashpad,當(dāng)檢測到崩潰時(shí),該對象報(bào)告給進(jìn)程外的幫助程序—稱為“處理程序”。
我們決定使用此庫來解決與進(jìn)程內(nèi)信號(hào)處理程序相關(guān)的許多可靠性問題。這個(gè)選擇對我們來說很容易,因?yàn)镃hromium是有史以來發(fā)布的最受歡迎的桌面應(yīng)用程序之一。我們也對Windows的更復(fù)雜支持感到滿意,這是一個(gè)與UNIX完全不同的平臺(tái)。faulthandler(在當(dāng)時(shí))僅支持Windows平臺(tái)的崩潰,因?yàn)樗浅R蕾囆盘?hào),一個(gè)UNIX / POSIX平臺(tái)的概念。Crashpad利用結(jié)構(gòu)化異常處理(或SEH)可以捕獲到更全面的致命Windows特定異常。
關(guān)于Linux的說明:盡管最近引入了Linux支持,但是當(dāng)我們第一次部署時(shí),Crashpad僅適用于Windows和MacOS,因此我們將庫的使用限制在這些平臺(tái)上。在Linux上,我們繼續(xù)使用進(jìn)程內(nèi)信號(hào)處理程序,但我們將來會(huì)做進(jìn)一步的改進(jìn)。
符號(hào)化
與大多數(shù)已編譯的應(yīng)用程序一樣,Dropbox將發(fā)布版本發(fā)送給用戶,發(fā)布版本中啟用了多個(gè)編譯器進(jìn)行優(yōu)化,同時(shí)去除符號(hào)表示以減少二進(jìn)制存儲(chǔ)大小。這意味著Dropbox收集到的信息幾乎是無用的,除非它可以“映射”回源代碼,這個(gè)過程就被稱為“符號(hào)化”。
為此我們?yōu)閮?nèi)部服務(wù)器上的每個(gè)Dropbox構(gòu)建保留符號(hào)。這是我們構(gòu)建過程的核心部分,若符號(hào)生成失敗則被認(rèn)為是構(gòu)建失敗,我們不會(huì)使用這種無法被符號(hào)化的發(fā)布版本。
當(dāng)應(yīng)用的崩潰報(bào)告中含有minidump(小存儲(chǔ)器轉(zhuǎn)儲(chǔ)文件:可幫助確定計(jì)算機(jī)為什么意外停止的最小的有用信息集)時(shí), 我們使用之前生成的符號(hào)來跟蹤應(yīng)用里每個(gè)堆棧內(nèi)容并將其鏈接到源代碼中。使用開發(fā)框架系統(tǒng)庫時(shí), 我們會(huì)遵循特定平臺(tái)的符號(hào)表示。此過程使我們的開發(fā)人員能夠快速定位到應(yīng)用崩潰位置,判斷其是源自框架平臺(tái)還是第三方代碼。
Microsoft維護(hù)所有 windows 版本的公共符號(hào)服務(wù)器,以便映射涉及各版本功能的堆棧幀。不幸的是,Apple沒有類似的系統(tǒng),但是Apple的平臺(tái)框架中包括了各版本的匹配符號(hào)。為了讓Dropbox支持各種版本, 我們使用測試虛擬機(jī)緩存各種 macOS框架(適用于各種操作系統(tǒng)版本)的符號(hào)(盡管我們?nèi)匀慌紶枙?huì)遇到版本未包含的問題)。
挎斗驗(yàn)證
從數(shù)百萬次安裝中更改崩潰報(bào)告的基礎(chǔ)架構(gòu)是一項(xiàng)冒險(xiǎn)嘗試,但是我們需要這樣來驗(yàn)證我們的新機(jī)制是否有效。同樣需要注意的是,并非所有終止都是應(yīng)用崩潰(例如用戶關(guān)閉應(yīng)用程序或應(yīng)用自動(dòng)更新就不屬于應(yīng)用崩潰)。盡管如此,有一些終止情況仍然表明應(yīng)用可能存在問題。因此,我們希望有一種方法能來記錄和判斷出哪種情況算是應(yīng)用正常退出,哪種情況算是應(yīng)用意外崩潰。 這也為我們提供一個(gè)基線,用來驗(yàn)證我們的新崩潰報(bào)告構(gòu)架是否捕獲了大部分應(yīng)用崩潰情況。
為了解決這個(gè)問題, 我們建立了一個(gè)被稱為 " watchdog "(看門狗) 的 "sidecar" (挎斗)過程。這是一個(gè)具有單一責(zé)任的小型 "配套" 進(jìn)程 (類似于Crashpad):當(dāng)桌面應(yīng)用退出時(shí), 它會(huì)捕獲其退出狀態(tài), 以確定它是否 "成功" (即用戶或應(yīng)用程序啟動(dòng)的關(guān)閉而不是被強(qiáng)行終止)。因?yàn)槲覀兿M哂懈叨瓤煽啃裕栽撨^程被設(shè)計(jì)的非常簡單。
我們讓應(yīng)用程序在啟動(dòng)時(shí)發(fā)送事件來生成啟動(dòng)事件,通過比較啟動(dòng)和退出事件,可以測量退出監(jiān)控的準(zhǔn)確性。我們可以確保退出監(jiān)控對絕大部分用戶是成功的 (請注意防火墻等其他程序會(huì)阻止它一直運(yùn)行)。此外, 我們可以將此退出事件與來自Crashpad的崩潰報(bào)告進(jìn)行匹配,以確保我們預(yù)計(jì)會(huì)引起崩潰的退出代碼確實(shí)包括大多數(shù)用戶的崩潰情況。下圖顯示了我們的退出監(jiān)控:
看門狗允許我們驗(yàn)證崩潰報(bào)告是否正確
看門狗允許我們在單個(gè)圖中對崩潰和終止進(jìn)行分類
我們用Rust編寫了看門狗進(jìn)程,為什么會(huì)選擇Rust呢:
1.Rust的安全設(shè)置使代碼可靠性非常高。
2.與操作系統(tǒng)的抽象接口設(shè)計(jì)良好,屬于系統(tǒng)標(biāo)準(zhǔn)庫的一部分,并且在需要時(shí)可以通過FFI輕松擴(kuò)展接口。
3.我們在開發(fā)Dropbox時(shí)很大一部分都使用了Rust,這讓Dropbox的搭建變得更加容易。
教Crashpad兼容Python
Crashpad主要是為本機(jī)代碼設(shè)計(jì)的,因?yàn)镃hromium主要是用C ++編寫的。但是,Dropbox客戶端大多是用Python編寫的。由于Python是一種解釋型語言,因此我們收到的大多數(shù)本機(jī)崩潰報(bào)告往往如下所示:
0 _ctypes.cpython-35m-darwin.so!_i_get + 0x4
1 _ctypes.cpython-35m-darwin.so!_Simple_repr + 0x4a
2 libdropbox_python.3.5.dylib!_PyObject_Str + 0x8e
3 libdropbox_python.3.5.dylib!_PyFile_WriteObject + 0x79
4 libdropbox_python.3.5.dylib!_builtin_print + 0x1dc
5 libdropbox_python.3.5.dylib!_PyCFunction_Call + 0x7a
6 libdropbox_python.3.5.dylib!_PyEval_EvalFrameEx + 0x5f12
7 libdropbox_python.3.5.dylib!_fast_function + 0x19d
8 libdropbox_python.3.5.dylib!_PyEval_EvalFrameEx + 0x5770
9 libdropbox_python.3.5.dylib!__PyEval_EvalCodeWithName + 0xc9e
10 libdropbox_python.3.5.dylib!_PyEval_EvalCodeEx + 0x24
11 libdropbox_python.3.5.dylib!_function_call + 0x16f
12 libdropbox_python.3.5.dylib!_PyObject_Call + 0x65
13 libdropbox_python.3.5.dylib!_PyEval_EvalFrameEx + 0x666a
14 libdropbox_python.3.5.dylib!__PyEval_EvalCodeWithName + 0xc9e
15 libdropbox_python.3.5.dylib!_PyEval_EvalCodeEx + 0x24
16 libdropbox_python.3.5.dylib!_function_call + 0x16f
17 libdropbox_python.3.5.dylib!_PyObject_Call + 0x65
18 libdropbox_python.3.5.dylib!_PyEval_EvalFrameEx + 0x666a
19 libdropbox_python.3.5.dylib!__PyEval_EvalCodeWithName + 0xc9e
20 libdropbox_python.3.5.dylib!_PyEval_EvalCodeEx + 0x24
21 libdropbox_python.3.5.dylib!_function_call + 0x16f
22 libdropbox_python.3.5.dylib!_PyObject_Call + 0x65
...onandon
這個(gè)堆棧跟蹤對于試圖發(fā)現(xiàn)崩潰原因的開發(fā)人員來說并不是很有幫助。雖然faulthandler包含了所有線程的Python堆棧幀,但默認(rèn)情況下Crashpad并沒有此功能。為了讓這個(gè)報(bào)告變得有用,我們需要加入相關(guān)的Python狀態(tài)。 但是,由于Crashpad不是用Python編寫的并且在進(jìn)程之外,我們無法訪問faulthandler本身,那我們要如何處理呢?
當(dāng)崩潰程序暫停時(shí),Crashpad可以讀取它的所有內(nèi)存以捕獲程序狀態(tài)。 由于程序可能處于錯(cuò)誤狀態(tài),因此我們無法執(zhí)行任何代碼。接下來我們就需要:
1.弄清楚Python數(shù)據(jù)在內(nèi)存中的結(jié)構(gòu)布局
2.遍歷相關(guān)數(shù)據(jù)結(jié)構(gòu)以定位程序崩潰時(shí)正在運(yùn)行的代碼
3.存儲(chǔ)此信息并將其安全地上傳到我們的服務(wù)器
我們之所以會(huì)選擇 Crashpad,,部分原因是它的可定制性,它非常容易被擴(kuò)展。因此,我們在 ProcessSnapshot 類中添加了代碼來捕獲 Python堆棧, 并引入了我們自己的自定義小型轉(zhuǎn)儲(chǔ) "流" (文件格式符合,同時(shí)Crashpad本身支持) 來保留和報(bào)告此信息。
Python 和線程本地存儲(chǔ)
首先, 我們需要知道去哪里找它們。在CPython中,解釋器線程始終由本機(jī)線程支持。因此,在 Dropbox應(yīng)用程序中, Python創(chuàng)建的每個(gè)本機(jī)線程都有一個(gè)關(guān)聯(lián)的 PyThreadState 結(jié)構(gòu)。解釋器使用本機(jī)線程特定的存儲(chǔ)來創(chuàng)建此對象和本機(jī)線程之間的連接。由于Crashpad可以訪問受監(jiān)視進(jìn)程的內(nèi)存,因此它可以讀取這個(gè)狀態(tài)并將其作為報(bào)告的一部分。
由于 Dropbox提供了CPython的自定義分支,因此我們可以有效地控制它的行為。這意味著我們不僅可以利用它改善Dropbox,而且可以依賴它, 因?yàn)槲覀冎浪目煽啃苑浅8摺?/p>
在Python中,特定于線程的存儲(chǔ)在不同平臺(tái)的實(shí)現(xiàn)方式不一樣:
在POSIX上,pthread_key_create 用于分配密鑰,而pthread_(get/set)specific用于交互
在Windows上,TlsAlloc 用于分配存儲(chǔ)在線程環(huán)境Block.aspx中可預(yù)測/記錄位置的線程本地“slots”
注意:我們?yōu)镃rashpad提供了修復(fù)程序以使其隨時(shí)可用。
參見:
https://chromium-review.googlesource.com/c/crashpad/crashpad/+/717040
但是,所有平臺(tái)的共同點(diǎn)是特定于Python的狀態(tài)存儲(chǔ)在本機(jī)線程狀態(tài)的特定偏移量處。遺憾的是,這種偏移不是靜態(tài)的:它可以根據(jù)各種因素而改變。此偏移量在Python運(yùn)行時(shí)的設(shè)置早期確定:這稱為特定于線程的存儲(chǔ)“密鑰”。此步驟為進(jìn)程中的所有線程創(chuàng)建一個(gè)特定于線程的存儲(chǔ)的“插槽”,然后由Python用它來存儲(chǔ)其特定于線程的狀態(tài)。
因此,如果crashpad可以為進(jìn)程實(shí)例檢索TSS“key”,它將能夠讀取任何給定線程的PyThreadState。
獲取線程本地存儲(chǔ)“密鑰”
我們考慮了多種方法,但最終選擇了一種受Crashpad本身啟發(fā)的方法。最后,我們修改了Python的fork【fork不知道怎么翻譯】,用在二進(jìn)制的命名部分(即__DATA)中公開運(yùn)行時(shí)狀態(tài)(包括TSS密鑰)。因此,Dropbox的所有實(shí)例現(xiàn)在都會(huì)以一種易于從Crashpad檢索它的方式公開Python運(yùn)行時(shí)狀態(tài)。
這是通過使用Clang中的__attribute__和在Windows上使用__declspec實(shí)現(xiàn)的。
這在Crashpad中使用起來很簡單,因?yàn)樗褂孟嗤募夹g(shù)允許客戶端向自己的進(jìn)程添加注釋(請參閱CrashpadInfo)。
這也很好地與Python自己不斷發(fā)展的解釋器的內(nèi)部設(shè)計(jì)保持一致,因?yàn)樗罱亟M了自己,運(yùn)行時(shí)狀態(tài)能夠整合到單個(gè)結(jié)構(gòu)_PyRuntime。(在Python / pylifecycle.c中)。此結(jié)構(gòu)包括TSS密鑰以及其他有趣的調(diào)試工具。
注意:我們已將此更改作為拉取上傳到github,希望能對大眾有所裨益。
https://github.com/python/cpython/pull/4802/files
現(xiàn)在Crashpad可以確定TSS密鑰,它可以訪問每個(gè)線程的PyThreadState。下一步是解釋此狀態(tài),提取相關(guān)信息,并將其作為崩潰報(bào)告的一部分發(fā)送。
解析Python堆棧幀
在CPython中,“frames”是函數(shù)執(zhí)行的單位,Python類似于本機(jī)堆棧幀。 PyThreadState將它們維護(hù)為PyFrameObjects的堆棧。線程狀態(tài)使用單個(gè)指針指向任何給定時(shí)間的最頂層幀。給定以上設(shè)置和TSS密鑰,我們可以從本機(jī)線程開始,找到PyThreadState,然后“遍歷堆棧”PyFrameObjects。
然而,事實(shí)比理論更加棘手一些。我們不能只是#include
對于每一幀,我們的目標(biāo)是將其解析為代碼位置。每個(gè)PyFrameObject都有一個(gè)指向PyCodeObject的指針,包括有關(guān)函數(shù)名,文件名和行號(hào)的信息(faulthandler利用相同的信息)。
文件名和函數(shù)名稱保存為Python字符串。解碼Python字符串可以相當(dāng)復(fù)雜,因?yàn)樗鼈儤?gòu)建在類型的層次結(jié)構(gòu)上。為簡單起見,我們假設(shè)所有函數(shù)和文件名都是ASCII編碼的(就可以映射到簡單的PyASCIIObject)。
獲取行號(hào)稍微復(fù)雜一些。為了節(jié)省空間,Python能夠?qū)⒚總€(gè)字節(jié)代碼指令映射到Python源,同時(shí)將行號(hào)壓縮成一個(gè)表(PyCodeObject的co_lnotab)。
解碼此表的算法是明確定義的,因此我們在Crashpad fork【fork】中重新實(shí)現(xiàn)了它。
算法參照:https://github.com/python/cpython/blob/3df85404d4bf420db3362eeae1345f2cad948a71/Objects/lnotab_notes.txt
關(guān)于Python 3轉(zhuǎn)換的注釋:由于Python 2和3的實(shí)現(xiàn)略有不同,我們在轉(zhuǎn)換過程中保持對Crashpad fork中兩個(gè)版本的Python結(jié)構(gòu)的支持。
堆棧框架重建
現(xiàn)在Crashpad的報(bào)告包含了所有Python堆棧幀,我們可以改進(jìn)符號(hào)化。為此,我們修改了我們的服務(wù)器基礎(chǔ)結(jié)構(gòu),以解析我們對minidump的擴(kuò)展并提取這些堆棧。具體來說,我們擴(kuò)充了崩潰管理系統(tǒng)Crashdash,以顯示本機(jī)崩潰報(bào)告的Python堆棧框架信息(如果可用)。
這是通過再次“遍歷堆棧”來實(shí)現(xiàn)的,但這次,對于調(diào)用PyEval_EvalFrameEx的每個(gè)本機(jī)幀,我們從報(bào)告中“彈出”匹配的PyFrameObjectcapture。由于我們現(xiàn)在擁有每個(gè)幀的函數(shù)名,文件名和行號(hào),現(xiàn)在我們可以顯示匹配的函數(shù)調(diào)用。因此,我們可以從上面提取基礎(chǔ)Python堆棧跟蹤:
file "ui/common/tray.py", line 758,in_do_segfault
file "dropbox/client/ui/cocoa/menu.py", line 169,inmenuAction_
file "dropbox/gui.py", line 274,inguarantee_message_queue
file "dropbox/gui.py", line 299,inhandle_exceptions
file "PyObjCTools/AppHelper.py", line 303,inrunEventLoop
file "ui/cocoa/uikit.py", line 256,inmainloop
file "ui/cocoa/uikit.py", line 929,inmainloop
file "dropbox/client/main.py", line 3263,inrun
file "dropbox/client/main.py", line 6904,inmain_startup
file "dropbox/client/main.py", line 7000,inmain
結(jié)語
有了這個(gè)系統(tǒng),我們的開發(fā)人員就可以直接調(diào)查所有崩潰,無論是Python,C,C ++還是Objective-C。此外,我們?yōu)闇y量系統(tǒng)可靠性而引入的新監(jiān)控使我們對應(yīng)用程序正常運(yùn)行的信心增加了。結(jié)果是為我們的桌面用戶提供了更穩(wěn)定的應(yīng)用程序。舉個(gè)例子:使用這個(gè)新系統(tǒng),我們能夠執(zhí)行Python 2到3的轉(zhuǎn)換,而不用擔(dān)心我們的用戶會(huì)受到負(fù)面影響。
-
服務(wù)器
+關(guān)注
關(guān)注
13文章
9793瀏覽量
87949 -
python
+關(guān)注
關(guān)注
56文章
4827瀏覽量
86711
原文標(biāo)題:Dropbox力薦!我們?nèi)绾螒?yīng)對Python桌面應(yīng)用程序的崩潰
文章出處:【微信號(hào):worldofai,微信公眾號(hào):worldofai】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
labview崩潰了。。。。
【orangepi zero試用體驗(yàn)】orangepi zero多次崩潰
OrCad Capture修改元件參數(shù)后保存軟件崩潰
加載由gcc 11生成的elf文件時(shí),freemaster崩潰了怎么解決?
分析機(jī)構(gòu):2012年光伏市場不會(huì)崩潰
Python和其他語言相較如何?
這樣運(yùn)行Python命令會(huì)給電腦帶來極大的隱患
Python之父:不要對Python 4.0抱有希望,可能不會(huì)有的
為什么在JVM中線程崩潰不會(huì)導(dǎo)致JVM進(jìn)程崩潰呢?
如何在Windows下使用 Supervisor 重新拉起崩潰的Python程序

Python3.10.0的特性介紹

評論