Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" />
<!-- <PackageReference Include="OpenTelemetry.Exporter.Zipkin" />-->

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Commenting out package references is generally discouraged as it leaves dead code in the project file. If the Zipkin exporter is no longer needed, it should be removed entirely. If it is intended to be optional or used only in specific environments, consider using conditional property groups or documenting the reason for keeping it commented out.

<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
<PackageReference Include="OpenTelemetry.Instrumentation.Process" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Task<Guid> EnqueueAsync<TPayload>(
TPayload payload,
string schedule,
Dictionary<string, object>? metadata = null,
JobScheduleFailurePolicy? failurePolicyOptions = null,
CancellationToken cancellationToken = default);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Task ScheduleAsync(
string jobName,
string schedule,
ReadOnlyMemory<byte> payload,
JobScheduleFailurePolicy? failurePolicyOptions = null,
CancellationToken cancellationToken = default);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace BBT.Aether.BackgroundJob;

public sealed class JobScheduleFailurePolicy
{
public FailurePolicyType PolicyType { get; private init; }
public TimeSpan? Interval { get; private init; }
public uint? MaxRetries { get; private init; }

public static JobScheduleFailurePolicy Drop() =>
new() { PolicyType = FailurePolicyType.Drop };

public static JobScheduleFailurePolicy Constant(TimeSpan interval, uint? maxRetries = null) =>
new() { PolicyType = FailurePolicyType.Constant, Interval = interval, MaxRetries = maxRetries };
}

public enum FailurePolicyType { Drop, Constant }
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public async Task<Guid> EnqueueAsync<TPayload>(
TPayload payload,
string schedule,
Dictionary<string, object>? metadata = null,
JobScheduleFailurePolicy? failurePolicyOptions = null,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(handlerName))
Expand Down Expand Up @@ -116,7 +117,7 @@ public async Task<Guid> EnqueueAsync<TPayload>(
// This prevents race condition where scheduler triggers before DB write completes
uow.OnCompleted(async _ =>
{
await jobScheduler.ScheduleAsync(handlerName, jobName, schedule, payloadBytes, cancellationToken);
await jobScheduler.ScheduleAsync(handlerName, jobName, schedule, payloadBytes, failurePolicyOptions, cancellationToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

While the failure policy is correctly passed to the scheduler here, it is not being persisted in the BackgroundJobInfo entity (the jobInfo object created on line 97). Since this service integrates job persistence with scheduling, the failure policy will be lost if the job needs to be reconstructed from the database or updated later. Consider adding a field to BackgroundJobInfo or storing the policy within ExtraProperties.


logger.LogInformation(
"Successfully scheduled job handler '{HandlerName}' with job name '{JobName}'. Entity ID: {EntityId}",
Expand All @@ -126,10 +127,6 @@ public async Task<Guid> EnqueueAsync<TPayload>(
// Commit transaction - OnCompleted handlers run after this completes
await uow.CommitAsync(cancellationToken);

logger.LogInformation(
"Successfully enqueued job handler '{HandlerName}' with job name '{JobName}'. Entity ID: {EntityId}",
handlerName, jobName, jobId);

activity?.SetStatus(ActivityStatusCode.Ok);
return jobId;
}
Expand Down Expand Up @@ -195,7 +192,7 @@ public async Task UpdateAsync(Guid id, string newSchedule, CancellationToken can
// Reschedule with new schedule
var payloadBytes = eventSerializer.Serialize(envelope);
await jobScheduler.ScheduleAsync(jobInfo.HandlerName, jobInfo.JobName, newSchedule, payloadBytes,
cancellationToken);
cancellationToken: cancellationToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The failurePolicyOptions parameter is missing from this ScheduleAsync call. When a job's schedule is updated, the original failure policy should be retrieved from the job store and passed here to ensure it is preserved. Currently, updating a job effectively resets its failure policy to null.


// Save updated entity
await jobStore.SaveAsync(jobInfo, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public async Task ScheduleAsync(
string jobName,
string schedule,
ReadOnlyMemory<byte> payload,
JobScheduleFailurePolicy? failurePolicyOptions = null,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(handlerName))
Expand Down Expand Up @@ -57,6 +58,7 @@ await daprJobsClient.ScheduleJobAsync(
schedule: daprSchedule,
payload: new ReadOnlyMemory<byte>(payloadBytes),
overwrite: true,
failurePolicyOptions: MapFailurePolicy(failurePolicyOptions),
cancellationToken: cancellationToken);

activity?.SetStatus(ActivityStatusCode.Ok);
Expand Down Expand Up @@ -92,7 +94,7 @@ public async Task UpdateScheduleAsync(
{
var jobInfo = await daprJobsClient.GetJobAsync(jobName, cancellationToken);
await daprJobsClient.DeleteJobAsync(jobName, cancellationToken);
await ScheduleAsync(handlerName, jobName, newSchedule, jobInfo.Payload, cancellationToken);
await ScheduleAsync(handlerName, jobName, newSchedule, jobInfo.Payload, cancellationToken: cancellationToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This call to ScheduleAsync does not pass the failure policy, causing it to be lost during schedule updates. You should extract the existing failure policy from the jobInfo object (returned by Dapr on line 95) and pass it to the ScheduleAsync method. Additionally, since ScheduleAsync uses overwrite: true in its implementation, the explicit call to DeleteJobAsync on line 96 is redundant and creates a non-atomic operation that could lead to job loss if the subsequent scheduling fails.


activity?.SetStatus(ActivityStatusCode.Ok);
}
Expand Down Expand Up @@ -157,6 +159,16 @@ private static void RecordException(Activity? activity, Exception ex)
}));
}

private static IJobFailurePolicyOptions? MapFailurePolicy(JobScheduleFailurePolicy? policy) =>
policy switch
{
null => null,
{ PolicyType: FailurePolicyType.Drop } => new JobFailurePolicyDropOptions(),
{ PolicyType: FailurePolicyType.Constant, Interval: { } interval } =>
new JobFailurePolicyConstantOptions(interval) { MaxRetries = policy.MaxRetries },
_ => null
};

/// <summary>
/// Parses a schedule string into a DaprJobSchedule.
/// Supports cron expressions and simple delay formats.
Expand Down
Loading