Skip to content

Commit

Permalink
Static callbacks and record contexts for CoreBackgroundJobFactory
Browse files Browse the repository at this point in the history
  • Loading branch information
odinserj committed Jun 20, 2024
1 parent 806ed76 commit 3e20dce
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 25 deletions.
68 changes: 43 additions & 25 deletions src/Hangfire.Core/Client/CoreBackgroundJobFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,14 @@ private BackgroundJob CreateBackgroundJobTwoSteps(CreateContext context, Diction
// identifiers. Since they also will be eventually expired leaving no trace, we can
// consider that only one background job is created, regardless of retry attempts
// number.
var jobId = RetryOnException(ref attemptsLeft, _ => context.Connection.CreateExpiredJob(
context.Job,
parameters,
createdAt,
expireIn));
var jobId = RetryOnException(
ref attemptsLeft,
static (_, ctx) => ctx.Context.Connection.CreateExpiredJob(
ctx.Context.Job,
ctx.Parameters,
ctx.CreatedAt,
ctx.ExpireIn),
new JobCreateContext { Context = context, Parameters = parameters, CreatedAt = createdAt, ExpireIn = expireIn });

if (String.IsNullOrEmpty(jobId))
{
Expand All @@ -99,7 +102,7 @@ private BackgroundJob CreateBackgroundJobTwoSteps(CreateContext context, Diction

if (context.InitialState != null)
{
RetryOnException(ref attemptsLeft, attempt =>
RetryOnException(ref attemptsLeft, static (attempt, ctx) =>
{
if (attempt > 0)
{
Expand All @@ -109,46 +112,46 @@ private BackgroundJob CreateBackgroundJobTwoSteps(CreateContext context, Diction
// its state is null, and since only the current thread knows the job's identifier
// when its state is null, and since we shouldn't do anything when it's non-null,
// there will be no any race conditions.
var data = context.Connection.GetJobData(jobId);
if (data == null) throw new InvalidOperationException($"Was unable to initialize a background job '{jobId}', because it doesn't exists.");
var data = ctx.Context.Connection.GetJobData(ctx.BackgroundJob.Id);
if (data == null) throw new InvalidOperationException($"Was unable to initialize a background job '{ctx.BackgroundJob.Id}', because it doesn't exists.");

if (!String.IsNullOrEmpty(data.State)) return;
}

using (var transaction = context.Connection.CreateWriteTransaction())
using (var transaction = ctx.Context.Connection.CreateWriteTransaction())
{
var applyContext = new ApplyStateContext(
context.Storage,
context.Connection,
ctx.Context.Storage,
ctx.Context.Connection,
transaction,
backgroundJob,
context.InitialState,
ctx.BackgroundJob,
ctx.Context.InitialState!,
oldStateName: null,
context.Profiler,
StateMachine);
ctx.Context.Profiler,
ctx.StateMachine);

StateMachine.ApplyState(applyContext);
ctx.StateMachine.ApplyState(applyContext);

transaction.Commit();
}
});
}, new JobInitializeContext { Context = context, StateMachine = StateMachine, BackgroundJob = backgroundJob });
}

return backgroundJob;
}

private void RetryOnException(ref int attemptsLeft, Action<int> action)
private void RetryOnException<TContext>(ref int attemptsLeft, Action<int, TContext> action, TContext context)
{
RetryOnException(ref attemptsLeft, attempt =>
RetryOnException(ref attemptsLeft, static (attempt, ctx) =>
{
action(attempt);
ctx.Key(attempt, ctx.Value);
return true;
});
}, new KeyValuePair<Action<int, TContext>, TContext>(action, context));
}

private T RetryOnException<T>(ref int attemptsLeft, Func<int, T> action)
private TResult RetryOnException<TContext, TResult>(ref int attemptsLeft, Func<int, TContext, TResult> action, TContext context)
{
var exceptions = new List<Exception>();
List<Exception> exceptions = null;
var attempt = 0;
var delay = TimeSpan.Zero;

Expand All @@ -161,11 +164,11 @@ private T RetryOnException<T>(ref int attemptsLeft, Func<int, T> action)
Thread.Sleep(delay);
}

return action(attempt++);
return action(attempt++, context);
}
catch (Exception ex) when (ex.IsCatchableExceptionType())
{
exceptions.Add(ex);
(exceptions ??= new List<Exception>()).Add(ex);
_logger.DebugException("An exception occurred while creating a background job, see inner exception for details.", ex);
delay = RetryDelayFunc(attempt);
}
Expand All @@ -189,5 +192,20 @@ private static TimeSpan GetRetryDelay(int retryAttempt)
default: return TimeSpan.FromMilliseconds(500);
}
}

private readonly record struct JobCreateContext
{
public required CreateContext Context { get; init; }
public required Dictionary<string, string> Parameters { get; init; }
public required DateTime CreatedAt { get; init; }
public required TimeSpan ExpireIn { get; init; }
}

private readonly record struct JobInitializeContext
{
public required CreateContext Context { get; init; }
public required IStateMachine StateMachine { get; init; }
public required BackgroundJob BackgroundJob { get; init; }
}
}
}
41 changes: 41 additions & 0 deletions src/Hangfire.Core/Common/LanguagePolyfills.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.ComponentModel;

namespace System.Runtime.CompilerServices
{
#if !NET5_0_OR_GREATER

[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit {}

#endif // !NET5_0_OR_GREATER

#if !NET7_0_OR_GREATER

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class RequiredMemberAttribute : Attribute {}

[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
internal sealed class CompilerFeatureRequiredAttribute : Attribute
{
public CompilerFeatureRequiredAttribute(string featureName)
{
FeatureName = featureName;
}

public string FeatureName { get; }
public bool IsOptional { get; init; }

public const string RefStructs = nameof(RefStructs);
public const string RequiredMembers = nameof(RequiredMembers);
}

#endif // !NET7_0_OR_GREATER
}

namespace System.Diagnostics.CodeAnalysis
{
#if !NET7_0_OR_GREATER
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
internal sealed class SetsRequiredMembersAttribute : Attribute {}
#endif
}

0 comments on commit 3e20dce

Please sign in to comment.