內(nei)(nei)存(cun)泄漏是(shi)一種常見的(de)(de)(de)問(wen)題(ti),它會導(dao)致程序的(de)(de)(de)內(nei)(nei)存(cun)占用(yong)逐漸(jian)增加,最(zui)終導(dao)致系統資源耗盡(jin)或程序崩潰。AddressSanitizer (ASan) 和 Valgrind 是(shi)很好的(de)(de)(de)內(nei)(nei)存(cun)檢(jian)測工具,TDengine 的(de)(de)(de) CI 過程就使(shi)用(yong)了 ASan 。不(bu)過這次內(nei)(nei)存(cun)泄漏問(wen)題(ti)發生在 Windows 下,我們 CI 暫時還(huan)沒有(you)覆(fu)蓋到(dao),因此 TDengine 研(yan)發選(xuan)擇使(shi)用(yong) Windbg 來(lai)解決問(wen)題(ti)。結果證明,在 Windows 下,使(shi)用(yong) Windbg 也是(shi)一個不(bu)錯的(de)(de)(de)選(xuan)擇。
內存泄漏的常用檢測方法
內存泄漏通常會發生在以下情況下:
- 程序未正確釋放已分配的內存
- 程序中存在循環引用,導致垃圾收集器無法回收內存
- 程序中存在內存泄漏的第三方庫或組件
內存泄漏的檢測方法主要包括以下幾種:
- 靜態代碼分析工具:未釋放的指針或內存分配錯誤等問題,不能檢測在程序運行時動態分配內存的情況。
- 動態分析工具:可以使用內存分配和釋放跟蹤器來跟蹤程序中的內存分配和釋放操作,并檢測是否存在內存泄漏的情況。然而,使用某些工具(如Valgrind)可能會對程序的性能產生一定的影響。
- 調試器:WinDbg 和 GDB。
優缺點:
- 靜態代碼分析工具可以在早期發現問題,但是它們不能檢測程序運行時動態分配內存的情況。
- 動態分析工具可以在程序運行時檢測問題,但是它們可能會影響程序性能,并且在檢測大型應用程序時可能需要大量的時間和資源。不過在資源充足的測試環境中跑的話,就都不是問題了,比如 ASan 就幫我們發現過不少問題。
- 調試器可以在程序運行時檢測問題,并提供強大的分析工具。
實踐分析
基本原理
使用(yong) Windbg 定(ding)位(wei)內(nei)存(cun)泄露(lu)(lu),依賴(lai) glags 組件記錄(lu)(lu)程序在運行期間所有(you)申請和釋(shi)放的內(nei)存(cun),同時(shi)(shi)記錄(lu)(lu)的還有(you)申請內(nei)存(cun)時(shi)(shi)的調(diao)用(yong)棧信息。這樣在程序運行期間,使用(yong) umdh 組件進(jin)行兩次快照(zhao)記錄(lu)(lu),通(tong)過比較(jiao)兩次快照(zhao)信息的差異,就可以(yi)發現兩次快照(zhao)間隔時(shi)(shi)間段中申請卻并未釋(shi)放的內(nei)存(cun)申請信息。如果(guo)(guo)(guo)有(you)內(nei)存(cun)泄露(lu)(lu),diff 結(jie)果(guo)(guo)(guo)最前邊一般就是泄漏(lou)點的調(diao)用(yong)棧信息。當(dang)然,兩次快照(zhao)期間,要盡量觸發內(nei)存(cun)泄露(lu)(lu),才能更準確的定(ding)位(wei)。diff 結(jie)果(guo)(guo)(guo)中還會有(you)少量正常的申請沒來得及釋(shi)放的調(diao)用(yong)信息,不(bu)過 diff 結(jie)果(guo)(guo)(guo)中能看到調(diao)用(yong)次數,比較(jiao)容易甄別(bie)。
問題介紹
taosdump 在 windows 導(dao)入數據出(chu)錯:
build and install latest TDengine 3.0 branch on Windows
use "taosBenchmark -I stmt -y" to create a lot of tables and data (10000 * 10000).
use "taosdump -D test -o outputFile" to dump out
use "taos -s 'drop database test'" to drop database
use "taosdump -i inputFile" to dump in.
錯(cuo)誤日志:taosd “tsem_init failed, errno: 28”
Taosdump: dumpInAvroDataImpl() LN7039 taos_stmt_execute() failed! reason: Out of Memory, timestamp: 1500000009256
定位過程
配置 gflags
gflags 工具應該(gai)位于路(lu)徑(jing):C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags,如果沒有的(de)話,可以直接前往 Microsoft 的(de)官方網(wang)站下(xia)載安裝:
安裝完成后(hou),在命令(ling)行(xing)執行(xing) gflags.exe /i your_application.exe 可設置跟(gen)蹤目標(biao),同時(shi)可以設置相關參數。雙擊運行(xing)也是可以的,Image File 對應(ying) /i 參數,選擇啟動程序 your_application.exe 后(hou)先按 tab 鍵(jian),然后(hou)選擇其(qi)他配置。

定位步驟
- 啟動 your_application.exe(我要調試的是 taosdump.exe,所以下邊是 taosdump.exe)
“C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags” -i taosdump.exe +ust
- 拷貝 pdb 文件到 mysymbols 目錄,pdb 文件存儲了編譯后的程序的調試信息,和可執行程序一起生成,可以在應用程序生成目錄中找到。
- Set pdb 目錄
set _NT_SYMBOL_PATH=c:\mysymbols;srv*c:\mycache*//msdl.microsoft.com/download/symbols
- 生成第一次內存快照
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\umdh" -pn:taosdump.exe -f:C:\xstest\umdhlog\taosdump11.log
- 生成第二次內存快照
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\umdh" -pn:taosdump.exe -f:C:\xstest\umdhlog\taosdump12.log
- 生成快照比較結果(umdh)
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\umdh" C:\xstest\umdhlog\taosdump11.log C:\xstest\umdhlog\taosdump12.log -f:C:\xstest\umdhlog\taosdumpdiff11_12.log
分析與解決
結果文件
因為 taosdump 程序啟動(dong)后直(zhi)至退出都在(zai)做大量的業務工作,內存泄露很容易發(fa)生在(zai)兩次快照(zhao)期間。 988040 – 6ecf0 表示”申請次數 – 釋放次數”, 很明顯(xian)發(fa)生了(le)內存泄露,泄漏(lou)點在(zai) buildRequest 函數的 sem_init 這里。
+ 919350 ( 988040 - 6ecf0) 201b0 allocs BackTrace9CB6973F
+ 1ea5c ( 201b0 - 1754) BackTrace9CB6973F allocations
ntdll!RtlpAllocateHeapInternal+948D5
taos!heap_alloc_dbg_internal+1F6 (minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp, 359)
taos!heap_alloc_dbg+4D (minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp, 450)
taos!_calloc_dbg+6C (minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp, 518)
taos!calloc+2E (minkernel\crts\ucrt\src\appcrt\heap\calloc.cpp, 30)
taos!sem_init+5D (C:\workroom\TDengine\contrib\pthread\sem_init.c, 109)
taos!buildRequest+209 (C:\workroom\TDengine\source\client\src\clientImpl.c, 192)
taos!stmtCreateRequest+73 (C:\workroom\TDengine\source\client\src\clientStmt.c, 15)
taos!stmtSetTbName+115 (C:\workroom\TDengine\source\client\src\clientStmt.c, 588)
taos!taos_stmt_set_tbname+7F (C:\workroom\TDengine\source\client\src\clientMain.c, 1350)
taosdump!dumpInAvroDataImpl+E25 (C:\workroom\TDengine\tools\taos-tools\src\taosdump.c, 6260)
taosdump!dumpInOneAvroFile+3D2 (C:\workroom\TDengine\tools\taos-tools\src\taosdump.c, 7229)
taosdump!dumpInAvroWorkThreadFp+20B (C:\workroom\TDengine\tools\taos-tools\src\taosdump.c, 7306)
taosdump!ptw32_threadStart+CD (C:\workroom\TDengine\contrib\pthread\ptw32_threadStart.c, 233)
taosdump!thread_start<unsigned int (__cdecl*)(void *),1>+9C (minkernel\crts\ucrt\src\appcrt\startup\thread.cpp, 97)
KERNEL32!BaseThreadInitThunk+10
ntdll!RtlUserThreadStart+2B
泄漏點修改
接下(xia)來查看代碼(ma)并修改,C 語言對內存(cun)的(de)使用自由(you)度很高(gao),因此(ci)也比較麻煩。可以看到有(you)些路徑(jing)遺(yi)漏(lou)了 tsem_destory 的(de)調用。

更加詳細的代碼方案請見
總結
工欲善其(qi)事(shi)必(bi)先(xian)利其(qi)器,掌握更多(duo)的工具(ju)和手段,在解決(jue)問(wen)題時才(cai)能比較(jiao)從(cong)容,Windbg 定位內存泄漏的方式非常簡單,但是(shi)很有效。不過需要(yao)注意,它(ta)依賴 pdb 文(wen)件,因此,發布應(ying)用(yong)程(cheng)序時要(yao)記得保(bao)留 pdb 文(wen)件。pdb 文(wen)件包含了程(cheng)序的符號信息,能夠幫助我們在調(diao)試過程(cheng)中準確定位問(wen)題所在。
另外,從出問題的(de)代碼可(ke)(ke)(ke)以看出,這塊內存的(de)管理方式還是比較容易(yi)出錯(cuo),RAII 機制能較好的(de)避免資(zi)源(yuan)泄露,C 語言中也可(ke)(ke)(ke)以通過模擬 RAII 來達到(dao)類似(si)的(de)效果,雖(sui)然沒有 C++ 那么流暢,也許以后可(ke)(ke)(ke)以考慮優(you)化(hua)一下。
RAII(Resource Acquisition Is Initialization)機制是一種重要的資源管理方式,它將資源的獲取和對象的生命周期關聯起來。通過在對象的構造函數中獲取資源,在析構函數中釋放資源,我們可以確保資源的正確管理,防止資源泄漏和內存泄漏等問題。RAII 機制在 C++ 等編程語言中得到廣泛應用,是一種有效的資源管理方式。


























