From 16d11990ecb61a8c904989f7df647e15f2d98675 Mon Sep 17 00:00:00 2001 From: Rikitav Date: Sat, 2 Aug 2025 02:32:38 +0400 Subject: [PATCH 1/2] * Added Result class to communicate with router from handler * Removed "ExecuteOnlyFirstFoundHanlder" in sake of testing new Result pattern based routing system * Removed obsolete option property "DescendDescriptorIndex" * Changed router logic * Changed handlers pool logic --- Telegrator.Analyzers/GlobalSuppressions.cs | 8 +++ Telegrator.Generators/GlobalSuppressions.cs | 8 +++ .../Polling/HostedUpdateWebhooker.cs | 5 +- Telegrator.Hosting.Web/TelegramBotWebHost.cs | 18 ++++-- .../TelegramBotWebHostBuilder.cs | 12 ++-- .../TelegramBotWebOptions.cs | 27 ++------- .../TelegratorWebOptions.cs | 35 +++++++++++ .../Polling/HostUpdateHandlersPool.cs | 8 --- Telegrator.Hosting/TelegramBotHostBuilder.cs | 4 +- Telegrator.Tests/TestUpdateHandler.cs | 4 +- .../Configuration/ITelegratorOptions.cs | 22 +++---- .../Handlers/Building/AwaiterHandler.cs | 4 +- .../Building/BuildedAbstractHandler.cs | 2 +- .../Handlers/Building/HandlerBuilder.cs | 2 +- .../Components/AbstractUpdateHandler.cs | 6 +- .../Components/BranchingUpdateHandler.cs | 17 +++--- .../Handlers/Components/UpdateHandlerBase.cs | 14 +++-- Telegrator/Handlers/Result.cs | 30 ++++++++++ .../Descriptors/DefaultCustomDescriptors.cs | 11 ++-- .../Descriptors/DescribedHandlerInfo.cs | 28 +++++---- .../MadiatorCore/IUpdateHandlersPool.cs | 7 ++- Telegrator/Polling/LimitedDictionary.cs | 52 ++++++++++++++++ Telegrator/Polling/LimitedQueue.cs | 42 +++++++++++++ Telegrator/Polling/UpdateHandlersPool.cs | 60 +++++++++++++++++-- Telegrator/Polling/UpdateRouter.cs | 23 +++---- Telegrator/TelegratorOptions.cs | 13 ++-- 26 files changed, 347 insertions(+), 115 deletions(-) create mode 100644 Telegrator.Analyzers/GlobalSuppressions.cs create mode 100644 Telegrator.Generators/GlobalSuppressions.cs create mode 100644 Telegrator.Hosting.Web/TelegratorWebOptions.cs create mode 100644 Telegrator/Handlers/Result.cs create mode 100644 Telegrator/Polling/LimitedDictionary.cs create mode 100644 Telegrator/Polling/LimitedQueue.cs diff --git a/Telegrator.Analyzers/GlobalSuppressions.cs b/Telegrator.Analyzers/GlobalSuppressions.cs new file mode 100644 index 0000000..361f387 --- /dev/null +++ b/Telegrator.Analyzers/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0090")] diff --git a/Telegrator.Generators/GlobalSuppressions.cs b/Telegrator.Generators/GlobalSuppressions.cs new file mode 100644 index 0000000..361f387 --- /dev/null +++ b/Telegrator.Generators/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0090")] diff --git a/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs b/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs index b2cccf6..b62ac92 100644 --- a/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs +++ b/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; @@ -22,7 +21,7 @@ namespace Telegrator.Hosting.Web.Polling private readonly ITelegramBotWebHost _botHost; private readonly ITelegramBotClient _botClient; private readonly IUpdateRouter _updateRouter; - private readonly TelegramBotWebOptions _options; + private readonly TelegratorWebOptions _options; /// /// Initiallizes new instance of @@ -32,7 +31,7 @@ namespace Telegrator.Hosting.Web.Polling /// /// /// - public HostedUpdateWebhooker(ITelegramBotWebHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions options) + public HostedUpdateWebhooker(ITelegramBotWebHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions options) { if (string.IsNullOrEmpty(options.Value.WebhookUri)) throw new ArgumentNullException(nameof(options), "Option \"WebhookUrl\" must be set to subscribe for update recieving"); diff --git a/Telegrator.Hosting.Web/TelegramBotWebHost.cs b/Telegrator.Hosting.Web/TelegramBotWebHost.cs index 5686ea4..44eb3e8 100644 --- a/Telegrator.Hosting.Web/TelegramBotWebHost.cs +++ b/Telegrator.Hosting.Web/TelegramBotWebHost.cs @@ -5,8 +5,10 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using System.Text; using Telegram.Bot.Types.Enums; +using Telegrator.Configuration; using Telegrator.Hosting.Components; using Telegrator.Hosting.Providers; using Telegrator.Hosting.Web.Components; @@ -22,7 +24,7 @@ namespace Telegrator.Hosting.Web { private readonly WebApplication _innerApp; private readonly IUpdateRouter _updateRouter; - private readonly ILogger _logger; + private readonly ILogger _logger; private bool _disposed; @@ -43,7 +45,7 @@ namespace Telegrator.Hosting.Web /// /// This application's logger /// - public ILogger Logger => _logger; + public ILogger Logger => _logger; // Private interface fields IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services; @@ -53,12 +55,20 @@ namespace Telegrator.Hosting.Web internal TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder, HostHandlersCollection handlers) { + // Registering this host in services for easy access RegisterHostServices(webApplicationBuilder, handlers); + + // Building proxy application _innerApp = webApplicationBuilder.Build(); - _updateRouter = Services.GetRequiredService(); - _logger = Services.GetRequiredService>(); + // Initializing bot info, as it requires to make a request via tg bot + Services.GetRequiredService(); + // Reruesting services for this host + _updateRouter = Services.GetRequiredService(); + _logger = Services.GetRequiredService>(); + + // Logging registering handlers in DEBUG purposes LogHandlers(handlers); } diff --git a/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs b/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs index cab6f3d..65b9402 100644 --- a/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs +++ b/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs @@ -43,10 +43,6 @@ namespace Telegrator.Hosting.Web _innerBuilder = webApplicationBuilder; _settings = settings ?? throw new ArgumentNullException(nameof(settings)); _handlers = new HostHandlersCollection(Services, _settings); - - Services.AddSingleton>(Options.Create(settings)); - Services.Configure(Configuration.GetSection(nameof(TelegratorOptions))); - Services.Configure(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); } /// @@ -67,6 +63,14 @@ namespace Telegrator.Hosting.Web } } + if (!_settings.DisableAutoConfigure) + { + Services.Configure(Configuration.GetSection(nameof(TelegratorWebOptions))); + Services.Configure(Configuration.GetSection(nameof(TelegratorOptions))); + Services.Configure(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); + } + + Services.AddSingleton>(Options.Create(_settings)); return new TelegramBotWebHost(_innerBuilder, _handlers); } } diff --git a/Telegrator.Hosting.Web/TelegramBotWebOptions.cs b/Telegrator.Hosting.Web/TelegramBotWebOptions.cs index 3682259..267ccb6 100644 --- a/Telegrator.Hosting.Web/TelegramBotWebOptions.cs +++ b/Telegrator.Hosting.Web/TelegramBotWebOptions.cs @@ -1,37 +1,18 @@ using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Options; using System.Diagnostics.CodeAnalysis; namespace Telegrator.Hosting.Web { /// - /// Configuration options for Telegram bot behavior and execution settings. - /// Controls various aspects of bot operation including concurrency, routing, webhook receiving, and execution policies. + /// Options for configuring the behavior for TelegramBotWebHost. /// public class TelegramBotWebOptions : TelegratorOptions { /// - /// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration + /// Disables automatic configuration for all of required instances /// - [StringSyntax(StringSyntaxAttribute.Uri)] - public required string WebhookUri { get; set; } - - /// - /// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” in every webhook request, 1-256 characters. - /// Only characters A-Z, a-z, 0-9, _ and - are allowed. - /// The header is useful to ensure that the request comes from a webhook set by you. - /// - public string? SecretToken { get; set; } - - /// - /// The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40. - /// Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput. - /// - public int MaxConnections { get; set; } = 40; - - /// - /// Pass true to drop all pending updates - /// - public bool DropPendingUpdates { get; set; } + public bool DisableAutoConfigure { get; set; } /// public string[]? Args { get; init; } diff --git a/Telegrator.Hosting.Web/TelegratorWebOptions.cs b/Telegrator.Hosting.Web/TelegratorWebOptions.cs new file mode 100644 index 0000000..2b809f9 --- /dev/null +++ b/Telegrator.Hosting.Web/TelegratorWebOptions.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Telegrator.Hosting.Web +{ + /// + /// Configuration options for Telegram bot behavior and execution settings. + /// Controls various aspects of bot operation including concurrency, routing, webhook receiving, and execution policies. + /// + public class TelegratorWebOptions + { + /// + /// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration + /// + [StringSyntax(StringSyntaxAttribute.Uri)] + public required string WebhookUri { get; set; } + + /// + /// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” in every webhook request, 1-256 characters. + /// Only characters A-Z, a-z, 0-9, _ and - are allowed. + /// The header is useful to ensure that the request comes from a webhook set by you. + /// + public string? SecretToken { get; set; } + + /// + /// The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40. + /// Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput. + /// + public int MaxConnections { get; set; } = 40; + + /// + /// Pass true to drop all pending updates + /// + public bool DropPendingUpdates { get; set; } + } +} diff --git a/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs b/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs index 82bb1a2..ad21b7a 100644 --- a/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs +++ b/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Telegrator.MadiatorCore.Descriptors; using Telegrator.Polling; namespace Telegrator.Hosting.Polling @@ -9,12 +8,5 @@ namespace Telegrator.Hosting.Polling public class HostUpdateHandlersPool(IOptions options, ILogger logger) : UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken) { private readonly ILogger _logger = logger; - - /// - protected override async Task ExecuteHandlerWrapper(DescribedHandlerInfo enqueuedHandler) - { - //_logger.LogInformation("Handler \"{0}\" has entered execution pool", enqueuedHandler.DisplayString); - await base.ExecuteHandlerWrapper(enqueuedHandler); - } } } diff --git a/Telegrator.Hosting/TelegramBotHostBuilder.cs b/Telegrator.Hosting/TelegramBotHostBuilder.cs index a5b3d39..0bba6bc 100644 --- a/Telegrator.Hosting/TelegramBotHostBuilder.cs +++ b/Telegrator.Hosting/TelegramBotHostBuilder.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Telegram.Bot; using Telegram.Bot.Polling; using Telegrator.Hosting; @@ -46,6 +47,7 @@ namespace Telegrator.Hosting { _innerBuilder = hostApplicationBuilder; _settings = settings ?? new TelegramBotHostBuilderSettings(); + _handlers = new HostHandlersCollection(Services, _settings); _innerBuilder.Logging.ClearProviders(); @@ -71,11 +73,11 @@ namespace Telegrator.Hosting if (!_settings.DisableAutoConfigure) { - Services.Configure(Configuration.GetSection(nameof(TelegratorOptions))); Services.Configure(Configuration.GetSection(nameof(ReceiverOptions))); Services.Configure(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); } + Services.AddSingleton>(Options.Create(_settings)); return new TelegramBotHost(_innerBuilder, _handlers); } } diff --git a/Telegrator.Tests/TestUpdateHandler.cs b/Telegrator.Tests/TestUpdateHandler.cs index 6248edf..341fcb5 100644 --- a/Telegrator.Tests/TestUpdateHandler.cs +++ b/Telegrator.Tests/TestUpdateHandler.cs @@ -13,11 +13,11 @@ namespace Telegrator.Tests { public bool WasExecuted { get; private set; } - public override Task Execute(IAbstractHandlerContainer container, CancellationToken cancellationToken) + public override Task Execute(IAbstractHandlerContainer container, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); WasExecuted = true; - return Task.CompletedTask; + return Task.FromResult(Result.Ok()); } } } diff --git a/Telegrator/Configuration/ITelegratorOptions.cs b/Telegrator/Configuration/ITelegratorOptions.cs index 82b61b9..5c0787b 100644 --- a/Telegrator/Configuration/ITelegratorOptions.cs +++ b/Telegrator/Configuration/ITelegratorOptions.cs @@ -6,11 +6,18 @@ /// public interface ITelegratorOptions { + /* /// /// Gets or sets a value indicating whether only the first found handler should be executed for each update. /// public bool ExecuteOnlyFirstFoundHanlder { get; set; } + /// + /// Gets or sets a value indicating whether to descend the indexr of handler's index on register. ('false' by default) + /// + public bool DescendDescriptorIndex { get; set; } + */ + /// /// Gets or sets the maximum number of parallel working handlers. Null means no limit. /// @@ -21,19 +28,14 @@ /// public bool ExclusiveAwaitingHandlerRouting { get; set; } - /// - /// Gets or sets the global cancellation token for all bot operations. - /// - public CancellationToken GlobalCancellationToken { get; set; } - - /// - /// Gets or sets a value indicating whether to descend the indexr of handler's index on register. ('false' by default) - /// - public bool DescendDescriptorIndex { get; set; } - /// /// Gets or sets a value indicating whether to exclude intersecting command aliases. /// public bool ExceptIntersectingCommandAliases { get; set; } + + /// + /// Gets or sets the global cancellation token for all bot operations. + /// + public CancellationToken GlobalCancellationToken { get; set; } } } diff --git a/Telegrator/Handlers/Building/AwaiterHandler.cs b/Telegrator/Handlers/Building/AwaiterHandler.cs index 3912aa8..acfd31a 100644 --- a/Telegrator/Handlers/Building/AwaiterHandler.cs +++ b/Telegrator/Handlers/Building/AwaiterHandler.cs @@ -51,10 +51,10 @@ namespace Telegrator.Handlers.Building /// The handler container (unused). /// The cancellation token (unused). /// A completed task. - protected override Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellation) + protected override Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellation) { ResetEvent.Set(); - return Task.CompletedTask; + return Task.FromResult(Result.Ok()); } /// diff --git a/Telegrator/Handlers/Building/BuildedAbstractHandler.cs b/Telegrator/Handlers/Building/BuildedAbstractHandler.cs index ff926cd..14c215b 100644 --- a/Telegrator/Handlers/Building/BuildedAbstractHandler.cs +++ b/Telegrator/Handlers/Building/BuildedAbstractHandler.cs @@ -31,7 +31,7 @@ namespace Telegrator.Handlers.Building /// The handler container with execution context. /// The cancellation token. /// A task representing the asynchronous execution. - public override Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) + public override Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) => HandlerAction.Invoke(container, cancellation); } } diff --git a/Telegrator/Handlers/Building/HandlerBuilder.cs b/Telegrator/Handlers/Building/HandlerBuilder.cs index a9dda1c..5a00776 100644 --- a/Telegrator/Handlers/Building/HandlerBuilder.cs +++ b/Telegrator/Handlers/Building/HandlerBuilder.cs @@ -11,7 +11,7 @@ namespace Telegrator.Handlers.Building /// The handler container with execution context. /// The cancellation token. /// A task representing the asynchronous execution. - public delegate Task AbstractHandlerAction(IAbstractHandlerContainer container, CancellationToken cancellation) where TUpdate : class; + public delegate Task AbstractHandlerAction(IAbstractHandlerContainer container, CancellationToken cancellation) where TUpdate : class; /// /// Builder class for creating regular handlers that can process updates. diff --git a/Telegrator/Handlers/Components/AbstractUpdateHandler.cs b/Telegrator/Handlers/Components/AbstractUpdateHandler.cs index db5b9c4..794204b 100644 --- a/Telegrator/Handlers/Components/AbstractUpdateHandler.cs +++ b/Telegrator/Handlers/Components/AbstractUpdateHandler.cs @@ -74,10 +74,10 @@ namespace Telegrator.Handlers.Components /// The handler container. /// Cancellation token. /// A task representing the asynchronous operation. - protected override sealed async Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken) + protected override sealed async Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken) { Container = (IAbstractHandlerContainer)container; - await Execute(Container, cancellationToken); + return await Execute(Container, cancellationToken); } /// @@ -86,6 +86,6 @@ namespace Telegrator.Handlers.Components /// The handler container. /// Cancellation token. /// A task representing the asynchronous operation. - public abstract Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation); + public abstract Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation); } } diff --git a/Telegrator/Handlers/Components/BranchingUpdateHandler.cs b/Telegrator/Handlers/Components/BranchingUpdateHandler.cs index a8b6108..a15cd6f 100644 --- a/Telegrator/Handlers/Components/BranchingUpdateHandler.cs +++ b/Telegrator/Handlers/Components/BranchingUpdateHandler.cs @@ -118,13 +118,13 @@ namespace Telegrator.Handlers.Components /// The handler container. /// The cancellation token. /// Thrown when no branch method is set. - public override async Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) + public override async Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) { if (branchMethodInfo is null) throw new Exception(); Cancellation = cancellation; - await BranchExecuteWrapper(container, branchMethodInfo); + return await BranchExecuteWrapper(container, branchMethodInfo); } /// @@ -132,21 +132,20 @@ namespace Telegrator.Handlers.Components /// /// The handler container. /// The method to execute. - protected virtual async Task BranchExecuteWrapper(IAbstractHandlerContainer container, MethodInfo methodInfo) + protected virtual async Task BranchExecuteWrapper(IAbstractHandlerContainer container, MethodInfo methodInfo) { if (methodInfo.ReturnType == typeof(void)) { methodInfo.Invoke(this, []); - return; + return Result.Ok(); } else { object branchReturn = methodInfo.Invoke(this, []); - if (branchReturn == null) - return; - - if (branchReturn is Task branchTask) - await branchTask; + if (branchReturn is not Task branchTask) + throw new InvalidOperationException(); + + return await branchTask; } } diff --git a/Telegrator/Handlers/Components/UpdateHandlerBase.cs b/Telegrator/Handlers/Components/UpdateHandlerBase.cs index a4ef62f..d2e3a37 100644 --- a/Telegrator/Handlers/Components/UpdateHandlerBase.cs +++ b/Telegrator/Handlers/Components/UpdateHandlerBase.cs @@ -24,10 +24,16 @@ namespace Telegrator.Handlers.Components /// The for the update. /// The cancellation token. /// A representing the asynchronous operation. - public async Task Execute(IHandlerContainer container, CancellationToken cancellationToken = default) + public async Task Execute(IHandlerContainer container, CancellationToken cancellationToken = default) { - await ExecuteInternal(container, cancellationToken); - LifetimeToken.LifetimeEnded(); + try + { + return await ExecuteInternal(container, cancellationToken); + } + finally + { + LifetimeToken.LifetimeEnded(); + } } /// @@ -36,6 +42,6 @@ namespace Telegrator.Handlers.Components /// The for the update. /// The cancellation token. /// A representing the asynchronous operation. - protected abstract Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken); + protected abstract Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken); } } diff --git a/Telegrator/Handlers/Result.cs b/Telegrator/Handlers/Result.cs new file mode 100644 index 0000000..589e7e0 --- /dev/null +++ b/Telegrator/Handlers/Result.cs @@ -0,0 +1,30 @@ +namespace Telegrator.Handlers +{ + public sealed class Result + { + public bool Positive { get; } + + public bool RouteNext { get; } + + public Type? NextType { get; } + + internal Result(bool positive, bool routeNext, Type? nextType) + { + Positive = positive; + RouteNext = routeNext; + NextType = nextType; + } + + public static Result Ok() + => new Result(true, false, null); + + public static Result Fault() + => new Result(false, false, null); + + public static Result Next() + => new Result(true, true, null); + + public static Result Next() + => new Result(true, true, typeof(T)); + } +} diff --git a/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs b/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs index caa272b..f8a7eaf 100644 --- a/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs +++ b/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs @@ -36,7 +36,7 @@ namespace Telegrator.MadiatorCore.Descriptors { private readonly MethodInfo Method = method; - public override async Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) + public override async Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) { if (Method is null) throw new Exception(); @@ -44,16 +44,15 @@ namespace Telegrator.MadiatorCore.Descriptors if (Method.ReturnType == typeof(void)) { Method.Invoke(this, [container, cancellation]); - return; + return Result.Ok(); } else { object branchReturn = Method.Invoke(this, [container, cancellation]); - if (branchReturn == null) - return; + if (branchReturn is not Task branchTask) + throw new InvalidOperationException(); - if (branchReturn is Task branchTask) - await branchTask; + return await branchTask; } } } diff --git a/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs b/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs index 8506fc3..b5ea82a 100644 --- a/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs +++ b/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs @@ -2,6 +2,7 @@ using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegrator.Filters.Components; +using Telegrator.Handlers; using Telegrator.Handlers.Components; namespace Telegrator.MadiatorCore.Descriptors @@ -81,36 +82,43 @@ namespace Telegrator.MadiatorCore.Descriptors /// Cancellation token. /// A task representing the asynchronous operation. /// Thrown if the handler lifetime has ended or the handler is not a container factory. - public async Task Execute(CancellationToken cancellationToken) + public async Task Execute(CancellationToken cancellationToken) { if (HandlerLifetime.IsEnded) throw new Exception(); - IHandlerContainerFactory? containerFactory = HandlerInstance is IHandlerContainerFactory handlerDefainedContainerFactory - ? handlerDefainedContainerFactory - : UpdateRouter.DefaultContainerFactory is not null - ? UpdateRouter.DefaultContainerFactory - : throw new Exception(); - try { - HandlerContainer = containerFactory.CreateContainer(UpdateRouter.AwaitingProvider, this); - await HandlerInstance.Execute(HandlerContainer, cancellationToken); + HandlerContainer = GetContainer(UpdateRouter.AwaitingProvider, this); + return await HandlerInstance.Execute(HandlerContainer, cancellationToken); } catch (OperationCanceledException) { // Cancelled _ = 0xBAD + 0xC0DE; - return; + return Result.Ok(); } catch (Exception exception) { await UpdateRouter .HandleErrorAsync(Client, exception, HandleErrorSource.HandleUpdateError, cancellationToken) .ConfigureAwait(false); + + return Result.Fault(); } } + private IHandlerContainer GetContainer(IAwaitingProvider awaitingProvider, DescribedHandlerInfo handlerInfo) + { + if (HandlerInstance is IHandlerContainerFactory handlerDefainedContainerFactory) + return handlerDefainedContainerFactory.CreateContainer(awaitingProvider, handlerInfo); + + if (UpdateRouter.DefaultContainerFactory is not null) + return UpdateRouter.DefaultContainerFactory.CreateContainer(awaitingProvider, handlerInfo); + + throw new Exception(); + } + /// public override string ToString() => DisplayString ?? HandlerInstance.GetType().Name; diff --git a/Telegrator/MadiatorCore/IUpdateHandlersPool.cs b/Telegrator/MadiatorCore/IUpdateHandlersPool.cs index 7547626..82b8817 100644 --- a/Telegrator/MadiatorCore/IUpdateHandlersPool.cs +++ b/Telegrator/MadiatorCore/IUpdateHandlersPool.cs @@ -7,6 +7,7 @@ namespace Telegrator.MadiatorCore /// /// The for the enqueued handler. public delegate void HandlerEnqueued(DescribedHandlerInfo args); + /// /// Represents a delegate for when a handler is executing. /// @@ -24,7 +25,7 @@ namespace Telegrator.MadiatorCore public event HandlerEnqueued? HandlerEnqueued; /// - /// Occurs when a handler is executing. + /// Occurs when a handler is entering execution. /// public event HandlerExecuting? HandlerExecuting; @@ -32,8 +33,9 @@ namespace Telegrator.MadiatorCore /// Enqueues a collection of handlers for execution. /// /// The handlers to enqueue. - public void Enqueue(IEnumerable handlers); + public Task Enqueue(IEnumerable handlers); + /* /// /// Enqueues a single handler for execution. /// @@ -45,5 +47,6 @@ namespace Telegrator.MadiatorCore /// /// The of the handler to dequeue. public void Dequeue(HandlerLifetimeToken token); + */ } } diff --git a/Telegrator/Polling/LimitedDictionary.cs b/Telegrator/Polling/LimitedDictionary.cs new file mode 100644 index 0000000..0d8bd47 --- /dev/null +++ b/Telegrator/Polling/LimitedDictionary.cs @@ -0,0 +1,52 @@ +using System.Collections; +using System.Collections.Concurrent; + +namespace Telegrator.Polling +{ + public class LimitedDictionary : IEnumerable>, IDisposable + { + private readonly int? _maximum; + private readonly SemaphoreSlim _semaphore = null!; + private readonly ConcurrentDictionary _dict = []; + + public LimitedDictionary(int? maximum) + { + _maximum = maximum; + if (maximum != null) + { + int value = maximum.Value; + _semaphore = new SemaphoreSlim(value, value); + } + } + + public async Task Add(TKey key, TValue value, CancellationToken cancellationToken) + { + if (_semaphore != null) + await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + return _dict.TryAdd(key, value); + } + + public bool Remove(TKey key, out TValue result) + { + if (_dict.TryRemove(key, out result)) + { + _semaphore?.Release(1); + return true; + } + + return false; + } + + public IEnumerator> GetEnumerator() => _dict.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _dict.GetEnumerator(); + + /// + public void Dispose() + { + GC.SuppressFinalize(this); + _semaphore.Dispose(); + } + } +} diff --git a/Telegrator/Polling/LimitedQueue.cs b/Telegrator/Polling/LimitedQueue.cs new file mode 100644 index 0000000..3d0978c --- /dev/null +++ b/Telegrator/Polling/LimitedQueue.cs @@ -0,0 +1,42 @@ +using System.Collections.Concurrent; + +namespace Telegrator.Polling +{ + public class LimitedQueue + { + private readonly int? _maximum; + private readonly ConcurrentQueue _queue = []; + private readonly SemaphoreSlim _semaphore = null!; + + public LimitedQueue(int? maximum) + { + _maximum = maximum; + if (maximum != null) + { + int value = maximum.Value; + _semaphore = new SemaphoreSlim(value, value); + } + } + + public async Task Enqueue(T item, CancellationToken cancellationToken) + { + if (_maximum.HasValue) + await _semaphore.WaitAsync(cancellationToken); + + _queue.Enqueue(item); + } + + public bool Dequeue(out T result) + { + if (_queue.TryDequeue(out result)) + { + if (_maximum.HasValue) + _semaphore?.Release(1); + + return true; + } + + return false; + } + } +} diff --git a/Telegrator/Polling/UpdateHandlersPool.cs b/Telegrator/Polling/UpdateHandlersPool.cs index b1a3ee9..2252fe2 100644 --- a/Telegrator/Polling/UpdateHandlersPool.cs +++ b/Telegrator/Polling/UpdateHandlersPool.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using Telegrator.Handlers; using Telegrator.MadiatorCore; using Telegrator.MadiatorCore.Descriptors; @@ -15,6 +16,7 @@ namespace Telegrator.Polling /// protected object SyncObj = new object(); + /* /// /// Event that signals when awaiting handlers are queued. /// @@ -34,6 +36,13 @@ namespace Telegrator.Polling /// Dictionary for tracking currently executing handlers. /// protected readonly ConcurrentDictionary ExecutingHandlersPool = []; + */ + + //protected readonly ConcurrentDictionary> AwaitingHandlersQueue; + + //protected readonly LimitedDictionary ExecutingHandlersPool; + + protected SemaphoreSlim ExecutingHandlersSemaphore = null!; /// /// The bot configuration options. @@ -65,26 +74,62 @@ namespace Telegrator.Polling { Options = options; GlobalCancellationToken = globalCancellationToken; + //AwaitingHandlersQueue = new ConcurrentDictionary>(); + //ExecutingHandlersPool = new LimitedDictionary(options.MaximumParallelWorkingHandlers); if (options.MaximumParallelWorkingHandlers != null) { - ExecutingHandlersSemaphore = new SemaphoreSlim(options.MaximumParallelWorkingHandlers ?? 0); - AwaitingHandlersQueuedEvent = new ManualResetEventSlim(false); + ExecutingHandlersSemaphore = new SemaphoreSlim(options.MaximumParallelWorkingHandlers.Value); + //AwaitingHandlersQueuedEvent = new ManualResetEventSlim(false); } + /* if (Options.MaximumParallelWorkingHandlers != null) HandlersCheckpoint(); + */ } /// - public void Enqueue(IEnumerable handlers) + public async Task Enqueue(IEnumerable handlers) { - handlers.ForEach(Enqueue); - } + //handlers.ForEach(Enqueue); + Result? lastResult = null; + foreach (DescribedHandlerInfo handlerInfo in handlers) + { + if (lastResult?.NextType != null) + { + if (lastResult.NextType != handlerInfo.HandlerInstance.GetType()) + continue; + } + + if (ExecutingHandlersSemaphore != null) + { + await ExecutingHandlersSemaphore.WaitAsync(); + } + + try + { + HandlerExecuting?.Invoke(handlerInfo); + lastResult = await handlerInfo.Execute(GlobalCancellationToken); + ExecutingHandlersSemaphore?.Release(1); + } + catch (OperationCanceledException) + { + break; + } + + if (!lastResult.RouteNext) + break; + } + } + + /* /// public void Enqueue(DescribedHandlerInfo handlerInfo) { + throw new NotImplementedException(); + if (Options.MaximumParallelWorkingHandlers == null) { Task.Run(async () => await ExecuteHandlerWrapper(handlerInfo)); @@ -111,7 +156,9 @@ namespace Telegrator.Polling ExecutingHandlersSemaphore.Release(1); } } + */ + /* /// /// Main checkpoint method that manages handler execution in a loop. /// Continuously processes queued handlers while respecting concurrency limits. @@ -206,6 +253,7 @@ namespace Telegrator.Polling return AwaitingHandlersQueue.TryDequeue(out enqueuedHandler); } } + */ /// /// Disposes of the handlers pool and releases all resources. @@ -221,11 +269,13 @@ namespace Telegrator.Polling ExecutingHandlersSemaphore = null!; } + /* if (AwaitingHandlersQueuedEvent != null) { AwaitingHandlersQueuedEvent.Dispose(); AwaitingHandlersQueuedEvent = null!; } + */ if (SyncObj != null) SyncObj = null!; diff --git a/Telegrator/Polling/UpdateRouter.cs b/Telegrator/Polling/UpdateRouter.cs index 691fc13..f75f49e 100644 --- a/Telegrator/Polling/UpdateRouter.cs +++ b/Telegrator/Polling/UpdateRouter.cs @@ -96,7 +96,7 @@ namespace Telegrator.Polling /// The update to handle. /// The cancellation token. /// A task representing the asynchronous update handling operation. - public virtual Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) + public virtual async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) { // Logging Alligator.RouterWriteLine("Received Update ({0}) of type \"{1}\"", update.Id, update.Type); @@ -109,31 +109,28 @@ namespace Telegrator.Polling if (handlers.Any()) { // Enqueuing found awiting handlers - HandlersPool.Enqueue(handlers); + await HandlersPool.Enqueue(handlers); // Chicking if awaiting handlers has exclusive routing if (Options.ExclusiveAwaitingHandlerRouting) { Alligator.RouterWriteLine("Receiving Update ({0}) completed with only awaiting handlers", update.Id); - return Task.CompletedTask; + return; } } // Queuing reagular handlers for execution - HandlersPool.Enqueue(GetHandlers(HandlersProvider, this, botClient, update, cancellationToken)); + await HandlersPool.Enqueue(GetHandlers(HandlersProvider, this, botClient, update, cancellationToken)); Alligator.RouterWriteLine("Receiving Update ({0}) finished", update.Id); - return Task.CompletedTask; } catch (OperationCanceledException) { Alligator.RouterWriteLine("Receiving Update ({0}) cancelled", update.Id); - return Task.CompletedTask; } catch (Exception ex) { Alligator.RouterWriteLine("Receiving Update ({0}) finished with exception {1}", update.Id, ex.Message); ExceptionHandler?.HandleException(botClient, ex, HandleErrorSource.PollingError, cancellationToken); - return Task.CompletedTask; } } @@ -162,10 +159,11 @@ namespace Telegrator.Polling return []; } - IEnumerable described = DescribeDescriptors(provider, descriptors, updateRouter, client, update, cancellationToken); - Alligator.RouterWriteLine("Described total of {0} handlers for Update ({1}) from {2} provider", described.Count(), update.Id, provider.GetType().Name); - Alligator.RouterWriteLine("Described handlers : {0}", string.Join(", ", described)); - return described; + //IEnumerable described = DescribeDescriptors(provider, descriptors, updateRouter, client, update, cancellationToken); + //Alligator.RouterWriteLine("Described total of {0} handlers for Update ({1}) from {2} provider", described.Count(), update.Id, provider.GetType().Name); + //Alligator.RouterWriteLine("Described handlers : {0}", string.Join(", ", described)); + + return DescribeDescriptors(provider, descriptors, updateRouter, client, update, cancellationToken); } /// @@ -192,8 +190,11 @@ namespace Telegrator.Polling continue; yield return describedHandler; + + /* if (Options.ExecuteOnlyFirstFoundHanlder) break; + */ } } finally diff --git a/Telegrator/TelegratorOptions.cs b/Telegrator/TelegratorOptions.cs index 6343b2d..0787b96 100644 --- a/Telegrator/TelegratorOptions.cs +++ b/Telegrator/TelegratorOptions.cs @@ -8,23 +8,24 @@ namespace Telegrator /// public class TelegratorOptions : ITelegratorOptions { + /* /// public bool ExecuteOnlyFirstFoundHanlder { get; set; } + /// + public bool DescendDescriptorIndex { get; set; } = true; + */ + /// public int? MaximumParallelWorkingHandlers { get; set; } /// public bool ExclusiveAwaitingHandlerRouting { get; set; } - /// - public CancellationToken GlobalCancellationToken { get; set; } - - /// - public bool DescendDescriptorIndex { get; set; } = true; - /// public bool ExceptIntersectingCommandAliases { get; set; } = true; + /// + public CancellationToken GlobalCancellationToken { get; set; } } } From 8e0d2719014bea6c2632d670a30e531efafe354d Mon Sep 17 00:00:00 2001 From: Rikitav Date: Sat, 2 Aug 2025 03:19:52 +0400 Subject: [PATCH 2/2] * Fixed method handlers behaviour --- .../TelegramBotWebHostBuilder.cs | 1 + Telegrator.Hosting/TelegramBotHostBuilder.cs | 2 +- Telegrator.sln | 8 ++++++++ .../Descriptors/DefaultCustomDescriptors.cs | 18 ++++++++++++++---- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs b/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs index 65b9402..bec7707 100644 --- a/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs +++ b/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs @@ -70,6 +70,7 @@ namespace Telegrator.Hosting.Web Services.Configure(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); } + Services.AddSingleton(Configuration); Services.AddSingleton>(Options.Create(_settings)); return new TelegramBotWebHost(_innerBuilder, _handlers); } diff --git a/Telegrator.Hosting/TelegramBotHostBuilder.cs b/Telegrator.Hosting/TelegramBotHostBuilder.cs index 0bba6bc..69a0606 100644 --- a/Telegrator.Hosting/TelegramBotHostBuilder.cs +++ b/Telegrator.Hosting/TelegramBotHostBuilder.cs @@ -47,7 +47,6 @@ namespace Telegrator.Hosting { _innerBuilder = hostApplicationBuilder; _settings = settings ?? new TelegramBotHostBuilderSettings(); - _handlers = new HostHandlersCollection(Services, _settings); _innerBuilder.Logging.ClearProviders(); @@ -78,6 +77,7 @@ namespace Telegrator.Hosting } Services.AddSingleton>(Options.Create(_settings)); + Services.AddSingleton(Configuration); return new TelegramBotHost(_innerBuilder, _handlers); } } diff --git a/Telegrator.sln b/Telegrator.sln index f9bed94..88c8e42 100644 --- a/Telegrator.sln +++ b/Telegrator.sln @@ -20,6 +20,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Analyzers", "Tel EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Hosting.Web", "Telegrator.Hosting.Web\Telegrator.Hosting.Web.csproj", "{98AB490F-6A36-CCFF-F6E6-B029D1665965}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SosalBot", "..\SosalBot\SosalBot\SosalBot.csproj", "{D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AnalyzersDebug|Any CPU = AnalyzersDebug|Any CPU @@ -63,6 +65,12 @@ Global {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Debug|Any CPU.Build.0 = Debug|Any CPU {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Release|Any CPU.ActiveCfg = Release|Any CPU {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Release|Any CPU.Build.0 = Release|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.AnalyzersDebug|Any CPU.ActiveCfg = Release|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.AnalyzersDebug|Any CPU.Build.0 = Release|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs b/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs index f8a7eaf..b34f4b4 100644 --- a/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs +++ b/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs @@ -15,6 +15,8 @@ namespace Telegrator.MadiatorCore.Descriptors /// public class MethodHandlerDescriptor : HandlerDescriptor where TUpdate : class { + private readonly MethodInfo Method; + /// /// Initializes new instance of /// @@ -28,13 +30,21 @@ namespace Telegrator.MadiatorCore.Descriptors UpdateType = handlerAttribute.Type; Indexer = handlerAttribute.GetIndexer(); Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters); - DisplayString = HandlerInspector.GetDisplayName(action.Method); - InstanceFactory = () => new MethodHandler(action.Method, UpdateType); + DisplayString = HandlerInspector.GetDisplayName(action.Method) ?? action.Method.Name; + Method = action.Method; + InstanceFactory = () => new MethodHandler(UpdateType); + LazyInitialization = handler => + { + if (handler is not MethodHandler methodHandler) + throw new InvalidDataException(); + + methodHandler.Method = Method; + }; } - private class MethodHandler(MethodInfo method, UpdateType updateType) : AbstractUpdateHandler(updateType) + private class MethodHandler(UpdateType updateType) : AbstractUpdateHandler(updateType) { - private readonly MethodInfo Method = method; + internal MethodInfo Method = null!; public override async Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) {