最近遇到一个非常诡异的问题,在一个 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. centos添加开放端口

    Centos 开放 80 端口 firewall-cmd --zone=public --add-port=80/tcp --permanent 重启防火墙 firewall-cmd --reload ...

  2. 有了GPRS为什么还要LoRa和NB-IoT?【转】

    转自:https://blog.csdn.net/i_am_Banmei2/article/details/81869724 与其说是GPRS和NB-IoT的比较,不如说是传统网络与新兴网络的比较,我 ...

  3. windows每天备份文件的bat脚本【原创】

    备份昨天文件的脚本 @echo off set yy=%DATE:~,% set mm=%DATE:~,% set ,% ::前一天的日期,格式化输出 ,date)>vbs.vbs for /f ...

  4. Lua中的表达式

    [算术操作符] Lua支持常规的算术操作符有:”+”(加法),”-“(减法),”*”(乘法),”/”(除法),”^”(指数),”%”(取模),一元的”-“(负号).所有的这些操作符都用于实数.例如:x ...

  5. django 实战篇之模板层

    模板层 ​ {{}} 变量相关 ​ {%%} 逻辑相关 ​ 前端获取容器类型的数据统一使用 句点符(.) ​ 两种给模板传递值的方式 return render(request,'index.html ...

  6. Allegro PCB Design GXL (legacy) 将brd文件另存为低版本文件

    Allegro PCB Design GXL (legacy) version 16.6-2015 参考:https://blog.csdn.net/qq_29761395/article/detai ...

  7. Python学习笔记六

    Python课堂笔记六 常用模块已经可以在单位实际项目中使用,可以实现运维自动化.无需手工备份文件,数据库,拷贝,压缩. 常用模块 time模块 time.time time.localtime ti ...

  8. memcached笔记

    启动memcached:./memcached -d -m 10 -l 127.0.0.1 -p 11211 -u root 连接memcached:telnet 127.0.0.1 11211 查看 ...

  9. js分析 汽_车_之_家 js生成css伪元素 hs_kw44_configUS::before

    0.参考 https://developer.mozilla.org/zh-CN/docs/Web/CSS/Pseudo-elements https://developer.mozilla.org/ ...

  10. Python与R的区别和联系

    转自:http://bbs.pinggu.org/thread-3078817-1-1.html 有人说Python和R的区别是显而易见的,因为R是针对统计的,python是给程序员设计的,其实这话对 ...