Browse Source

Resolved #590: Memory leak while checking background jobs periodically.

pull/625/head
Halil ibrahim Kalkan 8 years ago
parent
commit
6501b4d811
  1. 58
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs
  2. 8
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs
  3. 13
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs
  4. 19
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs
  5. 46
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs
  6. 74
      framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs
  7. 1
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs

58
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs

@ -1,8 +1,7 @@
using System; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BackgroundJobs namespace Volo.Abp.BackgroundJobs
@ -11,14 +10,10 @@ namespace Volo.Abp.BackgroundJobs
{ {
public ILogger<BackgroundJobExecuter> Logger { protected get; set; } public ILogger<BackgroundJobExecuter> Logger { protected get; set; }
protected IServiceProvider ServiceProvider { get; }
protected BackgroundJobOptions Options { get; } protected BackgroundJobOptions Options { get; }
public BackgroundJobExecuter( public BackgroundJobExecuter(IOptions<BackgroundJobOptions> options)
IServiceProvider serviceProvider,
IOptions<BackgroundJobOptions> options)
{ {
ServiceProvider = serviceProvider;
Options = options.Value; Options = options.Value;
Logger = NullLogger<BackgroundJobExecuter>.Instance; Logger = NullLogger<BackgroundJobExecuter>.Instance;
@ -26,34 +21,31 @@ namespace Volo.Abp.BackgroundJobs
public virtual void Execute(JobExecutionContext context) public virtual void Execute(JobExecutionContext context)
{ {
using (var scope = ServiceProvider.CreateScope()) var job = context.ServiceProvider.GetService(context.JobType);
if (job == null)
{ {
var job = scope.ServiceProvider.GetService(context.JobType); throw new AbpException("The job type is not registered to DI: " + context.JobType);
if (job == null) }
{
throw new AbpException("The job type is not registered to DI: " + context.JobType);
}
var jobExecuteMethod = context.JobType.GetMethod(nameof(IBackgroundJob<object>.Execute)); var jobExecuteMethod = context.JobType.GetMethod(nameof(IBackgroundJob<object>.Execute));
if (jobExecuteMethod == null) if (jobExecuteMethod == null)
{ {
throw new AbpException($"Given job type does not implement {typeof(IBackgroundJob<>).Name}. The job type was: " + context.JobType); throw new AbpException($"Given job type does not implement {typeof(IBackgroundJob<>).Name}. The job type was: " + context.JobType);
} }
try try
{ {
jobExecuteMethod.Invoke(job, new[] { context.JobArgs }); jobExecuteMethod.Invoke(job, new[] { context.JobArgs });
} }
catch (Exception ex) catch (Exception ex)
{
Logger.LogException(ex);
throw new BackgroundJobExecutionException("A background job execution is failed. See inner exception for details.", ex)
{ {
Logger.LogException(ex); JobType = context.JobType.AssemblyQualifiedName,
JobArgs = context.JobArgs
throw new BackgroundJobExecutionException("A background job execution is failed. See inner exception for details.", ex) };
{
JobType = context.JobType.AssemblyQualifiedName,
JobArgs = context.JobArgs
};
}
} }
} }
} }

8
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/JobExecutionContext.cs

@ -1,15 +1,19 @@
using System; using System;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BackgroundJobs namespace Volo.Abp.BackgroundJobs
{ {
public class JobExecutionContext public class JobExecutionContext : IServiceProviderAccessor
{ {
public IServiceProvider ServiceProvider { get; }
public Type JobType { get; } public Type JobType { get; }
public object JobArgs { get; } public object JobArgs { get; }
public JobExecutionContext(Type jobType, object jobArgs) public JobExecutionContext(IServiceProvider serviceProvider, Type jobType, object jobArgs)
{ {
ServiceProvider = serviceProvider;
JobType = jobType; JobType = jobType;
JobArgs = jobArgs; JobArgs = jobArgs;
} }

13
framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireBackgroundJobManager.cs

@ -13,11 +13,20 @@ namespace Volo.Abp.BackgroundJobs.Hangfire
{ {
if (!delay.HasValue) if (!delay.HasValue)
{ {
return Task.FromResult(BackgroundJob.Enqueue<HangfireJobExecutionAdapter<TArgs>>(adapter => adapter.Execute(args))); return Task.FromResult(
BackgroundJob.Enqueue<HangfireJobExecutionAdapter<TArgs>>(
adapter => adapter.Execute(args)
)
);
} }
else else
{ {
return Task.FromResult(BackgroundJob.Schedule<HangfireJobExecutionAdapter<TArgs>>(adapter => adapter.Execute(args), delay.Value)); return Task.FromResult(
BackgroundJob.Schedule<HangfireJobExecutionAdapter<TArgs>>(
adapter => adapter.Execute(args),
delay.Value
)
);
} }
} }
} }

19
framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs

@ -1,23 +1,32 @@
using Microsoft.Extensions.Options; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Volo.Abp.BackgroundJobs.Hangfire namespace Volo.Abp.BackgroundJobs.Hangfire
{ {
public class HangfireJobExecutionAdapter<TArgs> public class HangfireJobExecutionAdapter<TArgs>
{ {
protected BackgroundJobOptions Options { get; } protected BackgroundJobOptions Options { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected IBackgroundJobExecuter JobExecuter { get; } protected IBackgroundJobExecuter JobExecuter { get; }
public HangfireJobExecutionAdapter(IOptions<BackgroundJobOptions> options, IBackgroundJobExecuter jobExecuter) public HangfireJobExecutionAdapter(
IOptions<BackgroundJobOptions> options,
IBackgroundJobExecuter jobExecuter,
IServiceScopeFactory serviceScopeFactory)
{ {
JobExecuter = jobExecuter; JobExecuter = jobExecuter;
ServiceScopeFactory = serviceScopeFactory;
Options = options.Value; Options = options.Value;
} }
public void Execute(TArgs args) public void Execute(TArgs args)
{ {
var jobType = Options.GetJob(typeof(TArgs)).JobType; using (var scope = ServiceScopeFactory.CreateScope())
var context = new JobExecutionContext(jobType, args); {
JobExecuter.Execute(context); var jobType = Options.GetJob(typeof(TArgs)).JobType;
var context = new JobExecutionContext(scope.ServiceProvider, jobType, args);
JobExecuter.Execute(context);
}
} }
} }
} }

46
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -28,6 +29,7 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
protected IChannelPool ChannelPool { get; } protected IChannelPool ChannelPool { get; }
protected IRabbitMqSerializer Serializer { get; } protected IRabbitMqSerializer Serializer { get; }
protected IBackgroundJobExecuter JobExecuter { get; } protected IBackgroundJobExecuter JobExecuter { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected AsyncLock SyncObj = new AsyncLock(); protected AsyncLock SyncObj = new AsyncLock();
protected bool IsDiposed { get; private set; } protected bool IsDiposed { get; private set; }
@ -37,12 +39,14 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
IOptions<RabbitMqBackgroundJobOptions> rabbitMqBackgroundJobOptions, IOptions<RabbitMqBackgroundJobOptions> rabbitMqBackgroundJobOptions,
IChannelPool channelPool, IChannelPool channelPool,
IRabbitMqSerializer serializer, IRabbitMqSerializer serializer,
IBackgroundJobExecuter jobExecuter) IBackgroundJobExecuter jobExecuter,
IServiceScopeFactory serviceScopeFactory)
{ {
BackgroundJobOptions = backgroundJobOptions.Value; BackgroundJobOptions = backgroundJobOptions.Value;
RabbitMqBackgroundJobOptions = rabbitMqBackgroundJobOptions.Value; RabbitMqBackgroundJobOptions = rabbitMqBackgroundJobOptions.Value;
Serializer = serializer; Serializer = serializer;
JobExecuter = jobExecuter; JobExecuter = jobExecuter;
ServiceScopeFactory = serviceScopeFactory;
ChannelPool = channelPool; ChannelPool = channelPool;
JobConfiguration = BackgroundJobOptions.GetJob(typeof(TArgs)); JobConfiguration = BackgroundJobOptions.GetJob(typeof(TArgs));
@ -167,25 +171,29 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
protected virtual void MessageReceived(object sender, BasicDeliverEventArgs ea) protected virtual void MessageReceived(object sender, BasicDeliverEventArgs ea)
{ {
var context = new JobExecutionContext( using (var scope = ServiceScopeFactory.CreateScope())
JobConfiguration.JobType,
Serializer.Deserialize(ea.Body, typeof(TArgs))
);
try
{
JobExecuter.Execute(context);
ChannelAccessor.Channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
}
catch (BackgroundJobExecutionException)
{ {
//TODO: Reject like that? var context = new JobExecutionContext(
ChannelAccessor.Channel.BasicReject(deliveryTag: ea.DeliveryTag, requeue: true); scope.ServiceProvider,
} JobConfiguration.JobType,
catch (Exception) Serializer.Deserialize(ea.Body, typeof(TArgs))
{ );
//TODO: Reject like that?
ChannelAccessor.Channel.BasicReject(deliveryTag: ea.DeliveryTag, requeue: false); try
{
JobExecuter.Execute(context);
ChannelAccessor.Channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
}
catch (BackgroundJobExecutionException)
{
//TODO: Reject like that?
ChannelAccessor.Channel.BasicReject(deliveryTag: ea.DeliveryTag, requeue: true);
}
catch (Exception)
{
//TODO: Reject like that?
ChannelAccessor.Channel.BasicReject(deliveryTag: ea.DeliveryTag, requeue: false);
}
} }
} }

74
framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs

@ -1,4 +1,5 @@
using System; using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.BackgroundWorkers; using Volo.Abp.BackgroundWorkers;
@ -11,26 +12,26 @@ namespace Volo.Abp.BackgroundJobs
public class BackgroundJobWorker : PeriodicBackgroundWorkerBase, IBackgroundJobWorker, ISingletonDependency public class BackgroundJobWorker : PeriodicBackgroundWorkerBase, IBackgroundJobWorker, ISingletonDependency
{ {
protected IBackgroundJobExecuter JobExecuter { get; } protected IBackgroundJobExecuter JobExecuter { get; }
protected IBackgroundJobStore Store { get; }
protected BackgroundJobOptions JobOptions { get; } protected BackgroundJobOptions JobOptions { get; }
protected BackgroundJobWorkerOptions WorkerOptions { get; } protected BackgroundJobWorkerOptions WorkerOptions { get; }
protected IClock Clock { get; } protected IClock Clock { get; }
protected IBackgroundJobSerializer Serializer { get; } protected IBackgroundJobSerializer Serializer { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
public BackgroundJobWorker( public BackgroundJobWorker(
IBackgroundJobStore store,
AbpTimer timer, AbpTimer timer,
IBackgroundJobExecuter jobExecuter, IBackgroundJobExecuter jobExecuter,
IBackgroundJobSerializer serializer, IBackgroundJobSerializer serializer,
IOptions<BackgroundJobOptions> jobOptions, IOptions<BackgroundJobOptions> jobOptions,
IOptions<BackgroundJobWorkerOptions> workerOptions, IOptions<BackgroundJobWorkerOptions> workerOptions,
IClock clock) IClock clock,
IServiceScopeFactory serviceScopeFactory)
: base(timer) : base(timer)
{ {
JobExecuter = jobExecuter; JobExecuter = jobExecuter;
Serializer = serializer; Serializer = serializer;
Clock = clock; Clock = clock;
Store = store; ServiceScopeFactory = serviceScopeFactory;
WorkerOptions = workerOptions.Value; WorkerOptions = workerOptions.Value;
JobOptions = jobOptions.Value; JobOptions = jobOptions.Value;
Timer.Period = WorkerOptions.JobPollPeriod; Timer.Period = WorkerOptions.JobPollPeriod;
@ -38,53 +39,60 @@ namespace Volo.Abp.BackgroundJobs
protected override void DoWork() protected override void DoWork()
{ {
var waitingJobs = AsyncHelper.RunSync(() => Store.GetWaitingJobsAsync(WorkerOptions.MaxJobFetchCount)); using (var scope = ServiceScopeFactory.CreateScope())
foreach (var jobInfo in waitingJobs)
{ {
jobInfo.TryCount++; var store = scope.ServiceProvider.GetRequiredService<IBackgroundJobStore>();
jobInfo.LastTryTime = Clock.Now;
var waitingJobs = AsyncHelper.RunSync(
() => store.GetWaitingJobsAsync(WorkerOptions.MaxJobFetchCount)
);
try foreach (var jobInfo in waitingJobs)
{ {
var jobConfiguration = JobOptions.GetJob(jobInfo.JobName); jobInfo.TryCount++;
var jobArgs = Serializer.Deserialize(jobInfo.JobArgs, jobConfiguration.ArgsType); jobInfo.LastTryTime = Clock.Now;
var context = new JobExecutionContext(jobConfiguration.JobType, jobArgs);
try try
{ {
JobExecuter.Execute(context); var jobConfiguration = JobOptions.GetJob(jobInfo.JobName);
AsyncHelper.RunSync(() => Store.DeleteAsync(jobInfo.Id)); var jobArgs = Serializer.Deserialize(jobInfo.JobArgs, jobConfiguration.ArgsType);
} var context = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType, jobArgs);
catch (BackgroundJobExecutionException)
{ try
var nextTryTime = CalculateNextTryTime(jobInfo);
if (nextTryTime.HasValue)
{ {
jobInfo.NextTryTime = nextTryTime.Value; JobExecuter.Execute(context);
AsyncHelper.RunSync(() => store.DeleteAsync(jobInfo.Id));
} }
else catch (BackgroundJobExecutionException)
{ {
jobInfo.IsAbandoned = true; var nextTryTime = CalculateNextTryTime(jobInfo);
} if (nextTryTime.HasValue)
{
jobInfo.NextTryTime = nextTryTime.Value;
}
else
{
jobInfo.IsAbandoned = true;
}
TryUpdate(jobInfo); TryUpdate(store, jobInfo);
}
}
catch (Exception ex)
{
Logger.LogException(ex);
jobInfo.IsAbandoned = true;
TryUpdate(store, jobInfo);
} }
}
catch (Exception ex)
{
Logger.LogException(ex);
jobInfo.IsAbandoned = true;
TryUpdate(jobInfo);
} }
} }
} }
protected virtual void TryUpdate(BackgroundJobInfo jobInfo) protected virtual void TryUpdate(IBackgroundJobStore store, BackgroundJobInfo jobInfo)
{ {
try try
{ {
Store.UpdateAsync(jobInfo); store.UpdateAsync(jobInfo);
} }
catch (Exception updateEx) catch (Exception updateEx)
{ {

1
framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs

@ -25,6 +25,7 @@ namespace Volo.Abp.BackgroundJobs
_backgroundJobExecuter.Execute( _backgroundJobExecuter.Execute(
new JobExecutionContext( new JobExecutionContext(
ServiceProvider,
typeof(MyJob), typeof(MyJob),
new MyJobArgs("42") new MyJobArgs("42")
) )

Loading…
Cancel
Save