<article class=“article fmt article-content”><p>内存透露是一种常见的问题,它会导致程序的内存占用逐步减少,最终导致系统资源耗尽或程序解体。AddressSanitizer (ASan) 和 Valgrind 是很好的内存检测工具,TDengine 的 CI 过程就应用了 ASan 。不过这次内存透露问题产生在 Windows 下,咱们 CI 临时还没有笼罩到,因而 TDengine 研发抉择应用 Windbg 来解决问题。后果证实,在 Windows 下,应用 Windbg 也是一个不错的抉择。</p><h2>内存透露的罕用检测办法</h2><p><strong>内存透露通常会产生在以下状况下:</strong></p><ul><li>程序未正确开释已调配的内存</li><li>程序中存在循环援用,导致垃圾收集器无奈回收内存</li><li>程序中存在内存透露的第三方库或组件</li></ul><p><strong>内存透露的检测办法次要包含以下几种:</strong></p><ol><li>动态代码剖析工具:未开释的指针或内存调配谬误等问题,不能检测在程序运行时动静分配内存的状况。</li><li>动态分析工具:能够应用内存调配和开释跟踪器来跟踪程序中的内存调配和开释操作,并检测是否存在内存透露的状况。然而,应用某些工具(如Valgrind)可能会对程序的性能产生肯定的影响。</li><li>调试器:WinDbg 和 GDB。</li></ol><p><strong>优缺点:</strong></p><ul><li>动态代码剖析工具能够在晚期发现问题,然而它们不能检测程序运行时动静分配内存的状况。</li><li>动态分析工具能够在程序运行时检测问题,然而它们可能会影响程序性能,并且在检测大型应用程序时可能须要大量的工夫和资源。不过在资源短缺的测试环境中跑的话,就都不是问题了,比方 ASan 就帮咱们发现过不少问题。</li><li>调试器能够在程序运行时检测问题,并提供弱小的剖析工具。</li></ul><h2>实际剖析</h2><h3>基本原理</h3><p>应用 Windbg 定位内存泄露,依赖 glags 组件记录程序在运行期间所有申请和开释的内存,同时记录的还有申请内存时的调用栈信息。这样在程序运行期间,应用 umdh 组件进行两次快照记录,通过比拟两次快照信息的差别,就能够发现两次快照间隔时间段中申请却并未开释的内存申请信息。如果有内存泄露,diff 后果最前边个别就是透露点的调用栈信息。当然,两次快照期间,要尽量触发内存泄露,能力更精确的定位。diff 后果中还会有大量失常的申请没来得及开释的调用信息,不过 diff 后果中能看到调用次数,比拟容易甄别。</p><h3>问题介绍</h3><p>taosdump 在 windows 导入数据出错:</p><pre><code>build and install latest TDengine 3.0 branch on Windowsuse “taosBenchmark -I stmt -y” to create a lot of tables and data (10000 * 10000).use “taosdump -D test -o outputFile” to dump outuse “taos -s ‘drop database test’” to drop databaseuse “taosdump -i inputFile” to dump in.</code></pre><p>谬误日志:taosd “tsem_init failed, errno: 28”</p><p>Taosdump: dumpInAvroDataImpl() LN7039 taos_stmt_execute() failed! reason: Out of Memory, timestamp: 1500000009256</p><h3>定位过程</h3><h4>配置 gflags</h4><p>gflags 工具应该位于门路:C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags,如果没有的话,能够间接返回 Microsoft 的官方网站下载安装:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/de…</p><p>装置实现后,在命令行执行 gflags.exe /i your_application.exe 可设置跟踪目标,同时能够设置相干参数。双击运行也是能够的,Image File 对应 /i 参数,抉择启动程序 your_application.exe 后先按 tab 键,而后抉择其余配置。</p><p></p><h4>定位步骤</h4><ol><li>启动 your_application.exe(我要调试的是 taosdump.exe,所以下边是 taosdump.exe)</li></ol><p>“C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags” -i taosdump.exe +ust</p><ol start=“2”><li>拷贝 pdb 文件到 mysymbols 目录,pdb 文件存储了编译后的程序的调试信息,和可执行程序一起生成,能够在应用程序生成目录中找到。</li><li><p>Set pdb 目录</p><pre><code>set _NT_SYMBOL_PATH=c:\mysymbols;srvc:\mycachehttps://msdl.microsoft.com/download/symbols</code></pre></li><li><p>生成第一次内存快照</p><pre><code>“C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\umdh” -pn:taosdump.exe -f:C:\xstest\umdhlog\taosdump11.log</code></pre></li><li><p>生成第二次内存快照</p><pre><code>“C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\umdh” -pn:taosdump.exe -f:C:\xstest\umdhlog\taosdump12.log</code></pre></li><li><p>生成快照比拟后果(umdh)</p><pre><code>“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</code></pre></li></ol><h3>剖析与解决</h3><h4>后果文件</h4><p>因为 taosdump 程序启动后直至退出都在做大量的业务工作,内存泄露很容易产生在两次快照期间。 988040 – 6ecf0 示意”申请次数 – 开释次数”, 很显著产生了内存泄露,透露点在 buildRequest 函数的 sem_init 这里。</p><pre><code>+ 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</code></pre><h4>透露点批改</h4><p>接下来查看代码并批改,C 语言对内存的应用自由度很高,因而也比拟麻烦。能够看到有些门路脱漏了 tsem_destory 的调用。</p><p></p><p>更加具体的代码计划请见 https://github.com/taosdata/TDengine/pull/19580</p><h2>总结</h2><p>工欲善其事必先利其器,把握更多的工具和伎俩,在解决问题时能力比拟从容,Windbg 定位内存透露的形式非常简单,然而很无效。不过须要留神,它依赖 pdb 文件,因而,公布应用程序时要记得保留 pdb 文件。pdb 文件蕴含了程序的符号信息,可能帮忙咱们在调试过程中精确定位问题所在。</p><p>另外,从出问题的代码能够看出,这块内存的治理形式还是比拟容易出错,RAII 机制能较好的防止资源泄露,C 语言中也能够通过模仿 RAII 来达到相似的成果,尽管没有 C++ 那么晦涩,兴许当前能够思考优化一下。</p><blockquote>RAII(Resource Acquisition Is Initialization)机制是一种重要的资源管理形式,它将资源的获取和对象的生命周期关联起来。通过在对象的构造函数中获取资源,在析构函数中开释资源,咱们能够确保资源的正确治理,避免资源透露和内存透露等问题。RAII 机制在 C++ 等编程语言中失去广泛应用,是一种无效的资源管理形式。</blockquote></article>