问题描述

在使用Azure Function App的SendGrid Binging功能,调用SendGrid服务器发送邮件功能时,有时候遇见间歇性,偶发性异常。在重新触发SendGrid部分的Function,又能正常运行。所以本文基于Azure Function使用SendGrid的异常错误消息日志,一步一步,分析源码中的调用。然后调查为什么Azure Function没有自动Retry呢?

(如需要参考如何使用Azure Function SendGrid,参考:Azure Functions SendGrid 绑定)

错误消息:

System.Threading.Tasks.TaskCanceledException : The operation was canceled.
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async System.Net.Http.ConnectHelper.ConnectAsync(String host,Int32 port,CancellationToken cancellationToken)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Threading.Tasks.ValueTask`1.get_Result()
at async System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request,Boolean allowHttp2,CancellationToken cancellationToken)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Threading.Tasks.ValueTask`1.get_Result()
at async System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request,CancellationToken cancellationToken)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Threading.Tasks.ValueTask`1.get_Result()
at async System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request,CancellationToken cancellationToken)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Threading.Tasks.ValueTask`1.get_Result()
at async System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request,Boolean doRequestAuth,CancellationToken cancellationToken)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async System.Net.Http.DiagnosticsHandler.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async SendGrid.Helpers.Reliability.RetryDelegatingHandler.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask,HttpRequestMessage request,CancellationTokenSource cts,Boolean disposeCts)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async SendGrid.SendGridClient.MakeRequest(HttpRequestMessage request,CancellationToken cancellationToken)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async SendGrid.SendGridClient.RequestAsync(Method method,String requestBody,String queryParams,String urlPath,CancellationToken cancellationToken)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async SendGrid.SendGridClient.SendEmailAsync(SendGridMessage msg,CancellationToken cancellationToken)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async Client.SendGridClient.SendMessageAsync(SendGridMessage msg,CancellationToken cancellationToken) at C:\azure-webjobs-sdk-extensions\src\WebJobs.Extensions.SendGrid\Client\SendGridClient.cs : 23
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async Microsoft.Azure.WebJobs.Extensions.Bindings.SendGridMessageAsyncCollector.FlushAsync(CancellationToken cancellationToken) at C:\azure-webjobs-sdk-extensions\src\WebJobs.Extensions.SendGrid\Bindings\SendGridMessageAsyncCollector.cs : 56
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async Microsoft.Azure.WebJobs.Host.Bindings.AsyncCollectorValueProvider`2.SetValueAsync[TUser,TMessage](Object value,CancellationToken cancellationToken) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Bindings\AsyncCollector\AsyncCollectorValueProvider.cs : 49
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async Microsoft.Azure.WebJobs.Binder.Complete(CancellationToken cancellationToken) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Bindings\Runtime\Binder.cs : 150
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async Microsoft.Azure.WebJobs.Host.Bindings.Runtime.RuntimeValueProvider.SetValueAsync(Object value,CancellationToken cancellationToken) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Bindings\Runtime\RuntimeValueProvider.cs : 34
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ParameterHelper.ProcessOutputParameters(CancellationToken cancellationToken) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs : 925
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithWatchersAsync(IFunctionInstanceEx instance,ParameterHelper parameterHelper,ILogger logger,CancellationTokenSource functionCancellationTokenSource) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs : 518
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(IFunctionInstanceEx instance,FunctionStartedMessage message,FunctionInstanceLogEntry instanceLogEntry,ParameterHelper parameterHelper,ILogger logger,CancellationToken cancellationToken) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs : 279
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(IFunctionInstanceEx instance,FunctionStartedMessage message,FunctionInstanceLogEntry instanceLogEntry,ParameterHelper parameterHelper,ILogger logger,CancellationToken cancellationToken) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs : 326
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.TryExecuteAsync(IFunctionInstance functionInstance,CancellationToken cancellationToken) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs : 94

问题分析

查看异常日志:

1)从最后一行看, 根据方法 Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.TryExecuteAsync 可以得出,代码已经进入Function平台级别。可以初步排除是自己写的代码错误。

2)在逐行上看,发现 C:\azure-webjobs-sdk-extensions\src\WebJobs.Extensions.SendGrid\Client\SendGridClient.cs : 23 中,调用了 Client.SendGridClient.SendMessageAsync(SendGridMessage msg,CancellationToken cancellationToken)  并抛出异常。

因为Azure Funciton的Source Code已经开源,所以可以在Github中查看到 WebJobs.Extensions.SendGrid\Client\SendGridClient.cs 的源代码。

注意: 代码中直接调用SendGird SDK中的Client对象,发送邮件 SendEmailAsync。

3) 继续向上查看,发现进入 SendGrid的 SendGrid.SendGridClient.MakeRequest(HttpRequestMessage request,CancellationToken cancellationToken) 中。同第二步一样,继续在Github中搜索SendGrid的源码,查看MakeRequest方法中进行了那些操作。

注意:方法MakeRequest中也没有多余配置,只是更深一步传递 Request请求。然后对获取的结果进行异常处理或成功还回,外加设置响应编码为UTF8

4) 继续向上查看,进入 SendGrid.Helpers.Reliability.RetryDelegatingHandler.SendAsync(HttpRequestMessage request,CancellationToken cancellationToken) 中。在此,发现可以设置以下四种条件的Retry次数。

       private static readonly List<HttpStatusCode> RetriableServerErrorStatusCodes =
new List<HttpStatusCode>()
{
HttpStatusCode.InternalServerError,
HttpStatusCode.BadGateway,
HttpStatusCode.ServiceUnavailable,
HttpStatusCode.GatewayTimeout,
};

RetryDeletegatingHandler.SendAsync 代码:

注意:如果设置了MaximumNumberOfRetries值,则当发送请求到SendGrid不成功时,会自动重试所设置的次数。重试次数必须在0 ~ 5 次之间。

            if (maximumNumberOfRetries < 0)
{
throw new ArgumentOutOfRangeException(nameof(maximumNumberOfRetries), "maximumNumberOfRetries must be greater than 0");
} if (maximumNumberOfRetries > 5)
{
throw new ArgumentOutOfRangeException(nameof(maximumNumberOfRetries), "The maximum number of retries allowed is 5");
}

5) 继续向上查看,发现后期的方法全是 System.Net.Http 类中对HTTP请求的处理。至此,代码查看到达源端。

6)回看异常消息 System.Threading.Tasks.TaskCanceledException : The operation was canceled.  这是因为参数 cancellationToken 的原因。因为请求为异步 Task操作。当在请求长时间没有处理完成并且cancellationToken中设置的取消时间已到,当前任务会被取消并抛出以上消息:The operation was canceled.

为什么需要取消令牌(CancellationToken) ?

因为Task没有方法支持在外部取消Task,只能通过一个公共变量存放线程的取消状态,在线程内部通过变量判断线程是否被取消,当CancellationToken是取消状态,Task内部未启动的任务不会启动新线程。

(Source: https://www.cnblogs.com/fanfan-90/p/12660996.html)

通过查看源码,我们找到了为什么出现TaskCanceledException的异常,但是为什么 Function App 没有设置SendGrid的重试参数呢? 继续查看源码,发现,Function App在初始化SendGrid Client对象时,并没有做任何的配置修改。所以Retry默认保持为0.

/// <summary>
/// Gets the maximum number of retries to execute against when sending an HTTP Request before throwing an exception.
/// Defaults to 0 (no retries, you must explicitly enable).
/// </summary>
public int MaximumNumberOfRetries { get; }

Azure Function 的 SendGridOptions.cs源文件中,并没有任何MaximumNumberOfRetries设置。

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information. using SendGrid.Helpers.Mail; namespace Microsoft.Azure.WebJobs.Extensions.SendGrid
{
/// <summary>
/// Defines the configuration options for the SendGrid binding.
/// </summary>
public class SendGridOptions
{
/// <summary>
/// Gets or sets the SendGrid ApiKey. If not explicitly set, the value will be defaulted
/// to the value specified via the 'AzureWebJobsSendGridApiKey' app setting or the
/// 'AzureWebJobsSendGridApiKey' environment variable.
/// </summary>
public string ApiKey { get; set; } /// <summary>
/// Gets or sets the default "to" address that will be used for messages.
/// This value can be overridden by job functions.
/// </summary>
/// <remarks>
/// An example of when it would be useful to provide a default value for 'to'
/// would be for emailing your own admin account to notify you when particular
/// jobs are executed. In this case, job functions can specify minimal info in
/// their bindings, for example just a Subject and Text body.
/// </remarks>
public EmailAddress ToAddress { get; set; } /// <summary>
/// Gets or sets the default "from" address that will be used for messages.
/// This value can be overridden by job functions.
/// </summary>
public EmailAddress FromAddress { get; set; }
}
}

参考资料

Azure Functions SendGrid 绑定: https://docs.azure.cn/zh-cn/azure-functions/functions-bindings-sendgrid?tabs=csharp

WebJobs.Extensions.SendGrid: https://github.com/Azure/azure-webjobs-sdk-extensions/tree/dev/src/WebJobs.Extensions.SendGrid

SendGrid: https://github.com/sendgrid/sendgrid-csharp/tree/main/src/SendGrid

多线程笔记-CancellationToken(取消令牌):https://www.cnblogs.com/fanfan-90/p/12660996.html

【Azure 应用服务】Azure Function App使用SendGrid发送邮件遇见异常消息The operation was canceled,分析源码逐步最终源端的更多相关文章

  1. 【Azure 应用服务】一个 App Service 同时部署运行两个及多个 Java 应用程序(Jar包)

    问题描述 如何在一个AppService下同时部署运行多个Java 应用程序呢? 问题解答 因为App Service的默认根目录为 wwwroot.如果需要运行多个Java 应用程序,需要在 www ...

  2. 【Azure 应用服务】在 App Service for Windows 中自定义 PHP 版本的方法

    问题描述 在App Service for Windows的环境中,当前只提供了PHP 7.4 版本的选择情况下,如何实现自定义PHP Runtime的版本呢? 如 PHP Version 8.1.9 ...

  3. 【Azure 应用服务】App Service/Azure Function的出站连接过多而引起了SNAT端口耗尽,导致一些新的请求出现超时错误(Timeout)

    问题描述 当需要在应用中有大量的出站连接时候,就会涉及到SNAT(源地址网络转换)耗尽的问题.而通过Azure App Service/Function的默认监控指标图表中,却没有可以直接查看到SNA ...

  4. 【Azure 应用服务】Azure Function App 执行PowerShell指令[Get-Azsubscription -TenantId $tenantID -DefaultProfile $cxt]错误

    问题描述 使用PowerShell脚本执行获取Azure订阅列表的指令(Get-Azsubscription -TenantId $tenantID -DefaultProfile $cxt).在本地 ...

  5. Azure 上通过 SendGrid 发送邮件

    SendGrid 是什么? SendGrid 是架构在云端的电子邮件服务,它能提供基于事务的可靠的电子邮件传递. 并且具有可扩充性和实时分析的能力.常见的用例有: 自动回复用户的邮件 定期发送信息给用 ...

  6. Azure : 通过 SendGrid 发送邮件

    SendGrid 是什么? SendGrid 是架构在云端的电子邮件服务,它能提供基于事务的可靠的电子邮件传递.并且具有可扩充性和实时分析的能力.常见的用例有:1. 自动回复用户的邮件2. 定期发送信 ...

  7. 【Azure 应用服务】Azure App Service For Linux 上实现 Python Flask Web Socket 项目 Http/Https

    问题描述 在上篇博文"[Azure 应用服务]App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)"中,实现了通过 HT ...

  8. 【Azure 应用服务】部署Kafka Trigger Function到Azure Function服务中,解决自定义域名解析难题

    问题描述 经过前两篇文章,分别使用VM搭建了Kafka服务,创建了Azure Function项目,并且都在本地运行成功. [Azure Developer]在Azure VM (Windows) 中 ...

  9. 【应用服务 App Service】Azure 应用服务测试网络访问其他域名及请求超时限制(4分钟 ≈ 230秒)

    测试App Service是否可以访问其他DNS 当应用服务(Azure App Service)创建完成后,想通过ping命令来查看是否可以访问其他站点或解析DNS,但是发现ping命令无法使用.这 ...

随机推荐

  1. 推荐:C#命名规范12条

    编码规范对于程序员而言尤为重要,有以下几个原因: 1.一个项目的生命周期中,80%的花费在于维护; 2.几乎没有任何一个项目,在其整个生命周期中,均由最初的开发人员来维护; 3.命名规范可以改善项目的 ...

  2. 贪心算法leetcode-763

    int[] lastShow = new int[26]; var list = new LinkedList<Integer>(); for (int i = 0; i < s.l ...

  3. ngx_lua模块

    ngx_lua模块的原理: 1.每个worker(工作进程)创建一个Lua VM,worker内所有协程共享VM:2.将Nginx I/O原语封装后注入 Lua VM,允许Lua代码直接访问:3.每个 ...

  4. 北汽极狐ARCFOX与华为合作

    北汽极狐ARCFOX与华为合作 全球首款激光雷达量产车 2021年,是激光雷达"上车"的元年. 曾经价格高不可攀,只能用于Robotaxi.无人车测试的激光雷达,终于彻底具备商业化 ...

  5. MongoDB学习笔记01:入门

    MongoDB简介 MongoDB是一个开源.高性能.无模式的文档型数据库,当初的设计就是用于简化开发和方便扩展,是NoSQL数据库产品中的一种.是最 像关系型数据库(MySQL)的非关系型数据库. ...

  6. vulhub-struts2-s2-007

    0x00 漏洞原理   当配置了验证规则 <ActionName>-validation.xml 时,若类型验证转换出错,后端默认会将用户提交的表单值通过字符串拼接,然后执行一次 OGNL ...

  7. WebRTC 传输安全机制第二话:深入显出 SRTP 协议

    通过 DTLS 协商后,RTC 通信的双方完成 MasterKey 和 MasterSalt 的协商.接下来,我们继续分析在 WebRTC 中,如何使用交换的密钥,来对 RTP 和 RTCP 进行加密 ...

  8. Redis面试连环问,快看看你能走到哪一步

    今天,我不自量力的面试了某大厂的java开发岗位,迎面走来一位风尘仆仆的中年男子,手里拿着屏幕还亮着的mac,他冲着我礼貌的笑了笑,然后说了句"不好意思,让你久等了",然后示意我坐 ...

  9. Linux分区,格式化概念理解

    一.分区概念: 逻辑上分成不同的存储空间. 分区类型: 主分区:最多只能有4个 扩展分区:最多只能有1个. 主分区加扩展分区最多有4个. 布恩那个写入数据,只能包含逻辑分区 逻辑分区: 主分区为什么只 ...

  10. [UWP] WinUI 2.6 使用指南

    2021年6月24日,Windows 11 正式对外发布,对于UWP开发者来说,这一天同样值得纪念,因为WinUI 2.6也正式发布了! 相同的时间点意味着一件事,即WinUI 2.6和Windows ...