Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a3c57f9
Add HTTP Client Wrapper module: retry, serialization, exception handling
ducdaiii Aug 15, 2025
ea773ec
Add HTTP Client Wrapper module: retry, serialization, exception handling
ducdaiii Aug 15, 2025
26a411d
[CodeFactor] Apply fixes
code-factor Aug 15, 2025
7219c84
Refactor: HTTP Client Wrapper module: retry, serialization, exception…
ducdaiii Aug 15, 2025
ebdc245
Refactor: HTTP Client Wrapper module: retry, serialization, exception…
ducdaiii Aug 15, 2025
748644e
feat(background-jobs): add full background job module with queue, wor…
ducdaiii Aug 16, 2025
5e3ddd4
[CodeFactor] Apply fixes
code-factor Aug 16, 2025
4e52bd5
Merge branch 'main' into feature/background-jobs
guibranco Dec 4, 2025
61dcd96
Merge branch 'main' into feature/background-jobs
guibranco Apr 21, 2026
cf71f8f
Merge branch 'main' into feature/background-jobs
guibranco May 19, 2026
fef565a
Merge branch 'main' into feature/background-jobs
guibranco May 19, 2026
b1fde96
Merge branch 'main' into feature/background-jobs
guibranco May 19, 2026
72b4fb3
Merge branch 'main' into feature/background-jobs
guibranco May 23, 2026
9cec349
Merge branch 'main' into feature/background-jobs
guibranco May 23, 2026
cf5ac10
Merge branch 'main' into feature/background-jobs
guibranco Jul 4, 2026
be52be4
Merge branch 'main' into feature/background-jobs
guibranco Jul 4, 2026
a824470
Merge branch 'main' into feature/background-jobs
guibranco Jul 4, 2026
2c15147
style: improve code readability and consistency +semver: minor
guibranco Jul 4, 2026
54e5770
refactor: extract methods for job fetching and processing
guibranco Jul 4, 2026
f4af0a2
refactor: improve code readability with formatting adjustments
guibranco Jul 4, 2026
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
14 changes: 12 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,18 @@
<PackageVersion Include="FluentAssertions" Version="8.10.0" />
<PackageVersion Include="JunitXml.TestLogger" Version="8.0.0" />
<PackageVersion Include="log4net" Version="3.3.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.9" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
<PackageVersion
Include="Microsoft.Extensions.DependencyInjection.Abstractions"
Version="9.0.8"
/>
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.2.0" />
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.8" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
<PackageVersion Include="NEST" Version="7.17.5" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
Expand Down
13 changes: 13 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/IBackgroundJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CrispyWaffle.BackgroundJobs.Core;

namespace CrispyWaffle.BackgroundJobs.Abstractions
{
/// <summary>
/// Low-level job representation (mostly used for in-memory workflows).
/// For persisted jobs, prefer IBackgroundJobHandler<TData> with registry-based activation.
/// </summary>
public interface IBackgroundJob
{
Task<JobResult> ExecuteAsync(CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CrispyWaffle.BackgroundJobs.Core;

namespace CrispyWaffle.BackgroundJobs.Abstractions
{
/// <summary>
/// Typed handler used by persisted jobs. Implement this to allow DI-resolved handlers.
/// TData is the payload type stored as JSON in the job store.
/// </summary>
public interface IBackgroundJobHandler<TData>
{
Task<JobResult> HandleAsync(TData data, CancellationToken cancellationToken);
}
}
20 changes: 20 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/IJobScheduler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace CrispyWaffle.BackgroundJobs.Abstractions
{
public interface IJobScheduler
{
Task ScheduleAsync(
string handlerName,
object payload,
TimeSpan delay,
int maxAttempts = 3,
JobPriority priority = JobPriority.Normal
);

Task EnqueueAsync(
string handlerName,
object payload,
int maxAttempts = 3,
JobPriority priority = JobPriority.Normal
);
}
}
28 changes: 28 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/IJobStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace CrispyWaffle.BackgroundJobs.Abstractions
{
public interface IJobStore
{
Task SaveAsync(JobEntity job, CancellationToken cancellationToken = default);

/// <summary>
/// Fetch the next available job that is due (ScheduledAt <= UtcNow) and Pending.
/// Implementation should atomically mark it as Processing to avoid double pick-up.
/// </summary>
Task<JobEntity?> FetchNextAsync(CancellationToken cancellationToken = default);

Task MarkCompletedAsync(Guid jobId, CancellationToken cancellationToken = default);

Task MarkFailedAsync(
Guid jobId,
string error,
CancellationToken cancellationToken = default
);

Task MarkRetryAsync(
Guid jobId,
DateTimeOffset? nextAttemptAt,
int attemptCount,
CancellationToken cancellationToken = default
);
}
}
47 changes: 47 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/JobEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace CrispyWaffle.BackgroundJobs.Abstractions
{
using System;

/// <summary>
/// Persistent representation of a job.
/// HandlerName identifies a registered handler; Payload is JSON.
/// </summary>
public class JobEntity
{
public Guid Id { get; set; } = Guid.NewGuid();

/// <summary>
/// Logical handler name registered in IJobHandlerRegistry.
/// </summary>
public string HandlerName { get; set; } = string.Empty;

/// <summary>
/// JSON payload (serialized) for the handler.
/// </summary>
public string Payload { get; set; } = string.Empty;

public JobPriority Priority { get; set; } = JobPriority.Normal;

public JobStatus Status { get; set; } = JobStatus.Pending;

/// <summary>
/// When the job becomes due for execution. Null means immediately.
/// </summary>
public DateTimeOffset? ScheduledAt { get; set; }

public int Attempt { get; set; } = 0;

public int MaxAttempt { get; set; } = 3;

public string? LastError { get; set; }

public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;

public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow;

/// <summary>
/// Optional delay in seconds between retries (used only as hint).
/// </summary>
public int RetryDelaySeconds { get; set; } = 0;
}
}
9 changes: 9 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/JobPriority.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace CrispyWaffle.BackgroundJobs.Abstractions
{
public enum JobPriority
{
High = 0,
Normal = 1,
Low = 2,
}
}
11 changes: 11 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Abstractions/JobStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CrispyWaffle.BackgroundJobs.Abstractions
{
public enum JobStatus
{
Pending = 0,
Processing = 1,
Completed = 2,
Failed = 3,
Dead = 4,
}
}
52 changes: 52 additions & 0 deletions Src/CrispyWaffle.BackgroundJobs/Core/BackgroundJobQueue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Threading.Channels;
using CrispyWaffle.BackgroundJobs.Abstractions;

namespace CrispyWaffle.BackgroundJobs.Core
{
/// <summary>
/// Lightweight in-memory priority queue using Channel for signaling.
/// Priorities: lower int = higher priority.
/// This queue stores JobEntity (for uniformity with persistent store model).
/// </summary>
public class BackgroundJobQueue
{
private readonly object _lock = new();
private readonly PriorityQueue<JobEntity, int> _pq = new();
private readonly Channel<JobEntity> _signal = Channel.CreateUnbounded<JobEntity>(
new UnboundedChannelOptions { SingleReader = false, SingleWriter = false }
);

public void Enqueue(JobEntity job)
{
lock (_lock)
{
_pq.Enqueue(job, (int)job.Priority);
}
// Always write to signal channel to notify consumers.
_ = _signal.Writer.WriteAsync(job);
}

public async Task<JobEntity?> DequeueAsync(CancellationToken cancellationToken)
{
// Wait until a job is signaled and then pop from our priority queue.
try
{
await _signal.Reader.ReadAsync(cancellationToken);
}
catch (OperationCanceledException)
{
return null;
}

lock (_lock)
{
if (_pq.TryDequeue(out var job, out _))
{
return job;
}
}

return null;
}
}
}
Loading
Loading