-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
7 changed files
with
229 additions
and
5 deletions.
There are no files selected for viewing
90 changes: 90 additions & 0 deletions
90
MAD.Integration.Common.Tests/BatchContextAttributeTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
|
||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters