Skip to content

Commit

Permalink
Merge pull request #9 from /issues/8-trailing-job-context
Browse files Browse the repository at this point in the history
Implement BatchContext, where job parameter values are preserved if another job is enqueued / scheduled with the Hangfire Client inside an executing job
  • Loading branch information
maitlandmarshall authored Sep 11, 2020
2 parents 0fef8a6 + 9b157fb commit 0f65b95
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 5 deletions.
90 changes: 90 additions & 0 deletions MAD.Integration.Common.Tests/BatchContextAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using Hangfire;
using Hangfire.MemoryStorage;
using MAD.Integration.Common.Jobs;
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading.Tasks;

namespace MAD.Integration.Common.Tests
{
[TestClass]
public class BatchContextAttributeTests
{
private static readonly TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
public static async Task RootJob()
{
try
{
BackgroundJobContext.Current.SetBatchParameter("Test", "this is a test");
await Task.Delay(10);
BackgroundJobContext.Current.SetBatchParameter("Test2", "a separate test");

BackgroundJob.Enqueue(() => NestedJob());
}
catch(Exception ex)
{
taskCompletionSource.SetException(ex);
}
}

public static void NestedJob()
{
try
{
var batchId = BackgroundJobContext.Current.GetBatchParameter<Guid>("Id");
var started = BackgroundJobContext.Current.GetBatchParameter<DateTime>("Started");
var test1 = BackgroundJobContext.Current.GetBatchParameter<string>("Test");
var test2 = BackgroundJobContext.Current.GetBatchParameter<string>("Test2");

Assert.IsNotNull(batchId);
Assert.IsNotNull(started);
Assert.IsTrue(!string.IsNullOrEmpty(test1));
Assert.IsTrue(!string.IsNullOrEmpty(test2));

BackgroundJob.Enqueue(() => NestedJob2());
}
catch (Exception ex)
{
taskCompletionSource.SetException(ex);
}
}

public static void NestedJob2()
{
try
{
var batchId = BackgroundJobContext.Current.GetBatchParameter<Guid>("Id");
var started = BackgroundJobContext.Current.GetBatchParameter<DateTime>("Started");
var test1 = BackgroundJobContext.Current.GetBatchParameter<string>("Test");
var test2 = BackgroundJobContext.Current.GetBatchParameter<string>("Test2");

Assert.IsNotNull(batchId);
Assert.IsNotNull(started);
Assert.IsTrue(!string.IsNullOrEmpty(test1));
Assert.IsTrue(!string.IsNullOrEmpty(test2));

taskCompletionSource.SetResult(true);
}
catch (Exception ex)
{
taskCompletionSource.SetException(ex);
}
}

[TestMethod]
public async Task SetBatchParameter_NestedJob_ValuesIntact()
{
var host = IntegrationHost.CreateDefaultBuilder()
.UseHangfire(cfg => cfg.UseMemoryStorage().UseFilter(new BatchContextAttribute()))
.Build();

BackgroundJob.Enqueue(() => RootJob());

_ = host.RunAsync();

var result = await taskCompletionSource.Task;
Assert.IsTrue(result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ namespace MAD.Integration.Common.Jobs
{
[AttributeUsage(AttributeTargets.Method)]
public class ConsumerAttribute : Attribute { }
public class EnableBackgroundJobResultConsumersFilter : JobFilterAttribute, IServerFilter
public class BackgroundJobResultConsumersAttribute : JobFilterAttribute, IServerFilter
{
private Lazy<IEnumerable<MethodInfo>> consumers;
private IEnumerable<MethodInfo> Consumers
{
get => this.consumers.Value;
}

public EnableBackgroundJobResultConsumersFilter()
public BackgroundJobResultConsumersAttribute()
{
this.consumers = new Lazy<IEnumerable<MethodInfo>>(() =>
{
Expand Down
66 changes: 66 additions & 0 deletions MAD.Integration.Common/Jobs/BatchContextAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Hangfire;
using Hangfire.Client;
using Hangfire.Common;
using Hangfire.Server;
using MAD.Integration.Common.Jobs.Utils;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;

namespace MAD.Integration.Common.Jobs
{
public class BatchContextAttribute : JobFilterAttribute, IClientFilter, IServerFilter
{
public BatchContextAttribute()
{
this.Order = 100;
}

public void OnPerforming(PerformingContext filterContext)
{
var ctxIdJobParam = filterContext.GetBatchParameter<string>("Id");

if (string.IsNullOrEmpty(ctxIdJobParam))
{
filterContext
.SetBatchParameter($"Id", Guid.NewGuid())
.SetBatchParameter($"Started", DateTime.UtcNow);
}
else
{
var jobDetails = JobStorage.Current.GetMonitoringApi().JobDetails(filterContext.BackgroundJob.Id);

foreach (var kp in jobDetails.Properties)
{
if (!kp.Key.StartsWith("ctx")) continue;

var jObj = JToken.Parse(kp.Value);
filterContext.Items[kp.Key] = jObj;
}
}
}
public void OnPerformed(PerformedContext filterContext) { }

public void OnCreated(CreatedContext filterContext) { }
public void OnCreating(CreatingContext filterContext)
{
var currentJob = BackgroundJobContext.Current;
if (currentJob is null) return;

var ctxIdJobParam = currentJob.GetBatchParameter<Guid?>("Id");
if (!ctxIdJobParam.HasValue) return;

foreach (var itm in currentJob.Items)
{
if (!itm.Key.StartsWith("ctx:")) continue;
var key = itm.Key.Substring("ctx:".Length);

filterContext.SetBatchParameter(key, itm.Value);
}
}


}
}
64 changes: 64 additions & 0 deletions MAD.Integration.Common/Jobs/BatchContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Hangfire;
using Hangfire.Client;
using Hangfire.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MAD.Integration.Common.Jobs
{
public static class BatchContextExtensions
{
public static PerformContext SetBatchParameter(this PerformContext ctx, string name, object value)
{
SetBatchParameterImpl(name, value, ctx);
return ctx;
}

internal static CreateContext SetBatchParameter(this CreatingContext ctx, string name, object value)
{
SetBatchParameterImpl(name, value, ctx);
return ctx;
}

private static void SetBatchParameterImpl(string name, object value, object ctx)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));

name = $"ctx:{name}";

if (ctx is PerformContext performContext)
{
performContext.Items[name] = value;
performContext.SetJobParameter(name, value);
}
else if (ctx is CreatingContext creatingContext)
{
creatingContext.Items[name] = value;
creatingContext.SetJobParameter(name, value);
}
else throw new NotSupportedException();
}

public static T GetBatchParameter<T>(this PerformContext job, string name)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));

name = $"ctx:{name}";

try
{
var connection = JobStorage.Current.GetConnection();
var param = connection.GetJobParameter(job.BackgroundJob.Id, name);

return job.GetJobParameter<T>(name);
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Could not get a value of the job parameter `{name}`. See inner exception for details.", ex);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace MAD.Integration.Common.Jobs
{
public class DisableIdenticalQueuedItemsFilter : JobFilterAttribute, IClientFilter, IServerFilter, IApplyStateFilter
public class DisableIdenticalQueuedItemsAttribute : JobFilterAttribute, IClientFilter, IServerFilter, IApplyStateFilter
{
private static readonly TimeSpan LockTimeout = TimeSpan.FromSeconds(100);

Expand Down
2 changes: 1 addition & 1 deletion MAD.Integration.Common/MAD.Integration.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<Description>Provides an IntegrationHost which allows you to build integrations using Hangfire and AspNetCore</Description>
<AssemblyVersion>1.0.4.1</AssemblyVersion>
<FileVersion>1.0.4.1</FileVersion>
<Version>1.0.4.1</Version>
<Version>1.0.5-dev</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using System.IO;

namespace MAD.Integration.Common.Settings
Expand Down Expand Up @@ -30,7 +31,10 @@ private static void BuildSettingsFile()
}
else
{
settings.Create().Dispose();
File.WriteAllText(settings.FullName, JsonConvert.SerializeObject(new {
ConnectionString = "",
BindingPort = 666
}));
}
}
}
Expand Down

0 comments on commit 0f65b95

Please sign in to comment.