首页 > 数据库 >聊一聊坑人的 C# MySql.Data SDK

聊一聊坑人的 C# MySql.Data SDK

时间:2024-12-20 12:43:08浏览次数:3  
标签:src 00007ffc C# System Threading 聊一聊 MySql Data

一:背景

1. 讲故事

为什么说这东西比较坑人呢?是因为最近一个月接到了两个dump,都反应程序卡死无响应,最后分析下来是因为线程饥饿导致,那什么原因导致的线程饥饿呢?进一步分析发现罪魁祸首是 MySql.Data,这就让人无语了,并且反馈都是升级了MySql.Data驱动引发,接下来我们简单聊一下。

二: MySql.Data 到底怎么了

1. 祸根溯源

早期版本的 MySql.Data 访问数据库都是以同步的方式进行,比如:ExecuteReader 而不是 ExecuteReaderAsync,随着项目的升级改造需要提升MySql.Data的版本, MySql为了向前兼容保留了同步方法,下面引用最新的 MySql.Data 9.1.0 截图和参考代码如下:


// MySql.Data, Version=9.1.0.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d
// MySql.Data.MySqlClient.MySqlConnection
using System.Threading;

public override void Open()
{
	OpenAsync(execAsync: false, CancellationToken.None).GetAwaiter().GetResult();
}


// MySql.Data, Version=9.1.0.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d
// MySql.Data.MySqlClient.MySqlCommand
using System.Data;
using System.Threading;

public new MySqlDataReader ExecuteReader()
{
	return ExecuteReaderAsync(CommandBehavior.Default, execAsync: false, CancellationToken.None).GetAwaiter().GetResult();
}

public override object ExecuteScalar()
{
	return ExecuteScalarAsync(execAsync: false, CancellationToken.None).GetAwaiter().GetResult();
}

仔细看上面这段代码,不觉让人吸了一口凉气,所谓的同步方式竟然是用异步方法简单包装 而来的,这种异步混用同步的方式很容易导致线程饥饿,即线程池中已无可用线程来唤醒 GetResult() 下的 Event 事件,这个我准备后面用一篇文章详细来聊一下线程饥饿,这里用C#内功修炼训练营中的一张图来演示下.NET8 中异步在线程池中的走法。

2. 线程饥饿的现场

问题方法给大家列出来的,接下来用 windbg 看下dump中的故障现场吧。

  1. 某考试系统的故障

看故障现象比较简单,使用 !tp!tpq 即可,输出如下:


0:000> !tp
Using the Portable thread pool.

CPU utilization:  1%
Workers Total:    268
Workers Running:  268
Workers Idle:     0
Worker Min Limit: 4
Worker Max Limit: 32767

0:000> !sos tpq
global work item queue________________________________
0x000002410E750218 Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT<Microsoft.AspNetCore.Hosting.HostingApplication+Context>
0x000002410E7505A0 Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT<Microsoft.AspNetCore.Hosting.HostingApplication+Context>
0x000002410E750928 Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT<Microsoft.AspNetCore.Hosting.HostingApplication+Context>
...
local per thread work items_____________________________________
0x0000024114903310 System.Runtime.CompilerServices.AsyncTaskMethodBuilder<MySql.Data.MySqlClient.MySqlPool>+AsyncStateMachineBox<MySql.Data.MySqlClient.MySqlPoolManager+<GetPoolAsync>d__23>

从卦中可以看到线程池中目前有268个线程,此时都处于运行状态,并且线程池的全局队列积压了1000+的任务没有处理,接下来使用 ~*e !clrstack 观察每个线程都在做什么。


0:287> !clrstack
OS Thread Id: 0x39ec (287)
        Child SP               IP Call Site
000000858C5FD1B8 00007ffc95ca04e4 [HelperMethodFrame_1OBJ: 000000858c5fd1b8] System.Threading.Monitor.ObjWait(Int32, System.Object)
000000858C5FD2E0 00007ffc087cccc9 System.Threading.Monitor.Wait(System.Object, Int32) [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Monitor.CoreCLR.cs @ 156]
000000858C5FD310 00007ffc087cd027 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs @ 561]
000000858C5FD3D0 00007ffc087cc4f2 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3072]
000000858C5FD440 00007ffc087cc099 System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3007]
000000858C5FD4C0 00007ffc08796cc6 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task, System.Threading.Tasks.ConfigureAwaitOptions) [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs @ 111]
000000858C5FD500 00007ffc086ffbc4 xxxx.UpdateAnswerUrl(System.String, Int32, System.Collections.Generic.Dictionary`2<System.String,System.String>)

发现这些线程都卡在 xxxx.UpdateAnswerUrl 方法上,那到底卡在方法的何处呢?可以用 !U /d 00007ffc086ffbc4 观察方法的反汇编代码,看看这个00007ffc086ffbc4停留在何处?输出如下:

0:000> !U /d 00007ffc086ffbc4
Normal JIT generated code
xxx.UpdateAnswerUrl(System.String, Int32, System.Collections.Generic.Dictionary`2<System.String,System.String>)
...
00007ffc`086ffb79 ff15114bb9fe    call    qword ptr [00007ffc`07294690] (System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[[MySql.Data.MySqlClient.MySqlCommand+<ExecuteScalarAsync>d__117, MySql.Data]](<ExecuteScalarAsync>d__117 ByRef), mdToken: 000000000600646B)
00007ffc`086ffb7f 488b8c2468010000 mov     rcx,qword ptr [rsp+168h]
00007ffc`086ffb87 4885c9          test    rcx,rcx
00007ffc`086ffb8a 0f84890c0000    je      00007ffc`08700819
00007ffc`086ffb90 3809            cmp     byte ptr [rcx],cl
00007ffc`086ffb92 48898c2498010000 mov     qword ptr [rsp+198h],rcx
00007ffc`086ffb9a 488d8c2498010000 lea     rcx,[rsp+198h]
00007ffc`086ffba2 48baf02b5006fc7f0000 mov rdx,7FFC06502BF0h (MT: System.Runtime.CompilerServices.TaskAwaiter`1[[System.Object, System.Private.CoreLib]])
00007ffc`086ffbac ff158e7cdefd    call    qword ptr [00007ffc`064e7840] (System.Runtime.CompilerServices.TaskAwaiter`1[[System.__Canon, System.Private.CoreLib]].GetResult(), mdToken: 00000000060065F0)
00007ffc`086ffbb2 48898424e8000000 mov     qword ptr [rsp+0E8h],rax
00007ffc`086ffbba eb0d            jmp     00007ffc`086ffbc9
00007ffc`086ffbbc 33d2            xor     edx,edx
00007ffc`086ffbbe ff1544d4bffd    call    qword ptr [00007ffc`062fd008] (System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task, System.Threading.Tasks.ConfigureAwaitOptions), mdToken: 00000000060065E4)
>>> 00007ffc`086ffbc4 e960ffffff      jmp     00007ffc`086ffb29

从汇编代码中可以观测它是在获取 ExecuteScalarAsync 方法的 Result 结果,有了这个信息就可以翻源代码了,截图如下:

最终就发现了ExecuteScalar下面的荒唐一幕。。。

  1. 某跟踪埋点系统的故障

埋点系统也是一样的问题,使用 !tp 观察到线程池有 602 个线程都处于运行状态,输出如下:


0:000> !tp
Using the Portable thread pool.

CPU utilization:  11%
Workers Total:    602
Workers Running:  602
Workers Idle:     0
Worker Min Limit: 32
Worker Max Limit: 32767

然后通过 ~*e !clrstack 观察发现线程都处于 Open() 方法中,输出如下:


OS Thread Id: 0x1a9d4 (23)
        Child SP               IP Call Site
0000007AD4DBE228 00007ff9feb70b24 [HelperMethodFrame_1OBJ: 0000007ad4dbe228] System.Threading.Monitor.ObjWait(Int32, System.Object)
0000007AD4DBE350 00007ff9b655d55e System.Threading.Monitor.Wait(System.Object, Int32) [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Monitor.CoreCLR.cs @ 156]
0000007AD4DBE380 00007ff9b656860e System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs @ 561]
0000007AD4DBE420 00007ff9b6581729 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3072]
0000007AD4DBE4A0 00007ff9b6581516 System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3007]
0000007AD4DBE520 00007ff959e9e9f4 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task, System.Threading.Tasks.ConfigureAwaitOptions) [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs @ 111]
0000007AD4DBE560 00007ff95752e95b MySql.Data.MySqlClient.MySqlConnection.Open()
...

可恶的是 Open() 方法内部也是用 异步转同步 实现的,真的无语了。

3. 解决方法

要想解决这个问题,大概两种方法吧。

  1. 使用纯异步写法,这也是高版本 MySql.Data 极力推荐的,不然就给你埋坑。。。
  2. 退回到低版本的 MySql.Data,继续使用真正的同步版写法。

三:总结

挺意外的是 MySql.Data 项目在 github:https://github.com/mysql/mysql-connector-net 上没开 issue 栏。

这就无法让社区开发者介入,真的很奇葩,只能在这里给大家做个预警吧。
图片名称

标签:src,00007ffc,C#,System,Threading,聊一聊,MySql,Data
From: https://www.cnblogs.com/huangxincheng/p/18619048

相关文章

  • MapperScannerConfigurer 配置出错造成没有读取 db.properties 文件中的数据库连接参
    MyBatis-Spring实现MyBatis和Spring框架集成。问题现象在配置中碰到不能加载MySQLJDBC驱动的问题,报错如下(部分截取):09:59:06.595[C3P0PooledConnectionPoolManager[identityToken->z8kfltb71qnbl7e1cco0kz|23833818]-HelperThread-#2]WARNc.m.v2.c3p0.DriverManager......
  • 【产品经理修炼之道】-如何从业务价值角度去做CRM规划?
    编辑导读:如何做CRM规划?大部分人会用经典的水平的CRM规划方法,按战略规划、运营管理和系统落地三个维度,从客户、营销、服务和业务四条主线进行CRM规划。本文作者则介绍如何从业务价值角度去做CRM规划,希望对你有帮助。笔者在写本篇文章之前声明一下:写案例浅析并不是评判过去的项......
  • CNCF云原生生态版图-分类指南(五)- 应用定义和开发
    CNCF云原生生态版图-分类指南(五)-应用定义和开发CNCF云原生生态版图-分类指南五、应用定义和开发(AppDefinitionandDevelopment)(一)应用定义和镜像构建(ApplicationDefinition&ImageBuild)1.是什么?2.解决什么问题?3.如何解决问题?4.使用的技术5.项目和产品整体介绍......
  • (自用)[USACO2023-JAN-Bronze] T1 LEADERS 题解
    题目描述农夫约翰有\(N(2≤N≤10^5)\)头奶牛,每一头奶牛的品种是更赛牛G或荷斯坦牛H中的一种。每一头奶牛都有一个名单,第\(i\)头奶牛的名单上记录了从第\(i\)头奶牛(即自己)到第\(E_i(i≤E_i≤N)\)头奶牛的连续所有奶牛。每一种奶牛都有且仅有一位“领导者”,对于某一头牛......
  • RK3566触摸驱动产品实战 配置设备树 I2C驱动框架(附源码)
    引言:相信大家在学完相关驱动框架很少真正的运用在实际的产品案例中,对设备树以及驱动框架还是非常的陌生,其次就是在编写相关驱动还有完成项目任务时的一些思路的引导,这些都是需要补足的,接下来,我们将之前理论知识与此文章内容结合,真正的将理论运用在实际案例中,形成自己的知识框......
  • 5.3.2 Xenomai3:使用xeno-config获取编译和链接参数
    点击查看系列文章=》 InterruptPipeline系列文章大纲-CSDN博客原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!5.3.2Xenomai3:使用xeno-config获取编译和链接参数xeno-config是一个辅助脚本,用于为使用Xenomai库的应用程序提供正确的编译和链接标志。通过......
  • ChCore-Lab4
    lab4:多核调度与IPC结合IPADSOSLabManual一起阅读,风味更佳!多核启动支持:使ChCore通过树莓派厂商所提供的固件唤醒多核执行多核调度:使ChCore实现在多核上进行round-robin调度。IPC:使ChCore支持进程间通信IPC调优:为ChCore的IPC针对测试的特点进行调优。踩坑1:请记得......
  • 07 C++
    C++1.C++中类成员的访问权限访问权限总结表访问权限当前类内部派生类内部类外部public✔️✔️✔️protected✔️✔️❌private✔️❌❌继承与访问权限基类成员权限公有继承保护继承私有继承public仍为public变为protected变为private......
  • 31.在 Vue 3 中使用 OpenLayers 加载 CSV 数据,显示各个点
    目录一、前言二、项目准备1.安装Vue32.安装OpenLayers和D3三、CSV数据格式四、实现步骤1.配置Vue3+OpenLayers项目结构Map.vue文件2.解析代码dataSource和map变量showPoints方法featureStyle方法initMap方法3.启动开发服务器五、总结......
  • CopilotKit详解:用GPT-4快速集成AI,实现精准参数归纳与程序执行
    言简意赅的讲解CopilotKit解决的痛点使用AI提升项目体验:深入了解CopilotKit在现代软件开发中,融入AI的能力已经成为许多项目的亮点。然而,如何快速且优雅地实现这种能力,仍然困扰着许多开发者。让AI可以分析并帮助用户操作。今天,我要向大家推荐一个强大的工具:CopilotKit......