From f58741b9722d86503a2993df7fbe5d73db4ed5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 20 Jan 2016 22:00:07 +0200 Subject: [PATCH] #41: Created initial background job classes and HangFire adapter. --- nupkg/pack_abp.bat | 1 + src/Abp.HangFire/Abp.HangFire.csproj | 104 ++++++++++++++ src/Abp.HangFire/Abp.HangFire.nuspec | 14 ++ .../Hangfire/AbpHangfireModule.cs | 33 +++++ .../Configuration/AbpHangfireConfiguration.cs | 9 ++ ...bpHangfireModuleConfigurationExtensions.cs | 15 ++ .../HangfireGlobalConfigurationExtensions.cs | 27 ++++ .../IAbpHangfireConfiguration.cs | 9 ++ .../Hangfire/HangfireBackgroundJobManager.cs | 57 ++++++++ .../Hangfire/WindsorJobActivator.cs | 34 +++++ src/Abp.HangFire/Properties/AssemblyInfo.cs | 37 +++++ src/Abp.HangFire/app.config | 11 ++ src/Abp.HangFire/packages.config | 9 ++ src/Abp.sln | 10 +- src/Abp/Abp.csproj | 15 +- src/Abp/AbpKernelModule.cs | 23 ++- .../BackgroundJobConfiguration.cs | 12 ++ .../BackgroundJobs/BackgroundJobExecuter.cs | 134 ++++++++++++++++++ src/Abp/BackgroundJobs/BackgroundJobInfo.cs | 60 ++++++++ .../BackgroundJobs/BackgroundJobPriority.cs | 11 ++ src/Abp/BackgroundJobs/IBackgroundJob.cs | 7 + .../IBackgroundJobConfiguration.cs | 10 ++ .../BackgroundJobs/IBackgroundJobManager.cs | 11 ++ src/Abp/BackgroundJobs/IBackgroundJobStore.cs | 16 +++ .../InMemoryBackgroundJobStore.cs | 49 +++++++ .../BackgroundJobs/NullBackgroundJobStore.cs | 28 ++++ .../Startup/AbpStartupConfiguration.cs | 7 + .../Startup/IAbpStartupConfiguration.cs | 6 + .../Dependency/Installers/AbpCoreInstaller.cs | 2 + src/Abp/Dependency/IocRegistrarExtensions.cs | 24 ++-- .../BinarySerializationHelper.cs | 132 +++++++++++++++++ 31 files changed, 902 insertions(+), 15 deletions(-) create mode 100644 src/Abp.HangFire/Abp.HangFire.csproj create mode 100644 src/Abp.HangFire/Abp.HangFire.nuspec create mode 100644 src/Abp.HangFire/Hangfire/AbpHangfireModule.cs create mode 100644 src/Abp.HangFire/Hangfire/Configuration/AbpHangfireConfiguration.cs create mode 100644 src/Abp.HangFire/Hangfire/Configuration/AbpHangfireModuleConfigurationExtensions.cs create mode 100644 src/Abp.HangFire/Hangfire/Configuration/HangfireGlobalConfigurationExtensions.cs create mode 100644 src/Abp.HangFire/Hangfire/Configuration/IAbpHangfireConfiguration.cs create mode 100644 src/Abp.HangFire/Hangfire/HangfireBackgroundJobManager.cs create mode 100644 src/Abp.HangFire/Hangfire/WindsorJobActivator.cs create mode 100644 src/Abp.HangFire/Properties/AssemblyInfo.cs create mode 100644 src/Abp.HangFire/app.config create mode 100644 src/Abp.HangFire/packages.config create mode 100644 src/Abp/BackgroundJobs/BackgroundJobConfiguration.cs create mode 100644 src/Abp/BackgroundJobs/BackgroundJobExecuter.cs create mode 100644 src/Abp/BackgroundJobs/BackgroundJobInfo.cs create mode 100644 src/Abp/BackgroundJobs/BackgroundJobPriority.cs create mode 100644 src/Abp/BackgroundJobs/IBackgroundJob.cs create mode 100644 src/Abp/BackgroundJobs/IBackgroundJobConfiguration.cs create mode 100644 src/Abp/BackgroundJobs/IBackgroundJobManager.cs create mode 100644 src/Abp/BackgroundJobs/IBackgroundJobStore.cs create mode 100644 src/Abp/BackgroundJobs/InMemoryBackgroundJobStore.cs create mode 100644 src/Abp/BackgroundJobs/NullBackgroundJobStore.cs create mode 100644 src/Abp/Runtime/Serialization/BinarySerializationHelper.cs diff --git a/nupkg/pack_abp.bat b/nupkg/pack_abp.bat index 798b09e949..4eb030e8ae 100644 --- a/nupkg/pack_abp.bat +++ b/nupkg/pack_abp.bat @@ -1,5 +1,6 @@ "..\src\.nuget\NuGet.exe" "pack" "..\src\Abp\Abp.csproj" -Properties Configuration=Release -IncludeReferencedProjects -Symbols "..\src\.nuget\NuGet.exe" "pack" "..\src\Abp.AutoMapper\Abp.AutoMapper.csproj" -Properties Configuration=Release -IncludeReferencedProjects -Symbols +"..\src\.nuget\NuGet.exe" "pack" "..\src\Abp.HangFire\Abp.HangFire.csproj" -Properties Configuration=Release -IncludeReferencedProjects -Symbols "..\src\.nuget\NuGet.exe" "pack" "..\src\Abp.EntityFramework\Abp.EntityFramework.csproj" -Properties Configuration=Release -IncludeReferencedProjects -Symbols "..\src\.nuget\NuGet.exe" "pack" "..\src\Abp.FluentMigrator\Abp.FluentMigrator.csproj" -Properties Configuration=Release -IncludeReferencedProjects -Symbols "..\src\.nuget\NuGet.exe" "pack" "..\src\Abp.MemoryDb\Abp.MemoryDb.csproj" -Properties Configuration=Release -IncludeReferencedProjects -Symbols diff --git a/src/Abp.HangFire/Abp.HangFire.csproj b/src/Abp.HangFire/Abp.HangFire.csproj new file mode 100644 index 0000000000..800696b5e2 --- /dev/null +++ b/src/Abp.HangFire/Abp.HangFire.csproj @@ -0,0 +1,104 @@ + + + + + Debug + AnyCPU + {58A5158C-C78B-4451-BFD8-FFC31D48D713} + Library + Properties + Abp + Abp.HangFire + v4.5.2 + 512 + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Castle.Core.3.3.3\lib\net45\Castle.Core.dll + True + + + ..\packages\Castle.LoggingFacility.3.3.0\lib\net45\Castle.Facilities.Logging.dll + True + + + ..\packages\Castle.Windsor.3.3.0\lib\net45\Castle.Windsor.dll + True + + + ..\packages\Hangfire.Core.1.5.3\lib\net45\Hangfire.Core.dll + True + + + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + {2C221D3B-5F54-4C5B-8082-318636415132} + Abp + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/Abp.HangFire/Abp.HangFire.nuspec b/src/Abp.HangFire/Abp.HangFire.nuspec new file mode 100644 index 0000000000..2534a88d12 --- /dev/null +++ b/src/Abp.HangFire/Abp.HangFire.nuspec @@ -0,0 +1,14 @@ + + + + Abp.HangFire + $version$ + Abp.HangFire + Halil İbrahim Kalkan + Halil İbrahim Kalkan + HangFire integration package for ASP.NET Boilerplate. + http://www.aspnetboilerplate.com + http://www.aspnetboilerplate.com/images/abp_nupkg.png + asp.net boilerplate mvc application web framework domain driven design dependency injection hangfire + + \ No newline at end of file diff --git a/src/Abp.HangFire/Hangfire/AbpHangfireModule.cs b/src/Abp.HangFire/Hangfire/AbpHangfireModule.cs new file mode 100644 index 0000000000..bb974f4f7b --- /dev/null +++ b/src/Abp.HangFire/Hangfire/AbpHangfireModule.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using Abp.Hangfire.Configuration; +using Abp.Modules; +using Abp.Threading.BackgroundWorkers; +using Hangfire; + +namespace Abp.Hangfire +{ + [DependsOn(typeof(AbpKernelModule))] + public class AbpHangfireModule : AbpModule + { + public override void PreInitialize() + { + IocManager.Register(); + + GlobalConfiguration.Configuration + .UseWindsorJobActivator(IocManager); + //.UseSqlServerStorage("Default", options); // Here you can put any Connection String + } + + public override void Initialize() + { + IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); + } + + public override void PostInitialize() + { + IocManager.Resolve().Add( + IocManager.Resolve() + ); + } + } +} diff --git a/src/Abp.HangFire/Hangfire/Configuration/AbpHangfireConfiguration.cs b/src/Abp.HangFire/Hangfire/Configuration/AbpHangfireConfiguration.cs new file mode 100644 index 0000000000..c1786cea05 --- /dev/null +++ b/src/Abp.HangFire/Hangfire/Configuration/AbpHangfireConfiguration.cs @@ -0,0 +1,9 @@ +using Hangfire; + +namespace Abp.Hangfire.Configuration +{ + public class AbpHangfireConfiguration : IAbpHangfireConfiguration + { + public BackgroundJobServer Server { get; set; } + } +} \ No newline at end of file diff --git a/src/Abp.HangFire/Hangfire/Configuration/AbpHangfireModuleConfigurationExtensions.cs b/src/Abp.HangFire/Hangfire/Configuration/AbpHangfireModuleConfigurationExtensions.cs new file mode 100644 index 0000000000..de2b10feba --- /dev/null +++ b/src/Abp.HangFire/Hangfire/Configuration/AbpHangfireModuleConfigurationExtensions.cs @@ -0,0 +1,15 @@ +using Abp.Configuration.Startup; + +namespace Abp.Hangfire.Configuration +{ + public static class AbpHangfireConfigurationExtensions + { + /// + /// Used to configure ABP NHibernate module. + /// + public static IAbpHangfireConfiguration AbpNHibernate(this IModuleConfigurations configurations) + { + return configurations.AbpConfiguration.GetOrCreate("Modules.Abp.Hangfire", () => configurations.AbpConfiguration.IocManager.Resolve()); + } + } +} \ No newline at end of file diff --git a/src/Abp.HangFire/Hangfire/Configuration/HangfireGlobalConfigurationExtensions.cs b/src/Abp.HangFire/Hangfire/Configuration/HangfireGlobalConfigurationExtensions.cs new file mode 100644 index 0000000000..62ec36e916 --- /dev/null +++ b/src/Abp.HangFire/Hangfire/Configuration/HangfireGlobalConfigurationExtensions.cs @@ -0,0 +1,27 @@ +using System; +using Abp.Dependency; +using Hangfire; +using Hangfire.Annotations; + +namespace Abp.Hangfire.Configuration +{ + public static class HangfireGlobalConfigurationExtensions + { + public static IGlobalConfiguration UseWindsorJobActivator( + [NotNull] this IGlobalConfiguration configuration, + [NotNull] IIocResolver iocResolver) + { + if (configuration == null) + { + throw new ArgumentNullException("configuration"); + } + + if (iocResolver == null) + { + throw new ArgumentNullException("iocResolver"); + } + + return configuration.UseActivator(new WindsorJobActivator(iocResolver)); + } + } +} \ No newline at end of file diff --git a/src/Abp.HangFire/Hangfire/Configuration/IAbpHangfireConfiguration.cs b/src/Abp.HangFire/Hangfire/Configuration/IAbpHangfireConfiguration.cs new file mode 100644 index 0000000000..c52f5b7d65 --- /dev/null +++ b/src/Abp.HangFire/Hangfire/Configuration/IAbpHangfireConfiguration.cs @@ -0,0 +1,9 @@ +using Hangfire; + +namespace Abp.Hangfire.Configuration +{ + public interface IAbpHangfireConfiguration + { + BackgroundJobServer Server { get; set; } + } +} \ No newline at end of file diff --git a/src/Abp.HangFire/Hangfire/HangfireBackgroundJobManager.cs b/src/Abp.HangFire/Hangfire/HangfireBackgroundJobManager.cs new file mode 100644 index 0000000000..e3fb4a890e --- /dev/null +++ b/src/Abp.HangFire/Hangfire/HangfireBackgroundJobManager.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading.Tasks; +using Abp.BackgroundJobs; +using Abp.Dependency; +using Abp.Hangfire.Configuration; +using Abp.Threading.BackgroundWorkers; +using Castle.Core.Logging; +using Hangfire; + +namespace Abp.Hangfire +{ + public class HangfireBackgroundJobManager : BackgroundWorkerBase, IBackgroundJobManager, ISingletonDependency + { + public ILogger Logger { get; set; } + + private readonly IAbpHangfireConfiguration _configuration; + + public HangfireBackgroundJobManager(IAbpHangfireConfiguration configuration) + { + _configuration = configuration; + + Logger = NullLogger.Instance; + } + + public override void Start() + { + base.Start(); + + if (_configuration.Server == null) + { + _configuration.Server = new BackgroundJobServer(); + } + } + + public override void WaitToStop() + { + try + { + _configuration.Server.Dispose(); + } + catch(Exception ex) + { + Logger.Warn(ex.ToString(), ex); + } + + base.WaitToStop(); + } + + public Task EnqueueAsync(object state, BackgroundJobPriority priority = BackgroundJobPriority.Normal, + TimeSpan? delay = null) where TJob : IBackgroundJob + { + BackgroundJob.Enqueue(job => job.Execute(state)); + + return Task.FromResult(0); + } + } +} diff --git a/src/Abp.HangFire/Hangfire/WindsorJobActivator.cs b/src/Abp.HangFire/Hangfire/WindsorJobActivator.cs new file mode 100644 index 0000000000..e91ee09e05 --- /dev/null +++ b/src/Abp.HangFire/Hangfire/WindsorJobActivator.cs @@ -0,0 +1,34 @@ +using System; +using Abp.Dependency; +using Hangfire; + +namespace Abp.Hangfire +{ + public class WindsorJobActivator : JobActivator + { + readonly IIocResolver _iocResolver; + + /// + /// Initializes new instance of WindsorJobActivator with a Windsor Kernel + /// + public WindsorJobActivator(IIocResolver iocResolver) + { + if (iocResolver == null) + { + throw new ArgumentNullException("iocResolver"); + } + + _iocResolver = iocResolver; + } + + /// + /// Activates a job of a given type using the Windsor Kernel + /// + /// Type of job to activate + /// + public override object ActivateJob(Type jobType) + { + return _iocResolver.Resolve(jobType); + } + } +} \ No newline at end of file diff --git a/src/Abp.HangFire/Properties/AssemblyInfo.cs b/src/Abp.HangFire/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..502019e52a --- /dev/null +++ b/src/Abp.HangFire/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Abp; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Abp.HangFire")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Abp.HangFire")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2a68b114-5b41-423d-b5c7-a53a1841c807")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion(AbpConsts.CurrentVersion)] +[assembly: AssemblyFileVersion(AbpConsts.CurrentVersion)] diff --git a/src/Abp.HangFire/app.config b/src/Abp.HangFire/app.config new file mode 100644 index 0000000000..de5386a470 --- /dev/null +++ b/src/Abp.HangFire/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Abp.HangFire/packages.config b/src/Abp.HangFire/packages.config new file mode 100644 index 0000000000..1c04c83a95 --- /dev/null +++ b/src/Abp.HangFire/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Abp.sln b/src/Abp.sln index ff3e1a283d..00eb2c5751 100644 --- a/src/Abp.sln +++ b/src/Abp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{C1434FA4-ED3F-4A7B-A0D3-8CBB900B1609}" ProjectSection(SolutionItems) = preProject @@ -67,6 +67,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.RedisCache.Tests", "Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.Web.Api.OData", "Abp.Web.Api.OData\Abp.Web.Api.OData.csproj", "{99E64448-0F7C-4E5B-9373-289EC4B2F256}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.HangFire", "Abp.HangFire\Abp.HangFire.csproj", "{58A5158C-C78B-4451-BFD8-FFC31D48D713}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -173,6 +175,10 @@ Global {99E64448-0F7C-4E5B-9373-289EC4B2F256}.Debug|Any CPU.Build.0 = Debug|Any CPU {99E64448-0F7C-4E5B-9373-289EC4B2F256}.Release|Any CPU.ActiveCfg = Release|Any CPU {99E64448-0F7C-4E5B-9373-289EC4B2F256}.Release|Any CPU.Build.0 = Release|Any CPU + {58A5158C-C78B-4451-BFD8-FFC31D48D713}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58A5158C-C78B-4451-BFD8-FFC31D48D713}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58A5158C-C78B-4451-BFD8-FFC31D48D713}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58A5158C-C78B-4451-BFD8-FFC31D48D713}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Abp/Abp.csproj b/src/Abp/Abp.csproj index 6c3262020a..2456943f2f 100644 --- a/src/Abp/Abp.csproj +++ b/src/Abp/Abp.csproj @@ -81,6 +81,17 @@ + + + + + + + + + + + @@ -557,9 +568,7 @@ - - - + diff --git a/src/Abp/AbpKernelModule.cs b/src/Abp/AbpKernelModule.cs index 6a56dfe71c..ae73b66652 100644 --- a/src/Abp/AbpKernelModule.cs +++ b/src/Abp/AbpKernelModule.cs @@ -6,6 +6,7 @@ using Abp.Auditing; using Abp.Authorization; using Abp.Authorization.Interceptors; +using Abp.BackgroundJobs; using Abp.Configuration; using Abp.Dependency; using Abp.Domain.Uow; @@ -89,12 +90,19 @@ public override void PostInitialize() IocManager.Resolve().Initialize(); IocManager.Resolve().Initialize(); IocManager.Resolve().Initialize(); - IocManager.Resolve().Start(); + + if (Configuration.BackgroundJobs.IsEnabled) + { + IocManager.Resolve().Start(); + } } public override void Shutdown() { - IocManager.Resolve().StopAndWaitToStop(); + if (Configuration.BackgroundJobs.IsEnabled) + { + IocManager.Resolve().StopAndWaitToStop(); + } } private void ConfigureCaches() @@ -122,6 +130,17 @@ private void RegisterMissingComponents() IocManager.RegisterIfNot(DependencyLifeStyle.Transient); IocManager.RegisterIfNot(DependencyLifeStyle.Singleton); IocManager.RegisterIfNot(DependencyLifeStyle.Singleton); + + IocManager.RegisterIfNot(); + + if (Configuration.BackgroundJobs.IsEnabled) + { + IocManager.RegisterIfNot(); + } + else + { + IocManager.RegisterIfNot(); + } } } } \ No newline at end of file diff --git a/src/Abp/BackgroundJobs/BackgroundJobConfiguration.cs b/src/Abp/BackgroundJobs/BackgroundJobConfiguration.cs new file mode 100644 index 0000000000..4fa6df61a7 --- /dev/null +++ b/src/Abp/BackgroundJobs/BackgroundJobConfiguration.cs @@ -0,0 +1,12 @@ +namespace Abp.BackgroundJobs +{ + internal class BackgroundJobConfiguration : IBackgroundJobConfiguration + { + public bool IsEnabled { get; set; } + + public BackgroundJobConfiguration() + { + IsEnabled = true; + } + } +} \ No newline at end of file diff --git a/src/Abp/BackgroundJobs/BackgroundJobExecuter.cs b/src/Abp/BackgroundJobs/BackgroundJobExecuter.cs new file mode 100644 index 0000000000..4fd6da2bf0 --- /dev/null +++ b/src/Abp/BackgroundJobs/BackgroundJobExecuter.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Abp.Dependency; +using Abp.Domain.Uow; +using Abp.Runtime.Serialization; +using Abp.Threading; +using Abp.Threading.BackgroundWorkers; +using Abp.Threading.Timers; +using Abp.Timing; +using Castle.Core.Logging; + +namespace Abp.BackgroundJobs +{ + public class BackgroundJobManager : BackgroundWorkerBase, IBackgroundJobManager + { + public ILogger Logger { get; set; } + + private readonly IIocResolver _iocResolver; + private readonly IBackgroundJobStore _store; + private readonly AbpTimer _timer; + + public BackgroundJobManager( + IIocResolver iocResolver, + IBackgroundJobStore store, + AbpTimer timer) + { + _store = store; + _timer = timer; + _iocResolver = iocResolver; + + _timer.Period = 5000; //5 seconds + _timer.Elapsed += Timer_Elapsed; + } + + public async Task EnqueueAsync(object state, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) + where TJob : IBackgroundJob + { + var jobInfo = new BackgroundJobInfo + { + JobType = typeof(TJob).AssemblyQualifiedName, + State = BinarySerializationHelper.Serialize(state), + Priority = priority + }; + + if (delay.HasValue) + { + jobInfo.NextTryTime = Clock.Now.Add(delay.Value); + } + + await _store.InsertAsync(jobInfo); + } + + private void Timer_Elapsed(object sender, EventArgs e) + { + try + { + foreach (var task in AsyncHelper.RunSync(GetWaitingTasksAsync)) + { + TryProcessTask(task); + } + } + catch (Exception ex) + { + Logger.Error(ex.Message, ex); + } + } + + private void TryProcessTask(BackgroundJobInfo jobInfo) + { + try + { + jobInfo.TryCount++; + jobInfo.LastTryTime = DateTime.Now; + + var jobType = Type.GetType(jobInfo.JobType); + using (var job = _iocResolver.ResolveAsDisposable(jobType)) + { + var stateObj = BinarySerializationHelper.DeserializeExtended(jobInfo.State); + + try + { + job.Object.Execute(stateObj); + AsyncHelper.RunSync(() => _store.DeleteAsync(jobInfo)); + } + catch (Exception ex) + { + Logger.Warn(ex.Message, ex); + + var nextTryTime = jobInfo.CalculateNextTryTime(); + if (nextTryTime.HasValue) + { + jobInfo.NextTryTime = nextTryTime.Value; + } + else + { + jobInfo.IsAbandoned = true; + } + + try + { + _store.UpdateAsync(jobInfo); + } + catch (Exception updateEx) + { + Logger.Warn(updateEx.ToString(), updateEx); + } + } + } + } + catch (Exception ex) + { + Logger.Warn(ex.ToString(), ex); + + jobInfo.IsAbandoned = true; + + try + { + _store.UpdateAsync(jobInfo); + } + catch (Exception updateEx) + { + Logger.Warn(updateEx.ToString(), updateEx); + } + } + } + + [UnitOfWork] + protected virtual Task> GetWaitingTasksAsync() + { + return _store.GetWaitingJobsAsync(1000); + } + } +} diff --git a/src/Abp/BackgroundJobs/BackgroundJobInfo.cs b/src/Abp/BackgroundJobs/BackgroundJobInfo.cs new file mode 100644 index 0000000000..5f00d927fc --- /dev/null +++ b/src/Abp/BackgroundJobs/BackgroundJobInfo.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Abp.Domain.Entities; +using Abp.Domain.Entities.Auditing; +using Abp.Timing; + +namespace Abp.BackgroundJobs +{ + [Table("AbpBackgroundJobs")] + public class BackgroundJobInfo : Entity, IHasCreationTime + { + public const int MaxJobClassLength = 256; + public const int MaxJobDataLength = 1024 * 1024 * 1024; //1 megabyte + + [Required] + [StringLength(MaxJobClassLength)] + public virtual string JobType { get; set; } + + [Required] + [MaxLength(MaxJobDataLength)] + public virtual byte[] State { get; set; } + + public virtual short TryCount { get; set; } + + //[Index("IX_IsAbandoned_NextTryTime", 2)] + public virtual DateTime NextTryTime { get; set; } + + public virtual DateTime? LastTryTime { get; set; } + + public virtual DateTime CreationTime { get; set; } + + //[Index("IX_IsAbandoned_NextTryTime", 1)] + public virtual bool IsAbandoned { get; set; } + + public virtual BackgroundJobPriority Priority { get; set; } + + public BackgroundJobInfo() + { + CreationTime = Clock.Now; + NextTryTime = Clock.Now; + Priority = BackgroundJobPriority.Normal; + } + + internal virtual DateTime? CalculateNextTryTime() + { + //TODO: This constants can be configurable in the future + + var nextWaitDuration = 60 * (Math.Pow(2, TryCount - 1)); + var nextTryDate = Clock.Now.AddSeconds(nextWaitDuration); + + if (nextTryDate.Subtract(CreationTime).TotalDays > 2.0) + { + return null; + } + + return nextTryDate; + } + } +} \ No newline at end of file diff --git a/src/Abp/BackgroundJobs/BackgroundJobPriority.cs b/src/Abp/BackgroundJobs/BackgroundJobPriority.cs new file mode 100644 index 0000000000..746328b8c0 --- /dev/null +++ b/src/Abp/BackgroundJobs/BackgroundJobPriority.cs @@ -0,0 +1,11 @@ +namespace Abp.BackgroundJobs +{ + public enum BackgroundJobPriority : byte + { + Low = 5, + BelowNormal = 10, + Normal = 15, + AboveNormal = 20, + High = 25 + } +} \ No newline at end of file diff --git a/src/Abp/BackgroundJobs/IBackgroundJob.cs b/src/Abp/BackgroundJobs/IBackgroundJob.cs new file mode 100644 index 0000000000..cbb351c64a --- /dev/null +++ b/src/Abp/BackgroundJobs/IBackgroundJob.cs @@ -0,0 +1,7 @@ +namespace Abp.BackgroundJobs +{ + public interface IBackgroundJob + { + void Execute(object state); + } +} \ No newline at end of file diff --git a/src/Abp/BackgroundJobs/IBackgroundJobConfiguration.cs b/src/Abp/BackgroundJobs/IBackgroundJobConfiguration.cs new file mode 100644 index 0000000000..df30578612 --- /dev/null +++ b/src/Abp/BackgroundJobs/IBackgroundJobConfiguration.cs @@ -0,0 +1,10 @@ +namespace Abp.BackgroundJobs +{ + public interface IBackgroundJobConfiguration + { + /// + /// Used to enable/disable background job execution. + /// + bool IsEnabled { get; set; } + } +} \ No newline at end of file diff --git a/src/Abp/BackgroundJobs/IBackgroundJobManager.cs b/src/Abp/BackgroundJobs/IBackgroundJobManager.cs new file mode 100644 index 0000000000..e0c22f9c73 --- /dev/null +++ b/src/Abp/BackgroundJobs/IBackgroundJobManager.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; + +namespace Abp.BackgroundJobs +{ + public interface IBackgroundJobManager + { + Task EnqueueAsync(object state, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) + where TJob : IBackgroundJob; + } +} \ No newline at end of file diff --git a/src/Abp/BackgroundJobs/IBackgroundJobStore.cs b/src/Abp/BackgroundJobs/IBackgroundJobStore.cs new file mode 100644 index 0000000000..8c184b8746 --- /dev/null +++ b/src/Abp/BackgroundJobs/IBackgroundJobStore.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Abp.BackgroundJobs +{ + public interface IBackgroundJobStore + { + Task InsertAsync(BackgroundJobInfo jobInfo); + + Task> GetWaitingJobsAsync(int maxResultCount); + + Task DeleteAsync(BackgroundJobInfo jobInfo); + + Task UpdateAsync(BackgroundJobInfo jobInfo); + } +} \ No newline at end of file diff --git a/src/Abp/BackgroundJobs/InMemoryBackgroundJobStore.cs b/src/Abp/BackgroundJobs/InMemoryBackgroundJobStore.cs new file mode 100644 index 0000000000..c47a9d4600 --- /dev/null +++ b/src/Abp/BackgroundJobs/InMemoryBackgroundJobStore.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Abp.Timing; + +namespace Abp.BackgroundJobs +{ + public class InMemoryBackgroundJobStore : IBackgroundJobStore + { + private readonly List _jobs; + + public InMemoryBackgroundJobStore() + { + _jobs = new List(); + } + + public Task InsertAsync(BackgroundJobInfo jobInfo) + { + _jobs.Add(jobInfo); + + return Task.FromResult(0); + } + + public Task> GetWaitingJobsAsync(int maxResultCount) + { + var waitingJobs = _jobs + .Where(t => !t.IsAbandoned && t.NextTryTime <= Clock.Now) + .OrderByDescending(t => t.Priority) + .ThenBy(t => t.TryCount) + .ThenBy(t => t.NextTryTime) + .Take(1000) + .ToList(); + + return Task.FromResult(waitingJobs); + } + + public Task DeleteAsync(BackgroundJobInfo jobInfo) + { + _jobs.Remove(jobInfo); + + return Task.FromResult(0); + } + + public Task UpdateAsync(BackgroundJobInfo jobInfo) + { + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/src/Abp/BackgroundJobs/NullBackgroundJobStore.cs b/src/Abp/BackgroundJobs/NullBackgroundJobStore.cs new file mode 100644 index 0000000000..16a8ff2f1d --- /dev/null +++ b/src/Abp/BackgroundJobs/NullBackgroundJobStore.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Abp.BackgroundJobs +{ + public class NullBackgroundJobStore : IBackgroundJobStore + { + public Task InsertAsync(BackgroundJobInfo jobInfo) + { + return Task.FromResult(0); + } + + public Task> GetWaitingJobsAsync(int maxResultCount) + { + return Task.FromResult(new List()); + } + + public Task DeleteAsync(BackgroundJobInfo jobInfo) + { + return Task.FromResult(0); + } + + public Task UpdateAsync(BackgroundJobInfo jobInfo) + { + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/src/Abp/Configuration/Startup/AbpStartupConfiguration.cs b/src/Abp/Configuration/Startup/AbpStartupConfiguration.cs index 3c90168e47..f091c098a4 100644 --- a/src/Abp/Configuration/Startup/AbpStartupConfiguration.cs +++ b/src/Abp/Configuration/Startup/AbpStartupConfiguration.cs @@ -1,5 +1,6 @@ using Abp.Application.Features; using Abp.Auditing; +using Abp.BackgroundJobs; using Abp.Dependency; using Abp.Domain.Uow; using Abp.Events.Bus; @@ -51,6 +52,11 @@ internal class AbpStartupConfiguration : DictionayBasedConfig, IAbpStartupConfig /// public IFeatureConfiguration Features { get; private set; } + /// + /// Used to configure background job system. + /// + public IBackgroundJobConfiguration BackgroundJobs { get; private set; } + /// /// Used to configure navigation. /// @@ -94,6 +100,7 @@ public void Initialize() MultiTenancy = IocManager.Resolve(); Auditing = IocManager.Resolve(); Caching = IocManager.Resolve(); + BackgroundJobs = IocManager.Resolve(); } } } \ No newline at end of file diff --git a/src/Abp/Configuration/Startup/IAbpStartupConfiguration.cs b/src/Abp/Configuration/Startup/IAbpStartupConfiguration.cs index 2f55aa2b64..e75c96bed9 100644 --- a/src/Abp/Configuration/Startup/IAbpStartupConfiguration.cs +++ b/src/Abp/Configuration/Startup/IAbpStartupConfiguration.cs @@ -1,5 +1,6 @@ using Abp.Application.Features; using Abp.Auditing; +using Abp.BackgroundJobs; using Abp.Dependency; using Abp.Domain.Uow; using Abp.Events.Bus; @@ -78,5 +79,10 @@ public interface IAbpStartupConfiguration : IDictionaryBasedConfig /// Used to configure features. /// IFeatureConfiguration Features { get; } + + /// + /// Used to configure background job system. + /// + IBackgroundJobConfiguration BackgroundJobs { get; } } } \ No newline at end of file diff --git a/src/Abp/Dependency/Installers/AbpCoreInstaller.cs b/src/Abp/Dependency/Installers/AbpCoreInstaller.cs index 28f01ada05..ddff4bba28 100644 --- a/src/Abp/Dependency/Installers/AbpCoreInstaller.cs +++ b/src/Abp/Dependency/Installers/AbpCoreInstaller.cs @@ -1,5 +1,6 @@ using Abp.Application.Features; using Abp.Auditing; +using Abp.BackgroundJobs; using Abp.Configuration.Startup; using Abp.Domain.Uow; using Abp.Localization; @@ -28,6 +29,7 @@ public void Install(IWindsorContainer container, IConfigurationStore store) Component.For().ImplementedBy().LifestyleSingleton(), Component.For().ImplementedBy().LifestyleSingleton(), Component.For().ImplementedBy().LifestyleSingleton(), + Component.For().ImplementedBy().LifestyleSingleton(), Component.For().ImplementedBy().LifestyleSingleton(), Component.For().ImplementedBy().LifestyleSingleton(), Component.For().ImplementedBy().LifestyleTransient(), diff --git a/src/Abp/Dependency/IocRegistrarExtensions.cs b/src/Abp/Dependency/IocRegistrarExtensions.cs index cf45c82b39..6b43755e61 100644 --- a/src/Abp/Dependency/IocRegistrarExtensions.cs +++ b/src/Abp/Dependency/IocRegistrarExtensions.cs @@ -15,15 +15,17 @@ public static class IocRegistrarExtensions /// Type of the class /// Registrar /// Lifestyle of the objects of this type - public static void RegisterIfNot(this IIocRegistrar iocRegistrar, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton) + /// True, if registered for given implementation. + public static bool RegisterIfNot(this IIocRegistrar iocRegistrar, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton) where T : class { if (iocRegistrar.IsRegistered()) { - return; + return false; } iocRegistrar.Register(lifeStyle); + return true; } /// @@ -32,14 +34,16 @@ public static void RegisterIfNot(this IIocRegistrar iocRegistrar, DependencyL /// Registrar /// Type of the class /// Lifestyle of the objects of this type - public static void RegisterIfNot(this IIocRegistrar iocRegistrar, Type type, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton) + /// True, if registered for given implementation. + public static bool RegisterIfNot(this IIocRegistrar iocRegistrar, Type type, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton) { if (iocRegistrar.IsRegistered(type)) { - return; + return false; } iocRegistrar.Register(type, lifeStyle); + return true; } /// @@ -49,16 +53,18 @@ public static void RegisterIfNot(this IIocRegistrar iocRegistrar, Type type, Dep /// The type that implements /// Registrar /// Lifestyle of the objects of this type - public static void RegisterIfNot(this IIocRegistrar iocRegistrar, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton) + /// True, if registered for given implementation. + public static bool RegisterIfNot(this IIocRegistrar iocRegistrar, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton) where TType : class where TImpl : class, TType { if (iocRegistrar.IsRegistered()) { - return; + return false; } iocRegistrar.Register(lifeStyle); + return true; } @@ -69,14 +75,16 @@ public static void RegisterIfNot(this IIocRegistrar iocRegistrar, Type type, Dep /// Type of the class /// The type that implements /// Lifestyle of the objects of this type - public static void RegisterIfNot(this IIocRegistrar iocRegistrar, Type type, Type impl, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton) + /// True, if registered for given implementation. + public static bool RegisterIfNot(this IIocRegistrar iocRegistrar, Type type, Type impl, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton) { if (iocRegistrar.IsRegistered(type)) { - return; + return false; } iocRegistrar.Register(type, impl, lifeStyle); + return true; } #endregion diff --git a/src/Abp/Runtime/Serialization/BinarySerializationHelper.cs b/src/Abp/Runtime/Serialization/BinarySerializationHelper.cs new file mode 100644 index 0000000000..4e4ac517f4 --- /dev/null +++ b/src/Abp/Runtime/Serialization/BinarySerializationHelper.cs @@ -0,0 +1,132 @@ +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; + +namespace Abp.Runtime.Serialization +{ + /// + /// This class is used to simplify serialization/deserialization operations. + /// Uses .NET binary serialization. + /// + public static class BinarySerializationHelper + { + /// + /// Serializes an object and returns as a byte array. + /// + /// object to be serialized + /// bytes of object + public static byte[] Serialize(object obj) + { + using (var memoryStream = new MemoryStream()) + { + new BinaryFormatter().Serialize(memoryStream, obj); + return memoryStream.ToArray(); + } + } + + /// + /// Serializes an object into a stream. + /// + /// object to be serialized + /// Stream to serialize in + /// bytes of object + public static void Serialize(object obj, Stream stream) + { + new BinaryFormatter().Serialize(stream, obj); + } + + /// + /// Deserializes an object from given byte array. + /// + /// The byte array that contains object + /// deserialized object + public static object Deserialize(byte[] bytes) + { + using (var memoryStream = new MemoryStream(bytes)) + { + var binaryFormatter = new BinaryFormatter + { + AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple + }; + + return binaryFormatter.Deserialize(memoryStream); + } + } + + /// + /// Deserializes an object from given stream. + /// + /// The stream that contains object + /// deserialized object + public static object Deserialize(Stream stream) + { + var binaryFormatter = new BinaryFormatter + { + AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple, + }; + + return binaryFormatter.Deserialize(stream); + } + + /// + /// Deserializes an object from given byte array. + /// Difference from is that; this method can also deserialize + /// types that are defined in dynamically loaded assemblies (like PlugIns). + /// + /// The byte array that contains object + /// deserialized object + public static object DeserializeExtended(byte[] bytes) + { + using (var memoryStream = new MemoryStream(bytes)) + { + var binaryFormatter = new BinaryFormatter + { + AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple, + Binder = new DeserializationAppDomainBinder() + }; + + return binaryFormatter.Deserialize(memoryStream); + } + } + + /// + /// Deserializes an object from given stream. + /// Difference from is that; this method can also deserialize + /// types that are defined in dynamically loaded assemblies (like PlugIns). + /// + /// The stream that contains object + /// deserialized object + public static object DeserializeExtended(Stream stream) + { + var binaryFormatter = new BinaryFormatter + { + AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple, + Binder = new DeserializationAppDomainBinder() + }; + + return binaryFormatter.Deserialize(stream); + } + + /// + /// This class is used in deserializing to allow deserializing objects that are defined + /// in assemlies that are load in runtime (like PlugIns). + /// + private sealed class DeserializationAppDomainBinder : SerializationBinder + { + public override Type BindToType(string assemblyName, string typeName) + { + var toAssemblyName = assemblyName.Split(',')[0]; + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.FullName.Split(',')[0] == toAssemblyName) + { + return assembly.GetType(typeName); + } + } + + return null; + } + } + } +}