问题描述

在使用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. STM32 串口接收大量数据导致死机

    http://blog.csdn.net/origin333/article/details/49992383 以下文章出自上面的链接.感谢原创作者的分享. 在一项目中,使用STM32作为主控,程序运 ...

  2. PHP常用函数记录

    1.mixed print_r(mixed $expression [,bool $return=false ]) 打印变量信息. 相关的函数还有var_dump().var_export() $re ...

  3. Docker Registry 简化版

    目录 Docker Registry 为什么要使用Registry 依赖 启动 Configuring a registry 配置认证 Docker Registry https://docs.doc ...

  4. VMware vRealize Suite 8.3 发布 - 多云环境的云计算管理解决方案

    概述 VMware vRealize Suite 是一种多云环境的云计算管理解决方案,为 IT 组织提供了一个基于 DevOps 和 ML 原则的基础架构自动化.一致运维和监管的现代平台. vReal ...

  5. GO语言基础---值传递与引用传递

    package main import ( "fmt" ) /* 值传递 函数的[形式参数]是对[实际参数]的值拷贝 所有对地址中内容的修改都与外界的实际参数无关 所有基本数据类型 ...

  6. 机器学习PAI

    机器学习PAI 机器学习PAI(Platform of Artificial Intelligence)是阿里云人工智能平台,提供一站式的机器学习解决方案.本文介绍什么是机器学习PAI. 什么是机器学 ...

  7. Solon Auth 认证框架使用演示(更简单的认证框架)

    最近看了好几个认证框架,什么 Appache Shiro 啦.Sa-Token 啦.Spring Security啦...尤其是Spring Security,做为对标 Spring Boot &am ...

  8. JUC 并发编程--03, 深刻理解锁, 8 锁现象,

    如何判断锁的是谁? 永远知道是什么锁, 线程8锁:就是关于锁的8个问题 问题1: public class LockDemo01 { public static void main(String[] ...

  9. Qt中的多线程与线程池浅析+实例

    1. Qt中的多线程与线程池 今天学习了Qt中的多线程和线程池,特写这篇博客来记录一下 2. 多线程 2.1 线程类 QThread Qt 中提供了一个线程类,通过这个类就可以创建子线程了,Qt 中一 ...

  10. Python3中列表、字典、元组、集合的看法

    文首,我先强调一下我是一个弱鸡码农,这个随笔是在我学习完Python3中的元组.字典.列表,集合这四种常见数据的数据类型的一些感想,如果有什么不对的地方欢迎大家予以指正.谢谢大家啦 回归正题:这篇随笔 ...