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 ...
随机推荐
- 练习:javascript-setInterval动画运动平移,定时器动画练习
常见问题:定时器加速问题,每次点击会启动一个定时器,解决先清除定时器 <!DOCTYPE html> <html lang="en"> <head&g ...
- HTTP中application/x-www-form-urlencoded字符说明
一.概述 在学习ajax的时候,如果用post请求,需要设置如下代码. ajax.setRequestHeader("content-type","application ...
- Ansible之Playbook详解、案例
什么是playbook playbooks是一个不同于使用Ansible命令行执行方式的模式,其功能更强大灵活.简单来说,playbook是一个非常简单的配置管理和多主机部署系统,不同于任何已经存在的 ...
- [Linux容器]当我们谈容器的时候,我们在谈什么
Docker在当下很火,那么,当我们谈Docker,谈容器的时候,我们在谈什么? 或者说,你对Docker,对容器了解吗?容器,到底是怎么一回事儿? 这篇文章着重来讲一下Linux容器,为什么强调Li ...
- LNMP环境搭建:Nginx安装、测试与域名配置
Nginx作为一款优秀的Web Server软件同时也是一款优秀的负载均衡或前端反向代理.缓存服务软件 2.编译安装Nginx (1)安装Nginx依赖函数库pcre pcre为“perl兼容正则表达 ...
- JsRender练习总结
1.假设的数据,基础部分. <div id="list1"></div> <script type="text/tmp" id=& ...
- SQL入门(3):定义约束/断言assertion/触发器trigger
本文介绍数据库的完整性 完整性控制程序: 指定规则,检查规则 (规则就是约束条件) 动态约束 intergrity constraint::=(O,P,A,R) O : 数据集合, 约束的对象 ?: ...
- java中对象的创建过程
public class Test1 { public static void main(String[] args) { new B(); System.out.println("---- ...
- Android Studio打开项目,停在gradle的解决方案
之所以停留,是因为打开项目时,Android Studio解析项目使用的gradle,并下载对应版本的gradle,但是由于下载gradle的服务器在国外,这个下载速度非常不稳定,从而导致停留在这一步 ...
- AI数据分析(二)
NumPy库 NumPy数组对象 NumPy数据类型 数组的索引 数组的切片 数组的组合 数组的分割 数组的属性 NumPy数组对象 NumPy数据类型 #numpy数据类型 print " ...