diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1300b5f4d75..86df2d87485 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -51,11 +51,14 @@ jobs: timeout-minutes: 50 if: ${{ !github.event.pull_request.draft }} steps: + - uses: jlumbroso/free-disk-space@main + - uses: PSModule/install-powershell@v1 + with: + Version: latest - uses: actions/checkout@v2 - uses: actions/setup-dotnet@master with: dotnet-version: 10.0.x - - name: Build All run: ./build-all.ps1 working-directory: ./build diff --git a/Directory.Packages.props b/Directory.Packages.props index b2d490316cb..55b29df261f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -182,6 +182,10 @@ + + + + diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 62023f40876..4969fe54306 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -571,6 +571,10 @@ { "text": "Quartz Integration", "path": "framework/infrastructure/background-jobs/quartz.md" + }, + { + "text": "TickerQ Integration", + "path": "framework/infrastructure/background-jobs/tickerq.md" } ] }, @@ -589,6 +593,10 @@ { "text": "Hangfire Integration", "path": "framework/infrastructure/background-workers/hangfire.md" + }, + { + "text": "TickerQ Integration", + "path": "framework/infrastructure/background-workers/tickerq.md" } ] }, diff --git a/docs/en/framework/infrastructure/background-jobs/index.md b/docs/en/framework/infrastructure/background-jobs/index.md index af0c33de217..3b5628d39f2 100644 --- a/docs/en/framework/infrastructure/background-jobs/index.md +++ b/docs/en/framework/infrastructure/background-jobs/index.md @@ -348,6 +348,7 @@ See pre-built job manager alternatives: * [Hangfire Background Job Manager](./hangfire.md) * [RabbitMQ Background Job Manager](./rabbitmq.md) * [Quartz Background Job Manager](./quartz.md) +* [TickerQ Background Job Manager](./tickerq.md) ## See Also * [Background Workers](../background-workers) \ No newline at end of file diff --git a/docs/en/framework/infrastructure/background-jobs/tickerq.md b/docs/en/framework/infrastructure/background-jobs/tickerq.md new file mode 100644 index 00000000000..013a8fde746 --- /dev/null +++ b/docs/en/framework/infrastructure/background-jobs/tickerq.md @@ -0,0 +1,125 @@ +# TickerQ Background Job Manager + +[TickerQ](https://tickerq.net/) is a fast, reflection-free background task scheduler for .NET — built with source generators, EF Core integration, cron + time-based execution, and a real-time dashboard. You can integrate TickerQ with the ABP to use it instead of the [default background job manager](../background-jobs). In this way, you can use the same background job API for TickerQ and your code will be independent of TickerQ. If you like, you can directly use TickerQ's API, too. + +> See the [background jobs document](../background-jobs) to learn how to use the background job system. This document only shows how to install and configure the TickerQ integration. + +## Installation + +It is suggested to use the [ABP CLI](../../../cli) to install this package. + +### Using the ABP CLI + +Open a command line window in the folder of the project (.csproj file) and type the following command: + +````bash +abp add-package Volo.Abp.BackgroundJobs.TickerQ +```` + +> If you haven't done it yet, you first need to install the [ABP CLI](../../../cli). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundJobs.TickerQ). + +## Configuration + +### AddTickerQ + +You can call the `AddTickerQ` extension method in the `ConfigureServices` method of your module to configure TickerQ services: + +> This is optional. ABP will automatically register TickerQ services. + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + context.Services.AddTickerQ(x => + { + // Configure TickerQ options here + }); +} +``` + +### UseAbpTickerQ + +You need to call the `UseAbpTickerQ` extension method instead of `AddTickerQ` in the `OnApplicationInitialization` method of your module: + +```csharp +// (default: TickerQStartMode.Immediate) +app.UseAbpTickerQ(startMode: ...); +``` + +### AbpBackgroundJobsTickerQOptions + +You can configure the `TimeTicker` properties for specific jobs. For example, you can change `Priority`, `Retries` and `RetryIntervals` properties as shown below: + +```csharp +Configure(options => +{ + options.AddJobConfiguration(new AbpBackgroundJobsTimeTickerConfiguration() + { + Retries = 3, + RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min + Priority = TickerTaskPriority.High + + // Optional batching + //BatchParent = Guid.Parse("...."), + //BatchRunCondition = BatchRunCondition.OnSuccess + }); + + options.AddJobConfiguration(new AbpBackgroundJobsTimeTickerConfiguration() + { + Retries = 5, + RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min + Priority = TickerTaskPriority.Normal + }); +}); +``` + +### Add your own TickerQ Background Jobs Definitions + +ABP will handle the TickerQ job definitions by `AbpTickerQFunctionProvider` service. You shouldn't use `TickerFunction` to add your own job definitions. You can inject and use the `AbpTickerQFunctionProvider` to add your own definitions and use `ITimeTickerManager` or `ICronTickerManager` to manage the jobs. + +For example, you can add a `CleanupJobs` job definition in the `OnPreApplicationInitializationAsync` method of your module: + +```csharp +public class CleanupJobs +{ + public async Task CleanupLogsAsync(TickerFunctionContext tickerContext, CancellationToken cancellationToken) + { + var logFileName = tickerContext.Request; + Console.WriteLine($"Cleaning up log file: {logFileName} at {DateTime.Now}"); + } +} +``` + +```csharp +public override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context) +{ + var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService(); + abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => + { + var service = new CleanupJobs(); // Or get it from the serviceProvider + var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); + var genericContext = new TickerFunctionContext(tickerFunctionContext, request); + await service.CleanupLogsAsync(genericContext, cancellationToken); + }))); + abpTickerQFunctionProvider.RequestTypes.TryAdd(nameof(CleanupJobs), (typeof(string).FullName, typeof(string))); + return Task.CompletedTask; +} +``` + +And then you can add a job by using the `ITimeTickerManager`: + +```csharp +var timeTickerManager = context.ServiceProvider.GetRequiredService>(); +await timeTickerManager.AddAsync(new TimeTicker +{ + Function = nameof(CleanupJobs), + ExecutionTime = DateTime.UtcNow.AddSeconds(5), + Request = TickerHelper.CreateTickerRequest("cleanup_example_file.txt"), + Retries = 3, + RetryIntervals = new[] { 30, 60, 120 }, // Retry after 30s, 60s, then 2min +}); +``` + +### TickerQ Dashboard and EF Core Integration + +You can install the [TickerQ dashboard](https://tickerq.net/setup/dashboard.html) and [Entity Framework Core](https://tickerq.net/setup/tickerq-ef-core.html) integration by its documentation. There is no specific configuration needed for the ABP integration. + diff --git a/docs/en/framework/infrastructure/background-workers/index.md b/docs/en/framework/infrastructure/background-workers/index.md index 6ab88a9e534..498a52ed9bb 100644 --- a/docs/en/framework/infrastructure/background-workers/index.md +++ b/docs/en/framework/infrastructure/background-workers/index.md @@ -41,7 +41,7 @@ Start your worker in the `StartAsync` (which is called when the application begi Assume that we want to make a user passive, if the user has not logged in to the application in last 30 days. `AsyncPeriodicBackgroundWorkerBase` class simplifies to create periodic workers, so we will use it for the example below: -> You can use `CronExpression` property to set the cron expression for the background worker if you will use the [Hangfire Background Worker Manager](./hangfire.md) or [Quartz Background Worker Manager](./quartz.md). +> You can use `CronExpression` property to set the cron expression for the background worker if you will use the [Hangfire Background Worker Manager](./hangfire.md), [Quartz Background Worker Manager](./quartz.md), or [TickerQ Background Worker Manager](./tickerq.md). ````csharp public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase @@ -216,7 +216,8 @@ Background worker system is extensible and you can change the default background See pre-built worker manager alternatives: * [Quartz Background Worker Manager](./quartz.md) -* [Hangfire Background Worker Manager](./hangfire.md) +* [Hangfire Background Worker Manager](./hangfire.md) +* [TickerQ Background Worker Manager](./tickerq.md) ## See Also diff --git a/docs/en/framework/infrastructure/background-workers/tickerq.md b/docs/en/framework/infrastructure/background-workers/tickerq.md new file mode 100644 index 00000000000..840b5137cbb --- /dev/null +++ b/docs/en/framework/infrastructure/background-workers/tickerq.md @@ -0,0 +1,119 @@ +# TickerQ Background Worker Manager + +[TickerQ](https://tickerq.net/) is a fast, reflection-free background task scheduler for .NET — built with source generators, EF Core integration, cron + time-based execution, and a real-time dashboard. You can integrate TickerQ with the ABP to use it instead of the [default background worker manager](../background-workers). + +## Installation + +It is suggested to use the [ABP CLI](../../../cli) to install this package. + +### Using the ABP CLI + +Open a command line window in the folder of the project (.csproj file) and type the following command: + +````bash +abp add-package Volo.Abp.BackgroundWorkers.TickerQ +```` + +> If you haven't done it yet, you first need to install the [ABP CLI](../../../cli). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundWorkers.TickerQ). + +## Configuration + +### AddTickerQ + +You can call the `AddTickerQ` extension method in the `ConfigureServices` method of your module to configure TickerQ services: + +> This is optional. ABP will automatically register TickerQ services. + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + context.Services.AddTickerQ(x => + { + // Configure TickerQ options here + }); +} +``` + +### UseAbpTickerQ + +You need to call the `UseAbpTickerQ` extension method instead of `AddTickerQ` in the `OnApplicationInitialization` method of your module: + +```csharp +// (default: TickerQStartMode.Immediate) +app.UseAbpTickerQ(startMode: ...); +``` + +### AbpBackgroundWorkersTickerQOptions + +You can configure the `CronTicker` properties for specific jobs. For example, Change `Priority`, `Retries` and `RetryIntervals` properties: + +```csharp +Configure(options => +{ + options.AddConfiguration(new AbpBackgroundWorkersCronTickerConfiguration() + { + Retries = 3, + RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min, + Priority = TickerTaskPriority.High + }); +}); +``` + +### Add your own TickerQ Background Worker Definitions + +ABP will handle the TickerQ job definitions by `AbpTickerQFunctionProvider` service. You shouldn't use `TickerFunction` to add your own job definitions. You can inject and use the `AbpTickerQFunctionProvider` to add your own definitions and use `ITimeTickerManager` or `ICronTickerManager` to manage the jobs. + +For example, you can add a `CleanupJobs` job definition in the `OnPreApplicationInitializationAsync` method of your module: + +```csharp +public class CleanupJobs +{ + public async Task CleanupLogsAsync(TickerFunctionContext tickerContext, CancellationToken cancellationToken) + { + var logFileName = tickerContext.Request; + Console.WriteLine($"Cleaning up log file: {logFileName} at {DateTime.Now}"); + } +} +``` + +```csharp +public override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context) +{ + var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService(); + abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => + { + var service = new CleanupJobs(); // Or get it from the serviceProvider + var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); + var genericContext = new TickerFunctionContext(tickerFunctionContext, request); + await service.CleanupLogsAsync(genericContext, cancellationToken); + }))); + abpTickerQFunctionProvider.RequestTypes.TryAdd(nameof(CleanupJobs), (typeof(string).FullName, typeof(string))); + return Task.CompletedTask; +} +``` + +And then you can add a job by using the `ICronTickerManager`: + +```csharp +var cronTickerManager = context.ServiceProvider.GetRequiredService>(); +await cronTickerManager.AddAsync(new CronTicker +{ + Function = nameof(CleanupJobs), + Expression = "0 */6 * * *", // Every 6 hours + Request = TickerHelper.CreateTickerRequest("cleanup_example_file.txt"), + Retries = 2, + RetryIntervals = new[] { 60, 300 } +}); +``` + +You can specify a cron expression instead of use `ICronTickerManager` to add a worker: + +```csharp +abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => +{ + var service = new CleanupJobs(); + var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); + var genericContext = new TickerFunctionContext(tickerFunctionContext, request); + await service.CleanupLogsAsync(genericContext, cancellationToken); +}))); +``` diff --git a/framework/Volo.Abp.slnx b/framework/Volo.Abp.slnx index ab63be61ead..f4c0d2b3739 100644 --- a/framework/Volo.Abp.slnx +++ b/framework/Volo.Abp.slnx @@ -166,6 +166,9 @@ + + + @@ -252,4 +255,4 @@ - + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xml b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xml new file mode 100644 index 00000000000..1715698ccd2 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xsd b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xsd new file mode 100644 index 00000000000..ffa6fc4b782 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj new file mode 100644 index 00000000000..df450f5ff6d --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj @@ -0,0 +1,24 @@ + + + + + + + netstandard2.1;net8.0;net9.0;net10.0 + enable + Nullable + Volo.Abp.BackgroundJobs.TickerQ + Volo.Abp.BackgroundJobs.TickerQ + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs new file mode 100644 index 00000000000..b5ed5989324 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using TickerQ.Utilities; +using TickerQ.Utilities.Enums; +using Volo.Abp.Modularity; +using Volo.Abp.TickerQ; + +namespace Volo.Abp.BackgroundJobs.TickerQ; + +[DependsOn( + typeof(AbpBackgroundJobsAbstractionsModule), + typeof(AbpTickerQModule) +)] +public class AbpBackgroundJobsTickerQModule : AbpModule +{ + private static readonly MethodInfo GetTickerFunctionDelegateMethod = + typeof(AbpBackgroundJobsTickerQModule).GetMethod(nameof(GetTickerFunctionDelegate), BindingFlags.NonPublic | BindingFlags.Static)!; + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var abpBackgroundJobOptions = context.ServiceProvider.GetRequiredService>(); + var abpBackgroundJobsTickerQOptions = context.ServiceProvider.GetRequiredService>(); + var tickerFunctionDelegates = new Dictionary(); + var requestTypes = new Dictionary(); + foreach (var jobConfiguration in abpBackgroundJobOptions.Value.GetJobs()) + { + var genericMethod = GetTickerFunctionDelegateMethod.MakeGenericMethod(jobConfiguration.ArgsType); + var tickerFunctionDelegate = (TickerFunctionDelegate)genericMethod.Invoke(null, [jobConfiguration.ArgsType])!; + var config = abpBackgroundJobsTickerQOptions.Value.GetConfigurationOrNull(jobConfiguration.JobType); + tickerFunctionDelegates.TryAdd(jobConfiguration.JobName, (string.Empty, config?.Priority ?? TickerTaskPriority.Normal, tickerFunctionDelegate)); + requestTypes.TryAdd(jobConfiguration.JobName, (jobConfiguration.ArgsType.FullName, jobConfiguration.ArgsType)!); + } + + var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService(); + foreach (var functionDelegate in tickerFunctionDelegates) + { + abpTickerQFunctionProvider.Functions.TryAdd(functionDelegate.Key, functionDelegate.Value); + } + + foreach (var requestType in requestTypes) + { + abpTickerQFunctionProvider.RequestTypes.TryAdd(requestType.Key, requestType.Value); + } + } + + private static TickerFunctionDelegate GetTickerFunctionDelegate(Type argsType) + { + return async (cancellationToken, serviceProvider, context) => + { + var options = serviceProvider.GetRequiredService>().Value; + if (!options.IsJobExecutionEnabled) + { + throw new AbpException( + "Background job execution is disabled. " + + "This method should not be called! " + + "If you want to enable the background job execution, " + + $"set {nameof(AbpBackgroundJobOptions)}.{nameof(AbpBackgroundJobOptions.IsJobExecutionEnabled)} to true! " + + "If you've intentionally disabled job execution and this seems a bug, please report it." + ); + } + + using (var scope = serviceProvider.CreateScope()) + { + var jobExecuter = serviceProvider.GetRequiredService(); + var args = await TickerRequestProvider.GetRequestAsync(serviceProvider, context.Id, context.Type); + var jobType = options.GetJob(typeof(TArgs)).JobType; + var jobExecutionContext = new JobExecutionContext(scope.ServiceProvider, jobType, args!, cancellationToken: cancellationToken); + await jobExecuter.ExecuteAsync(jobExecutionContext); + } + }; + } +} diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQOptions.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQOptions.cs new file mode 100644 index 00000000000..f85b3e5feff --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQOptions.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.BackgroundJobs.TickerQ; + +public class AbpBackgroundJobsTickerQOptions +{ + private readonly Dictionary _configurations; + + public AbpBackgroundJobsTickerQOptions() + { + _configurations = new Dictionary(); + } + + public void AddConfiguration(AbpBackgroundJobsTimeTickerConfiguration configuration) + { + AddConfiguration(typeof(TJob), configuration); + } + + public void AddConfiguration(Type jobType, AbpBackgroundJobsTimeTickerConfiguration configuration) + { + _configurations[jobType] = configuration; + } + + public AbpBackgroundJobsTimeTickerConfiguration? GetConfigurationOrNull() + { + return GetConfigurationOrNull(typeof(TJob)); + } + + public AbpBackgroundJobsTimeTickerConfiguration? GetConfigurationOrNull(Type jobType) + { + return _configurations.GetValueOrDefault(jobType); + } +} diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTimeTickerConfiguration.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTimeTickerConfiguration.cs new file mode 100644 index 00000000000..65b150ea483 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTimeTickerConfiguration.cs @@ -0,0 +1,17 @@ +using System; +using TickerQ.Utilities.Enums; + +namespace Volo.Abp.BackgroundJobs.TickerQ; + +public class AbpBackgroundJobsTimeTickerConfiguration +{ + public int? Retries { get; set; } + + public int[]? RetryIntervals { get; set; } + + public TickerTaskPriority? Priority { get; set; } + + public Guid? BatchParent { get; set; } + + public BatchRunCondition? BatchRunCondition { get; set; } +} diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs new file mode 100644 index 00000000000..b1a2dbe36dc --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using TickerQ.Utilities; +using TickerQ.Utilities.Interfaces.Managers; +using TickerQ.Utilities.Models.Ticker; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BackgroundJobs.TickerQ; + +[Dependency(ReplaceServices = true)] +public class AbpTickerQBackgroundJobManager : IBackgroundJobManager, ITransientDependency +{ + protected ITimeTickerManager TimeTickerManager { get; } + protected AbpBackgroundJobOptions Options { get; } + protected AbpBackgroundJobsTickerQOptions TickerQOptions { get; } + + public AbpTickerQBackgroundJobManager( + ITimeTickerManager timeTickerManager, + IOptions options, + IOptions tickerQOptions) + { + TimeTickerManager = timeTickerManager; + Options = options.Value; + TickerQOptions = tickerQOptions.Value; + } + + public virtual async Task EnqueueAsync(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) + { + var job = Options.GetJob(typeof(TArgs)); + var timeTicker = new TimeTicker + { + Id = Guid.NewGuid(), + Function = job.JobName, + ExecutionTime = delay == null ? DateTime.UtcNow : DateTime.UtcNow.Add(delay.Value), + Request = TickerHelper.CreateTickerRequest(args), + }; + + var config = TickerQOptions.GetConfigurationOrNull(job.JobType); + if (config != null) + { + timeTicker.Retries = config.Retries ?? timeTicker.Retries; + timeTicker.RetryIntervals = config.RetryIntervals ?? timeTicker.RetryIntervals; + timeTicker.BatchParent = config.BatchParent ?? timeTicker.BatchParent; + timeTicker.BatchRunCondition = config.BatchRunCondition ?? timeTicker.BatchRunCondition; + } + + var result = await TimeTickerManager.AddAsync(timeTicker); + return !result.IsSucceded ? timeTicker.Id.ToString() : result.Result.Id.ToString(); + } +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xml b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xml new file mode 100644 index 00000000000..1715698ccd2 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xsd b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xsd new file mode 100644 index 00000000000..ffa6fc4b782 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj new file mode 100644 index 00000000000..2c4ab29c97e --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj @@ -0,0 +1,24 @@ + + + + + + + netstandard2.1;net8.0;net9.0;net10.0 + enable + Nullable + Volo.Abp.BackgroundWorkers.TickerQ + Volo.Abp.BackgroundWorkers.TickerQ + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersCronTickerConfiguration.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersCronTickerConfiguration.cs new file mode 100644 index 00000000000..0e8ed89a145 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersCronTickerConfiguration.cs @@ -0,0 +1,12 @@ +using TickerQ.Utilities.Enums; + +namespace Volo.Abp.BackgroundWorkers.TickerQ; + +public class AbpBackgroundWorkersCronTickerConfiguration +{ + public int? Retries { get; set; } + + public int[]? RetryIntervals { get; set; } + + public TickerTaskPriority? Priority { get; set; } +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs new file mode 100644 index 00000000000..3fb15a50b83 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using TickerQ.Utilities.Interfaces.Managers; +using TickerQ.Utilities.Models.Ticker; +using Volo.Abp.Modularity; +using Volo.Abp.TickerQ; + +namespace Volo.Abp.BackgroundWorkers.TickerQ; + +[DependsOn(typeof(AbpBackgroundWorkersModule), typeof(AbpTickerQModule))] +public class AbpBackgroundWorkersTickerQModule : AbpModule +{ + public override async Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context) + { + var abpTickerQBackgroundWorkersProvider = context.ServiceProvider.GetRequiredService(); + var cronTickerManager = context.ServiceProvider.GetRequiredService>(); + var abpBackgroundWorkersTickerQOptions = context.ServiceProvider.GetRequiredService>().Value; + foreach (var backgroundWorker in abpTickerQBackgroundWorkersProvider.BackgroundWorkers) + { + var cronTicker = new CronTicker + { + Function = backgroundWorker.Value.Function, + Expression = backgroundWorker.Value.CronExpression + }; + + var config = abpBackgroundWorkersTickerQOptions.GetConfigurationOrNull(backgroundWorker.Value.WorkerType); + if (config != null) + { + cronTicker.Retries = config.Retries ?? cronTicker.Retries; + cronTicker.RetryIntervals = config.RetryIntervals ?? cronTicker.RetryIntervals; + } + + await cronTickerManager.AddAsync(cronTicker); + } + } +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs new file mode 100644 index 00000000000..6d48a212626 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.BackgroundWorkers.TickerQ; + +public class AbpBackgroundWorkersTickerQOptions +{ + private readonly Dictionary _onfigurations; + + public AbpBackgroundWorkersTickerQOptions() + { + _onfigurations = new Dictionary(); + } + + public void AddConfiguration(AbpBackgroundWorkersCronTickerConfiguration configuration) + { + AddConfiguration(typeof(TWorker), configuration); + } + + public void AddConfiguration(Type workerType, AbpBackgroundWorkersCronTickerConfiguration configuration) + { + _onfigurations[workerType] = configuration; + } + + public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull() + { + return GetConfigurationOrNull(typeof(TJob)); + } + + public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull(Type workerType) + { + return _onfigurations.GetValueOrDefault(workerType); + } +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkerManager.cs new file mode 100644 index 00000000000..922cad294dd --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkerManager.cs @@ -0,0 +1,105 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using TickerQ.Utilities.Enums; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; +using Volo.Abp.TickerQ; + +namespace Volo.Abp.BackgroundWorkers.TickerQ; + +[Dependency(ReplaceServices = true)] +public class AbpTickerQBackgroundWorkerManager : BackgroundWorkerManager, ISingletonDependency +{ + protected AbpTickerQFunctionProvider AbpTickerQFunctionProvider { get; } + protected AbpTickerQBackgroundWorkersProvider AbpTickerQBackgroundWorkersProvider { get; } + protected AbpBackgroundWorkersTickerQOptions Options { get; } + + public AbpTickerQBackgroundWorkerManager( + AbpTickerQFunctionProvider abpTickerQFunctionProvider, + AbpTickerQBackgroundWorkersProvider abpTickerQBackgroundWorkersProvider, + IOptions options) + { + AbpTickerQFunctionProvider = abpTickerQFunctionProvider; + AbpTickerQBackgroundWorkersProvider = abpTickerQBackgroundWorkersProvider; + Options = options.Value; + } + + public override async Task AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken = default) + { + if (worker is AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase) + { + int? period = null; + string? cronExpression = null; + + if (worker is AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase) + { + period = asyncPeriodicBackgroundWorkerBase.Period; + cronExpression = asyncPeriodicBackgroundWorkerBase.CronExpression; + } + else if (worker is PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase) + { + period = periodicBackgroundWorkerBase.Period; + cronExpression = periodicBackgroundWorkerBase.CronExpression; + } + + if (period == null && cronExpression.IsNullOrWhiteSpace()) + { + throw new AbpException($"Both 'Period' and 'CronExpression' are not set for {worker.GetType().FullName}. You must set at least one of them."); + } + + cronExpression = cronExpression ?? GetCron(period!.Value); + var name = BackgroundWorkerNameAttribute.GetNameOrNull(worker.GetType()) ?? worker.GetType().FullName; + + var config = Options.GetConfigurationOrNull(ProxyHelper.GetUnProxiedType(worker)); + AbpTickerQFunctionProvider.Functions.TryAdd(name!, (string.Empty, config?.Priority ?? TickerTaskPriority.LongRunning, async (tickerQCancellationToken, serviceProvider, tickerFunctionContext) => + { + var workerInvoker = new AbpTickerQPeriodicBackgroundWorkerInvoker(worker, serviceProvider); + await workerInvoker.DoWorkAsync(tickerFunctionContext, tickerQCancellationToken); + })); + + AbpTickerQBackgroundWorkersProvider.BackgroundWorkers.Add(name!, new AbpTickerQCronBackgroundWorker + { + Function = name!, + CronExpression = cronExpression, + WorkerType = ProxyHelper.GetUnProxiedType(worker) + }); + } + + await base.AddAsync(worker, cancellationToken); + } + + protected virtual string GetCron(int period) + { + var time = TimeSpan.FromMilliseconds(period); + if (time.TotalMinutes < 1) + { + // Less than 1 minute — 5-field cron doesn't support seconds, so run every minute + return "* * * * *"; + } + + if (time.TotalMinutes < 60) + { + // Run every N minutes + var minutes = (int)Math.Round(time.TotalMinutes); + return $"*/{minutes} * * * *"; + } + + if (time.TotalHours < 24) + { + // Run every N hours + var hours = (int)Math.Round(time.TotalHours); + return $"0 */{hours} * * *"; + } + + if (time.TotalDays <= 31) + { + // Run every N days + var days = (int)Math.Round(time.TotalDays); + return $"0 0 */{days} * *"; + } + + throw new AbpException($"Cannot convert period: {period} to cron expression."); + } +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkersProvider.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkersProvider.cs new file mode 100644 index 00000000000..3c0fe763ece --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkersProvider.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BackgroundWorkers.TickerQ; + +public class AbpTickerQBackgroundWorkersProvider : ISingletonDependency +{ + public Dictionary BackgroundWorkers { get;} + + public AbpTickerQBackgroundWorkersProvider() + { + BackgroundWorkers = new Dictionary(); + } +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQCronBackgroundWorker.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQCronBackgroundWorker.cs new file mode 100644 index 00000000000..55c97a00a6a --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQCronBackgroundWorker.cs @@ -0,0 +1,12 @@ +using System; + +namespace Volo.Abp.BackgroundWorkers.TickerQ; + +public class AbpTickerQCronBackgroundWorker +{ + public string Function { get; set; } = null!; + + public string CronExpression { get; set; } = null!; + + public Type WorkerType { get; set; } = null!; +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQPeriodicBackgroundWorkerInvoker.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQPeriodicBackgroundWorkerInvoker.cs new file mode 100644 index 00000000000..17cf7cdc874 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQPeriodicBackgroundWorkerInvoker.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using TickerQ.Utilities.Models; + +namespace Volo.Abp.BackgroundWorkers.TickerQ; + +public class AbpTickerQPeriodicBackgroundWorkerInvoker +{ + private readonly Func? _doWorkAsyncDelegate; + private readonly Action? _doWorkDelegate; + + protected IBackgroundWorker Worker { get; } + protected IServiceProvider ServiceProvider { get; } + + public AbpTickerQPeriodicBackgroundWorkerInvoker(IBackgroundWorker worker, IServiceProvider serviceProvider) + { + Worker = worker; + ServiceProvider = serviceProvider; + + switch (worker) + { + case AsyncPeriodicBackgroundWorkerBase: + { + var workerType = worker.GetType(); + var method = workerType.GetMethod("DoWorkAsync", BindingFlags.Instance | BindingFlags.NonPublic); + if (method == null) + { + throw new AbpException($"Could not find 'DoWorkAsync' method on type '{workerType.FullName}'."); + } + + var instanceParam = Expression.Parameter(typeof(AsyncPeriodicBackgroundWorkerBase), "worker"); + var contextParam = Expression.Parameter(typeof(PeriodicBackgroundWorkerContext), "context"); + var call = Expression.Call(Expression.Convert(instanceParam, workerType), method, contextParam); + var lambda = Expression.Lambda>(call, instanceParam, contextParam); + _doWorkAsyncDelegate = lambda.Compile(); + break; + } + case PeriodicBackgroundWorkerBase: + { + var workerType = worker.GetType(); + var method = workerType.GetMethod("DoWork", BindingFlags.Instance | BindingFlags.NonPublic); + if (method == null) + { + throw new AbpException($"Could not find 'DoWork' method on type '{workerType.FullName}'."); + } + + var instanceParam = Expression.Parameter(typeof(PeriodicBackgroundWorkerBase), "worker"); + var contextParam = Expression.Parameter(typeof(PeriodicBackgroundWorkerContext), "context"); + var call = Expression.Call(Expression.Convert(instanceParam, workerType), method, contextParam); + var lambda = Expression.Lambda>(call, instanceParam, contextParam); + _doWorkDelegate = lambda.Compile(); + break; + } + } + } + + public virtual async Task DoWorkAsync(TickerFunctionContext context, CancellationToken cancellationToken = default) + { + var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider); + switch (Worker) + { + case AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorker: + await _doWorkAsyncDelegate!(asyncPeriodicBackgroundWorker, workerContext); + break; + case PeriodicBackgroundWorkerBase periodicBackgroundWorker: + _doWorkDelegate!(periodicBackgroundWorker, workerContext); + break; + } + } +} diff --git a/framework/src/Volo.Abp.TickerQ/FodyWeavers.xml b/framework/src/Volo.Abp.TickerQ/FodyWeavers.xml new file mode 100644 index 00000000000..1715698ccd2 --- /dev/null +++ b/framework/src/Volo.Abp.TickerQ/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TickerQ/FodyWeavers.xsd b/framework/src/Volo.Abp.TickerQ/FodyWeavers.xsd new file mode 100644 index 00000000000..ffa6fc4b782 --- /dev/null +++ b/framework/src/Volo.Abp.TickerQ/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TickerQ/Microsoft/AspNetCore/Builder/AbpTickerQApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.TickerQ/Microsoft/AspNetCore/Builder/AbpTickerQApplicationBuilderExtensions.cs new file mode 100644 index 00000000000..4e81565e992 --- /dev/null +++ b/framework/src/Volo.Abp.TickerQ/Microsoft/AspNetCore/Builder/AbpTickerQApplicationBuilderExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using TickerQ.DependencyInjection; +using TickerQ.Utilities; +using TickerQ.Utilities.Enums; +using Volo.Abp.TickerQ; + +namespace Microsoft.AspNetCore.Builder; + +public static class AbpTickerQApplicationBuilderExtensions +{ + public static IApplicationBuilder UseAbpTickerQ(this IApplicationBuilder app, TickerQStartMode qStartMode = TickerQStartMode.Immediate) + { + var abpTickerQFunctionProvider = app.ApplicationServices.GetRequiredService(); + TickerFunctionProvider.RegisterFunctions(abpTickerQFunctionProvider.Functions); + TickerFunctionProvider.RegisterRequestType(abpTickerQFunctionProvider.RequestTypes); + + app.UseTickerQ(qStartMode); + return app; + } +} diff --git a/framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj b/framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj new file mode 100644 index 00000000000..89a037bab76 --- /dev/null +++ b/framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj @@ -0,0 +1,27 @@ + + + + + + + netstandard2.1;net8.0;net9.0;net10.0 + enable + Nullable + Volo.Abp.TickerQ + Volo.Abp.TickerQ + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQFunctionProvider.cs b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQFunctionProvider.cs new file mode 100644 index 00000000000..b92888e5338 --- /dev/null +++ b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQFunctionProvider.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using TickerQ.Utilities; +using TickerQ.Utilities.Enums; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TickerQ; + +public class AbpTickerQFunctionProvider : ISingletonDependency +{ + public Dictionary Functions { get;} + + public Dictionary RequestTypes { get; } + + public AbpTickerQFunctionProvider() + { + Functions = new Dictionary(); + RequestTypes = new Dictionary(); + } +} diff --git a/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs new file mode 100644 index 00000000000..b6c140e9622 --- /dev/null +++ b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; +using TickerQ.DependencyInjection; +using Volo.Abp.Modularity; + +namespace Volo.Abp.TickerQ; + +public class AbpTickerQModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddTickerQ(options => + { + options.SetInstanceIdentifier(context.Services.GetApplicationName()); + }); + } +} diff --git a/modules/background-jobs/Volo.Abp.BackgroundJobs.slnx b/modules/background-jobs/Volo.Abp.BackgroundJobs.slnx index d8a41ccb9d9..9863504e7cd 100644 --- a/modules/background-jobs/Volo.Abp.BackgroundJobs.slnx +++ b/modules/background-jobs/Volo.Abp.BackgroundJobs.slnx @@ -5,6 +5,7 @@ + @@ -19,4 +20,4 @@ - + \ No newline at end of file diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs index ad002a5e186..7e67dd48a99 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs @@ -4,9 +4,6 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Shared { - [DependsOn( - typeof(AbpBackgroundJobsModule) - )] public class DemoAppSharedModule : AbpModule { public override void OnPostApplicationInitialization(ApplicationInitializationContext context) diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/CleanupJobs.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/CleanupJobs.cs new file mode 100644 index 00000000000..6eca55e09f4 --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/CleanupJobs.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using TickerQ.Utilities.Models; + +namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ; + +public class CleanupJobs +{ + public async Task CleanupLogsAsync(TickerFunctionContext tickerContext, CancellationToken cancellationToken) + { + var logFileName = tickerContext.Request; + Console.WriteLine($"Cleaning up log file: {logFileName} at {DateTime.Now}"); + } +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/DemoAppTickerQModule.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/DemoAppTickerQModule.cs new file mode 100644 index 00000000000..c9a3b957dee --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/DemoAppTickerQModule.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using TickerQ.Dashboard.DependencyInjection; +using TickerQ.DependencyInjection; +using TickerQ.Utilities; +using TickerQ.Utilities.Enums; +using TickerQ.Utilities.Interfaces.Managers; +using TickerQ.Utilities.Models; +using TickerQ.Utilities.Models.Ticker; +using Volo.Abp.AspNetCore; +using Volo.Abp.Autofac; +using Volo.Abp.BackgroundJobs.DemoApp.Shared; +using Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs; +using Volo.Abp.BackgroundJobs.TickerQ; +using Volo.Abp.BackgroundWorkers; +using Volo.Abp.BackgroundWorkers.TickerQ; +using Volo.Abp.Modularity; +using Volo.Abp.TickerQ; + +namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ; + +[DependsOn( + typeof(AbpBackgroundJobsTickerQModule), + typeof(AbpBackgroundWorkersTickerQModule), + typeof(DemoAppSharedModule), + typeof(AbpAutofacModule), + typeof(AbpAspNetCoreModule) +)] +public class DemoAppTickerQModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddTickerQ(options => + { + options.UpdateMissedJobCheckDelay(TimeSpan.FromSeconds(30)); + + options.AddDashboard(x => + { + x.BasePath = "/tickerq-dashboard"; + + x.UseHostAuthentication = true; + }); + }); + + Configure(options => + { + options.AddConfiguration(new AbpBackgroundJobsTimeTickerConfiguration() + { + Retries = 3, + RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min, + Priority = TickerTaskPriority.High + }); + + options.AddConfiguration(new AbpBackgroundJobsTimeTickerConfiguration() + { + Retries = 5, + RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min + }); + }); + + Configure(options => + { + options.AddConfiguration(new AbpBackgroundWorkersCronTickerConfiguration() + { + Retries = 3, + RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min, + Priority = TickerTaskPriority.High + }); + }); + } + + public override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context) + { + var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService(); + abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => + { + var service = new CleanupJobs(); + var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); + var genericContext = new TickerFunctionContext(tickerFunctionContext, request); + await service.CleanupLogsAsync(genericContext, cancellationToken); + }))); + abpTickerQFunctionProvider.RequestTypes.TryAdd(nameof(CleanupJobs), (typeof(string).FullName, typeof(string))); + return Task.CompletedTask; + } + + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + var backgroundWorkerManager = context.ServiceProvider.GetRequiredService(); + await backgroundWorkerManager.AddAsync(context.ServiceProvider.GetRequiredService()); + + var app = context.GetApplicationBuilder(); + app.UseAbpTickerQ(); + + var timeTickerManager = context.ServiceProvider.GetRequiredService>(); + await timeTickerManager.AddAsync(new TimeTicker + { + Function = nameof(CleanupJobs), + ExecutionTime = DateTime.UtcNow.AddSeconds(5), + Request = TickerHelper.CreateTickerRequest("cleanup_example_file.txt"), + Retries = 3, + RetryIntervals = new[] { 30, 60, 120 }, // Retry after 30s, 60s, then 2min + }); + + var cronTickerManager = context.ServiceProvider.GetRequiredService>(); + await cronTickerManager.AddAsync(new CronTicker + { + Function = nameof(CleanupJobs), + Expression = "* * * * *", // Every minute + Request = TickerHelper.CreateTickerRequest("cleanup_example_file.txt"), + Retries = 2, + RetryIntervals = new[] { 60, 300 } + }); + + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapGet("/", async httpContext => + { + httpContext.Response.Redirect("/tickerq-dashboard", true); + }); + }); + + await CancelableBackgroundJobAsync(context.ServiceProvider); + } + + private async Task CancelableBackgroundJobAsync(IServiceProvider serviceProvider) + { + var backgroundJobManager = serviceProvider.GetRequiredService(); + var jobId = await backgroundJobManager.EnqueueAsync(new LongRunningJobArgs { Value = "test-cancel-job" }); + await backgroundJobManager.EnqueueAsync(new LongRunningJobArgs { Value = "test-3" }); + + await Task.Delay(1000); + + var timeTickerManager = serviceProvider.GetRequiredService>(); + var result = await timeTickerManager.DeleteAsync(Guid.Parse(jobId)); + } +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/MyBackgroundWorker.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/MyBackgroundWorker.cs new file mode 100644 index 00000000000..cd35b42bb4d --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/MyBackgroundWorker.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.BackgroundWorkers; +using Volo.Abp.Threading; + +namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ; + +public class MyBackgroundWorker : AsyncPeriodicBackgroundWorkerBase +{ + public MyBackgroundWorker([NotNull] AbpAsyncTimer timer, [NotNull] IServiceScopeFactory serviceScopeFactory) : base(timer, serviceScopeFactory) + { + timer.Period = 60 * 1000; // 60 seconds + CronExpression = "* * * * *"; // every minute + } + + protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) + { + Console.WriteLine($"MyBackgroundWorker executed at {DateTime.Now}"); + } +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Program.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Program.cs new file mode 100644 index 00000000000..72e7f9090c0 --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Program.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ; + +public class Program +{ + public static async Task Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Host.UseAutofac(); + await builder.AddApplicationAsync(); + var app = builder.Build(); + await app.InitializeApplicationAsync(); + await app.RunAsync(); + } +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Properties/launchSettings.json b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Properties/launchSettings.json new file mode 100644 index 00000000000..1b8cdbbc25e --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Volo.Abp.BackgroundJobs.DemoApp.TickerQ": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Volo.Abp.BackgroundJobs.DemoApp.TickerQ.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Volo.Abp.BackgroundJobs.DemoApp.TickerQ.csproj new file mode 100644 index 00000000000..f30eba83dda --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Volo.Abp.BackgroundJobs.DemoApp.TickerQ.csproj @@ -0,0 +1,26 @@ + + + + Exe + net10.0 + + + + + + + + + + + + + + Always + + + Always + + + + diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/appsettings.json b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/appsettings.json new file mode 100644 index 00000000000..1b2d3bafd88 --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} \ No newline at end of file diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index 20b08ac2acd..6fbc34e80c3 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -148,9 +148,11 @@ $projects = ( "framework/src/Volo.Abp.BackgroundJobs.HangFire", "framework/src/Volo.Abp.BackgroundJobs.RabbitMQ", "framework/src/Volo.Abp.BackgroundJobs.Quartz", + "framework/src/Volo.Abp.BackgroundJobs.TickerQ", "framework/src/Volo.Abp.BackgroundWorkers", "framework/src/Volo.Abp.BackgroundWorkers.Quartz", "framework/src/Volo.Abp.BackgroundWorkers.Hangfire", + "framework/src/Volo.Abp.BackgroundWorkers.TickerQ", "framework/src/Volo.Abp.BlazoriseUI", "framework/src/Volo.Abp.BlobStoring", "framework/src/Volo.Abp.BlobStoring.FileSystem", @@ -201,6 +203,7 @@ $projects = ( "framework/src/Volo.Abp.GlobalFeatures", "framework/src/Volo.Abp.Guids", "framework/src/Volo.Abp.HangFire", + "framework/src/Volo.Abp.TickerQ", "framework/src/Volo.Abp.Http.Abstractions", "framework/src/Volo.Abp.Http.Client", "framework/src/Volo.Abp.Http.Client.Dapr",