最近遇到一个非常诡异的问题,在一个 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 执行延迟的那行代码的更多相关文章

  1. 源码追踪,解决Could not locate executable null\bin\winutils.exe in the Hadoop binaries.问题

    在windows系统本地运行spark的wordcount程序,会出现一个异常,但不影响现有程序运行. >>提君博客原创  http://www.cnblogs.com/tijun/  & ...

  2. Saiku登录源码追踪.(十三)

    Saiku登录源码追踪呀~ >>首先我们需要debug跟踪saiku登录执行的源码信息 saiku源码的debug方式上一篇博客已有说明,这里简单介绍一下 在saiku启动脚本中添加如下命 ...

  3. Spring Boot 注解之ObjectProvider源码追踪

    最近依旧在学习阅读Spring Boot的源代码,在此过程中涉及到很多在日常项目中比较少见的功能特性,对此深入研究一下,也挺有意思,这也是阅读源码的魅力之一.这里写成文章,分享给大家. 自动配置中的O ...

  4. [源码解析]Oozie来龙去脉之内部执行

    [源码解析]Oozie来龙去脉之内部执行 目录 [源码解析]Oozie来龙去脉之内部执行 0x00 摘要 0x01 Oozie阶段 1.1 ActionStartXCommand 1.2 HiveAc ...

  5. [实践] Android5.1.1源码 - 让某个APP以解释执行模式运行

    [实践] Android5.1.1源码 - 让某个APP以解释执行模式运行   作者:寻禹@阿里聚安全 前言 本文的实践修改了Android5.1.1的源码. 本文只简单的讲了一下原理.在“实践”一节 ...

  6. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  7. corefx 源码学习:NetworkStream.ReadAsync 是如何从 Socket 异步读取数据的

    最近遇到 NetworkStream.ReadAsync 在 Linux 上高并发读取数据的问题,由此激发了阅读 corefx 中 System.Net.Sockets 实现源码(基于 corefx ...

  8. Windows 上编译 corefx 源码生成 Linux 上可用的 System.Data.SqlClient.dll

    最近在排查一个奇怪的 EF Core 查询速度慢的问题,需要在 corefx 2.2.3 的 System.Data.SqlClient 源码中打点. github 上签出 corefx 的源代码,运 ...

  9. C# Monitor.Wait() 源码追踪 (转载)

    source: 释放对象上的锁并阻止当前线程,直到它重新获取该锁. 如果已用指定的超时时间间隔,则线程进入就绪队列. 可以在等待之前退出同步上下文的同步域,随后重新获取该域. [SecuritySaf ...

随机推荐

  1. github及git使用

    一.github及git Github用于托管和协作项目的网站,git是通常与github一起使用的版本控制系统.Github上有上百万个开源项目,在使用git的时候每个开发人员都在他们的计算机上有一 ...

  2. 移动端控制视频点击播放点击下一个视频时自动停止播放&监听滑动溢出屏幕高度时停止播放

    直接上代码js部分: <script type="text/javascript"> var go;//记录video播放器位置 var video=document. ...

  3. mycat 测试主从读写分离

    下载解压及创建用户组和目录属性 下载地址:1.https://github.com/MyCATApache/Mycat-download.2.http://dl.mycat.io/ wget http ...

  4. 51nod--1174 区间中最大的数 (RMQ)

    题目: 1174 区间中最大的数 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 给出一个有N个数的序列,编号0 - N - 1.进行Q次查询,查询编号i至j ...

  5. JsRender练习总结

    1.假设的数据,基础部分. <div id="list1"></div> <script type="text/tmp" id=& ...

  6. yapi部署

    官方提供了两种安装方式,由于环境或者权限问题可能会遇到不少麻烦 最简单的安装方式: 第一种方式 npm install -g yapi-cli --registry https://registry. ...

  7. 微信H5支付坑一--手续费未结算

    简单随笔小记: 场景:在微信H5支付的过程中,无论怎么支付完成,在微信商户后台查询手续费依然未扣除,当时手续费账户月为5元. 解决方法:起初无论怎么测试都不知道代码到底问题出在哪里了,想一下手续费账户 ...

  8. vue-创建新项目

    1.安装node,下载地址:https://nodejs.org/en/download/ 2.安装完成后打开cmd,输入node-v,出现版本信息说明安装成功 3.输入npm install -g ...

  9. [DIV+CSS] set the screen capture Part 1 (div截取屏幕)

    使用下面的代码来获取屏幕.用DIV加CSS 来控制. 使用mousemove来获取移动的时候DIV的变化, 效果图如下: 使用5个DIV来组成实现截图目的第一部分,现在只是实现了选择的第一部分. HT ...

  10. select2 api参数的文档

    具体参数可以参考一下: 参数 类型 描述 Width 字符串 控制 宽度 样式属性的Select2容器div minimumInputLength int 最小数量的字符 maximumInputLe ...