* 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
This commit is contained in:
2025-08-02 02:32:38 +04:00
parent b8e4398b50
commit 16d11990ec
26 changed files with 347 additions and 115 deletions
@@ -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")]
@@ -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")]
@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@@ -22,7 +21,7 @@ namespace Telegrator.Hosting.Web.Polling
private readonly ITelegramBotWebHost _botHost; private readonly ITelegramBotWebHost _botHost;
private readonly ITelegramBotClient _botClient; private readonly ITelegramBotClient _botClient;
private readonly IUpdateRouter _updateRouter; private readonly IUpdateRouter _updateRouter;
private readonly TelegramBotWebOptions _options; private readonly TelegratorWebOptions _options;
/// <summary> /// <summary>
/// Initiallizes new instance of <see cref="HostedUpdateWebhooker"/> /// Initiallizes new instance of <see cref="HostedUpdateWebhooker"/>
@@ -32,7 +31,7 @@ namespace Telegrator.Hosting.Web.Polling
/// <param name="updateRouter"></param> /// <param name="updateRouter"></param>
/// <param name="options"></param> /// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
public HostedUpdateWebhooker(ITelegramBotWebHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions<TelegramBotWebOptions> options) public HostedUpdateWebhooker(ITelegramBotWebHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions<TelegratorWebOptions> options)
{ {
if (string.IsNullOrEmpty(options.Value.WebhookUri)) if (string.IsNullOrEmpty(options.Value.WebhookUri))
throw new ArgumentNullException(nameof(options), "Option \"WebhookUrl\" must be set to subscribe for update recieving"); throw new ArgumentNullException(nameof(options), "Option \"WebhookUrl\" must be set to subscribe for update recieving");
+14 -4
View File
@@ -5,8 +5,10 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Text; using System.Text;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegrator.Configuration;
using Telegrator.Hosting.Components; using Telegrator.Hosting.Components;
using Telegrator.Hosting.Providers; using Telegrator.Hosting.Providers;
using Telegrator.Hosting.Web.Components; using Telegrator.Hosting.Web.Components;
@@ -22,7 +24,7 @@ namespace Telegrator.Hosting.Web
{ {
private readonly WebApplication _innerApp; private readonly WebApplication _innerApp;
private readonly IUpdateRouter _updateRouter; private readonly IUpdateRouter _updateRouter;
private readonly ILogger<TelegramBotHost> _logger; private readonly ILogger<TelegramBotWebHost> _logger;
private bool _disposed; private bool _disposed;
@@ -43,7 +45,7 @@ namespace Telegrator.Hosting.Web
/// <summary> /// <summary>
/// This application's logger /// This application's logger
/// </summary> /// </summary>
public ILogger<TelegramBotHost> Logger => _logger; public ILogger<TelegramBotWebHost> Logger => _logger;
// Private interface fields // Private interface fields
IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services; IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services;
@@ -53,12 +55,20 @@ namespace Telegrator.Hosting.Web
internal TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder, HostHandlersCollection handlers) internal TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder, HostHandlersCollection handlers)
{ {
// Registering this host in services for easy access
RegisterHostServices(webApplicationBuilder, handlers); RegisterHostServices(webApplicationBuilder, handlers);
// Building proxy application
_innerApp = webApplicationBuilder.Build(); _innerApp = webApplicationBuilder.Build();
_updateRouter = Services.GetRequiredService<IUpdateRouter>(); // Initializing bot info, as it requires to make a request via tg bot
_logger = Services.GetRequiredService<ILogger<TelegramBotHost>>(); Services.GetRequiredService<ITelegramBotInfo>();
// Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>();
_logger = Services.GetRequiredService<ILogger<TelegramBotWebHost>>();
// Logging registering handlers in DEBUG purposes
LogHandlers(handlers); LogHandlers(handlers);
} }
@@ -43,10 +43,6 @@ namespace Telegrator.Hosting.Web
_innerBuilder = webApplicationBuilder; _innerBuilder = webApplicationBuilder;
_settings = settings ?? throw new ArgumentNullException(nameof(settings)); _settings = settings ?? throw new ArgumentNullException(nameof(settings));
_handlers = new HostHandlersCollection(Services, _settings); _handlers = new HostHandlersCollection(Services, _settings);
Services.AddSingleton<IOptions<TelegramBotWebOptions>>(Options.Create(settings));
Services.Configure<TelegratorOptions>(Configuration.GetSection(nameof(TelegratorOptions)));
Services.Configure<TelegramBotClientOptions>(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy());
} }
/// <summary> /// <summary>
@@ -67,6 +63,14 @@ namespace Telegrator.Hosting.Web
} }
} }
if (!_settings.DisableAutoConfigure)
{
Services.Configure<TelegratorWebOptions>(Configuration.GetSection(nameof(TelegratorWebOptions)));
Services.Configure<TelegratorOptions>(Configuration.GetSection(nameof(TelegratorOptions)));
Services.Configure<TelegramBotClientOptions>(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy());
}
Services.AddSingleton<IOptions<TelegratorOptions>>(Options.Create(_settings));
return new TelegramBotWebHost(_innerBuilder, _handlers); return new TelegramBotWebHost(_innerBuilder, _handlers);
} }
} }
@@ -1,37 +1,18 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Options;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace Telegrator.Hosting.Web namespace Telegrator.Hosting.Web
{ {
/// <summary> /// <summary>
/// Configuration options for Telegram bot behavior and execution settings. /// Options for configuring the behavior for TelegramBotWebHost.
/// Controls various aspects of bot operation including concurrency, routing, webhook receiving, and execution policies.
/// </summary> /// </summary>
public class TelegramBotWebOptions : TelegratorOptions public class TelegramBotWebOptions : TelegratorOptions
{ {
/// <summary> /// <summary>
/// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration /// Disables automatic configuration for all of required <see cref="IOptions{TOptions}"/> instances
/// </summary> /// </summary>
[StringSyntax(StringSyntaxAttribute.Uri)] public bool DisableAutoConfigure { get; set; }
public required string WebhookUri { get; set; }
/// <summary>
/// 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.
/// </summary>
public string? SecretToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public int MaxConnections { get; set; } = 40;
/// <summary>
/// Pass true to drop all pending updates
/// </summary>
public bool DropPendingUpdates { get; set; }
/// <inheritdoc cref="WebApplicationOptions.Args"/> /// <inheritdoc cref="WebApplicationOptions.Args"/>
public string[]? Args { get; init; } public string[]? Args { get; init; }
@@ -0,0 +1,35 @@
using System.Diagnostics.CodeAnalysis;
namespace Telegrator.Hosting.Web
{
/// <summary>
/// Configuration options for Telegram bot behavior and execution settings.
/// Controls various aspects of bot operation including concurrency, routing, webhook receiving, and execution policies.
/// </summary>
public class TelegratorWebOptions
{
/// <summary>
/// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration
/// </summary>
[StringSyntax(StringSyntaxAttribute.Uri)]
public required string WebhookUri { get; set; }
/// <summary>
/// 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.
/// </summary>
public string? SecretToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public int MaxConnections { get; set; } = 40;
/// <summary>
/// Pass true to drop all pending updates
/// </summary>
public bool DropPendingUpdates { get; set; }
}
}
@@ -1,6 +1,5 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Telegrator.MadiatorCore.Descriptors;
using Telegrator.Polling; using Telegrator.Polling;
namespace Telegrator.Hosting.Polling namespace Telegrator.Hosting.Polling
@@ -9,12 +8,5 @@ namespace Telegrator.Hosting.Polling
public class HostUpdateHandlersPool(IOptions<TelegratorOptions> options, ILogger<HostUpdateHandlersPool> logger) : UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken) public class HostUpdateHandlersPool(IOptions<TelegratorOptions> options, ILogger<HostUpdateHandlersPool> logger) : UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken)
{ {
private readonly ILogger<HostUpdateHandlersPool> _logger = logger; private readonly ILogger<HostUpdateHandlersPool> _logger = logger;
/// <inheritdoc/>
protected override async Task ExecuteHandlerWrapper(DescribedHandlerInfo enqueuedHandler)
{
//_logger.LogInformation("Handler \"{0}\" has entered execution pool", enqueuedHandler.DisplayString);
await base.ExecuteHandlerWrapper(enqueuedHandler);
}
} }
} }
+3 -1
View File
@@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Polling; using Telegram.Bot.Polling;
using Telegrator.Hosting; using Telegrator.Hosting;
@@ -46,6 +47,7 @@ namespace Telegrator.Hosting
{ {
_innerBuilder = hostApplicationBuilder; _innerBuilder = hostApplicationBuilder;
_settings = settings ?? new TelegramBotHostBuilderSettings(); _settings = settings ?? new TelegramBotHostBuilderSettings();
_handlers = new HostHandlersCollection(Services, _settings); _handlers = new HostHandlersCollection(Services, _settings);
_innerBuilder.Logging.ClearProviders(); _innerBuilder.Logging.ClearProviders();
@@ -71,11 +73,11 @@ namespace Telegrator.Hosting
if (!_settings.DisableAutoConfigure) if (!_settings.DisableAutoConfigure)
{ {
Services.Configure<TelegratorOptions>(Configuration.GetSection(nameof(TelegratorOptions)));
Services.Configure<ReceiverOptions>(Configuration.GetSection(nameof(ReceiverOptions))); Services.Configure<ReceiverOptions>(Configuration.GetSection(nameof(ReceiverOptions)));
Services.Configure<TelegramBotClientOptions>(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); Services.Configure<TelegramBotClientOptions>(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy());
} }
Services.AddSingleton<IOptions<TelegratorOptions>>(Options.Create(_settings));
return new TelegramBotHost(_innerBuilder, _handlers); return new TelegramBotHost(_innerBuilder, _handlers);
} }
} }
+2 -2
View File
@@ -13,11 +13,11 @@ namespace Telegrator.Tests
{ {
public bool WasExecuted { get; private set; } public bool WasExecuted { get; private set; }
public override Task Execute(IAbstractHandlerContainer<Message> container, CancellationToken cancellationToken) public override Task<Result> Execute(IAbstractHandlerContainer<Message> container, CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
WasExecuted = true; WasExecuted = true;
return Task.CompletedTask; return Task.FromResult(Result.Ok());
} }
} }
} }
+12 -10
View File
@@ -6,11 +6,18 @@
/// </summary> /// </summary>
public interface ITelegratorOptions public interface ITelegratorOptions
{ {
/*
/// <summary> /// <summary>
/// Gets or sets a value indicating whether only the first found handler should be executed for each update. /// Gets or sets a value indicating whether only the first found handler should be executed for each update.
/// </summary> /// </summary>
public bool ExecuteOnlyFirstFoundHanlder { get; set; } public bool ExecuteOnlyFirstFoundHanlder { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to descend the indexr of handler's index on register. ('false' by default)
/// </summary>
public bool DescendDescriptorIndex { get; set; }
*/
/// <summary> /// <summary>
/// Gets or sets the maximum number of parallel working handlers. Null means no limit. /// Gets or sets the maximum number of parallel working handlers. Null means no limit.
/// </summary> /// </summary>
@@ -21,19 +28,14 @@
/// </summary> /// </summary>
public bool ExclusiveAwaitingHandlerRouting { get; set; } public bool ExclusiveAwaitingHandlerRouting { get; set; }
/// <summary>
/// Gets or sets the global cancellation token for all bot operations.
/// </summary>
public CancellationToken GlobalCancellationToken { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to descend the indexr of handler's index on register. ('false' by default)
/// </summary>
public bool DescendDescriptorIndex { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to exclude intersecting command aliases. /// Gets or sets a value indicating whether to exclude intersecting command aliases.
/// </summary> /// </summary>
public bool ExceptIntersectingCommandAliases { get; set; } public bool ExceptIntersectingCommandAliases { get; set; }
/// <summary>
/// Gets or sets the global cancellation token for all bot operations.
/// </summary>
public CancellationToken GlobalCancellationToken { get; set; }
} }
} }
@@ -51,10 +51,10 @@ namespace Telegrator.Handlers.Building
/// <param name="container">The handler container (unused).</param> /// <param name="container">The handler container (unused).</param>
/// <param name="cancellation">The cancellation token (unused).</param> /// <param name="cancellation">The cancellation token (unused).</param>
/// <returns>A completed task.</returns> /// <returns>A completed task.</returns>
protected override Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellation) protected override Task<Result> ExecuteInternal(IHandlerContainer container, CancellationToken cancellation)
{ {
ResetEvent.Set(); ResetEvent.Set();
return Task.CompletedTask; return Task.FromResult(Result.Ok());
} }
/// <summary> /// <summary>
@@ -31,7 +31,7 @@ namespace Telegrator.Handlers.Building
/// <param name="container">The handler container with execution context.</param> /// <param name="container">The handler container with execution context.</param>
/// <param name="cancellation">The cancellation token.</param> /// <param name="cancellation">The cancellation token.</param>
/// <returns>A task representing the asynchronous execution.</returns> /// <returns>A task representing the asynchronous execution.</returns>
public override Task Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation) public override Task<Result> Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation)
=> HandlerAction.Invoke(container, cancellation); => HandlerAction.Invoke(container, cancellation);
} }
} }
@@ -11,7 +11,7 @@ namespace Telegrator.Handlers.Building
/// <param name="container">The handler container with execution context.</param> /// <param name="container">The handler container with execution context.</param>
/// <param name="cancellation">The cancellation token.</param> /// <param name="cancellation">The cancellation token.</param>
/// <returns>A task representing the asynchronous execution.</returns> /// <returns>A task representing the asynchronous execution.</returns>
public delegate Task AbstractHandlerAction<TUpdate>(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation) where TUpdate : class; public delegate Task<Result> AbstractHandlerAction<TUpdate>(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation) where TUpdate : class;
/// <summary> /// <summary>
/// Builder class for creating regular handlers that can process updates. /// Builder class for creating regular handlers that can process updates.
@@ -74,10 +74,10 @@ namespace Telegrator.Handlers.Components
/// <param name="container">The handler container.</param> /// <param name="container">The handler container.</param>
/// <param name="cancellationToken">Cancellation token.</param> /// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns> /// <returns>A task representing the asynchronous operation.</returns>
protected override sealed async Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken) protected override sealed async Task<Result> ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken)
{ {
Container = (IAbstractHandlerContainer<TUpdate>)container; Container = (IAbstractHandlerContainer<TUpdate>)container;
await Execute(Container, cancellationToken); return await Execute(Container, cancellationToken);
} }
/// <summary> /// <summary>
@@ -86,6 +86,6 @@ namespace Telegrator.Handlers.Components
/// <param name="container">The handler container.</param> /// <param name="container">The handler container.</param>
/// <param name="cancellation">Cancellation token.</param> /// <param name="cancellation">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns> /// <returns>A task representing the asynchronous operation.</returns>
public abstract Task Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation); public abstract Task<Result> Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation);
} }
} }
@@ -118,13 +118,13 @@ namespace Telegrator.Handlers.Components
/// <param name="container">The handler container.</param> /// <param name="container">The handler container.</param>
/// <param name="cancellation">The cancellation token.</param> /// <param name="cancellation">The cancellation token.</param>
/// <exception cref="Exception">Thrown when no branch method is set.</exception> /// <exception cref="Exception">Thrown when no branch method is set.</exception>
public override async Task Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation) public override async Task<Result> Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation)
{ {
if (branchMethodInfo is null) if (branchMethodInfo is null)
throw new Exception(); throw new Exception();
Cancellation = cancellation; Cancellation = cancellation;
await BranchExecuteWrapper(container, branchMethodInfo); return await BranchExecuteWrapper(container, branchMethodInfo);
} }
/// <summary> /// <summary>
@@ -132,21 +132,20 @@ namespace Telegrator.Handlers.Components
/// </summary> /// </summary>
/// <param name="container">The handler container.</param> /// <param name="container">The handler container.</param>
/// <param name="methodInfo">The method to execute.</param> /// <param name="methodInfo">The method to execute.</param>
protected virtual async Task BranchExecuteWrapper(IAbstractHandlerContainer<TUpdate> container, MethodInfo methodInfo) protected virtual async Task<Result> BranchExecuteWrapper(IAbstractHandlerContainer<TUpdate> container, MethodInfo methodInfo)
{ {
if (methodInfo.ReturnType == typeof(void)) if (methodInfo.ReturnType == typeof(void))
{ {
methodInfo.Invoke(this, []); methodInfo.Invoke(this, []);
return; return Result.Ok();
} }
else else
{ {
object branchReturn = methodInfo.Invoke(this, []); object branchReturn = methodInfo.Invoke(this, []);
if (branchReturn == null) if (branchReturn is not Task<Result> branchTask)
return; throw new InvalidOperationException();
if (branchReturn is Task branchTask) return await branchTask;
await branchTask;
} }
} }
@@ -24,10 +24,16 @@ namespace Telegrator.Handlers.Components
/// <param name="container">The <see cref="IHandlerContainer"/> for the update.</param> /// <param name="container">The <see cref="IHandlerContainer"/> for the update.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task Execute(IHandlerContainer container, CancellationToken cancellationToken = default) public async Task<Result> Execute(IHandlerContainer container, CancellationToken cancellationToken = default)
{ {
await ExecuteInternal(container, cancellationToken); try
LifetimeToken.LifetimeEnded(); {
return await ExecuteInternal(container, cancellationToken);
}
finally
{
LifetimeToken.LifetimeEnded();
}
} }
/// <summary> /// <summary>
@@ -36,6 +42,6 @@ namespace Telegrator.Handlers.Components
/// <param name="container">The <see cref="IHandlerContainer"/> for the update.</param> /// <param name="container">The <see cref="IHandlerContainer"/> for the update.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected abstract Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken); protected abstract Task<Result> ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken);
} }
} }
+30
View File
@@ -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<T>()
=> new Result(true, true, typeof(T));
}
}
@@ -36,7 +36,7 @@ namespace Telegrator.MadiatorCore.Descriptors
{ {
private readonly MethodInfo Method = method; private readonly MethodInfo Method = method;
public override async Task Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation) public override async Task<Result> Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation)
{ {
if (Method is null) if (Method is null)
throw new Exception(); throw new Exception();
@@ -44,16 +44,15 @@ namespace Telegrator.MadiatorCore.Descriptors
if (Method.ReturnType == typeof(void)) if (Method.ReturnType == typeof(void))
{ {
Method.Invoke(this, [container, cancellation]); Method.Invoke(this, [container, cancellation]);
return; return Result.Ok();
} }
else else
{ {
object branchReturn = Method.Invoke(this, [container, cancellation]); object branchReturn = Method.Invoke(this, [container, cancellation]);
if (branchReturn == null) if (branchReturn is not Task<Result> branchTask)
return; throw new InvalidOperationException();
if (branchReturn is Task branchTask) return await branchTask;
await branchTask;
} }
} }
} }
@@ -2,6 +2,7 @@
using Telegram.Bot.Polling; using Telegram.Bot.Polling;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegrator.Filters.Components; using Telegrator.Filters.Components;
using Telegrator.Handlers;
using Telegrator.Handlers.Components; using Telegrator.Handlers.Components;
namespace Telegrator.MadiatorCore.Descriptors namespace Telegrator.MadiatorCore.Descriptors
@@ -81,36 +82,43 @@ namespace Telegrator.MadiatorCore.Descriptors
/// <param name="cancellationToken">Cancellation token.</param> /// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns> /// <returns>A task representing the asynchronous operation.</returns>
/// <exception cref="Exception">Thrown if the handler lifetime has ended or the handler is not a container factory.</exception> /// <exception cref="Exception">Thrown if the handler lifetime has ended or the handler is not a container factory.</exception>
public async Task Execute(CancellationToken cancellationToken) public async Task<Result> Execute(CancellationToken cancellationToken)
{ {
if (HandlerLifetime.IsEnded) if (HandlerLifetime.IsEnded)
throw new Exception(); throw new Exception();
IHandlerContainerFactory? containerFactory = HandlerInstance is IHandlerContainerFactory handlerDefainedContainerFactory
? handlerDefainedContainerFactory
: UpdateRouter.DefaultContainerFactory is not null
? UpdateRouter.DefaultContainerFactory
: throw new Exception();
try try
{ {
HandlerContainer = containerFactory.CreateContainer(UpdateRouter.AwaitingProvider, this); HandlerContainer = GetContainer(UpdateRouter.AwaitingProvider, this);
await HandlerInstance.Execute(HandlerContainer, cancellationToken); return await HandlerInstance.Execute(HandlerContainer, cancellationToken);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
// Cancelled // Cancelled
_ = 0xBAD + 0xC0DE; _ = 0xBAD + 0xC0DE;
return; return Result.Ok();
} }
catch (Exception exception) catch (Exception exception)
{ {
await UpdateRouter await UpdateRouter
.HandleErrorAsync(Client, exception, HandleErrorSource.HandleUpdateError, cancellationToken) .HandleErrorAsync(Client, exception, HandleErrorSource.HandleUpdateError, cancellationToken)
.ConfigureAwait(false); .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();
}
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
=> DisplayString ?? HandlerInstance.GetType().Name; => DisplayString ?? HandlerInstance.GetType().Name;
@@ -7,6 +7,7 @@ namespace Telegrator.MadiatorCore
/// </summary> /// </summary>
/// <param name="args">The <see cref="DescribedHandlerInfo"/> for the enqueued handler.</param> /// <param name="args">The <see cref="DescribedHandlerInfo"/> for the enqueued handler.</param>
public delegate void HandlerEnqueued(DescribedHandlerInfo args); public delegate void HandlerEnqueued(DescribedHandlerInfo args);
/// <summary> /// <summary>
/// Represents a delegate for when a handler is executing. /// Represents a delegate for when a handler is executing.
/// </summary> /// </summary>
@@ -24,7 +25,7 @@ namespace Telegrator.MadiatorCore
public event HandlerEnqueued? HandlerEnqueued; public event HandlerEnqueued? HandlerEnqueued;
/// <summary> /// <summary>
/// Occurs when a handler is executing. /// Occurs when a handler is entering execution.
/// </summary> /// </summary>
public event HandlerExecuting? HandlerExecuting; public event HandlerExecuting? HandlerExecuting;
@@ -32,8 +33,9 @@ namespace Telegrator.MadiatorCore
/// Enqueues a collection of handlers for execution. /// Enqueues a collection of handlers for execution.
/// </summary> /// </summary>
/// <param name="handlers">The handlers to enqueue.</param> /// <param name="handlers">The handlers to enqueue.</param>
public void Enqueue(IEnumerable<DescribedHandlerInfo> handlers); public Task Enqueue(IEnumerable<DescribedHandlerInfo> handlers);
/*
/// <summary> /// <summary>
/// Enqueues a single handler for execution. /// Enqueues a single handler for execution.
/// </summary> /// </summary>
@@ -45,5 +47,6 @@ namespace Telegrator.MadiatorCore
/// </summary> /// </summary>
/// <param name="token">The <see cref="HandlerLifetimeToken"/> of the handler to dequeue.</param> /// <param name="token">The <see cref="HandlerLifetimeToken"/> of the handler to dequeue.</param>
public void Dequeue(HandlerLifetimeToken token); public void Dequeue(HandlerLifetimeToken token);
*/
} }
} }
+52
View File
@@ -0,0 +1,52 @@
using System.Collections;
using System.Collections.Concurrent;
namespace Telegrator.Polling
{
public class LimitedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IDisposable
{
private readonly int? _maximum;
private readonly SemaphoreSlim _semaphore = null!;
private readonly ConcurrentDictionary<TKey, TValue> _dict = [];
public LimitedDictionary(int? maximum)
{
_maximum = maximum;
if (maximum != null)
{
int value = maximum.Value;
_semaphore = new SemaphoreSlim(value, value);
}
}
public async Task<bool> 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<KeyValuePair<TKey, TValue>> GetEnumerator() => _dict.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _dict.GetEnumerator();
/// <inheritdoc/>
public void Dispose()
{
GC.SuppressFinalize(this);
_semaphore.Dispose();
}
}
}
+42
View File
@@ -0,0 +1,42 @@
using System.Collections.Concurrent;
namespace Telegrator.Polling
{
public class LimitedQueue<T>
{
private readonly int? _maximum;
private readonly ConcurrentQueue<T> _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;
}
}
}
+55 -5
View File
@@ -1,4 +1,5 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using Telegrator.Handlers;
using Telegrator.MadiatorCore; using Telegrator.MadiatorCore;
using Telegrator.MadiatorCore.Descriptors; using Telegrator.MadiatorCore.Descriptors;
@@ -15,6 +16,7 @@ namespace Telegrator.Polling
/// </summary> /// </summary>
protected object SyncObj = new object(); protected object SyncObj = new object();
/*
/// <summary> /// <summary>
/// Event that signals when awaiting handlers are queued. /// Event that signals when awaiting handlers are queued.
/// </summary> /// </summary>
@@ -34,6 +36,13 @@ namespace Telegrator.Polling
/// Dictionary for tracking currently executing handlers. /// Dictionary for tracking currently executing handlers.
/// </summary> /// </summary>
protected readonly ConcurrentDictionary<HandlerLifetimeToken, Task> ExecutingHandlersPool = []; protected readonly ConcurrentDictionary<HandlerLifetimeToken, Task> ExecutingHandlersPool = [];
*/
//protected readonly ConcurrentDictionary<Type, LimitedQueue<DescribedHandlerInfo>> AwaitingHandlersQueue;
//protected readonly LimitedDictionary<HandlerLifetimeToken, Task> ExecutingHandlersPool;
protected SemaphoreSlim ExecutingHandlersSemaphore = null!;
/// <summary> /// <summary>
/// The bot configuration options. /// The bot configuration options.
@@ -65,26 +74,62 @@ namespace Telegrator.Polling
{ {
Options = options; Options = options;
GlobalCancellationToken = globalCancellationToken; GlobalCancellationToken = globalCancellationToken;
//AwaitingHandlersQueue = new ConcurrentDictionary<Type, LimitedQueue<DescribedHandlerInfo>>();
//ExecutingHandlersPool = new LimitedDictionary<HandlerLifetimeToken, Task>(options.MaximumParallelWorkingHandlers);
if (options.MaximumParallelWorkingHandlers != null) if (options.MaximumParallelWorkingHandlers != null)
{ {
ExecutingHandlersSemaphore = new SemaphoreSlim(options.MaximumParallelWorkingHandlers ?? 0); ExecutingHandlersSemaphore = new SemaphoreSlim(options.MaximumParallelWorkingHandlers.Value);
AwaitingHandlersQueuedEvent = new ManualResetEventSlim(false); //AwaitingHandlersQueuedEvent = new ManualResetEventSlim(false);
} }
/*
if (Options.MaximumParallelWorkingHandlers != null) if (Options.MaximumParallelWorkingHandlers != null)
HandlersCheckpoint(); HandlersCheckpoint();
*/
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Enqueue(IEnumerable<DescribedHandlerInfo> handlers) public async Task Enqueue(IEnumerable<DescribedHandlerInfo> 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;
}
}
/*
/// <inheritdoc/> /// <inheritdoc/>
public void Enqueue(DescribedHandlerInfo handlerInfo) public void Enqueue(DescribedHandlerInfo handlerInfo)
{ {
throw new NotImplementedException();
if (Options.MaximumParallelWorkingHandlers == null) if (Options.MaximumParallelWorkingHandlers == null)
{ {
Task.Run(async () => await ExecuteHandlerWrapper(handlerInfo)); Task.Run(async () => await ExecuteHandlerWrapper(handlerInfo));
@@ -111,7 +156,9 @@ namespace Telegrator.Polling
ExecutingHandlersSemaphore.Release(1); ExecutingHandlersSemaphore.Release(1);
} }
} }
*/
/*
/// <summary> /// <summary>
/// Main checkpoint method that manages handler execution in a loop. /// Main checkpoint method that manages handler execution in a loop.
/// Continuously processes queued handlers while respecting concurrency limits. /// Continuously processes queued handlers while respecting concurrency limits.
@@ -206,6 +253,7 @@ namespace Telegrator.Polling
return AwaitingHandlersQueue.TryDequeue(out enqueuedHandler); return AwaitingHandlersQueue.TryDequeue(out enqueuedHandler);
} }
} }
*/
/// <summary> /// <summary>
/// Disposes of the handlers pool and releases all resources. /// Disposes of the handlers pool and releases all resources.
@@ -221,11 +269,13 @@ namespace Telegrator.Polling
ExecutingHandlersSemaphore = null!; ExecutingHandlersSemaphore = null!;
} }
/*
if (AwaitingHandlersQueuedEvent != null) if (AwaitingHandlersQueuedEvent != null)
{ {
AwaitingHandlersQueuedEvent.Dispose(); AwaitingHandlersQueuedEvent.Dispose();
AwaitingHandlersQueuedEvent = null!; AwaitingHandlersQueuedEvent = null!;
} }
*/
if (SyncObj != null) if (SyncObj != null)
SyncObj = null!; SyncObj = null!;
+12 -11
View File
@@ -96,7 +96,7 @@ namespace Telegrator.Polling
/// <param name="update">The update to handle.</param> /// <param name="update">The update to handle.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous update handling operation.</returns> /// <returns>A task representing the asynchronous update handling operation.</returns>
public virtual Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) public virtual async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{ {
// Logging // Logging
Alligator.RouterWriteLine("Received Update ({0}) of type \"{1}\"", update.Id, update.Type); Alligator.RouterWriteLine("Received Update ({0}) of type \"{1}\"", update.Id, update.Type);
@@ -109,31 +109,28 @@ namespace Telegrator.Polling
if (handlers.Any()) if (handlers.Any())
{ {
// Enqueuing found awiting handlers // Enqueuing found awiting handlers
HandlersPool.Enqueue(handlers); await HandlersPool.Enqueue(handlers);
// Chicking if awaiting handlers has exclusive routing // Chicking if awaiting handlers has exclusive routing
if (Options.ExclusiveAwaitingHandlerRouting) if (Options.ExclusiveAwaitingHandlerRouting)
{ {
Alligator.RouterWriteLine("Receiving Update ({0}) completed with only awaiting handlers", update.Id); Alligator.RouterWriteLine("Receiving Update ({0}) completed with only awaiting handlers", update.Id);
return Task.CompletedTask; return;
} }
} }
// Queuing reagular handlers for execution // 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); Alligator.RouterWriteLine("Receiving Update ({0}) finished", update.Id);
return Task.CompletedTask;
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
Alligator.RouterWriteLine("Receiving Update ({0}) cancelled", update.Id); Alligator.RouterWriteLine("Receiving Update ({0}) cancelled", update.Id);
return Task.CompletedTask;
} }
catch (Exception ex) catch (Exception ex)
{ {
Alligator.RouterWriteLine("Receiving Update ({0}) finished with exception {1}", update.Id, ex.Message); Alligator.RouterWriteLine("Receiving Update ({0}) finished with exception {1}", update.Id, ex.Message);
ExceptionHandler?.HandleException(botClient, ex, HandleErrorSource.PollingError, cancellationToken); ExceptionHandler?.HandleException(botClient, ex, HandleErrorSource.PollingError, cancellationToken);
return Task.CompletedTask;
} }
} }
@@ -162,10 +159,11 @@ namespace Telegrator.Polling
return []; return [];
} }
IEnumerable<DescribedHandlerInfo> described = DescribeDescriptors(provider, descriptors, updateRouter, client, update, cancellationToken); //IEnumerable<DescribedHandlerInfo> 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 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)); //Alligator.RouterWriteLine("Described handlers : {0}", string.Join(", ", described));
return described;
return DescribeDescriptors(provider, descriptors, updateRouter, client, update, cancellationToken);
} }
/// <summary> /// <summary>
@@ -192,8 +190,11 @@ namespace Telegrator.Polling
continue; continue;
yield return describedHandler; yield return describedHandler;
/*
if (Options.ExecuteOnlyFirstFoundHanlder) if (Options.ExecuteOnlyFirstFoundHanlder)
break; break;
*/
} }
} }
finally finally
+7 -6
View File
@@ -8,23 +8,24 @@ namespace Telegrator
/// </summary> /// </summary>
public class TelegratorOptions : ITelegratorOptions public class TelegratorOptions : ITelegratorOptions
{ {
/*
/// <inheritdoc/> /// <inheritdoc/>
public bool ExecuteOnlyFirstFoundHanlder { get; set; } public bool ExecuteOnlyFirstFoundHanlder { get; set; }
/// <inheritdoc/>
public bool DescendDescriptorIndex { get; set; } = true;
*/
/// <inheritdoc/> /// <inheritdoc/>
public int? MaximumParallelWorkingHandlers { get; set; } public int? MaximumParallelWorkingHandlers { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public bool ExclusiveAwaitingHandlerRouting { get; set; } public bool ExclusiveAwaitingHandlerRouting { get; set; }
/// <inheritdoc/>
public CancellationToken GlobalCancellationToken { get; set; }
/// <inheritdoc/>
public bool DescendDescriptorIndex { get; set; } = true;
/// <inheritdoc/> /// <inheritdoc/>
public bool ExceptIntersectingCommandAliases { get; set; } = true; public bool ExceptIntersectingCommandAliases { get; set; } = true;
/// <inheritdoc/>
public CancellationToken GlobalCancellationToken { get; set; }
} }
} }