From 11127e2b6d7b93812b66de07124d506a60450345 Mon Sep 17 00:00:00 2001 From: savorboard Date: Sat, 8 Jul 2023 21:40:20 +0800 Subject: [PATCH] Add docs about TimeoutException thrown in consumer using HTTPClient. (#1368) --- docs/content/user-guide/en/cap/messaging.md | 3 + .../user-guide/en/cap/serialization.md | 54 ------------------ docs/content/user-guide/zh/cap/messaging.md | 3 + .../user-guide/zh/cap/serialization.md | 55 ------------------- samples/Sample.ConsoleApp/EventSubscriber.cs | 20 ++++++- samples/Sample.ConsoleApp/Program.cs | 35 +++++++++++- 6 files changed, 58 insertions(+), 112 deletions(-) diff --git a/docs/content/user-guide/en/cap/messaging.md b/docs/content/user-guide/en/cap/messaging.md index 6b3586ab6..9cc54de6e 100644 --- a/docs/content/user-guide/en/cap/messaging.md +++ b/docs/content/user-guide/en/cap/messaging.md @@ -2,6 +2,9 @@ The data sent by using the `ICapPublisher` interface is called `Message`. +!!! WARNING "TimeoutException thrown in consumer using HTTPClient" + By default, if the consumer throws an `OperationCanceledException` (including `TaskCanceledException`), we consider this to be normal user behavior and ignore the exception. If you use HTTPClient in the consumer method and configure the request timeout, due to the [design issue](https://github.com/dotnet/runtime/issues/21965) of HTTP Client, you may need to handle the exception separately and re-throw non `OperationCanceledException`, refer to #1368. + ## Compensating transaction Wiki : diff --git a/docs/content/user-guide/en/cap/serialization.md b/docs/content/user-guide/en/cap/serialization.md index c15e9323b..621781e8e 100644 --- a/docs/content/user-guide/en/cap/serialization.md +++ b/docs/content/user-guide/en/cap/serialization.md @@ -29,57 +29,3 @@ services.AddSingleton(); services.AddCap ``` - -## Message Adapter (removed in v3.0) - -In heterogeneous systems, sometimes you need to communicate with other systems, but other systems use message objects that may be different from CAP's [**Wrapper Object**](../storage/general.md#_7). This time maybe you need to customize the message wapper. - -CAP provides the `IMessagePacker` interface for customizing the [**Wrapper Object**](../storage/general.md#_7). Custom MessagePacker usually packs and unpacks the `CapMessage` In this process you can add your own business objects. - -Usage : - -```csharp - -class MyMessagePacker : IMessagePacker -{ - private readonly IContentSerializer _serializer; - - public DefaultMessagePacker(IContentSerializer serializer) - { - _serializer = serializer; - } - - public string Pack(CapMessage obj) - { - var myStructure = new - { - Id = obj.Id, - Body = obj.Content, - Date = obj.Timestamp, - Callback = obj.CallbackName - }; - return _serializer.Serialize(myStructure); - } - - public CapMessage UnPack(string packingMessage) - { - var myStructure = _serializer.DeSerialize(packingMessage); - - return new CapMessageDto - { - Id = myStructure.Id, - Timestamp = myStructure.Date, - Content = myStructure.Body, - CallbackName = myStructure.Callback - }; - } -} -``` - -Next, add the custom `MyMessagePacker` to the service. - -```csharp - -services.AddCap(x =>{ }).AddMessagePacker(); - -``` \ No newline at end of file diff --git a/docs/content/user-guide/zh/cap/messaging.md b/docs/content/user-guide/zh/cap/messaging.md index ecaff4544..e8c397b8a 100644 --- a/docs/content/user-guide/zh/cap/messaging.md +++ b/docs/content/user-guide/zh/cap/messaging.md @@ -6,6 +6,9 @@ 你可以阅读 [quick-start](../getting-started/quick-start.md#_3) 来学习如何发送和处理消息。 +!!! WARNING "消费者中使用 HTTPClient 引发的 TimeoutException" + 默认情况下,如果消费者抛出 `OperationCanceledException`(包括 `TaskCanceledException`),我们会认为这是用户的正常行为而对异常进行忽略。如果你在消费者方法中使用 HTTPClient 并且进行了配置了Timeout配置,由于HTTP Client的[设计问题](https://github.com/dotnet/runtime/issues/21965),你可能需要单独对异常进行处理并重新引发非OperationCanceledException,参考 #1368 + ## 补偿事务 [Compensating transaction](https://en.wikipedia.org/wiki/Compensating_transaction) diff --git a/docs/content/user-guide/zh/cap/serialization.md b/docs/content/user-guide/zh/cap/serialization.md index 84c3624a4..01fb7b326 100644 --- a/docs/content/user-guide/zh/cap/serialization.md +++ b/docs/content/user-guide/zh/cap/serialization.md @@ -29,59 +29,4 @@ services.AddSingleton(); // --- services.AddCap -``` - - -## 消息适配器 (v3.0移除 ) - -在异构系统中,有时候需要和其他系统进行通讯,但是其他系统使用的消息对象可能和 CAP 的[**包装器对象**](../storage/general.md#_7)不一样,这个时候就需要对消息进行自定义适配。 - -CAP 提供了 `IMessagePacker` 接口用于对 [**包装器对象**](../storage/general.md#_7) 进行自定义,自定义的 MessagePacker 通常是将 `CapMessage` 进行打包和解包操作,在这个过程中可以添加自己的业务对象。 - -使用方法: - -```csharp - -class MyMessagePacker : IMessagePacker -{ - private readonly IContentSerializer _serializer; - - public DefaultMessagePacker(IContentSerializer serializer) - { - _serializer = serializer; - } - - public string Pack(CapMessage obj) - { - var myStructure = new - { - Id = obj.Id, - Body = obj.Content, - Date = obj.Timestamp, - Callback = obj.CallbackName - }; - return _serializer.Serialize(myStructure); - } - - public CapMessage UnPack(string packingMessage) - { - var myStructure = _serializer.DeSerialize(packingMessage); - - return new CapMessageDto - { - Id = myStructure.Id, - Timestamp = myStructure.Date, - Content = myStructure.Body, - CallbackName = myStructure.Callback - }; - } -} -``` - -接下来,配置自定义的 `MyMessagePacker` 到服务中。 - -```csharp - -services.AddCap(x =>{ }).AddMessagePacker(); - ``` \ No newline at end of file diff --git a/samples/Sample.ConsoleApp/EventSubscriber.cs b/samples/Sample.ConsoleApp/EventSubscriber.cs index d09a7a84b..15bc28624 100644 --- a/samples/Sample.ConsoleApp/EventSubscriber.cs +++ b/samples/Sample.ConsoleApp/EventSubscriber.cs @@ -1,4 +1,6 @@ using System; +using System.Net.Http; +using System.Threading.Tasks; using DotNetCore.CAP; namespace Sample.ConsoleApp @@ -6,9 +8,25 @@ namespace Sample.ConsoleApp public class EventSubscriber : ICapSubscribe { [CapSubscribe("sample.console.showtime")] - public void ShowTime(DateTime date) + public async Task ShowTime(DateTime date) { Console.WriteLine(date); + + string baseAddress = "http://localhost:8080/"; + var client = new HttpClient() + { + BaseAddress = new Uri(baseAddress), + Timeout = TimeSpan.FromMilliseconds(10) + }; + //try + //{ + var s = await client.GetAsync(baseAddress); + //} + //catch (Exception e) + //{ + // Console.WriteLine(e.Message); + // Console.WriteLine(e.InnerException.Message); + //} } } } diff --git a/samples/Sample.ConsoleApp/Program.cs b/samples/Sample.ConsoleApp/Program.cs index 97808d701..ab2fd58f2 100644 --- a/samples/Sample.ConsoleApp/Program.cs +++ b/samples/Sample.ConsoleApp/Program.cs @@ -1,5 +1,7 @@ using System; +using System.Threading.Tasks; using DotNetCore.CAP; +using DotNetCore.CAP.Filter; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Savorboard.CAP.InMemoryMessageQueue; @@ -10,6 +12,7 @@ public class Program { public static void Main(string[] args) { + var cts = new System.Threading.CancellationTokenSource(); var container = new ServiceCollection(); container.AddLogging(x => x.AddConsole()); @@ -19,15 +22,43 @@ public static void Main(string[] args) x.UseInMemoryStorage(); x.UseInMemoryMessageQueue(); - }); + }).AddSubscribeFilter(); container.AddSingleton(); var sp = container.BuildServiceProvider(); - sp.GetService().BootstrapAsync(); + sp.GetService().BootstrapAsync(cts.Token); + + _ = Task.Run(async () => + { + while (!cts.IsCancellationRequested) + { + await Task.Delay(2000, cts.Token); + + await sp.GetService().PublishAsync("sample.console.showtime", DateTime.Now, cancellationToken: cts.Token); + } + }, cts.Token); + + AppDomain.CurrentDomain.ProcessExit += (_, _) => + { + cts.Cancel(); + }; Console.ReadLine(); } } + + public class Filter : SubscribeFilter + { + public override Task OnSubscribeExceptionAsync(ExceptionContext context) + { + if (context.Exception.InnerException is TimeoutException) + { + throw new TimeoutException("Http request timeout"); + } + + return base.OnSubscribeExceptionAsync(context); + } + } } \ No newline at end of file