corefx 源码追踪:找到引起 SqlDataReader.ReadAsync 执行延迟的那行代码
最近遇到一个非常诡异的问题,在一个 ASP.NET Core 2.2 项目中,从 SQL Server 数据库查询 100 条数据记录,会出现 16-22s 左右的延迟。延迟出现在执行 SqlDataReader 的 ReadAsync 方法时,在一行一行读取数据时,读取某些行时会出现 2-3s 的延迟。
前两天通过在 corefx 中 System.Data.SqlClient 源码中打点 Console.WriteLine($"{DateTime.Now}") (打点方法见之前的博文),定位到了延迟出现在 SqlDataReader.cs 的 ContinueRetryable 方法下面的代码处。
return completionSource.Task.ContinueWith((retryTask) =>
{
//...
}
发生延迟时 ContinueWith 中的任务会延迟执行,延迟发生在 completionSource.Task 中,completionSource 的类型是 TaskCompletionSource<object> ,值来自 _stateObj._networkPacketTaskSource ,_stateObj 的类型是 TdsParserStateObject 。
接下来进入 TdsParserStateObject.cs 进行追踪,追踪什么地方进行对 TaskCompletionSource 进行了 SetResult 或者 TrySetResult 操作,ContinueWith 就是在 SetResult/TrySetResult 之后执行的。
通过打点发现是在 ReadAsyncCallback<T>(IntPtr key, T packet, UInt32 error) 方法中进行了 TrySetResult 操作:
if ((processFinallyBlock) && (source != null) && (pendingCallbacks < ))
{
if (error == )
{
if (_executionContext != null)
{
ExecutionContext.Run(_executionContext, (state) => source.TrySetResult(null), null);
}
else
{
source.TrySetResult(null);
}
}
//...
}
同时打点时间戳信息显示 ReadAsyncCallback 方法中没有发生延迟,说明延迟发生在调用 ReadAsyncCallback 方法之前。
经过无数次的打点,终于发现延迟发生在 SNIPacket.cs 的 ReadFromStreamAsync(Stream stream, SNIAsyncCallback callback) 方法中的那行 stream.ReadAsync 代码:
stream.ReadAsync(_data, , _capacity, CancellationToken.None).ContinueWith(t =>
{
//...
}
沿着 ReadFromStreamAsync 继续追踪,弄明白 stream.ReadAsync 延迟为什么会造成 source.TrySetResult 延迟?
stream.ReadAsync 的延迟造成了紧随其后的 ContinueWith 延迟执行,ContinueWith 中执行了下面的 callback
callback(this, error ? TdsEnums.SNI_ERROR : TdsEnums.SNI_SUCCESS);
callback 的值是通过 ReadFromStreamAsync 的方法参数传递进来的,传参操作发生在 SNITcpHandle.cs 中的 ReceiveAsync(ref SNIPacket packet) 方法中
public override uint ReceiveAsync(ref SNIPacket packet)
{
packet = new SNIPacket(_bufferSize);
try
{
packet.ReadFromStreamAsync(_stream, _receiveCallback);
return TdsEnums.SNI_SUCCESS_IO_PENDING;
}
//...
}
继续追踪
SNITcpHandle.ReceiveAsync()
-> SNIProxy.ReadAsync()
-> TdsParserStateObjectManaged.ReadAsync()
-> TdsParserStateObject.ReadSni()
来到了 TdsParserStateObject.cs 的 ReadSni() 方法
handle = SessionHandle;
if (handle != null)
{
IncrementPendingCallbacks(); readPacket = ReadAsync(out error, ref handle); if (!(TdsEnums.SNI_SUCCESS == error || TdsEnums.SNI_SUCCESS_IO_PENDING == error))
{
DecrementPendingCallbacks(false); // Failure - we won't receive callback!
}
}
handle 的值是来自 SessionHandle ,继续追,来到了 TdsParserStateObjectManaged.cs 的 CreatePhysicalSNIHandle 方法
internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[] spnBuffer, bool flushCache, bool async, bool parallel, bool isIntegratedSecurity)
{
_sessionHandle = SNIProxy.Singleton.CreateConnectionHandle(this, serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, flushCache, async, parallel, isIntegratedSecurity);
if (_sessionHandle == null)
{
_parser.ProcessSNIError(this);
}
else if (async)
{
// Create call backs and allocate to the session handle
SNIAsyncCallback ReceiveAsyncCallbackDispatcher = new SNIAsyncCallback(ReadAsyncCallback);
SNIAsyncCallback SendAsyncCallbackDispatcher = new SNIAsyncCallback(WriteAsyncCallback);
_sessionHandle.SetAsyncCallbacks(ReceiveAsyncCallbackDispatcher, SendAsyncCallbackDispatcher);
}
}
通过上面的 ReadAsyncCallback 追踪到了前面提到的进行 TrySetResult 操作的 ReadAsyncCallback 方法。
internal void ReadAsyncCallback(SNIPacket packet, UInt32 error) => ReadAsyncCallback(IntPtr.Zero, packet, error);
通过源码追踪也确认了延迟的确是来自 SNIPacket.ReadFromStreamAsync 中的 stream.ReadAsync 操作,而 stream 的值是一个 NetworkStream 的实例(详见 SNITcpHandle 的构造函数)。
为什么读取 NetworkStream 的过程中发生了延迟?这是接下来需要进一步排查的问题。
corefx 源码追踪:找到引起 SqlDataReader.ReadAsync 执行延迟的那行代码的更多相关文章
- 源码追踪,解决Could not locate executable null\bin\winutils.exe in the Hadoop binaries.问题
在windows系统本地运行spark的wordcount程序,会出现一个异常,但不影响现有程序运行. >>提君博客原创 http://www.cnblogs.com/tijun/ & ...
- Saiku登录源码追踪.(十三)
Saiku登录源码追踪呀~ >>首先我们需要debug跟踪saiku登录执行的源码信息 saiku源码的debug方式上一篇博客已有说明,这里简单介绍一下 在saiku启动脚本中添加如下命 ...
- Spring Boot 注解之ObjectProvider源码追踪
最近依旧在学习阅读Spring Boot的源代码,在此过程中涉及到很多在日常项目中比较少见的功能特性,对此深入研究一下,也挺有意思,这也是阅读源码的魅力之一.这里写成文章,分享给大家. 自动配置中的O ...
- [源码解析]Oozie来龙去脉之内部执行
[源码解析]Oozie来龙去脉之内部执行 目录 [源码解析]Oozie来龙去脉之内部执行 0x00 摘要 0x01 Oozie阶段 1.1 ActionStartXCommand 1.2 HiveAc ...
- [实践] Android5.1.1源码 - 让某个APP以解释执行模式运行
[实践] Android5.1.1源码 - 让某个APP以解释执行模式运行 作者:寻禹@阿里聚安全 前言 本文的实践修改了Android5.1.1的源码. 本文只简单的讲了一下原理.在“实践”一节 ...
- Spring AOP 源码分析 - 拦截器链的执行过程
1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...
- corefx 源码学习:NetworkStream.ReadAsync 是如何从 Socket 异步读取数据的
最近遇到 NetworkStream.ReadAsync 在 Linux 上高并发读取数据的问题,由此激发了阅读 corefx 中 System.Net.Sockets 实现源码(基于 corefx ...
- Windows 上编译 corefx 源码生成 Linux 上可用的 System.Data.SqlClient.dll
最近在排查一个奇怪的 EF Core 查询速度慢的问题,需要在 corefx 2.2.3 的 System.Data.SqlClient 源码中打点. github 上签出 corefx 的源代码,运 ...
- C# Monitor.Wait() 源码追踪 (转载)
source: 释放对象上的锁并阻止当前线程,直到它重新获取该锁. 如果已用指定的超时时间间隔,则线程进入就绪队列. 可以在等待之前退出同步上下文的同步域,随后重新获取该域. [SecuritySaf ...
随机推荐
- nc工具使用
0x00 功能说明: 功能强大的网络工具,在网络工具中有“瑞士军刀”美誉,其有Windows和Linux的版本.因为它短小精悍,功能实用,被设计为一个简单.可靠的网络工具,可通过TCP或UDP协议传输 ...
- Bootstrap模态框钩子事件
事件类型 描述show.bs.modal show 方法调用之后立即触发该事件.如果是通过点击某个作为触发器的元素,则此元素可以通过事件的 relatedTarget 属性进行访问.shown.b ...
- monkeyrunner环境配置
1.安装JDK.android SDK.Python (安装完成后,配置环境变量:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;%ANDROID_HOME%\platfor ...
- innodb表碎片处理
本次测试环境是 mysql 5.7.23,表空间为每个表单独表空间 mysql> sHOW VARIABLES LIKE 'innodb_file_per_tabl%'; +---------- ...
- prototype、proto、constructor 之间的关系
- Python 八皇后问题
八皇后问题描述:在一个8✖️8的棋盘上,任意摆放8个棋子,要求任意两个棋子不能在同一行,同一列,同一斜线上,问有多少种解法. 规则分析: 任意两个棋子不能在同一行比较好办,设置一个队列,队列里的每个元 ...
- [转] 如何写好.babelrc?Babel的presets和plugins配置解析
什么是Babel The compiler for writing next generation JavaScript. 官网是这么说的,翻译一下就是下一代JavaScript 语法的编译器. 作为 ...
- 【SQL】 MySql与SqlServer差异比较(MySql踩坑全集)
本文主要记录将数据库从SqlServer移植到MySql的过程中,发现的各种坑爹问题.以SqlServer为主,记录MySql的差异性. 一.IF语句 首先MySql中的的IF语法不同. IF Con ...
- tensorflow 传入值-【老鱼学tensorflow】
上个文章中讲述了tensorflow中如何定义变量以及如何读取变量的方式,本节主要讲述关于传入值. 变量主要用于在tensorflow系统中经常会被改变的值,而对于传入值,它只是当tensorflow ...
- django模板中获取域名地址
获取域名: {{ request.get_host }} 获取路径:{{ request.path }} 获取协议 {{ request.scheme }}