mirror of https://github.com/abpframework/abp.git
160 changed files with 1435 additions and 392 deletions
@ -0,0 +1,3 @@ |
|||||
|
codecov: |
||||
|
branch: dev |
||||
|
require_ci_to_pass: yes |
||||
@ -0,0 +1,99 @@ |
|||||
|
# Hangfire Background Worker Manager |
||||
|
|
||||
|
[Hangfire](https://https://www.hangfire.io/) is an advanced background jobs and worker manager. You can integrate Hangfire with the ABP Framework to use it instead of the [default background worker manager](Background-Workers.md). |
||||
|
|
||||
|
The major advantage is that you can use the same server farm to manage your Background Jobs and Workers, as well as leverage the advanced scheduling that is available from Hangfire for [Recurring Jobs](https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html?highlight=recurring), aka Background Workers. |
||||
|
|
||||
|
## Installation |
||||
|
|
||||
|
It is suggested to use the [ABP CLI](CLI.md) 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.Hangfire |
||||
|
```` |
||||
|
|
||||
|
### Manual Installation |
||||
|
|
||||
|
If you want to manually install; |
||||
|
|
||||
|
1. Add the [Volo.Abp.BackgroundWorkers.Hangfire](https://www.nuget.org/packages/Volo.Abp.BackgroundWorkers.Hangfire) NuGet package to your project: |
||||
|
|
||||
|
```` |
||||
|
Install-Package Volo.Abp.BackgroundWorkers.Hangfire |
||||
|
```` |
||||
|
|
||||
|
2. Add the `AbpBackgroundWorkersHangfireModule` to the dependency list of your module: |
||||
|
|
||||
|
````csharp |
||||
|
[DependsOn( |
||||
|
//...other dependencies |
||||
|
typeof(AbpBackgroundWorkersHangfireModule) //Add the new module dependency |
||||
|
)] |
||||
|
public class YourModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
> Hangfire background worker integration provides an adapter `HangfirePeriodicBackgroundWorkerAdapter` to automatically load any `PeriodicBackgroundWorkerBase` and `AsyncPeriodicBackgroundWorkerBase` derived classes as `IHangfireBackgroundWorker` instances. This allows you to still to easily switch over to use Hangfire as the background manager even you have existing background workers that are based on the [default background workers implementation](Background-Workers.md). |
||||
|
|
||||
|
## Create a Background Worker |
||||
|
|
||||
|
`HangfireBackgroundWorkerBase` is an easy way to create a background worker. |
||||
|
|
||||
|
```` csharp |
||||
|
public class MyLogWorker : HangfireBackgroundWorkerBase |
||||
|
{ |
||||
|
public MyLogWorker() |
||||
|
{ |
||||
|
RecurringJobId = nameof(MyLogWorker); |
||||
|
CronExpression = Cron.Daily(); |
||||
|
} |
||||
|
|
||||
|
public override Task DoWorkAsync() |
||||
|
{ |
||||
|
Logger.LogInformation("Executed MyLogWorker..!"); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* **RecurringJobId** Is an optional parameter, see [Hangfire document](https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html) |
||||
|
* **CronExpression** Is a CRON expression, see [CRON expression](https://en.wikipedia.org/wiki/Cron#CRON_expression) |
||||
|
|
||||
|
> You can directly implement the `IHangfireBackgroundWorker`, but `HangfireBackgroundWorkerBase` provides some useful properties like Logger. |
||||
|
|
||||
|
## Register BackgroundWorkerManager |
||||
|
|
||||
|
After creating a background worker class, you should add it to the `IBackgroundWorkerManager`. The most common place is the `OnApplicationInitialization` method of your module class: |
||||
|
|
||||
|
```` csharp |
||||
|
[DependsOn(typeof(AbpBackgroundWorkersModule))] |
||||
|
public class MyModule : AbpModule |
||||
|
{ |
||||
|
public override void OnApplicationInitialization( |
||||
|
ApplicationInitializationContext context) |
||||
|
{ |
||||
|
context.AddBackgroundWorker<MyLogWorker>(); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`context.AddBackgroundWorker(...)` is a shortcut extension method for the expression below: |
||||
|
|
||||
|
```` csharp |
||||
|
context.ServiceProvider |
||||
|
.GetRequiredService<IBackgroundWorkerManager>() |
||||
|
.Add( |
||||
|
context |
||||
|
.ServiceProvider |
||||
|
.GetRequiredService<MyLogWorker>() |
||||
|
); |
||||
|
```` |
||||
|
|
||||
|
So, it resolves the given background worker and adds to the `IBackgroundWorkerManager`. |
||||
|
|
||||
|
While we generally add workers in OnApplicationInitialization, there are no restrictions on that. You can inject IBackgroundWorkerManager anywhere and add workers at runtime. Background worker manager will stop and release all the registered workers when your application is being shut down. |
||||
@ -0,0 +1,10 @@ |
|||||
|
## Angular UI Extensions |
||||
|
|
||||
|
Angular UI extensions system allows you to add a new action to the actions menu, a new column to the data table, a new action to the toolbar of a page, and add a new field to the create and/or edit forms. |
||||
|
|
||||
|
See the documents below for the details: |
||||
|
|
||||
|
* [Entity Action Extensions](Entity-Action-Extensions.md) |
||||
|
* [Data Table Column (or Entity Prop) Extensions](Data-Table-Column-Extensions.md) |
||||
|
* [Page Toolbar Extension](Page-Toolbar-Extensions.md) |
||||
|
* [Dynamic Form (or Form Prop) Extensions](Dynamic-Form-Extensions.md) |
||||
@ -0,0 +1,100 @@ |
|||||
|
# Hangfire Background Worker Manager |
||||
|
|
||||
|
[Hangfire](https://https://www.hangfire.io/)是一个高级的后台工作者管理. 你可以用ABP框架Hangfire集成代替[默认后台工作者管理](Background-Workers.md). |
||||
|
|
||||
|
主要优点是你可以使用相同的服务器群来管理你的后台作业和工作线程以及利用 Hangfire 提供的[Recurring Jobs](https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html?highlight=recurring)高级调度功能. |
||||
|
|
||||
|
## 安装 |
||||
|
|
||||
|
建议使用[ABP CLI](CLI.md)安装包. |
||||
|
|
||||
|
|
||||
|
### 使用ABP CLI |
||||
|
|
||||
|
在项目的文件夹(.csproj文件)中打开命令行窗口输入以下命令: |
||||
|
|
||||
|
````bash |
||||
|
abp add-package Volo.Abp.BackgroundWorkers.Hangfire |
||||
|
```` |
||||
|
|
||||
|
### 手动安装 |
||||
|
|
||||
|
如果你想手动安装; |
||||
|
|
||||
|
1. 添加 [Volo.Abp.BackgroundWorkers.Hangfire](https://www.nuget.org/packages/Volo.Abp.BackgroundWorkers.Hangfire) NuGet包添加到你的项目: |
||||
|
|
||||
|
```` |
||||
|
Install-Package Volo.Abp.BackgroundWorkers.Hangfire |
||||
|
```` |
||||
|
|
||||
|
2. 添加 `AbpBackgroundWorkersHangfireModule` 到你的模块的依赖列表: |
||||
|
|
||||
|
````csharp |
||||
|
[DependsOn( |
||||
|
//...other dependencies |
||||
|
typeof(AbpBackgroundWorkersHangfireModule) //Add the new module dependency |
||||
|
)] |
||||
|
public class YourModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
> Hangfire后台工作者集成提供了 `HangfirePeriodicBackgroundWorkerAdapter` 来适配 `PeriodicBackgroundWorkerBase` 和 `AsyncPeriodicBackgroundWorkerBase` 派生类. 所以你依然可以按照[后台工作者文档](Background-Workers.md)来定义后台作业. |
||||
|
|
||||
|
## 创建后台工作者 |
||||
|
|
||||
|
`HangfireBackgroundWorkerBase` 是创建一个后台工作者简单的方法. |
||||
|
|
||||
|
```` csharp |
||||
|
public class MyLogWorker : HangfireBackgroundWorkerBase |
||||
|
{ |
||||
|
public MyLogWorker() |
||||
|
{ |
||||
|
RecurringJobId = nameof(MyLogWorker); |
||||
|
CronExpression = Cron.Daily(); |
||||
|
} |
||||
|
|
||||
|
public override Task DoWorkAsync() |
||||
|
{ |
||||
|
Logger.LogInformation("Executed MyLogWorker..!"); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
* **RecurringJobId** 是一个可选参数, 参阅 [Hangfire文档](https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html) |
||||
|
* **CronExpression** 是CRON表达式, 参阅 [CRON 表达式](https://en.wikipedia.org/wiki/Cron#CRON_expression) |
||||
|
|
||||
|
> 你可以直接实现 `IHangfireBackgroundWorker`, 但是 `HangfireBackgroundWorkerBase` 提供了一些有用的属性,例如 `Logger`. |
||||
|
|
||||
|
## 注册到后台工作者管理器 |
||||
|
|
||||
|
创建一个后台工作者后, 你应该添加到 `IBackgroundWorkerManager`, 最常用的地方是在你模块类的 `OnApplicationInitialization` 方法中: |
||||
|
|
||||
|
```` csharp |
||||
|
[DependsOn(typeof(AbpBackgroundWorkersModule))] |
||||
|
public class MyModule : AbpModule |
||||
|
{ |
||||
|
public override void OnApplicationInitialization( |
||||
|
ApplicationInitializationContext context) |
||||
|
{ |
||||
|
context.AddBackgroundWorker<MyLogWorker>(); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`context.AddBackgroundWorker(...)` 是一个是以下代码快捷的扩展方法: |
||||
|
|
||||
|
```` csharp |
||||
|
context.ServiceProvider |
||||
|
.GetRequiredService<IBackgroundWorkerManager>() |
||||
|
.Add( |
||||
|
context |
||||
|
.ServiceProvider |
||||
|
.GetRequiredService<MyLogWorker>() |
||||
|
); |
||||
|
```` |
||||
|
|
||||
|
它解析给定的后台工作者并添加到 `IBackgroundWorkerManager`. |
||||
|
|
||||
|
虽然我们通常在 `OnApplicationInitialization` 中添加后台工作者, 但对此没有限制. 你可以在任何地方注入 `IBackgroundWorkerManager` 并在运行时添加后台工作者. |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -1,4 +1,4 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
<xs:element name="Weavers"> |
<xs:element name="Weavers"> |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System; |
||||
|
using Hangfire; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.Hangfire; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.Hangfire |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpBackgroundWorkersModule), |
||||
|
typeof(AbpHangfireModule))] |
||||
|
public class AbpBackgroundWorkerHangfireModule : AbpModule |
||||
|
{ |
||||
|
public override void OnPreApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value; |
||||
|
if (!options.IsEnabled) |
||||
|
{ |
||||
|
var hangfireOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpHangfireOptions>>().Value; |
||||
|
hangfireOptions.BackgroundJobServerFactory = CreateOnlyEnqueueJobServer; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddSingleton(typeof(HangfirePeriodicBackgroundWorkerAdapter<>)); |
||||
|
} |
||||
|
|
||||
|
private BackgroundJobServer CreateOnlyEnqueueJobServer(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
serviceProvider.GetRequiredService<JobStorage>(); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.Hangfire |
||||
|
{ |
||||
|
public abstract class HangfireBackgroundWorkerBase : BackgroundWorkerBase, IHangfireBackgroundWorker |
||||
|
{ |
||||
|
public string RecurringJobId { get; set; } |
||||
|
|
||||
|
public string CronExpression { get; set; } |
||||
|
|
||||
|
public abstract Task DoWorkAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,91 @@ |
|||||
|
using System; |
||||
|
using System.Reflection; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Hangfire; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.Hangfire |
||||
|
{ |
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class HangfireBackgroundWorkerManager : IBackgroundWorkerManager, ISingletonDependency |
||||
|
{ |
||||
|
public Task StartAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public Task StopAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public void Add(IBackgroundWorker worker) |
||||
|
{ |
||||
|
if (worker is IHangfireBackgroundWorker hangfireBackgroundWorker) |
||||
|
{ |
||||
|
if (hangfireBackgroundWorker.RecurringJobId.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
RecurringJob.AddOrUpdate(() => hangfireBackgroundWorker.DoWorkAsync(), |
||||
|
hangfireBackgroundWorker.CronExpression); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
RecurringJob.AddOrUpdate(hangfireBackgroundWorker.RecurringJobId,() => hangfireBackgroundWorker.DoWorkAsync(), |
||||
|
hangfireBackgroundWorker.CronExpression); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
int? period; |
||||
|
|
||||
|
if (worker is AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase) |
||||
|
{ |
||||
|
var timer = (AbpTimer) worker.GetType() |
||||
|
.GetProperty("Timer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(worker); |
||||
|
period = timer?.Period; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (period == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var adapterType = typeof(HangfirePeriodicBackgroundWorkerAdapter<>).MakeGenericType(worker.GetType()); |
||||
|
var workerAdapter = Activator.CreateInstance(adapterType) as IHangfireBackgroundWorker; |
||||
|
|
||||
|
RecurringJob.AddOrUpdate(() => workerAdapter.DoWorkAsync(), GetCron(period.Value)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual string GetCron(int period) |
||||
|
{ |
||||
|
var time = TimeSpan.FromMilliseconds(period); |
||||
|
string cron; |
||||
|
|
||||
|
if (time.TotalSeconds <= 59) |
||||
|
{ |
||||
|
cron = $"*/{time.TotalSeconds} * * * * *"; |
||||
|
} |
||||
|
else if (time.TotalMinutes <= 59) |
||||
|
{ |
||||
|
cron = $"*/{time.TotalMinutes} * * * *"; |
||||
|
} |
||||
|
else if (time.TotalHours <= 23) |
||||
|
{ |
||||
|
cron = $"0 */{time.TotalHours} * * *"; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
cron = $"0 0 */{time.TotalDays} * *"; |
||||
|
} |
||||
|
|
||||
|
return cron; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System.Reflection; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.Hangfire |
||||
|
{ |
||||
|
public class HangfirePeriodicBackgroundWorkerAdapter<TWorker> : HangfireBackgroundWorkerBase |
||||
|
where TWorker : IBackgroundWorker |
||||
|
{ |
||||
|
private readonly MethodInfo _doWorkAsyncMethod; |
||||
|
private readonly MethodInfo _doWorkMethod; |
||||
|
|
||||
|
public HangfirePeriodicBackgroundWorkerAdapter() |
||||
|
{ |
||||
|
_doWorkAsyncMethod = |
||||
|
typeof(TWorker).GetMethod("DoWorkAsync", BindingFlags.Instance | BindingFlags.NonPublic); |
||||
|
_doWorkMethod = typeof(TWorker).GetMethod("DoWork", BindingFlags.Instance | BindingFlags.NonPublic); |
||||
|
} |
||||
|
|
||||
|
public override async Task DoWorkAsync() |
||||
|
{ |
||||
|
var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider); |
||||
|
var worker = ServiceProvider.GetRequiredService<TWorker>(); |
||||
|
|
||||
|
switch (worker) |
||||
|
{ |
||||
|
case AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorker: |
||||
|
await (Task) _doWorkAsyncMethod.Invoke(asyncPeriodicBackgroundWorker, new object[] {workerContext}); |
||||
|
break; |
||||
|
case PeriodicBackgroundWorkerBase periodicBackgroundWorker: |
||||
|
_doWorkMethod.Invoke(periodicBackgroundWorker, new object[] {workerContext}); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.Hangfire |
||||
|
{ |
||||
|
public interface IHangfireBackgroundWorker : IBackgroundWorker |
||||
|
{ |
||||
|
string RecurringJobId { get; set; } |
||||
|
|
||||
|
string CronExpression { get; set; } |
||||
|
|
||||
|
Task DoWorkAsync(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
@ -1,2 +0,0 @@ |
|||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> |
|
||||
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp71</s:String></wpf:ResourceDictionary> |
|
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,21 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<AssemblyName>Volo.Abp.DistributedLocking.Abstractions</AssemblyName> |
||||
|
<PackageId>Volo.Abp.DistributedLocking.Abstractions</PackageId> |
||||
|
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking |
||||
|
{ |
||||
|
public class AbpDistributedLockingAbstractionsModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking |
||||
|
{ |
||||
|
public interface IAbpDistributedLock |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Tries to acquire a named lock.
|
||||
|
/// Returns a disposable object to release the lock.
|
||||
|
/// It is suggested to use this method within a using block.
|
||||
|
/// Returns null if the lock could not be handled.
|
||||
|
/// </summary>
|
||||
|
/// <param name="name">The name of the lock</param>
|
||||
|
/// <param name="timeout">Timeout value</param>
|
||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||
|
[ItemCanBeNull] |
||||
|
Task<IAbpDistributedLockHandle> TryAcquireAsync( |
||||
|
[NotNull] string name, |
||||
|
TimeSpan timeout = default, |
||||
|
CancellationToken cancellationToken = default |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking |
||||
|
{ |
||||
|
public interface IAbpDistributedLockHandle : IAsyncDisposable |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking |
||||
|
{ |
||||
|
public class LocalAbpDistributedLock : IAbpDistributedLock, ISingletonDependency |
||||
|
{ |
||||
|
private readonly ConcurrentDictionary<string, SemaphoreSlim> _localSyncObjects = new(); |
||||
|
|
||||
|
public async Task<IAbpDistributedLockHandle> TryAcquireAsync( |
||||
|
string name, |
||||
|
TimeSpan timeout = default, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(name, nameof(name)); |
||||
|
|
||||
|
var semaphore = _localSyncObjects.GetOrAdd(name, _ => new SemaphoreSlim(1, 1)); |
||||
|
|
||||
|
if (!await semaphore.WaitAsync(timeout, cancellationToken)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return new LocalAbpDistributedLockHandle(semaphore); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking |
||||
|
{ |
||||
|
public class LocalAbpDistributedLockHandle : IAbpDistributedLockHandle |
||||
|
{ |
||||
|
private readonly SemaphoreSlim _semaphore; |
||||
|
|
||||
|
public LocalAbpDistributedLockHandle(SemaphoreSlim semaphore) |
||||
|
{ |
||||
|
_semaphore = semaphore; |
||||
|
} |
||||
|
|
||||
|
public ValueTask DisposeAsync() |
||||
|
{ |
||||
|
_semaphore.Release(); |
||||
|
return default; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using Medallion.Threading; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking |
||||
|
{ |
||||
|
public static class AbpDistributedLockHandleExtensions |
||||
|
{ |
||||
|
public static IDistributedSynchronizationHandle ToDistributedSynchronizationHandle( |
||||
|
this IAbpDistributedLockHandle handle) |
||||
|
{ |
||||
|
return handle.As<MedallionAbpDistributedLockHandle>().Handle; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Medallion.Threading; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking |
||||
|
{ |
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class MedallionAbpDistributedLock : IAbpDistributedLock, ITransientDependency |
||||
|
{ |
||||
|
protected IDistributedLockProvider DistributedLockProvider { get; } |
||||
|
|
||||
|
public MedallionAbpDistributedLock(IDistributedLockProvider distributedLockProvider) |
||||
|
{ |
||||
|
DistributedLockProvider = distributedLockProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task<IAbpDistributedLockHandle> TryAcquireAsync( |
||||
|
string name, |
||||
|
TimeSpan timeout = default, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(name, nameof(name)); |
||||
|
|
||||
|
var handle = await DistributedLockProvider.TryAcquireLockAsync(name, timeout, cancellationToken); |
||||
|
if (handle == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return new MedallionAbpDistributedLockHandle(handle); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Medallion.Threading; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking |
||||
|
{ |
||||
|
public class MedallionAbpDistributedLockHandle : IAbpDistributedLockHandle |
||||
|
{ |
||||
|
public IDistributedSynchronizationHandle Handle { get; } |
||||
|
|
||||
|
public MedallionAbpDistributedLockHandle(IDistributedSynchronizationHandle handle) |
||||
|
{ |
||||
|
Handle = handle; |
||||
|
} |
||||
|
|
||||
|
public ValueTask DisposeAsync() |
||||
|
{ |
||||
|
return Handle.DisposeAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,3 +0,0 @@ |
|||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|
||||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|
||||
</Weavers> |
|
||||
@ -1,20 +0,0 @@ |
|||||
using Volo.Abp.BackgroundWorkers; |
|
||||
using Volo.Abp.DistributedLocking; |
|
||||
using Volo.Abp.Modularity; |
|
||||
|
|
||||
namespace Volo.Abp.EventBus.Boxes |
|
||||
{ |
|
||||
[DependsOn( |
|
||||
typeof(AbpEventBusModule), |
|
||||
typeof(AbpBackgroundWorkersModule), |
|
||||
typeof(AbpDistributedLockingModule) |
|
||||
)] |
|
||||
public class AbpEventBusBoxesModule : AbpModule |
|
||||
{ |
|
||||
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
|
||||
{ |
|
||||
context.AddBackgroundWorker<OutboxSenderManager>(); |
|
||||
context.AddBackgroundWorker<InboxProcessManager>(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,20 +0,0 @@ |
|||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Volo.Abp.EventBus.Boxes |
|
||||
{ |
|
||||
internal static class TaskDelayHelper |
|
||||
{ |
|
||||
public static async Task DelayAsync(int milliseconds, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
await Task.Delay(milliseconds, cancellationToken); |
|
||||
} |
|
||||
catch (TaskCanceledException) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,18 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.test.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Volo.Abp.DistributedLocking.Abstractions\Volo.Abp.DistributedLocking.Abstractions.csproj" /> |
||||
|
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,12 @@ |
|||||
|
using Volo.Abp.Testing; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking |
||||
|
{ |
||||
|
public class AbpDistributedLockingAbstractionsTestBase : AbpIntegratedTest<AbpDistributedLockingAbstractionsTestModule> |
||||
|
{ |
||||
|
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
|
{ |
||||
|
options.UseAutofac(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using Volo.Abp.Autofac; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpTestBaseModule), |
||||
|
typeof(AbpDistributedLockingAbstractionsModule), |
||||
|
typeof(AbpAutofacModule) |
||||
|
)] |
||||
|
public class AbpDistributedLockingAbstractionsTestModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Shouldly; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.DistributedLocking |
||||
|
{ |
||||
|
public class LocalDistributedLock_Tests : AbpDistributedLockingAbstractionsTestBase |
||||
|
{ |
||||
|
private readonly IAbpDistributedLock _distributedLock; |
||||
|
|
||||
|
public LocalDistributedLock_Tests() |
||||
|
{ |
||||
|
_distributedLock = GetRequiredService<IAbpDistributedLock>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Be_Instance_Of_LocalAbpDistributedLock() |
||||
|
{ |
||||
|
_distributedLock.ShouldBeOfType<LocalAbpDistributedLock>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Lock_With_TryAcquire() |
||||
|
{ |
||||
|
await using (var handle = await _distributedLock.TryAcquireAsync("lock1")) |
||||
|
{ |
||||
|
handle.ShouldNotBeNull(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Not_Acquire_If_Already_Locked() |
||||
|
{ |
||||
|
await using (var handle = await _distributedLock.TryAcquireAsync("lock1")) |
||||
|
{ |
||||
|
handle.ShouldNotBeNull(); |
||||
|
|
||||
|
await Task.Run(async () => |
||||
|
{ |
||||
|
await using (var handle2 = await _distributedLock.TryAcquireAsync("lock1")) |
||||
|
{ |
||||
|
handle2.ShouldBeNull(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
await Task.Run(async () => |
||||
|
{ |
||||
|
await using (var handle = await _distributedLock.TryAcquireAsync("lock1")) |
||||
|
{ |
||||
|
handle.ShouldNotBeNull(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Obtain_Multiple_Locks() |
||||
|
{ |
||||
|
await using (var handle = await _distributedLock.TryAcquireAsync("lock1")) |
||||
|
{ |
||||
|
handle.ShouldNotBeNull(); |
||||
|
|
||||
|
await Task.Run(async () => |
||||
|
{ |
||||
|
await using (var handle2 = await _distributedLock.TryAcquireAsync("lock2")) |
||||
|
{ |
||||
|
handle2.ShouldNotBeNull(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,6 +1,6 @@ |
|||||
{ |
{ |
||||
"sdk": { |
"sdk": { |
||||
"version": "6.0.100-rc.1.21458.32", |
"version": "6.0.100-rc.2.21505.57", |
||||
"rollForward": "latestFeature" |
"rollForward": "latestFeature" |
||||
} |
} |
||||
} |
} |
||||
|
|||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue